From fd242d50c1e84d95e042f1acb7560fc587b59c41 Mon Sep 17 00:00:00 2001 From: dijunkun Date: Mon, 19 Jan 2026 17:42:22 +0800 Subject: [PATCH] [feat] show server window in the bottom-right corner of the screen --- src/gui/render.cpp | 64 ++++++++- src/gui/render.h | 23 +++- src/gui/render_callback.cpp | 6 +- src/gui/windows/server_window.cpp | 222 ++++++++++++++++++++++++++++-- 4 files changed, 289 insertions(+), 26 deletions(-) diff --git a/src/gui/render.cpp b/src/gui/render.cpp index ddb1dd8..6a33b35 100644 --- a/src/gui/render.cpp +++ b/src/gui/render.cpp @@ -949,24 +949,43 @@ int Render::CreateServerWindow() { return -1; } ImGui::SetCurrentContext(server_ctx_); - if (!SDL_CreateWindowAndRenderer( - "Server window", (int)server_window_width_, - (int)server_window_height_, - SDL_WINDOW_HIGH_PIXEL_DENSITY | SDL_WINDOW_BORDERLESS, - &server_window_, &server_renderer_)) { + if (!SDL_CreateWindowAndRenderer("Server window", (int)server_window_width_, + (int)server_window_height_, + SDL_WINDOW_HIGH_PIXEL_DENSITY | + SDL_WINDOW_BORDERLESS | + SDL_WINDOW_TRANSPARENT, + &server_window_, &server_renderer_)) { LOG_ERROR("Error creating server_window_ and server_renderer_: {}", SDL_GetError()); return -1; } - SDL_SetWindowResizable(server_window_, true); + + // Set window position to bottom-right corner + SDL_Rect display_bounds; + if (SDL_GetDisplayUsableBounds(SDL_GetDisplayForWindow(server_window_), + &display_bounds)) { + int window_x = + display_bounds.x + display_bounds.w - (int)server_window_width_; + int window_y = + display_bounds.y + display_bounds.h - (int)server_window_height_; + SDL_SetWindowPosition(server_window_, window_x, window_y); + } + + SDL_SetWindowResizable(server_window_, false); + + SDL_SetRenderDrawBlendMode(server_renderer_, SDL_BLENDMODE_BLEND); + // for window region action SDL_SetWindowHitTest(server_window_, HitTestCallback, this); SetupFontAndStyle(false); ImGui_ImplSDL3_InitForSDLRenderer(server_window_, server_renderer_); ImGui_ImplSDLRenderer3_Init(server_renderer_); + server_window_created_ = true; server_window_inited_ = true; + LOG_INFO("Server window inited"); + return 0; } @@ -1237,12 +1256,28 @@ int Render::DrawServerWindow() { LOG_ERROR("Server context is null"); return -1; } + + if (server_window_) { + int w = 0, h = 0; + SDL_GetWindowSize(server_window_, &w, &h); + if (w > 0 && h > 0) { + server_window_width_ = (float)w; + server_window_height_ = (float)h; + } + } + ImGui::SetCurrentContext(server_ctx_); ImGui_ImplSDLRenderer3_NewFrame(); ImGui_ImplSDL3_NewFrame(); ImGui::NewFrame(); ServerWindow(); ImGui::Render(); + // Transparent clear for compact (shaped) mode; opaque clear for normal mode. + if (server_window_compact_) { + SDL_SetRenderDrawColor(server_renderer_, 0, 0, 0, 0); + } else { + SDL_SetRenderDrawColor(server_renderer_, 255, 255, 255, 255); + } SDL_RenderClear(server_renderer_); ImGui_ImplSDLRenderer3_RenderDrawData(ImGui::GetDrawData(), server_renderer_); SDL_RenderPresent(server_renderer_); @@ -1430,7 +1465,7 @@ void Render::MainLoop() { DrawStreamWindow(); } - if (need_to_send_host_info_) { + if (is_server_mode_) { DrawServerWindow(); } @@ -1530,6 +1565,11 @@ void Render::HandleServerWindow() { CreateServerWindow(); need_to_create_server_window_ = false; } + + if (need_to_destroy_server_window_) { + DestroyServerWindow(); + need_to_destroy_server_window_ = false; + } } void Render::Cleanup() { @@ -1910,6 +1950,16 @@ void Render::ProcessSdlEvent(const SDL_Event& event) { } } + if (server_window_inited_) { + if (server_ctx_) { + ImGui::SetCurrentContext(server_ctx_); + ImGui_ImplSDL3_ProcessEvent(&event); + } else { + LOG_ERROR("Server context is null"); + return; + } + } + switch (event.type) { case SDL_EVENT_QUIT: if (stream_window_inited_) { diff --git a/src/gui/render.h b/src/gui/render.h index eebcd7a..5a4a1c6 100644 --- a/src/gui/render.h +++ b/src/gui/render.h @@ -500,18 +500,28 @@ class Render { // server window properties bool need_to_create_server_window_ = false; + bool need_to_destroy_server_window_ = false; bool server_window_created_ = false; bool server_window_inited_ = false; - int server_window_width_default_ = 600; - int server_window_height_default_ = 400; - float server_window_width_ = 600; - float server_window_height_ = 400; + int server_window_width_default_ = 300; + int server_window_height_default_ = 450; + float server_window_width_ = 300; + float server_window_height_ = 450; + float server_window_title_bar_height_ = 30.0f; SDL_PixelFormat server_pixformat_ = SDL_PIXELFORMAT_NV12; - int server_window_width_real_ = 600; - int server_window_height_real_ = 400; + int server_window_width_real_ = 400; + int server_window_height_real_ = 450; float server_window_dpi_scaling_w_ = 1.0f; float server_window_dpi_scaling_h_ = 1.0f; + // server window compact mode (50x50) toggle + bool server_window_compact_ = false; + int server_window_width_before_compact_ = 0; + int server_window_height_before_compact_ = 0; + int server_window_x_before_compact_ = 0; + int server_window_y_before_compact_ = 0; + bool server_window_bounds_saved_ = false; + bool label_inited_ = false; bool connect_button_pressed_ = false; bool password_validating_ = false; @@ -528,6 +538,7 @@ class Render { bool fullscreen_button_pressed_ = false; bool focus_on_input_widget_ = true; bool is_client_mode_ = false; + bool is_server_mode_ = false; bool reload_recent_connections_ = true; bool show_confirm_delete_connection_ = false; bool delete_connection_ = false; diff --git a/src/gui/render_callback.cpp b/src/gui/render_callback.cpp index 0c33b02..51ae876 100644 --- a/src/gui/render_callback.cpp +++ b/src/gui/render_callback.cpp @@ -623,7 +623,7 @@ void Render::OnConnectionStatusCb(ConnectionStatus status, const char* user_id, switch (status) { case ConnectionStatus::Connected: { render->need_to_create_server_window_ = true; - render->need_to_send_host_info_ = true; + render->is_server_mode_ = true; render->start_screen_capturer_ = true; render->start_speaker_capturer_ = true; #ifdef CROSSDESK_DEBUG @@ -648,8 +648,8 @@ void Render::OnConnectionStatusCb(ConnectionStatus status, const char* user_id, kv.second == ConnectionStatus::Failed || kv.second == ConnectionStatus::Disconnected; })) { - render->need_to_create_server_window_ = false; - render->need_to_send_host_info_ = false; + render->need_to_destroy_server_window_ = true; + render->is_server_mode_ = false; render->start_screen_capturer_ = false; render->start_speaker_capturer_ = false; render->start_mouse_controller_ = false; diff --git a/src/gui/windows/server_window.cpp b/src/gui/windows/server_window.cpp index 4398097..5daa5e9 100644 --- a/src/gui/windows/server_window.cpp +++ b/src/gui/windows/server_window.cpp @@ -1,22 +1,224 @@ +#include +#include + +#include "rd_log.h" #include "render.h" namespace crossdesk { + +static void SetServerWindowCircleShape(SDL_Window* window, int diameter) { + if (!window || diameter <= 0) { + return; + } + + const int pitch = diameter * 4; + std::vector pixels((size_t)diameter * (size_t)diameter * 4, 0); + + // Sub-pixel centered circle helps symmetry on even diameters. + const float r = (float)diameter * 0.5f; + const float cx = r - 0.5f; + const float cy = r - 0.5f; + + for (int y = 0; y < diameter; ++y) { + for (int x = 0; x < diameter; ++x) { + const float dx = (float)x - cx; + const float dy = (float)y - cy; + const float dist = std::sqrt(dx * dx + dy * dy); + + // 1px soft edge to reduce jaggies on small circles. + float a = r + 0.5f - dist; + if (a < 0.0f) a = 0.0f; + if (a > 1.0f) a = 1.0f; + const unsigned char alpha = (unsigned char)(a * 255.0f); + const size_t idx = ((size_t)y * (size_t)diameter + (size_t)x) * 4; + pixels[idx + 0] = 255; // R + pixels[idx + 1] = 255; // G + pixels[idx + 2] = 255; // B + pixels[idx + 3] = alpha; // A + } + } + + SDL_Surface* shape = SDL_CreateSurfaceFrom( + diameter, diameter, SDL_PIXELFORMAT_RGBA32, pixels.data(), pitch); + if (!shape) { + LOG_ERROR("SDL_CreateSurfaceFrom failed: {}", SDL_GetError()); + return; + } + + if (!SDL_SetWindowShape(window, shape)) { + LOG_ERROR("SDL_SetWindowShape failed: {}", SDL_GetError()); + } + + SDL_DestroySurface(shape); +} + int Render::ServerWindow() { - ImGui::SetNextWindowSize( - ImVec2(main_window_width_ - 2 * main_child_window_x_padding_, - main_window_height_ - status_bar_height_ - - main_window_text_y_padding_ - main_child_window_y_padding_), - ImGuiCond_Always); - ImGui::SetNextWindowPos( - ImVec2(main_child_window_x_padding_, main_window_text_y_padding_), - ImGuiCond_Always); + ImGui::SetNextWindowSize(ImVec2(server_window_width_, server_window_height_), + ImGuiCond_Always); + ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always); + + if (server_window_compact_) { + ImGui::SetNextWindowBgAlpha(0.0f); + } + ImGui::Begin("##server_window", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); - // server window content goes here - ImGui::Text("Server Window Content"); + + // Compact mode: show a 50x50 clickable square that restores the window. + if (server_window_compact_) { + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + ImGui::SetCursorPos(ImVec2(0.0f, 0.0f)); + if (ImGui::InvisibleButton( + "##server_compact_restore", + ImVec2(server_window_width_, server_window_height_))) { + if (server_window_) { + SDL_SetWindowShape(server_window_, nullptr); + } + if (server_window_ && server_window_bounds_saved_ && + server_window_width_before_compact_ > 0 && + server_window_height_before_compact_ > 0) { + SDL_SetWindowSize(server_window_, server_window_width_before_compact_, + server_window_height_before_compact_); + SDL_SetWindowPosition(server_window_, server_window_x_before_compact_, + server_window_y_before_compact_); + + server_window_width_ = (float)server_window_width_before_compact_; + server_window_height_ = (float)server_window_height_before_compact_; + } + server_window_compact_ = false; + server_window_bounds_saved_ = false; + } + + // Draw a visible circular affordance. + const float w = server_window_width_; + const float h = server_window_height_; + const float radius = (w < h ? w : h) * 0.5f - 1.0f; + const ImVec2 center(w * 0.5f, h * 0.5f); + draw_list->AddCircleFilled(center, radius, IM_COL32(255, 255, 255, 220), + 32); + draw_list->AddCircle(center, radius, IM_COL32(0, 0, 0, 255), 32, 2.0f); + // A simple "restore" hint. + draw_list->AddRect( + ImVec2(center.x - radius * 0.35f, center.y - radius * 0.35f), + ImVec2(center.x + radius * 0.35f, center.y + radius * 0.35f), + IM_COL32(0, 0, 0, 255), 2.0f, 0, 2.0f); + + ImGui::End(); + return 0; + } + + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); + ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always); + ImGui::BeginChild( + "ServerTitleBar", + ImVec2(server_window_width_, server_window_title_bar_height_), + ImGuiChildFlags_Border, + ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoBringToFrontOnFocus); + + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); + + float server_title_bar_button_width = server_window_title_bar_height_; + float server_title_bar_button_height = server_window_title_bar_height_; + + float minimize_button_pos_x = + server_window_width_ - server_title_bar_button_width * 2; + ImGui::SetCursorPos(ImVec2(minimize_button_pos_x, 0.0f)); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0, 0, 0, 0.1f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); + + float minimize_pos_x = + minimize_button_pos_x + server_title_bar_button_width * 0.33f; + float minimize_pos_y = server_title_bar_button_height * 0.5f; + std::string server_minimize_button = "##minimize"; // ICON_FA_MINUS; + if (ImGui::Button(server_minimize_button.c_str(), + ImVec2(server_title_bar_button_width, + server_title_bar_button_height))) { + if (server_window_) { + int w = 0, h = 0; + int x = 0, y = 0; + SDL_GetWindowSize(server_window_, &w, &h); + SDL_GetWindowPosition(server_window_, &x, &y); + server_window_width_before_compact_ = w; + server_window_height_before_compact_ = h; + server_window_x_before_compact_ = x; + server_window_y_before_compact_ = y; + server_window_bounds_saved_ = true; + + constexpr int kCompactSize = 50; + SDL_SetWindowSize(server_window_, kCompactSize, kCompactSize); + + // Move to bottom-right of the current display's usable bounds. + SDL_Rect display_bounds; + if (SDL_GetDisplayUsableBounds(SDL_GetDisplayForWindow(server_window_), + &display_bounds)) { + int compact_x = display_bounds.x + display_bounds.w - kCompactSize; + int compact_y = display_bounds.y + display_bounds.h - kCompactSize; + SDL_SetWindowPosition(server_window_, compact_x, compact_y); + } + + // Use pixel size to match transparency buffer on HiDPI. + int w_px = kCompactSize; + int h_px = kCompactSize; + SDL_GetWindowSizeInPixels(server_window_, &w_px, &h_px); + const int diameter = (w_px < h_px ? w_px : h_px); + SetServerWindowCircleShape(server_window_, diameter); + + server_window_compact_ = true; + } + } + draw_list->AddLine( + ImVec2(minimize_pos_x, minimize_pos_y), + ImVec2(minimize_pos_x + server_title_bar_button_width * 0.33f, + minimize_pos_y), + IM_COL32(0, 0, 0, 255)); + ImGui::PopStyleColor(3); + + float xmark_button_pos_x = + server_window_width_ - server_title_bar_button_width; + ImGui::SetCursorPos(ImVec2(xmark_button_pos_x, 0.0f)); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0f, 0, 0, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1.0f, 0, 0, 0.5f)); + + float xmark_pos_x = xmark_button_pos_x + server_title_bar_button_width * 0.5f; + float xmark_pos_y = server_title_bar_button_height * 0.5f; + float xmark_size = server_title_bar_button_width * 0.33f; + std::string server_close_button = "##xmark"; // ICON_FA_XMARK; + if (ImGui::Button(server_close_button.c_str(), + ImVec2(server_title_bar_button_width, + server_title_bar_button_height))) { + LOG_ERROR("Close button clicked"); + LeaveConnection(peer_, self_hosted_id_); + } + + draw_list->AddLine(ImVec2(xmark_pos_x - xmark_size / 2 - 0.25f, + xmark_pos_y - xmark_size / 2 + 0.75f), + ImVec2(xmark_pos_x + xmark_size / 2 - 1.5f, + xmark_pos_y + xmark_size / 2 - 0.5f), + IM_COL32(0, 0, 0, 255)); + draw_list->AddLine( + ImVec2(xmark_pos_x + xmark_size / 2 - 1.75f, + xmark_pos_y - xmark_size / 2 + 0.75f), + ImVec2(xmark_pos_x - xmark_size / 2, xmark_pos_y + xmark_size / 2 - 1.0f), + IM_COL32(0, 0, 0, 255)); + + ImGui::PopStyleColor(3); + ImGui::PopStyleVar(); + ImGui::PopStyleColor(); + + ImGui::EndChild(); + ImGui::PopStyleVar(); + ImGui::PopStyleColor(); + ImGui::End(); return 0; }