/* * @Author: DI JUNKUN * @Date: 2024-05-29 * Copyright (c) 2024 by DI JUNKUN, All Rights Reserved. */ #ifndef _MAIN_WINDOW_H_ #define _MAIN_WINDOW_H_ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "IconsFontAwesome6.h" #include "config_center.h" #include "device_controller_factory.h" #include "imgui.h" #include "imgui_impl_sdl3.h" #include "imgui_impl_sdlrenderer3.h" #include "imgui_internal.h" #include "minirtc.h" #include "path_manager.h" #include "screen_capturer_factory.h" #include "speaker_capturer_factory.h" #include "thumbnail.h" #if _WIN32 #include "win_tray.h" #endif namespace crossdesk { class Render { public: struct FileTransferState { std::atomic file_sending_ = false; std::atomic file_sent_bytes_ = 0; std::atomic file_total_bytes_ = 0; std::atomic file_send_rate_bps_ = 0; std::mutex file_transfer_mutex_; std::chrono::steady_clock::time_point file_send_start_time_; std::chrono::steady_clock::time_point file_send_last_update_time_; uint64_t file_send_last_bytes_ = 0; bool file_transfer_window_visible_ = false; std::atomic current_file_id_{0}; struct QueuedFile { std::filesystem::path file_path; std::string file_label; std::string remote_id; }; std::queue file_send_queue_; std::mutex file_queue_mutex_; enum class FileTransferStatus { Queued, Sending, Completed, Failed }; struct FileTransferInfo { std::string file_name; std::filesystem::path file_path; uint64_t file_size = 0; FileTransferStatus status = FileTransferStatus::Queued; uint64_t sent_bytes = 0; uint32_t file_id = 0; uint32_t rate_bps = 0; }; std::vector file_transfer_list_; std::mutex file_transfer_list_mutex_; }; struct SubStreamWindowProperties { Params params_; PeerPtr* peer_ = nullptr; std::string audio_label_ = "control_audio"; std::string data_label_ = "data"; std::string file_label_ = "file"; std::string control_data_label_ = "control_data"; std::string file_feedback_label_ = "file_feedback"; std::string clipboard_label_ = "clipboard"; std::string local_id_ = ""; std::string remote_id_ = ""; bool exit_ = false; bool signal_connected_ = false; SignalStatus signal_status_ = SignalStatus::SignalClosed; bool connection_established_ = false; bool rejoin_ = false; bool net_traffic_stats_button_pressed_ = false; bool mouse_control_button_pressed_ = true; bool mouse_controller_is_started_ = false; bool audio_capture_button_pressed_ = true; bool control_mouse_ = true; bool streaming_ = false; bool is_control_bar_in_left_ = true; bool control_bar_hovered_ = false; bool display_selectable_hovered_ = false; bool control_bar_expand_ = true; bool reset_control_bar_pos_ = false; bool control_window_width_is_changing_ = false; bool control_window_height_is_changing_ = false; bool p2p_mode_ = true; bool remember_password_ = false; char remote_password_[7] = ""; float sub_stream_window_width_ = 1280; float sub_stream_window_height_ = 720; float control_window_min_width_ = 20; float control_window_max_width_ = 230; float control_window_min_height_ = 38; float control_window_max_height_ = 180; float control_window_width_ = 230; float control_window_height_ = 38; float control_bar_pos_x_ = 0; float control_bar_pos_y_ = 30; float mouse_diff_control_bar_pos_x_ = 0; float mouse_diff_control_bar_pos_y_ = 0; double control_bar_button_pressed_time_ = 0; double net_traffic_stats_button_pressed_time_ = 0; // Double-buffered NV12 frame storage. Written by decode callback thread, // consumed by SDL main thread. std::mutex video_frame_mutex_; std::shared_ptr> front_frame_; std::shared_ptr> back_frame_; bool render_rect_dirty_ = false; bool stream_cleanup_pending_ = false; float mouse_pos_x_ = 0; float mouse_pos_y_ = 0; float mouse_pos_x_last_ = 0; float mouse_pos_y_last_ = 0; int texture_width_ = 1280; int texture_height_ = 720; int video_width_ = 0; int video_height_ = 0; int video_width_last_ = 0; int video_height_last_ = 0; int selected_display_ = 0; size_t video_size_ = 0; bool tab_selected_ = false; bool tab_opened_ = true; std::optional pos_x_before_docked_; std::optional pos_y_before_docked_; float render_window_x_ = 0; float render_window_y_ = 0; float render_window_width_ = 0; float render_window_height_ = 0; std::string fullscreen_button_label_ = "Fullscreen"; std::string net_traffic_stats_button_label_ = "Show Net Traffic Stats"; std::string mouse_control_button_label_ = "Mouse Control"; std::string audio_capture_button_label_ = "Audio Capture"; std::string remote_host_name_ = ""; std::vector display_info_list_; SDL_Texture* stream_texture_ = nullptr; uint8_t* argb_buffer_ = nullptr; int argb_buffer_size_ = 0; SDL_Rect stream_render_rect_; SDL_Rect stream_render_rect_last_; ImVec2 control_window_pos_; ConnectionStatus connection_status_ = ConnectionStatus::Closed; TraversalMode traversal_mode_ = TraversalMode::UnknownMode; int fps_ = 0; int frame_count_ = 0; std::chrono::steady_clock::time_point last_time_; XNetTrafficStats net_traffic_stats_; using QueuedFile = FileTransferState::QueuedFile; using FileTransferStatus = FileTransferState::FileTransferStatus; using FileTransferInfo = FileTransferState::FileTransferInfo; FileTransferState file_transfer_; }; public: Render(); ~Render(); public: int Run(); private: void InitializeLogger(); void InitializeSettings(); void InitializeSDL(); void InitializeModules(); void InitializeMainWindow(); void MainLoop(); void UpdateLabels(); void UpdateInteractions(); void HandleRecentConnections(); void HandleStreamWindow(); void HandleServerWindow(); void Cleanup(); void CleanupFactories(); void CleanupPeer(std::shared_ptr props); void CleanupPeers(); void CleanSubStreamWindowProperties( std::shared_ptr props); void UpdateRenderRect(); void ProcessSdlEvent(const SDL_Event& event); void ProcessFileDropEvent(const SDL_Event& event); void ProcessSelectedFile( const std::string& path, const std::shared_ptr& props, const std::string& file_label, const std::string& remote_id = ""); std::shared_ptr GetSubStreamWindowPropertiesByRemoteId(const std::string& remote_id); private: int CreateStreamRenderWindow(); 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& props); int ControlBar(std::shared_ptr& props); int AboutWindow(); int StatusBar(); bool ConnectionStatusWindow( std::shared_ptr& props); int ShowRecentConnections(); void Hyperlink(const std::string& label, const std::string& url, const float window_width); int FileTransferWindow(std::shared_ptr& props); std::string OpenFileDialog(std::string title); private: int ConnectTo(const std::string& remote_id, const char* password, bool remember_password); 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(); int ConfirmDeleteConnection(); int NetTrafficStats(std::shared_ptr& props); void DrawConnectionStatusText( std::shared_ptr& props); #ifdef __APPLE__ int RequestPermissionWindow(); bool CheckScreenRecordingPermission(); bool CheckAccessibilityPermission(); void OpenScreenRecordingPreferences(); void OpenAccessibilityPreferences(); bool DrawToggleSwitch(const char* id, bool active, bool enabled); #endif public: static void OnReceiveVideoBufferCb(const XVideoFrame* video_frame, const char* user_id, size_t user_id_size, const char* src_id, size_t src_id_size, void* user_data); static void OnReceiveAudioBufferCb(const char* data, size_t size, const char* user_id, size_t user_id_size, const char* src_id, size_t src_id_size, void* user_data); static void OnReceiveDataBufferCb(const char* data, size_t size, const char* user_id, size_t user_id_size, const char* src_id, size_t src_id_size, void* user_data); static void OnSignalStatusCb(SignalStatus status, const char* user_id, size_t user_id_size, void* user_data); static void OnConnectionStatusCb(ConnectionStatus status, const char* user_id, size_t user_id_size, void* user_data); static void OnNetStatusReport(const char* client_id, size_t client_id_size, TraversalMode mode, const XNetTrafficStats* net_traffic_stats, const char* user_id, const size_t user_id_size, void* user_data); static SDL_HitTestResult HitTestCallback(SDL_Window* window, const SDL_Point* area, void* data); static std::vector SerializeRemoteAction(const RemoteAction& action); static bool DeserializeRemoteAction(const char* data, size_t size, RemoteAction& out); static void FreeRemoteAction(RemoteAction& action); private: int SendKeyCommand(int key_code, bool is_down); int ProcessMouseEvent(const SDL_Event& event); static void SdlCaptureAudioIn(void* userdata, Uint8* stream, int len); static void SdlCaptureAudioOut(void* userdata, Uint8* stream, int len); private: int SaveSettingsIntoCacheFile(); int LoadSettingsFromCacheFile(); int ScreenCapturerInit(); int StartScreenCapturer(); int StopScreenCapturer(); int StartSpeakerCapturer(); int StopSpeakerCapturer(); int StartMouseController(); int StopMouseController(); int StartKeyboardCapturer(); int StopKeyboardCapturer(); int CreateConnectionPeer(); // File transfer helper functions void StartFileTransfer(std::shared_ptr props, const std::filesystem::path& file_path, const std::string& file_label, const std::string& remote_id = ""); void ProcessFileQueue(std::shared_ptr props); int AudioDeviceInit(); int AudioDeviceDestroy(); private: struct CDCache { char client_id_with_password[17]; int language; int video_quality; int video_frame_rate; int video_encode_format; bool enable_hardware_video_codec; bool enable_turn; bool enable_srtp; unsigned char key[16]; unsigned char iv[16]; }; struct CDCacheV2 { char client_id_with_password[17]; int language; int video_quality; int video_frame_rate; int video_encode_format; bool enable_hardware_video_codec; bool enable_turn; bool enable_srtp; unsigned char key[16]; unsigned char iv[16]; char self_hosted_id[17]; }; private: CDCache cd_cache_; CDCacheV2 cd_cache_v2_; std::mutex cd_cache_mutex_; std::unique_ptr config_center_; ConfigCenter::LANGUAGE localization_language_ = ConfigCenter::LANGUAGE::CHINESE; std::unique_ptr path_manager_; std::string exec_log_path_; std::string dll_log_path_; std::string cache_path_; int localization_language_index_ = -1; int localization_language_index_last_ = -1; bool modules_inited_ = false; /* ------ all windows property start ------ */ float title_bar_width_ = 640; float title_bar_height_ = 30; float title_bar_button_width_ = 30; float title_bar_button_height_ = 30; /* ------ all windows property end ------ */ /* ------ main window property start ------ */ // thumbnail unsigned char aes128_key_[16]; unsigned char aes128_iv_[16]; std::shared_ptr thumbnail_; // recent connections std::vector> recent_connections_; int recent_connection_image_width_ = 160; int recent_connection_image_height_ = 90; uint32_t recent_connection_image_save_time_ = 0; // main window render 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; // ~60 FPS #if _WIN32 std::unique_ptr tray_; #endif // main window properties nlohmann::json latest_version_info_ = nlohmann::json{}; bool update_available_ = false; std::string latest_version_ = ""; std::string release_notes_ = ""; 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_ = true; bool show_cursor_ = false; bool keyboard_capturer_is_started_ = false; bool foucs_on_main_window_ = false; bool focus_on_stream_window_ = false; bool main_window_minimized_ = false; uint32_t last_main_minimize_request_tick_ = 0; uint32_t last_stream_minimize_request_tick_ = 0; bool audio_capture_ = false; 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; 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_; bool just_created_ = false; std::string controlled_remote_id_ = ""; std::string focused_remote_id_ = ""; std::string remote_client_id_ = ""; SDL_Event last_mouse_event; SDL_AudioStream* output_stream_; uint32_t STREAM_REFRESH_EVENT = 0; // stream window render SDL_Window* stream_window_ = nullptr; SDL_Renderer* stream_renderer_ = nullptr; ImGuiContext* stream_ctx_ = nullptr; // stream window properties 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; // server window render SDL_Window* server_window_ = nullptr; SDL_Renderer* server_renderer_ = nullptr; ImGuiContext* server_ctx_ = nullptr; // 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_ = 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; // server window collapsed mode 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; // server window drag normal mode 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; 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 delete_connection_ = false; bool is_tab_bar_hovered_ = false; std::string delete_connection_name_ = ""; bool re_enter_remote_id_ = false; double copy_start_time_ = 0; SignalStatus signal_status_ = SignalStatus::SignalClosed; std::string signal_status_str_ = ""; bool signal_connected_ = false; PeerPtr* peer_ = nullptr; PeerPtr* peer_reserved_ = nullptr; std::string video_primary_label_ = "primary_display"; std::string video_secondary_label_ = "secondary_display"; std::string audio_label_ = "audio"; std::string data_label_ = "data"; std::string info_label_ = "info"; std::string control_data_label_ = "control_data"; std::string file_label_ = "file"; std::string file_feedback_label_ = "file_feedback"; std::string clipboard_label_ = "clipboard"; Params params_; // Map file_id to props for tracking file transfer progress via ACK std::unordered_map> file_id_to_props_; std::shared_mutex file_id_to_props_mutex_; // Map file_id to FileTransferState for global file transfer (props == null) std::unordered_map file_id_to_transfer_state_; std::shared_mutex file_id_to_transfer_state_mutex_; SDL_AudioDeviceID input_dev_; SDL_AudioDeviceID output_dev_; ScreenCapturerFactory* screen_capturer_factory_ = nullptr; ScreenCapturer* screen_capturer_ = nullptr; SpeakerCapturerFactory* speaker_capturer_factory_ = nullptr; SpeakerCapturer* speaker_capturer_ = nullptr; DeviceControllerFactory* device_controller_factory_ = nullptr; MouseController* mouse_controller_ = nullptr; KeyboardCapturer* keyboard_capturer_ = nullptr; std::vector display_info_list_; uint64_t last_frame_time_; 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; #ifdef __APPLE__ bool show_request_permission_window_ = true; #endif char client_id_[10] = ""; char client_id_display_[12] = ""; char client_id_with_password_[17] = ""; char password_saved_[7] = ""; char self_hosted_id_[17] = ""; char self_hosted_user_id_[17] = ""; int language_button_value_ = 0; int video_quality_button_value_ = 2; int video_frame_rate_button_value_ = 1; int video_encode_format_button_value_ = 0; bool enable_hardware_video_codec_ = true; bool enable_turn_ = true; bool enable_srtp_ = false; char signal_server_ip_[256] = "api.crossdesk.cn"; char signal_server_port_[6] = "9099"; char coturn_server_port_[6] = "3478"; bool enable_self_hosted_ = false; int language_button_value_last_ = 0; int video_quality_button_value_last_ = 0; int video_frame_rate_button_value_last_ = 0; int video_encode_format_button_value_last_ = 0; bool enable_hardware_video_codec_last_ = false; bool enable_turn_last_ = true; bool enable_srtp_last_ = false; bool enable_self_hosted_last_ = false; bool enable_autostart_ = false; bool enable_autostart_last_ = false; bool enable_daemon_ = false; bool enable_daemon_last_ = false; bool enable_minimize_to_tray_ = false; bool enable_minimize_to_tray_last_ = false; char file_transfer_save_path_buf_[512] = ""; std::string file_transfer_save_path_last_ = ""; char signal_server_ip_self_[256] = ""; char signal_server_port_self_[6] = ""; char coturn_server_port_self_[6] = ""; bool settings_window_pos_reset_ = true; bool self_hosted_server_config_window_pos_reset_ = true; std::string selected_current_file_path_ = ""; bool show_file_browser_ = true; /* ------ main window property end ------ */ /* ------ sub stream window property start ------ */ std::unordered_map> client_properties_; std::shared_mutex client_properties_mutex_; void CloseTab(decltype(client_properties_)::iterator& it); /* ------ stream window property end ------ */ /* ------ async thumbnail save tasks ------ */ std::vector thumbnail_save_threads_; std::mutex thumbnail_save_threads_mutex_; void WaitForThumbnailSaveTasks(); /* ------ server mode ------ */ std::unordered_map connection_status_; std::unordered_map connection_host_names_; std::string selected_server_remote_id_ = ""; std::string selected_server_remote_hostname_ = ""; FileTransferState file_transfer_; }; } // namespace crossdesk #endif