[refactor] organize GUI code by responsibility

This commit is contained in:
dijunkun
2026-07-19 21:23:20 +08:00
parent 1f86c43458
commit eeb6a2a1ae
73 changed files with 7918 additions and 7229 deletions
+228
View File
@@ -0,0 +1,228 @@
#ifndef CROSSDESK_GUI_APPLICATION_STATE_H_
#define CROSSDESK_GUI_APPLICATION_STATE_H_
#include <SDL3/SDL.h>
#include <chrono>
#include <cstdint>
#include <memory>
#include <nlohmann/json.hpp>
#include <string>
#include "imgui.h"
#if _WIN32
#include "platform/tray/win_tray.h"
#elif defined(__APPLE__)
#include "platform/tray/mac_tray.h"
#elif defined(__linux__)
#include "platform/tray/linux_tray.h"
#endif
namespace crossdesk::gui_detail {
struct MainWindowState {
float title_bar_width_ = 640;
float title_bar_height_ = 30;
float title_bar_button_width_ = 30;
float title_bar_button_height_ = 30;
SDL_Window *main_window_ = nullptr;
SDL_Renderer *main_renderer_ = nullptr;
ImGuiContext *main_ctx_ = nullptr;
ImFont *main_windows_system_chinese_font_ = nullptr;
ImFont *stream_windows_system_chinese_font_ = nullptr;
ImFont *server_windows_system_chinese_font_ = nullptr;
bool exit_ = false;
const int sdl_refresh_ms_ = 16;
#if _WIN32
std::unique_ptr<WinTray> tray_;
#elif defined(__APPLE__)
std::unique_ptr<MacTray> tray_;
#elif defined(__linux__)
std::unique_ptr<LinuxTray> tray_;
#endif
bool main_window_minimized_ = false;
uint32_t last_main_minimize_request_tick_ = 0;
uint32_t last_stream_minimize_request_tick_ = 0;
int main_window_width_real_ = 720;
int main_window_height_real_ = 540;
float main_window_dpi_scaling_w_ = 1.0f;
float main_window_dpi_scaling_h_ = 1.0f;
float dpi_scale_ = 1.0f;
float main_window_width_default_ = 640;
float main_window_height_default_ = 480;
float main_window_width_ = 640;
float main_window_height_ = 480;
float main_window_width_last_ = 640;
float main_window_height_last_ = 480;
float local_window_width_ = 320;
float local_window_height_ = 235;
float remote_window_width_ = 320;
float remote_window_height_ = 235;
float local_child_window_width_ = 266;
float local_child_window_height_ = 180;
float remote_child_window_width_ = 266;
float remote_child_window_height_ = 180;
float main_window_text_y_padding_ = 10;
float main_child_window_x_padding_ = 27;
float main_child_window_y_padding_ = 45;
float status_bar_height_ = 22;
float connection_status_window_width_ = 200;
float connection_status_window_height_ = 150;
float notification_window_width_ = 200;
float notification_window_height_ = 80;
float about_window_width_ = 300;
float about_window_height_ = 170;
float update_notification_window_width_ = 400;
float update_notification_window_height_ = 320;
uint32_t STREAM_REFRESH_EVENT = 0;
uint32_t APP_EXIT_EVENT = 0;
};
// Application-global interaction flags that coordinate SDL events with media
// and remote-control devices.
struct InteractionState {
bool start_mouse_controller_ = false;
bool mouse_controller_is_started_ = false;
bool start_screen_capturer_ = false;
bool screen_capturer_is_started_ = false;
bool start_speaker_capturer_ = false;
bool speaker_capturer_is_started_ = false;
bool start_keyboard_capturer_ = false;
bool show_cursor_ = false;
bool keyboard_capturer_is_started_ = false;
bool keyboard_capturer_uses_sdl_events_ = false;
bool foucs_on_main_window_ = false;
bool focus_on_stream_window_ = false;
bool audio_capture_ = false;
int screen_width_ = 1280;
int screen_height_ = 720;
int selected_display_ = 0;
std::string connect_button_label_ = "Connect";
char input_password_tmp_[7] = "";
char input_password_[7] = "";
std::string random_password_;
char new_password_[7] = "";
char remote_id_display_[12] = "";
unsigned char audio_buffer_[720]{};
int audio_len_ = 0;
bool audio_buffer_fresh_ = false;
bool need_to_rejoin_ = false;
std::chrono::steady_clock::time_point last_rejoin_check_time_ =
std::chrono::steady_clock::now();
bool just_created_ = false;
std::string controlled_remote_id_;
std::string focused_remote_id_;
std::string remote_client_id_;
SDL_Event last_mouse_event{};
};
struct UpdateState {
nlohmann::json latest_version_info_ = nlohmann::json{};
bool update_available_ = false;
std::string latest_version_;
std::string release_notes_;
bool show_new_version_icon_ = false;
bool show_new_version_icon_in_menu_ = true;
double new_version_icon_last_trigger_time_ = 0.0;
double new_version_icon_render_start_time_ = 0.0;
};
struct StreamWindowState {
SDL_Window *stream_window_ = nullptr;
SDL_Renderer *stream_renderer_ = nullptr;
ImGuiContext *stream_ctx_ = nullptr;
bool need_to_create_stream_window_ = false;
bool stream_window_created_ = false;
bool stream_window_inited_ = false;
bool window_maximized_ = false;
bool stream_window_grabbed_ = false;
bool control_mouse_ = false;
int stream_window_width_default_ = 1280;
int stream_window_height_default_ = 720;
float stream_window_width_ = 1280;
float stream_window_height_ = 720;
SDL_PixelFormat stream_pixformat_ = SDL_PIXELFORMAT_NV12;
int stream_window_width_real_ = 1280;
int stream_window_height_real_ = 720;
float stream_window_dpi_scaling_w_ = 1.0f;
float stream_window_dpi_scaling_h_ = 1.0f;
};
struct ServerWindowState {
SDL_Window *server_window_ = nullptr;
SDL_Renderer *server_renderer_ = nullptr;
ImGuiContext *server_ctx_ = nullptr;
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_ = 250;
int server_window_height_default_ = 150;
float server_window_width_ = 250;
float server_window_height_ = 150;
float server_window_title_bar_height_ = 30.0f;
SDL_PixelFormat server_pixformat_ = SDL_PIXELFORMAT_NV12;
int server_window_normal_width_ = 250;
int server_window_normal_height_ = 150;
float server_window_dpi_scaling_w_ = 1.0f;
float server_window_dpi_scaling_h_ = 1.0f;
float window_rounding_ = 6.0f;
float window_rounding_default_ = 6.0f;
bool server_window_collapsed_ = false;
bool server_window_collapsed_dragging_ = false;
float server_window_collapsed_drag_start_mouse_x_ = 0.0f;
float server_window_collapsed_drag_start_mouse_y_ = 0.0f;
int server_window_collapsed_drag_start_win_x_ = 0;
int server_window_collapsed_drag_start_win_y_ = 0;
bool server_window_dragging_ = false;
float server_window_drag_start_mouse_x_ = 0.0f;
float server_window_drag_start_mouse_y_ = 0.0f;
int server_window_drag_start_win_x_ = 0;
int server_window_drag_start_win_y_ = 0;
};
struct UiState {
bool label_inited_ = false;
bool connect_button_pressed_ = false;
bool password_validating_ = false;
uint32_t password_validating_time_ = 0;
bool show_settings_window_ = false;
bool show_self_hosted_server_config_window_ = false;
bool rejoin_ = false;
bool local_id_copied_ = false;
bool show_password_ = true;
bool show_about_window_ = false;
bool show_connection_status_window_ = false;
bool show_reset_password_window_ = false;
bool show_update_notification_window_ = false;
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 show_edit_connection_alias_window_ = false;
bool show_offline_warning_window_ = false;
bool delete_connection_ = false;
bool is_tab_bar_hovered_ = false;
std::string delete_connection_name_;
std::string edit_connection_alias_remote_id_;
char edit_connection_alias_[128] = "";
std::string offline_warning_text_;
bool re_enter_remote_id_ = false;
double copy_start_time_ = 0;
};
struct ApplicationState : MainWindowState,
InteractionState,
UpdateState,
StreamWindowState,
ServerWindowState,
UiState {};
} // namespace crossdesk::gui_detail
#endif // CROSSDESK_GUI_APPLICATION_STATE_H_
+295
View File
@@ -0,0 +1,295 @@
#include "application/gui_application.h"
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <memory>
#include <mutex>
#include <string>
#include <vector>
#include "localization.h"
#include "rd_log.h"
#include "version_checker.h"
namespace crossdesk {
GuiApplication::GuiApplication() = default;
GuiApplication::~GuiApplication() = default;
int GuiApplication::Run() {
path_manager_ = std::make_unique<PathManager>("CrossDesk");
if (path_manager_) {
exec_log_path_ = path_manager_->GetLogPath().string();
dll_log_path_ = path_manager_->GetLogPath().string();
cache_path_ = path_manager_->GetCachePath().string();
config_center_ =
std::make_unique<ConfigCenter>(cache_path_ + "/config.ini");
strncpy(signal_server_ip_self_,
config_center_->GetSignalServerHost().c_str(),
sizeof(signal_server_ip_self_) - 1);
signal_server_ip_self_[sizeof(signal_server_ip_self_) - 1] = '\0';
int signal_port_init = config_center_->GetSignalServerPort();
if (signal_port_init > 0) {
strncpy(signal_server_port_self_,
std::to_string(signal_port_init).c_str(),
sizeof(signal_server_port_self_) - 1);
signal_server_port_self_[sizeof(signal_server_port_self_) - 1] = '\0';
} else {
signal_server_port_self_[0] = '\0';
}
} else {
std::cerr << "Failed to create PathManager" << std::endl;
return -1;
}
InitializeLogger();
LOG_INFO("CrossDesk version: {}", CROSSDESK_VERSION);
latest_version_info_ = CheckUpdate();
if (!latest_version_info_.empty()) {
std::string version;
if (latest_version_info_.contains("latest_version") &&
latest_version_info_["latest_version"].is_string()) {
version = latest_version_info_["latest_version"].get<std::string>();
} else if (latest_version_info_.contains("version") &&
latest_version_info_["version"].is_string()) {
version = latest_version_info_["version"].get<std::string>();
}
if (!version.empty()) {
latest_version_ = 'v' + version;
} else {
latest_version_ = "";
}
if (latest_version_info_.contains("releaseNotes") &&
latest_version_info_["releaseNotes"].is_string()) {
release_notes_ = latest_version_info_["releaseNotes"].get<std::string>();
} else {
release_notes_ = "";
}
update_available_ =
!version.empty() && IsNewerVersion(CROSSDESK_VERSION, latest_version_);
LOG_INFO("Update check: current={}, latest={}, available={}",
CROSSDESK_VERSION, latest_version_, update_available_);
if (update_available_) {
show_update_notification_window_ = true;
}
} else {
latest_version_ = "";
update_available_ = false;
LOG_WARN("Update check skipped: version.json is empty or missing "
"latest_version");
}
InitializeSettings();
InitializeSDL();
InitializeModules();
InitializeMainWindow();
#if _WIN32 && CROSSDESK_PORTABLE
CheckPortableWindowsService();
#endif
const int scaled_video_width_ = 160;
const int scaled_video_height_ = 90;
MainLoop();
Cleanup();
return 0;
}
void GuiApplication::InitializeLogger() { InitLogger(exec_log_path_); }
void GuiApplication::InitializeSettings() {
settings_.Load();
settings_.LoadRecentConnectionAliases();
localization_language_index_ =
localization::detail::ClampLanguageIndex(language_button_value_);
language_button_value_ = localization_language_index_;
if (localization_language_index_ == 0) {
localization_language_ = ConfigCenter::LANGUAGE::CHINESE;
} else if (localization_language_index_ == 1) {
localization_language_ = ConfigCenter::LANGUAGE::ENGLISH;
} else {
localization_language_ = ConfigCenter::LANGUAGE::RUSSIAN;
}
}
void GuiApplication::InitializeSDL() {
#if defined(__linux__) && !defined(__APPLE__)
if (!getenv("SDL_AUDIODRIVER")) {
// Prefer PulseAudio first on Linux to avoid hard ALSA plugin dependency.
setenv("SDL_AUDIODRIVER", "pulseaudio", 0);
}
#endif
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO)) {
LOG_ERROR("Error: {}", SDL_GetError());
return;
}
const SDL_DisplayMode *dm = SDL_GetCurrentDisplayMode(0);
if (dm) {
screen_width_ = dm->w;
screen_height_ = dm->h;
}
const uint32_t custom_event_base = SDL_RegisterEvents(3);
if (custom_event_base == static_cast<uint32_t>(-1)) {
LOG_ERROR("Failed to register custom SDL events");
} else {
STREAM_REFRESH_EVENT = custom_event_base;
APP_EXIT_EVENT = custom_event_base + 1;
clipboard_.SetEventType(custom_event_base + 2);
}
LOG_INFO("Screen resolution: [{}x{}]", screen_width_, screen_height_);
}
void GuiApplication::InitializeModules() {
if (!modules_inited_) {
devices_.Initialize();
CreateConnectionPeer();
modules_inited_ = true;
}
}
void GuiApplication::InitializeMainWindow() {
CreateMainWindow();
clipboard_.Initialize();
if (SDL_WINDOW_HIDDEN & SDL_GetWindowFlags(main_window_)) {
SDL_ShowWindow(main_window_);
}
}
void GuiApplication::MainLoop() {
while (!exit_) {
if (!peer_) {
CreateConnectionPeer();
}
SDL_Event event;
if (SDL_WaitEventTimeout(&event, sdl_refresh_ms_)) {
ProcessSdlEvent(event);
}
// Also drain the pending value after a timeout so a full SDL event queue
// cannot indefinitely delay a clipboard update received from the network.
clipboard_.ApplyPendingRemoteText();
#if _WIN32
MSG msg;
while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
#elif defined(__linux__) && !defined(__APPLE__)
if (tray_) {
tray_->ProcessEvents();
}
#endif
UpdateLabels();
HandleRecentConnections();
HandleConnectionStatusChange();
HandlePendingPresenceProbe();
HandleConnectionTimeouts();
HandleStreamWindow();
HandleServerWindow();
HandleWindowsServiceIntegration();
const bool main_window_visible =
main_window_ && !(SDL_GetWindowFlags(main_window_) & SDL_WINDOW_HIDDEN);
if (main_window_visible) {
DrawMainWindow();
}
if (stream_window_inited_) {
DrawStreamWindow();
}
if (is_server_mode_) {
DrawServerWindow();
}
devices_.UpdateInteractions();
}
}
void GuiApplication::HandleStreamWindow() {
if (need_to_create_stream_window_) {
CreateStreamWindow();
need_to_create_stream_window_ = false;
}
if (stream_window_inited_) {
if (!stream_window_grabbed_ && control_mouse_) {
SDL_SetWindowMouseGrab(stream_window_, true);
stream_window_grabbed_ = true;
} else if (stream_window_grabbed_ && !control_mouse_) {
SDL_SetWindowMouseGrab(stream_window_, false);
stream_window_grabbed_ = false;
}
}
}
void GuiApplication::HandleServerWindow() {
if (need_to_create_server_window_) {
CreateServerWindow();
need_to_create_server_window_ = false;
}
if (need_to_destroy_server_window_) {
DestroyServerWindow();
DestroyServerWindowContext();
need_to_destroy_server_window_ = false;
}
}
void GuiApplication::Cleanup() {
clipboard_.Shutdown();
devices_.DestroyDevices();
devices_.DestroyFactories();
CloseAllRemoteSessions();
#if _WIN32 && CROSSDESK_PORTABLE
JoinPortableWindowsServiceInstallThread();
#endif
WaitForThumbnailSaveTasks();
devices_.DestroyAudioOutput();
#if defined(_WIN32) || defined(__APPLE__) || defined(__linux__)
tray_.reset();
#endif
if (stream_window_created_) {
if (stream_window_) {
SDL_SetWindowMouseGrab(stream_window_, false);
}
DestroyStreamWindow();
}
if (stream_ctx_) {
DestroyStreamWindowContext();
}
if (server_window_created_) {
DestroyServerWindow();
}
if (server_ctx_) {
DestroyServerWindowContext();
}
DestroyMainWindowContext();
DestroyMainWindow();
SDL_Quit();
}
} // namespace crossdesk
+112
View File
@@ -0,0 +1,112 @@
#ifndef CROSSDESK_GUI_APPLICATION_H_
#define CROSSDESK_GUI_APPLICATION_H_
#include <filesystem>
#include <string>
#include <vector>
#include "IconsFontAwesome6.h"
#include "imgui_impl_sdl3.h"
#include "imgui_impl_sdlrenderer3.h"
#include "imgui_internal.h"
#include "runtime/gui_runtime.h"
namespace crossdesk {
// SDL/ImGui application shell. Feature state and protocol behavior live in
// GuiRuntime and its composed controllers.
class GuiApplication final : private GuiRuntime {
public:
GuiApplication();
~GuiApplication();
int Run();
private:
// Native window integration.
static SDL_HitTestResult HitTestCallback(SDL_Window *window,
const SDL_Point *area, void *data);
// Application lifecycle.
void InitializeLogger();
void InitializeSettings();
void InitializeSDL();
void InitializeModules();
void InitializeMainWindow();
void MainLoop();
void HandleStreamWindow();
void HandleServerWindow();
void Cleanup();
// Window lifecycle and rendering.
int CreateMainWindow();
int DestroyMainWindow();
int CreateStreamWindow();
int DestroyStreamWindow();
int CreateServerWindow();
int DestroyServerWindow();
int SetupFontAndStyle(ImFont **system_chinese_font_out);
int DestroyMainWindowContext();
int DestroyStreamWindowContext();
int DestroyServerWindowContext();
int DrawMainWindow();
int DrawStreamWindow();
int DrawServerWindow();
// Views and dialogs.
int TitleBar(bool main_window);
int MainWindow();
int UpdateNotificationWindow();
int StreamWindow();
int ServerWindow();
int RemoteClientInfoWindow();
int LocalWindow();
int RemoteWindow();
int RecentConnectionsWindow();
int SettingWindow();
int SelfHostedServerWindow();
int ControlWindow(std::shared_ptr<RemoteSession> &props);
int ControlBar(std::shared_ptr<RemoteSession> &props);
int AboutWindow();
int StatusBar();
bool
ConnectionStatusWindow(std::shared_ptr<RemoteSession> &props);
int ShowRecentConnections();
int ConfirmDeleteConnection();
int EditRecentConnectionAliasWindow();
int OfflineWarningWindow();
int NetTrafficStats(std::shared_ptr<RemoteSession> &props);
int FileTransferWindow(std::shared_ptr<RemoteSession> &props);
void
DrawConnectionStatusText(std::shared_ptr<RemoteSession> &props);
void
DrawReceivingScreenText(std::shared_ptr<RemoteSession> &props);
bool OpenUrl(const std::string &url);
void Hyperlink(const std::string &label, const std::string &url,
float window_width);
std::string OpenFileDialog(std::string title);
bool MinimizeMainWindowToTray();
// SDL event dispatch and input translation.
void UpdateRenderRect();
void ProcessSdlEvent(const SDL_Event &event);
int ProcessKeyboardEvent(const SDL_Event &event);
int ProcessMouseEvent(const SDL_Event &event);
void CloseTab(decltype(remote_sessions_)::iterator &it);
#if _WIN32 && CROSSDESK_PORTABLE
void CheckPortableWindowsService();
int PortableServiceInstallWindow();
void StartPortableWindowsServiceInstall();
void JoinPortableWindowsServiceInstallThread();
#endif
#ifdef __APPLE__
int RequestPermissionWindow();
bool DrawToggleSwitch(const char *id, bool active, bool enabled);
#endif
};
} // namespace crossdesk
#endif // CROSSDESK_GUI_APPLICATION_H_
+320
View File
@@ -0,0 +1,320 @@
#include "application/gui_application.h"
#include <cstring>
#include <memory>
#include <mutex>
#include <string>
#include <vector>
#include "rd_log.h"
namespace crossdesk {
void GuiApplication::ProcessSdlEvent(const SDL_Event &event) {
if (main_ctx_) {
ImGui::SetCurrentContext(main_ctx_);
ImGui_ImplSDL3_ProcessEvent(&event);
} else {
LOG_ERROR("Main context is null");
return;
}
if (stream_window_inited_) {
if (stream_ctx_) {
ImGui::SetCurrentContext(stream_ctx_);
ImGui_ImplSDL3_ProcessEvent(&event);
} else {
LOG_ERROR("Stream context is null");
return;
}
}
if (server_window_inited_) {
if (server_ctx_) {
ImGui::SetCurrentContext(server_ctx_);
ImGui_ImplSDL3_ProcessEvent(&event);
} else {
LOG_ERROR("Server context is null");
return;
}
}
if (APP_EXIT_EVENT != 0 && event.type == APP_EXIT_EVENT) {
LOG_INFO("Quit program from system tray");
if (stream_window_) {
SDL_SetWindowMouseGrab(stream_window_, false);
}
#if defined(__linux__) && !defined(__APPLE__)
if (tray_) {
tray_->RemoveTrayIcon();
}
#endif
exit_ = true;
return;
}
if (clipboard_.event_type() != 0 && event.type == clipboard_.event_type()) {
clipboard_.ApplyPendingRemoteText();
return;
}
switch (event.type) {
case SDL_EVENT_QUIT:
if (stream_window_inited_) {
LOG_INFO("Destroy stream window");
SDL_SetWindowMouseGrab(stream_window_, false);
DestroyStreamWindow();
DestroyStreamWindowContext();
{
// std::shared_lock lock(remote_sessions_mutex_);
for (auto &[host_name, props] : remote_sessions_) {
std::shared_ptr<std::vector<unsigned char>> frame_snapshot;
int video_width = 0;
int video_height = 0;
{
std::lock_guard<std::mutex> lock(props->video_frame_mutex_);
frame_snapshot = props->front_frame_;
video_width = props->video_width_;
video_height = props->video_height_;
}
if (frame_snapshot && !frame_snapshot->empty() && video_width > 0 &&
video_height > 0) {
thumbnail_->SaveToThumbnail(
(char *)frame_snapshot->data(), video_width, video_height,
host_name, props->remote_host_name_,
props->remember_password_ ? props->remote_password_ : "");
}
if (props->peer_) {
std::string client_id = (host_name == client_id_)
? "C-" + std::string(client_id_)
: client_id_;
LOG_INFO("[{}] Leave connection [{}]", client_id, host_name);
LeaveConnection(props->peer_, host_name.c_str());
LOG_INFO("Destroy peer [{}]", client_id);
DestroyPeer(&props->peer_);
}
props->streaming_ = false;
props->remember_password_ = false;
props->connection_established_ = false;
props->audio_capture_button_pressed_ = false;
memset(&props->net_traffic_stats_, 0,
sizeof(props->net_traffic_stats_));
SDL_SetWindowFullscreen(main_window_, false);
SDL_FlushEvents(STREAM_REFRESH_EVENT, STREAM_REFRESH_EVENT);
memset(audio_buffer_, 0, 720);
}
}
{
// std::unique_lock lock(remote_sessions_mutex_);
remote_sessions_.clear();
}
rejoin_ = false;
is_client_mode_ = false;
reload_recent_connections_ = true;
fullscreen_button_pressed_ = false;
start_keyboard_capturer_ = false;
just_created_ = false;
recent_connection_image_save_time_ = SDL_GetTicks();
} else {
LOG_INFO("Quit program");
exit_ = true;
}
break;
case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
if (stream_window_ &&
event.window.windowID == SDL_GetWindowID(stream_window_)) {
break;
}
if (main_window_ &&
event.window.windowID == SDL_GetWindowID(main_window_) &&
MinimizeMainWindowToTray()) {
break;
}
exit_ = true;
break;
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
if (stream_window_created_ &&
event.window.windowID == SDL_GetWindowID(stream_window_)) {
UpdateRenderRect();
}
break;
case SDL_EVENT_WINDOW_FOCUS_GAINED:
if (stream_window_ &&
SDL_GetWindowID(stream_window_) == event.window.windowID) {
focus_on_stream_window_ = true;
} else if (main_window_ &&
SDL_GetWindowID(main_window_) == event.window.windowID) {
foucs_on_main_window_ = true;
}
break;
case SDL_EVENT_WINDOW_FOCUS_LOST:
if (stream_window_ &&
SDL_GetWindowID(stream_window_) == event.window.windowID) {
keyboard_.ForceReleasePressedKeys();
focus_on_stream_window_ = false;
} else if (main_window_ &&
SDL_GetWindowID(main_window_) == event.window.windowID) {
foucs_on_main_window_ = false;
}
break;
case SDL_EVENT_DROP_FILE:
transfers_.HandleDropEvent(event);
break;
case SDL_EVENT_CLIPBOARD_UPDATE:
clipboard_.HandleLocalUpdate();
break;
case SDL_EVENT_MOUSE_MOTION:
case SDL_EVENT_MOUSE_BUTTON_DOWN:
case SDL_EVENT_MOUSE_BUTTON_UP:
case SDL_EVENT_MOUSE_WHEEL: {
Uint32 mouse_window_id = 0;
if (event.type == SDL_EVENT_MOUSE_MOTION) {
mouse_window_id = event.motion.windowID;
} else if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN ||
event.type == SDL_EVENT_MOUSE_BUTTON_UP) {
mouse_window_id = event.button.windowID;
} else if (event.type == SDL_EVENT_MOUSE_WHEEL) {
mouse_window_id = event.wheel.windowID;
}
if (focus_on_stream_window_ && stream_window_ &&
SDL_GetWindowID(stream_window_) == mouse_window_id) {
ProcessMouseEvent(event);
}
break;
}
case SDL_EVENT_KEY_DOWN:
case SDL_EVENT_KEY_UP:
if (keyboard_capturer_is_started_ && keyboard_capturer_uses_sdl_events_ &&
focus_on_stream_window_ && stream_window_ &&
SDL_GetWindowID(stream_window_) == event.key.windowID) {
ProcessKeyboardEvent(event);
}
break;
default:
if (event.type == STREAM_REFRESH_EVENT) {
auto *props = static_cast<RemoteSession *>(event.user.data1);
if (!props) {
break;
}
std::shared_ptr<std::vector<unsigned char>> frame_snapshot;
int video_width = 0;
int video_height = 0;
bool render_rect_dirty = false;
bool cleanup_pending = false;
{
std::lock_guard<std::mutex> lock(props->video_frame_mutex_);
cleanup_pending = props->stream_cleanup_pending_;
if (!cleanup_pending) {
frame_snapshot = props->front_frame_;
video_width = props->video_width_;
video_height = props->video_height_;
}
render_rect_dirty = props->render_rect_dirty_;
}
if (cleanup_pending) {
if (props->stream_texture_) {
SDL_DestroyTexture(props->stream_texture_);
props->stream_texture_ = nullptr;
}
{
std::lock_guard<std::mutex> lock(props->video_frame_mutex_);
props->stream_cleanup_pending_ = false;
}
if (render_rect_dirty) {
UpdateRenderRect();
std::lock_guard<std::mutex> lock(props->video_frame_mutex_);
props->render_rect_dirty_ = false;
}
break;
}
if (video_width <= 0 || video_height <= 0) {
break;
}
if (!frame_snapshot || frame_snapshot->empty()) {
break;
}
if (props->stream_texture_) {
if (video_width != props->texture_width_ ||
video_height != props->texture_height_) {
props->texture_width_ = video_width;
props->texture_height_ = video_height;
SDL_DestroyTexture(props->stream_texture_);
// props->stream_texture_ = SDL_CreateTexture(
// stream_renderer_, stream_pixformat_,
// SDL_TEXTUREACCESS_STREAMING, props->texture_width_,
// props->texture_height_);
SDL_PropertiesID nvProps = SDL_CreateProperties();
SDL_SetNumberProperty(nvProps, SDL_PROP_TEXTURE_CREATE_WIDTH_NUMBER,
props->texture_width_);
SDL_SetNumberProperty(nvProps, SDL_PROP_TEXTURE_CREATE_HEIGHT_NUMBER,
props->texture_height_);
SDL_SetNumberProperty(nvProps, SDL_PROP_TEXTURE_CREATE_FORMAT_NUMBER,
SDL_PIXELFORMAT_NV12);
SDL_SetNumberProperty(nvProps,
SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER,
SDL_COLORSPACE_BT601_LIMITED);
props->stream_texture_ =
SDL_CreateTextureWithProperties(stream_renderer_, nvProps);
SDL_DestroyProperties(nvProps);
}
} else {
props->texture_width_ = video_width;
props->texture_height_ = video_height;
// props->stream_texture_ = SDL_CreateTexture(
// stream_renderer_, stream_pixformat_,
// SDL_TEXTUREACCESS_STREAMING, props->texture_width_,
// props->texture_height_);
SDL_PropertiesID nvProps = SDL_CreateProperties();
SDL_SetNumberProperty(nvProps, SDL_PROP_TEXTURE_CREATE_WIDTH_NUMBER,
props->texture_width_);
SDL_SetNumberProperty(nvProps, SDL_PROP_TEXTURE_CREATE_HEIGHT_NUMBER,
props->texture_height_);
SDL_SetNumberProperty(nvProps, SDL_PROP_TEXTURE_CREATE_FORMAT_NUMBER,
SDL_PIXELFORMAT_NV12);
SDL_SetNumberProperty(nvProps,
SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER,
SDL_COLORSPACE_BT601_LIMITED);
props->stream_texture_ =
SDL_CreateTextureWithProperties(stream_renderer_, nvProps);
SDL_DestroyProperties(nvProps);
}
SDL_UpdateTexture(props->stream_texture_, NULL, frame_snapshot->data(),
props->texture_width_);
if (render_rect_dirty) {
UpdateRenderRect();
std::lock_guard<std::mutex> lock(props->video_frame_mutex_);
props->render_rect_dirty_ = false;
}
}
break;
}
}
} // namespace crossdesk
+415
View File
@@ -0,0 +1,415 @@
#include "application/gui_application.h"
#include <algorithm>
#include <cmath>
#include <string>
#include "device_controller.h"
#include "rd_log.h"
namespace crossdesk {
namespace {
int TranslateSdlKeypadScancodeToVk(const SDL_KeyboardEvent &event) {
const bool numlock_enabled = (event.mod & SDL_KMOD_NUM) != 0;
switch (event.scancode) {
case SDL_SCANCODE_NUMLOCKCLEAR:
return 0x90;
case SDL_SCANCODE_KP_ENTER:
return 0x0D;
case SDL_SCANCODE_KP_0:
if (!numlock_enabled) {
return 0x2D;
}
return 0x60;
case SDL_SCANCODE_KP_1:
if (!numlock_enabled) {
return 0x23;
}
return 0x61;
case SDL_SCANCODE_KP_2:
if (!numlock_enabled) {
return 0x28;
}
return 0x62;
case SDL_SCANCODE_KP_3:
if (!numlock_enabled) {
return 0x22;
}
return 0x63;
case SDL_SCANCODE_KP_4:
if (!numlock_enabled) {
return 0x25;
}
return 0x64;
case SDL_SCANCODE_KP_5:
return 0x65;
case SDL_SCANCODE_KP_6:
if (!numlock_enabled) {
return 0x27;
}
return 0x66;
case SDL_SCANCODE_KP_7:
if (!numlock_enabled) {
return 0x24;
}
return 0x67;
case SDL_SCANCODE_KP_8:
if (!numlock_enabled) {
return 0x26;
}
return 0x68;
case SDL_SCANCODE_KP_9:
if (!numlock_enabled) {
return 0x21;
}
return 0x69;
case SDL_SCANCODE_KP_PERIOD:
case SDL_SCANCODE_KP_COMMA:
if (!numlock_enabled) {
return 0x2E;
}
return 0x6E;
case SDL_SCANCODE_KP_DIVIDE:
return 0x6F;
case SDL_SCANCODE_KP_MULTIPLY:
return 0x6A;
case SDL_SCANCODE_KP_MINUS:
return 0x6D;
case SDL_SCANCODE_KP_PLUS:
return 0x6B;
case SDL_SCANCODE_KP_EQUALS:
return 0xBB;
default:
return -1;
}
}
int TranslateSdlKeyboardEventToVk(const SDL_KeyboardEvent &event) {
const int keypad_key_code = TranslateSdlKeypadScancodeToVk(event);
if (keypad_key_code >= 0) {
return keypad_key_code;
}
const int key = static_cast<int>(event.key);
if (key >= 'a' && key <= 'z') {
return key - 'a' + 0x41;
}
if (key >= 'A' && key <= 'Z') {
return key;
}
if (key >= '0' && key <= '9') {
return key;
}
switch (key) {
case ';':
return 0xBA;
case '\'':
return 0xDE;
case '`':
return 0xC0;
case ',':
return 0xBC;
case '.':
return 0xBE;
case '/':
return 0xBF;
case '\\':
return 0xDC;
case '[':
return 0xDB;
case ']':
return 0xDD;
case '-':
return 0xBD;
case '=':
return 0xBB;
default:
break;
}
switch (event.scancode) {
case SDL_SCANCODE_ESCAPE:
return 0x1B;
case SDL_SCANCODE_RETURN:
return 0x0D;
case SDL_SCANCODE_SPACE:
return 0x20;
case SDL_SCANCODE_BACKSPACE:
return 0x08;
case SDL_SCANCODE_TAB:
return 0x09;
case SDL_SCANCODE_PRINTSCREEN:
return 0x2C;
case SDL_SCANCODE_SCROLLLOCK:
return 0x91;
case SDL_SCANCODE_PAUSE:
return 0x13;
case SDL_SCANCODE_INSERT:
return 0x2D;
case SDL_SCANCODE_DELETE:
return 0x2E;
case SDL_SCANCODE_HOME:
return 0x24;
case SDL_SCANCODE_END:
return 0x23;
case SDL_SCANCODE_PAGEUP:
return 0x21;
case SDL_SCANCODE_PAGEDOWN:
return 0x22;
case SDL_SCANCODE_LEFT:
return 0x25;
case SDL_SCANCODE_RIGHT:
return 0x27;
case SDL_SCANCODE_UP:
return 0x26;
case SDL_SCANCODE_DOWN:
return 0x28;
case SDL_SCANCODE_F1:
return 0x70;
case SDL_SCANCODE_F2:
return 0x71;
case SDL_SCANCODE_F3:
return 0x72;
case SDL_SCANCODE_F4:
return 0x73;
case SDL_SCANCODE_F5:
return 0x74;
case SDL_SCANCODE_F6:
return 0x75;
case SDL_SCANCODE_F7:
return 0x76;
case SDL_SCANCODE_F8:
return 0x77;
case SDL_SCANCODE_F9:
return 0x78;
case SDL_SCANCODE_F10:
return 0x79;
case SDL_SCANCODE_F11:
return 0x7A;
case SDL_SCANCODE_F12:
return 0x7B;
case SDL_SCANCODE_CAPSLOCK:
return 0x14;
case SDL_SCANCODE_LSHIFT:
return 0xA0;
case SDL_SCANCODE_RSHIFT:
return 0xA1;
case SDL_SCANCODE_LCTRL:
return 0xA2;
case SDL_SCANCODE_RCTRL:
return 0xA3;
case SDL_SCANCODE_LALT:
return 0xA4;
case SDL_SCANCODE_RALT:
return 0xA5;
case SDL_SCANCODE_LGUI:
return 0x5B;
case SDL_SCANCODE_RGUI:
return 0x5C;
default:
return -1;
}
}
} // namespace
int GuiApplication::ProcessKeyboardEvent(const SDL_Event &event) {
if (event.type != SDL_EVENT_KEY_DOWN && event.type != SDL_EVENT_KEY_UP) {
return -1;
}
if (event.type == SDL_EVENT_KEY_DOWN && event.key.repeat) {
return 0;
}
const int key_code = TranslateSdlKeyboardEventToVk(event.key);
if (key_code < 0) {
return 0;
}
return keyboard_.SendKeyCommand(key_code, event.type == SDL_EVENT_KEY_DOWN);
}
int GuiApplication::ProcessMouseEvent(const SDL_Event &event) {
controlled_remote_id_ = "";
RemoteAction remote_action{};
float cursor_x = last_mouse_event.motion.x;
float cursor_y = last_mouse_event.motion.y;
auto normalize_cursor_to_window_space = [&](float *x, float *y) {
if (!x || !y || !stream_window_) {
return;
}
int window_width = 0;
int window_height = 0;
int pixel_width = 0;
int pixel_height = 0;
SDL_GetWindowSize(stream_window_, &window_width, &window_height);
SDL_GetWindowSizeInPixels(stream_window_, &pixel_width, &pixel_height);
if (window_width <= 0 || window_height <= 0 || pixel_width <= 0 ||
pixel_height <= 0) {
return;
}
if ((window_width != pixel_width || window_height != pixel_height) &&
(*x > static_cast<float>(window_width) + 1.0f ||
*y > static_cast<float>(window_height) + 1.0f)) {
const float scale_x =
static_cast<float>(window_width) / static_cast<float>(pixel_width);
const float scale_y =
static_cast<float>(window_height) / static_cast<float>(pixel_height);
*x *= scale_x;
*y *= scale_y;
static bool logged_pixel_to_window_conversion = false;
if (!logged_pixel_to_window_conversion) {
LOG_INFO(
"Mouse coordinate space converted from pixels to window units: "
"window={}x{}, pixels={}x{}, scale=({:.4f},{:.4f})",
window_width, window_height, pixel_width, pixel_height, scale_x,
scale_y);
logged_pixel_to_window_conversion = true;
}
}
};
if (event.type == SDL_EVENT_MOUSE_MOTION) {
cursor_x = event.motion.x;
cursor_y = event.motion.y;
normalize_cursor_to_window_space(&cursor_x, &cursor_y);
} else if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN ||
event.type == SDL_EVENT_MOUSE_BUTTON_UP) {
cursor_x = event.button.x;
cursor_y = event.button.y;
normalize_cursor_to_window_space(&cursor_x, &cursor_y);
} else if (event.type == SDL_EVENT_MOUSE_WHEEL) {
cursor_x = last_mouse_event.motion.x;
cursor_y = last_mouse_event.motion.y;
}
const bool is_pointer_position_event =
(event.type == SDL_EVENT_MOUSE_MOTION ||
event.type == SDL_EVENT_MOUSE_BUTTON_DOWN ||
event.type == SDL_EVENT_MOUSE_BUTTON_UP);
// std::shared_lock lock(remote_sessions_mutex_);
for (auto &it : remote_sessions_) {
auto props = it.second;
if (!props->control_mouse_) {
continue;
}
const bool file_transfer_window_hovered =
props->file_transfer_.file_transfer_window_hovered_;
const bool overlay_hovered =
props->control_bar_hovered_ || props->display_selectable_hovered_ ||
props->shortcut_selectable_hovered_ || file_transfer_window_hovered;
const SDL_FRect render_rect = props->stream_render_rect_f_;
if (render_rect.w <= 1.0f || render_rect.h <= 1.0f) {
continue;
}
if (is_pointer_position_event && cursor_x >= render_rect.x &&
cursor_x <= render_rect.x + render_rect.w &&
cursor_y >= render_rect.y &&
cursor_y <= render_rect.y + render_rect.h) {
controlled_remote_id_ = it.first;
last_mouse_event.motion.x = cursor_x;
last_mouse_event.motion.y = cursor_y;
last_mouse_event.button.x = cursor_x;
last_mouse_event.button.y = cursor_y;
remote_action.m.x = (cursor_x - render_rect.x) / render_rect.w;
remote_action.m.y = (cursor_y - render_rect.y) / render_rect.h;
remote_action.m.x = std::clamp(remote_action.m.x, 0.0f, 1.0f);
remote_action.m.y = std::clamp(remote_action.m.y, 0.0f, 1.0f);
if (SDL_EVENT_MOUSE_BUTTON_DOWN == event.type) {
remote_action.type = ControlType::mouse;
if (SDL_BUTTON_LEFT == event.button.button) {
remote_action.m.flag = MouseFlag::left_down;
} else if (SDL_BUTTON_RIGHT == event.button.button) {
remote_action.m.flag = MouseFlag::right_down;
} else if (SDL_BUTTON_MIDDLE == event.button.button) {
remote_action.m.flag = MouseFlag::middle_down;
}
} else if (SDL_EVENT_MOUSE_BUTTON_UP == event.type) {
remote_action.type = ControlType::mouse;
if (SDL_BUTTON_LEFT == event.button.button) {
remote_action.m.flag = MouseFlag::left_up;
} else if (SDL_BUTTON_RIGHT == event.button.button) {
remote_action.m.flag = MouseFlag::right_up;
} else if (SDL_BUTTON_MIDDLE == event.button.button) {
remote_action.m.flag = MouseFlag::middle_up;
}
} else if (SDL_EVENT_MOUSE_MOTION == event.type) {
remote_action.type = ControlType::mouse;
remote_action.m.flag = MouseFlag::move;
}
if (overlay_hovered) {
break;
}
if (props->peer_) {
std::string msg = remote_action.to_json();
SendDataFrame(props->peer_, msg.c_str(), msg.size(),
props->mouse_label_.c_str());
}
} else if (SDL_EVENT_MOUSE_WHEEL == event.type &&
last_mouse_event.button.x >= render_rect.x &&
last_mouse_event.button.x <= render_rect.x + render_rect.w &&
last_mouse_event.button.y >= render_rect.y &&
last_mouse_event.button.y <= render_rect.y + render_rect.h) {
float scroll_x = event.wheel.x;
float scroll_y = event.wheel.y;
if (event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) {
scroll_x = -scroll_x;
scroll_y = -scroll_y;
}
remote_action.type = ControlType::mouse;
auto roundUp = [](float value) -> int {
if (value > 0) {
return static_cast<int>(std::ceil(value));
} else if (value < 0) {
return static_cast<int>(std::floor(value));
}
return 0;
};
if (std::abs(scroll_y) >= std::abs(scroll_x)) {
remote_action.m.flag = MouseFlag::wheel_vertical;
remote_action.m.s = roundUp(scroll_y);
} else {
remote_action.m.flag = MouseFlag::wheel_horizontal;
remote_action.m.s = roundUp(scroll_x);
}
remote_action.m.x = (last_mouse_event.button.x - render_rect.x) /
(std::max)(render_rect.w, 1.0f);
remote_action.m.y = (last_mouse_event.button.y - render_rect.y) /
(std::max)(render_rect.h, 1.0f);
remote_action.m.x = std::clamp(remote_action.m.x, 0.0f, 1.0f);
remote_action.m.y = std::clamp(remote_action.m.y, 0.0f, 1.0f);
if (overlay_hovered) {
continue;
}
if (props->peer_) {
std::string msg = remote_action.to_json();
SendDataFrame(props->peer_, msg.c_str(), msg.size(),
props->mouse_label_.c_str());
}
}
}
return 0;
}
} // namespace crossdesk
+687
View File
@@ -0,0 +1,687 @@
#include "application/gui_application.h"
#include <libyuv.h>
#if defined(__linux__) && !defined(__APPLE__)
#include <X11/Xatom.h>
#include <X11/Xlib.h>
#endif
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <memory>
#include <mutex>
#include <shared_mutex>
#include <string>
#include <thread>
#include "fa_regular_400.h"
#include "fa_solid_900.h"
#include "layout_relative.h"
#include "localization.h"
#include "rd_log.h"
#include "version_checker.h"
#if defined(__APPLE__)
#include "window_util_mac.h"
#endif
namespace crossdesk {
namespace {
const ImWchar *GetMultilingualGlyphRanges() {
static std::vector<ImWchar> glyph_ranges;
if (glyph_ranges.empty()) {
ImGuiIO &io = ImGui::GetIO();
ImFontGlyphRangesBuilder builder;
builder.AddRanges(io.Fonts->GetGlyphRangesDefault());
builder.AddRanges(io.Fonts->GetGlyphRangesChineseFull());
builder.AddRanges(io.Fonts->GetGlyphRangesCyrillic());
ImVector<ImWchar> built_ranges;
builder.BuildRanges(&built_ranges);
glyph_ranges.assign(built_ranges.Data,
built_ranges.Data + built_ranges.Size);
}
return glyph_ranges.empty() ? nullptr : glyph_ranges.data();
}
bool CanReadFontFile(const char *font_path) {
if (!font_path) {
return false;
}
std::ifstream font_file(font_path, std::ios::binary);
return font_file.good();
}
#if _WIN32
HICON LoadTrayIcon() {
HMODULE module = GetModuleHandleW(nullptr);
HICON icon = reinterpret_cast<HICON>(
LoadImageW(module, L"IDI_ICON1", IMAGE_ICON, 0, 0, LR_DEFAULTSIZE));
if (icon) {
return icon;
}
return LoadIconW(nullptr, IDI_APPLICATION);
}
#endif
#if defined(__linux__) && !defined(__APPLE__)
inline bool X11GetDisplayAndWindow(SDL_Window *window, Display **display_out,
::Window *x11_window_out) {
if (!window || !display_out || !x11_window_out) {
return false;
}
#if !defined(SDL_PROP_WINDOW_X11_DISPLAY_POINTER) || \
!defined(SDL_PROP_WINDOW_X11_WINDOW_NUMBER)
// SDL build does not expose X11 window properties.
return false;
#else
SDL_PropertiesID props = SDL_GetWindowProperties(window);
Display *display = (Display *)SDL_GetPointerProperty(
props, SDL_PROP_WINDOW_X11_DISPLAY_POINTER, NULL);
const Sint64 x11_window_num =
SDL_GetNumberProperty(props, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0);
const ::Window x11_window = (::Window)x11_window_num;
if (!display || !x11_window) {
return false;
}
*display_out = display;
*x11_window_out = x11_window;
return true;
#endif
}
inline void X11SendNetWmState(Display *display, ::Window x11_window,
long action, Atom state1, Atom state2 = 0) {
if (!display || !x11_window) {
return;
}
const Atom wm_state = XInternAtom(display, "_NET_WM_STATE", False);
XEvent event;
memset(&event, 0, sizeof(event));
event.xclient.type = ClientMessage;
event.xclient.serial = 0;
event.xclient.send_event = True;
event.xclient.message_type = wm_state;
event.xclient.window = x11_window;
event.xclient.format = 32;
event.xclient.data.l[0] = action;
event.xclient.data.l[1] = (long)state1;
event.xclient.data.l[2] = (long)state2;
event.xclient.data.l[3] = 1; // normal source indication
event.xclient.data.l[4] = 0;
XSendEvent(display, DefaultRootWindow(display), False,
SubstructureRedirectMask | SubstructureNotifyMask, &event);
}
inline void X11SetWindowTypeUtility(Display *display, ::Window x11_window) {
if (!display || !x11_window) {
return;
}
const Atom wm_window_type =
XInternAtom(display, "_NET_WM_WINDOW_TYPE", False);
const Atom wm_window_type_utility =
XInternAtom(display, "_NET_WM_WINDOW_TYPE_UTILITY", False);
XChangeProperty(display, x11_window, wm_window_type, XA_ATOM, 32,
PropModeReplace, (unsigned char *)&wm_window_type_utility, 1);
}
inline void X11SetWindowAlwaysOnTop(SDL_Window *window) {
Display *display = nullptr;
::Window x11_window = 0;
if (!X11GetDisplayAndWindow(window, &display, &x11_window)) {
return;
}
const Atom state_above = XInternAtom(display, "_NET_WM_STATE_ABOVE", False);
const Atom state_stays_on_top =
XInternAtom(display, "_NET_WM_STATE_STAYS_ON_TOP", False);
// Request _NET_WM_STATE_ADD for ABOVE + STAYS_ON_TOP.
X11SendNetWmState(display, x11_window, 1, state_above, state_stays_on_top);
XFlush(display);
}
inline void X11SetWindowSkipTaskbar(SDL_Window *window) {
Display *display = nullptr;
::Window x11_window = 0;
if (!X11GetDisplayAndWindow(window, &display, &x11_window)) {
return;
}
const Atom skip_taskbar =
XInternAtom(display, "_NET_WM_STATE_SKIP_TASKBAR", False);
const Atom skip_pager =
XInternAtom(display, "_NET_WM_STATE_SKIP_PAGER", False);
// Request _NET_WM_STATE_ADD for SKIP_TASKBAR + SKIP_PAGER.
X11SendNetWmState(display, x11_window, 1, skip_taskbar, skip_pager);
// Hint the WM that this is an auxiliary/utility window.
X11SetWindowTypeUtility(display, x11_window);
XFlush(display);
}
#endif
} // namespace
SDL_HitTestResult GuiApplication::HitTestCallback(SDL_Window *window,
const SDL_Point *area,
void *data) {
GuiApplication *application = (GuiApplication *)data;
if (!application) {
return SDL_HITTEST_NORMAL;
}
if (application->fullscreen_button_pressed_) {
return SDL_HITTEST_NORMAL;
}
// Server window: OS-level dragging for the title bar, but keep the left-side
// collapse/expand button clickable.
if (application->server_window_ && window == application->server_window_) {
const float title_h = application->server_window_title_bar_height_;
const float button_w = title_h;
if (area->y >= 0 && area->y < title_h) {
if (area->x >= 0 && area->x < button_w) {
return SDL_HITTEST_NORMAL;
}
return SDL_HITTEST_DRAGGABLE;
}
return SDL_HITTEST_NORMAL;
}
int window_width, window_height;
SDL_GetWindowSize(window, &window_width, &window_height);
// check if curosor is in tab bar
if (application->stream_window_inited_ && application->stream_window_created_ &&
!application->fullscreen_button_pressed_ && application->stream_ctx_) {
ImGuiContext *prev_ctx = ImGui::GetCurrentContext();
ImGui::SetCurrentContext(application->stream_ctx_);
ImGuiWindow *tab_bar_window = ImGui::FindWindowByName("TabBar");
if (tab_bar_window && tab_bar_window->Active) {
ImGuiIO &io = ImGui::GetIO();
float scale_x = io.DisplayFramebufferScale.x;
float scale_y = io.DisplayFramebufferScale.y;
float tab_bar_x = tab_bar_window->Pos.x * scale_x;
float tab_bar_y = tab_bar_window->Pos.y * scale_y;
float tab_bar_width = tab_bar_window->Size.x * scale_x;
float tab_bar_height = tab_bar_window->Size.y * scale_y;
ImGui::SetCurrentContext(prev_ctx);
if (area->x >= tab_bar_x && area->x <= tab_bar_x + tab_bar_width &&
area->y >= tab_bar_y && area->y <= tab_bar_y + tab_bar_height) {
return SDL_HITTEST_NORMAL;
}
} else {
ImGui::SetCurrentContext(prev_ctx);
}
}
float mouse_grab_padding = application->title_bar_button_width_ * 0.16f;
if (area->y < application->title_bar_button_width_ &&
area->y > mouse_grab_padding &&
area->x < window_width - application->title_bar_button_width_ * 3.0f &&
area->x > mouse_grab_padding) {
return SDL_HITTEST_DRAGGABLE;
}
// if (!application->streaming_) {
// return SDL_HITTEST_NORMAL;
// }
if (area->y < mouse_grab_padding) {
if (area->x < mouse_grab_padding) {
return SDL_HITTEST_RESIZE_TOPLEFT;
} else if (area->x > window_width - mouse_grab_padding) {
return SDL_HITTEST_RESIZE_TOPRIGHT;
} else {
return SDL_HITTEST_RESIZE_TOP;
}
} else if (area->y > window_height - mouse_grab_padding) {
if (area->x < mouse_grab_padding) {
return SDL_HITTEST_RESIZE_BOTTOMLEFT;
} else if (area->x > window_width - mouse_grab_padding) {
return SDL_HITTEST_RESIZE_BOTTOMRIGHT;
} else {
return SDL_HITTEST_RESIZE_BOTTOM;
}
} else if (area->x < mouse_grab_padding) {
return SDL_HITTEST_RESIZE_LEFT;
} else if (area->x > window_width - mouse_grab_padding) {
return SDL_HITTEST_RESIZE_RIGHT;
}
return SDL_HITTEST_NORMAL;
}
int GuiApplication::CreateMainWindow() {
main_ctx_ = ImGui::CreateContext();
if (!main_ctx_) {
LOG_ERROR("Main context is null");
return -1;
}
ImGui::SetCurrentContext(main_ctx_);
if (!SDL_CreateWindowAndRenderer(
"CrossDesk Main Window", (int)main_window_width_,
(int)main_window_height_,
SDL_WINDOW_HIGH_PIXEL_DENSITY | SDL_WINDOW_BORDERLESS |
SDL_WINDOW_HIDDEN | SDL_WINDOW_TRANSPARENT,
&main_window_, &main_renderer_)) {
LOG_ERROR("Error creating MainWindow and MainRenderer: {}", SDL_GetError());
return -1;
}
float dpi_scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay());
if (std::abs(dpi_scale_ - dpi_scale) > 0.01f) {
dpi_scale_ = dpi_scale;
main_window_width_ = (int)(main_window_width_default_ * dpi_scale_);
main_window_height_ = (int)(main_window_height_default_ * dpi_scale_);
stream_window_width_ = (int)(stream_window_width_default_ * dpi_scale_);
stream_window_height_ = (int)(stream_window_height_default_ * dpi_scale_);
server_window_width_ = (int)(server_window_width_default_ * dpi_scale_);
server_window_height_ = (int)(server_window_height_default_ * dpi_scale_);
server_window_normal_width_ =
(int)(server_window_width_default_ * dpi_scale_);
server_window_normal_height_ =
(int)(server_window_height_default_ * dpi_scale_);
window_rounding_ = window_rounding_default_ * dpi_scale_;
SDL_SetWindowSize(main_window_, (int)main_window_width_,
(int)main_window_height_);
}
SDL_SetWindowResizable(main_window_, false);
// for window region action
SDL_SetWindowHitTest(main_window_, HitTestCallback, this);
SDL_SetRenderDrawBlendMode(main_renderer_, SDL_BLENDMODE_BLEND);
SetupFontAndStyle(&main_windows_system_chinese_font_);
ImGuiStyle &style = ImGui::GetStyle();
style.ScaleAllSizes(dpi_scale_);
style.FontScaleDpi = dpi_scale_;
#if _WIN32
SDL_PropertiesID props = SDL_GetWindowProperties(main_window_);
HWND main_hwnd = (HWND)SDL_GetPointerProperty(
props, SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL);
HICON tray_icon = LoadTrayIcon();
tray_ = std::make_unique<WinTray>(main_hwnd, tray_icon, L"CrossDesk",
localization_language_index_);
#elif defined(__APPLE__)
tray_ = std::make_unique<MacTray>(main_window_, "CrossDesk",
localization_language_index_);
#elif defined(__linux__) && !defined(__APPLE__)
tray_ = std::make_unique<LinuxTray>(
main_window_, "CrossDesk", localization_language_index_, APP_EXIT_EVENT);
#endif
ImGui_ImplSDL3_InitForSDLRenderer(main_window_, main_renderer_);
ImGui_ImplSDLRenderer3_Init(main_renderer_);
return 0;
}
int GuiApplication::DestroyMainWindow() {
if (main_ctx_) {
ImGui::SetCurrentContext(main_ctx_);
}
if (main_renderer_) {
SDL_DestroyRenderer(main_renderer_);
}
if (main_window_) {
SDL_DestroyWindow(main_window_);
}
return 0;
}
int GuiApplication::CreateStreamWindow() {
if (stream_window_created_) {
return 0;
}
stream_window_width_ = (int)(stream_window_width_default_ * dpi_scale_);
stream_window_height_ = (int)(stream_window_height_default_ * dpi_scale_);
stream_ctx_ = ImGui::CreateContext();
if (!stream_ctx_) {
LOG_ERROR("Stream context is null");
return -1;
}
ImGui::SetCurrentContext(stream_ctx_);
if (!SDL_CreateWindowAndRenderer(
"CrossDesk Stream Window", (int)stream_window_width_,
(int)stream_window_height_,
SDL_WINDOW_HIGH_PIXEL_DENSITY | SDL_WINDOW_BORDERLESS |
SDL_WINDOW_TRANSPARENT,
&stream_window_, &stream_renderer_)) {
LOG_ERROR("Error creating stream_window_ and stream_renderer_: {}",
SDL_GetError());
return -1;
}
stream_pixformat_ = SDL_PIXELFORMAT_NV12;
SDL_SetWindowResizable(stream_window_, true);
// for window region action
SDL_SetWindowHitTest(stream_window_, HitTestCallback, this);
SDL_SetRenderDrawBlendMode(stream_renderer_, SDL_BLENDMODE_BLEND);
SetupFontAndStyle(&stream_windows_system_chinese_font_);
ImGuiStyle &style = ImGui::GetStyle();
style.ScaleAllSizes(dpi_scale_);
style.FontScaleDpi = dpi_scale_;
ImGui_ImplSDL3_InitForSDLRenderer(stream_window_, stream_renderer_);
ImGui_ImplSDLRenderer3_Init(stream_renderer_);
// change props->stream_render_rect_
SDL_Event event;
event.type = SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED;
event.window.windowID = SDL_GetWindowID(stream_window_);
SDL_PushEvent(&event);
stream_window_created_ = true;
just_created_ = true;
stream_window_inited_ = true;
LOG_INFO("Stream window inited");
return 0;
}
int GuiApplication::DestroyStreamWindow() {
stream_window_width_ = (float)stream_window_width_default_;
stream_window_height_ = (float)stream_window_height_default_;
if (stream_ctx_) {
ImGui::SetCurrentContext(stream_ctx_);
}
if (stream_renderer_) {
SDL_DestroyRenderer(stream_renderer_);
stream_renderer_ = nullptr;
}
if (stream_window_) {
SDL_DestroyWindow(stream_window_);
stream_window_ = nullptr;
}
stream_window_created_ = false;
focus_on_stream_window_ = false;
stream_window_grabbed_ = false;
control_mouse_ = false;
return 0;
}
int GuiApplication::CreateServerWindow() {
if (server_window_created_) {
return 0;
}
server_ctx_ = ImGui::CreateContext();
if (!server_ctx_) {
LOG_ERROR("Server context is null");
return -1;
}
ImGui::SetCurrentContext(server_ctx_);
if (!SDL_CreateWindowAndRenderer(
"CrossDesk 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;
}
#if _WIN32
// Hide server window from the taskbar by making it a tool window.
{
SDL_PropertiesID server_props = SDL_GetWindowProperties(server_window_);
HWND server_hwnd = (HWND)SDL_GetPointerProperty(
server_props, SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL);
if (server_hwnd) {
LONG_PTR ex_style = GetWindowLongPtr(server_hwnd, GWL_EXSTYLE);
ex_style |= WS_EX_TOOLWINDOW;
ex_style &= ~WS_EX_APPWINDOW;
SetWindowLongPtr(server_hwnd, GWL_EXSTYLE, ex_style);
// Keep the server window above normal windows.
SetWindowPos(server_hwnd, HWND_TOPMOST, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED | SWP_NOACTIVATE);
}
}
#endif
#if defined(__linux__) && !defined(__APPLE__)
// Best-effort keep above other windows on X11.
X11SetWindowAlwaysOnTop(server_window_);
// Best-effort hide from taskbar on X11.
X11SetWindowSkipTaskbar(server_window_);
#endif
#if defined(__APPLE__)
// Best-effort keep above other windows on macOS.
MacSetWindowAlwaysOnTop(server_window_, true);
// Best-effort exclude from Window menu / window cycling.
MacSetWindowExcludedFromWindowMenu(server_window_, true);
#endif
// 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(&server_windows_system_chinese_font_);
ImGuiStyle &style = ImGui::GetStyle();
style.ScaleAllSizes(dpi_scale_);
style.FontScaleDpi = dpi_scale_;
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;
}
int GuiApplication::DestroyServerWindow() {
if (server_ctx_) {
ImGui::SetCurrentContext(server_ctx_);
}
if (server_renderer_) {
SDL_DestroyRenderer(server_renderer_);
server_renderer_ = nullptr;
}
if (server_window_) {
SDL_DestroyWindow(server_window_);
server_window_ = nullptr;
}
server_window_created_ = false;
server_window_inited_ = false;
return 0;
}
int GuiApplication::SetupFontAndStyle(ImFont **system_chinese_font_out) {
float font_size = 32.0f;
// Setup Dear ImGui style
ImGuiIO &io = ImGui::GetIO();
io.IniFilename = NULL; // disable imgui.ini
// Build one merged atlas: UI font + icon font + multilingual fallback fonts.
ImFontConfig config;
config.FontDataOwnedByAtlas = false;
config.MergeMode = false;
if (system_chinese_font_out) {
*system_chinese_font_out = nullptr;
}
ImFont *ui_font = nullptr;
const ImWchar *multilingual_ranges = GetMultilingualGlyphRanges();
#if defined(_WIN32)
const char *base_font_paths[] = {
"C:/Windows/Fonts/msyh.ttc", "C:/Windows/Fonts/msyhbd.ttc",
"C:/Windows/Fonts/segoeui.ttf", "C:/Windows/Fonts/arial.ttf",
"C:/Windows/Fonts/simsun.ttc", nullptr};
#elif defined(__APPLE__)
const char *base_font_paths[] = {
"/System/Library/Fonts/PingFang.ttc",
"/System/Library/Fonts/Supplemental/Arial Unicode.ttf",
"/System/Library/Fonts/Supplemental/Arial.ttf",
"/System/Library/Fonts/SFNS.ttf", nullptr};
#else
const char *base_font_paths[] = {
"/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
"/usr/share/fonts/opentype/noto/NotoSans-Regular.ttf",
"/usr/share/fonts/truetype/wqy/wqy-microhei.ttc",
"/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc",
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
nullptr};
#endif
for (int i = 0; base_font_paths[i] != nullptr && ui_font == nullptr; ++i) {
if (!CanReadFontFile(base_font_paths[i])) {
continue;
}
ui_font = io.Fonts->AddFontFromFileTTF(base_font_paths[i], font_size,
&config, multilingual_ranges);
if (ui_font != nullptr) {
LOG_INFO("Loaded base UI font: {}", base_font_paths[i]);
}
}
if (!ui_font) {
ui_font = io.Fonts->AddFontDefault(&config);
}
if (!ui_font) {
LOG_WARN("Failed to initialize base UI font");
ImGui::StyleColorsLight();
return 0;
}
ImFontConfig icon_config = config;
icon_config.MergeMode = true;
static const ImWchar icon_ranges[] = {ICON_MIN_FA, ICON_MAX_FA, 0};
io.Fonts->AddFontFromMemoryTTF(fa_solid_900_ttf, fa_solid_900_ttf_len,
font_size, &icon_config, icon_ranges);
io.FontDefault = ui_font;
if (system_chinese_font_out) {
*system_chinese_font_out = ui_font;
}
ImGui::StyleColorsLight();
return 0;
}
int GuiApplication::DestroyMainWindowContext() {
if (!main_ctx_) {
return 0;
}
ImGui::SetCurrentContext(main_ctx_);
ImGui_ImplSDLRenderer3_Shutdown();
ImGui_ImplSDL3_Shutdown();
ImGui::DestroyContext(main_ctx_);
main_ctx_ = nullptr;
return 0;
}
int GuiApplication::DestroyStreamWindowContext() {
if (!stream_ctx_) {
stream_window_inited_ = false;
return 0;
}
stream_window_inited_ = false;
ImGui::SetCurrentContext(stream_ctx_);
ImGui_ImplSDLRenderer3_Shutdown();
ImGui_ImplSDL3_Shutdown();
ImGui::DestroyContext(stream_ctx_);
stream_ctx_ = nullptr;
return 0;
}
int GuiApplication::DestroyServerWindowContext() {
if (!server_ctx_) {
server_window_inited_ = false;
return 0;
}
server_window_inited_ = false;
ImGui::SetCurrentContext(server_ctx_);
ImGui_ImplSDLRenderer3_Shutdown();
ImGui_ImplSDL3_Shutdown();
ImGui::DestroyContext(server_ctx_);
server_ctx_ = nullptr;
return 0;
}
} // namespace crossdesk
+236
View File
@@ -0,0 +1,236 @@
#include "application/gui_application.h"
#include <cmath>
#include "rd_log.h"
namespace crossdesk {
int GuiApplication::DrawMainWindow() {
if (!main_ctx_) {
LOG_ERROR("Main context is null");
return -1;
}
ImGui::SetCurrentContext(main_ctx_);
ImGui_ImplSDLRenderer3_NewFrame();
ImGui_ImplSDL3_NewFrame();
ImGui::NewFrame();
ImGuiIO &io = ImGui::GetIO();
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, window_rounding_);
ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always);
ImGui::SetNextWindowSize(ImVec2(io.DisplaySize.x, io.DisplaySize.y),
ImGuiCond_Always);
ImGui::Begin("MainRender", nullptr,
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration |
ImGuiWindowFlags_NoBringToFrontOnFocus |
ImGuiWindowFlags_NoDocking);
ImGui::PopStyleColor();
ImGui::PopStyleVar();
TitleBar(true);
MainWindow();
UpdateNotificationWindow();
#if _WIN32 && CROSSDESK_PORTABLE
PortableServiceInstallWindow();
#endif
#ifdef __APPLE__
if (show_request_permission_window_) {
RequestPermissionWindow();
}
#endif
ImGui::End();
// Rendering
(void)io;
ImGui::Render();
SDL_SetRenderScale(main_renderer_, io.DisplayFramebufferScale.x,
io.DisplayFramebufferScale.y);
SDL_SetRenderDrawColor(main_renderer_, 0, 0, 0, 0);
SDL_RenderClear(main_renderer_);
ImGui_ImplSDLRenderer3_RenderDrawData(ImGui::GetDrawData(), main_renderer_);
SDL_RenderPresent(main_renderer_);
return 0;
}
int GuiApplication::DrawStreamWindow() {
if (!stream_ctx_) {
LOG_ERROR("Stream context is null");
return -1;
}
ImGui::SetCurrentContext(stream_ctx_);
ImGui_ImplSDLRenderer3_NewFrame();
ImGui_ImplSDL3_NewFrame();
ImGui::NewFrame();
StreamWindow();
ImGuiIO &io = ImGui::GetIO();
float stream_title_window_height =
fullscreen_button_pressed_ ? 0 : title_bar_height_;
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
// Set minimum window size to 0 to allow exact height control
ImGui::PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0, 0));
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always);
ImGui::SetNextWindowSize(ImVec2(io.DisplaySize.x, stream_title_window_height),
ImGuiCond_Always);
ImGui::Begin("StreamTitleWindow", nullptr,
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration |
ImGuiWindowFlags_NoBringToFrontOnFocus |
ImGuiWindowFlags_NoDocking);
ImGui::PopStyleVar(2);
ImGui::PopStyleColor();
if (!fullscreen_button_pressed_) {
TitleBar(false);
}
ImGui::End();
// Rendering
(void)io;
ImGui::Render();
SDL_SetRenderScale(stream_renderer_, io.DisplayFramebufferScale.x,
io.DisplayFramebufferScale.y);
SDL_SetRenderDrawColor(stream_renderer_, 0, 0, 0, 255);
SDL_RenderClear(stream_renderer_);
// std::shared_lock lock(remote_sessions_mutex_);
for (auto &it : remote_sessions_) {
auto props = it.second;
if (props->tab_selected_) {
SDL_FRect render_rect_f = {
props->stream_render_rect_f_.x, props->stream_render_rect_f_.y,
props->stream_render_rect_f_.w, props->stream_render_rect_f_.h};
SDL_RenderTexture(stream_renderer_, props->stream_texture_, NULL,
&render_rect_f);
}
}
ImGui_ImplSDLRenderer3_RenderDrawData(ImGui::GetDrawData(), stream_renderer_);
SDL_RenderPresent(stream_renderer_);
return 0;
}
int GuiApplication::DrawServerWindow() {
if (!server_ctx_) {
LOG_ERROR("Server context is null");
return -1;
}
ImGui::SetCurrentContext(server_ctx_);
ImGui_ImplSDLRenderer3_NewFrame();
ImGui_ImplSDL3_NewFrame();
ImGui::NewFrame();
ImGuiIO &io = ImGui::GetIO();
server_window_width_ = io.DisplaySize.x;
server_window_height_ = io.DisplaySize.y;
ServerWindow();
ImGui::Render();
SDL_SetRenderScale(server_renderer_, io.DisplayFramebufferScale.x,
io.DisplayFramebufferScale.y);
SDL_SetRenderDrawColor(server_renderer_, 0, 0, 0, 0);
SDL_RenderClear(server_renderer_);
ImGui_ImplSDLRenderer3_RenderDrawData(ImGui::GetDrawData(), server_renderer_);
SDL_RenderPresent(server_renderer_);
return 0;
}
bool GuiApplication::MinimizeMainWindowToTray() {
if (!enable_minimize_to_tray_ || !main_window_) {
return false;
}
#if defined(_WIN32) || defined(__APPLE__)
if (!tray_) {
return false;
}
tray_->MinimizeToTray();
return true;
#elif defined(__linux__) && !defined(__APPLE__)
if (!tray_) {
return false;
}
return tray_->MinimizeToTray();
#else
return false;
#endif
}
void GuiApplication::UpdateRenderRect() {
// std::shared_lock lock(remote_sessions_mutex_);
for (auto &[_, props] : remote_sessions_) {
if (!props->reset_control_bar_pos_) {
props->mouse_diff_control_bar_pos_x_ = 0;
props->mouse_diff_control_bar_pos_y_ = 0;
}
if (!just_created_) {
props->reset_control_bar_pos_ = true;
}
int stream_window_width, stream_window_height;
SDL_GetWindowSize(stream_window_, &stream_window_width,
&stream_window_height);
stream_window_width_ = (float)stream_window_width;
stream_window_height_ = (float)stream_window_height;
float video_ratio =
(float)props->video_width_ / (float)props->video_height_;
float video_ratio_reverse =
(float)props->video_height_ / (float)props->video_width_;
float render_area_width = props->render_window_width_;
float render_area_height = props->render_window_height_;
props->stream_render_rect_last_ = props->stream_render_rect_;
SDL_FRect rect_f{props->render_window_x_, props->render_window_y_,
render_area_width, render_area_height};
if (render_area_width < render_area_height * video_ratio) {
rect_f.x = props->render_window_x_;
rect_f.y = std::abs(render_area_height -
render_area_width * video_ratio_reverse) /
2.0f +
props->render_window_y_;
rect_f.w = render_area_width;
rect_f.h = render_area_width * video_ratio_reverse;
} else if (render_area_width > render_area_height * video_ratio) {
rect_f.x =
std::abs(render_area_width - render_area_height * video_ratio) /
2.0f +
props->render_window_x_;
rect_f.y = props->render_window_y_;
rect_f.w = render_area_height * video_ratio;
rect_f.h = render_area_height;
} else {
rect_f.x = props->render_window_x_;
rect_f.y = props->render_window_y_;
rect_f.w = render_area_width;
rect_f.h = render_area_height;
}
props->stream_render_rect_f_ = rect_f;
props->stream_render_rect_ = {static_cast<int>(std::lround(rect_f.x)),
static_cast<int>(std::lround(rect_f.y)),
static_cast<int>(std::lround(rect_f.w)),
static_cast<int>(std::lround(rect_f.h))};
}
}
} // namespace crossdesk