#include "render.h" #include #include #include #include #include #include "OPPOSans_Regular.h" #include "device_controller_factory.h" #include "fa_regular_400.h" #include "fa_solid_900.h" #include "layout_style.h" #include "localization.h" #include "platform.h" #include "rd_log.h" #include "screen_capturer_factory.h" #define NV12_BUFFER_SIZE 1280 * 720 * 3 / 2 #define MOUSE_GRAB_PADDING 5 std::vector Render::SerializeRemoteAction(const RemoteAction& action) { std::vector buffer; buffer.push_back(static_cast(action.type)); auto insert_bytes = [&](const void* ptr, size_t len) { buffer.insert(buffer.end(), (const char*)ptr, (const char*)ptr + len); }; if (action.type == ControlType::host_infomation) { insert_bytes(&action.i.host_name_size, sizeof(size_t)); insert_bytes(action.i.host_name, action.i.host_name_size); size_t num = action.i.display_num; insert_bytes(&num, sizeof(size_t)); for (size_t i = 0; i < num; ++i) { size_t len = strlen(action.i.display_list[i]); insert_bytes(&len, sizeof(size_t)); insert_bytes(action.i.display_list[i], len); } insert_bytes(action.i.left, sizeof(int) * num); insert_bytes(action.i.top, sizeof(int) * num); insert_bytes(action.i.right, sizeof(int) * num); insert_bytes(action.i.bottom, sizeof(int) * num); } return buffer; } bool Render::DeserializeRemoteAction(const char* data, size_t size, RemoteAction& out) { size_t offset = 0; auto read = [&](void* dst, size_t len) -> bool { if (offset + len > size) return false; memcpy(dst, data + offset, len); offset += len; return true; }; if (size < 1) return false; out.type = static_cast(data[offset++]); if (out.type == ControlType::host_infomation) { size_t name_len; if (!read(&name_len, sizeof(size_t)) || name_len >= sizeof(out.i.host_name)) return false; if (!read(out.i.host_name, name_len)) return false; out.i.host_name[name_len] = '\0'; out.i.host_name_size = name_len; size_t num; if (!read(&num, sizeof(size_t))) return false; out.i.display_num = num; out.i.display_list = (char**)malloc(num * sizeof(char*)); for (size_t i = 0; i < num; ++i) { size_t len; if (!read(&len, sizeof(size_t))) return false; if (offset + len > size) return false; out.i.display_list[i] = (char*)malloc(len + 1); memcpy(out.i.display_list[i], data + offset, len); out.i.display_list[i][len] = '\0'; offset += len; } auto alloc_int_array = [&](int*& arr) { arr = (int*)malloc(num * sizeof(int)); return read(arr, num * sizeof(int)); }; return alloc_int_array(out.i.left) && alloc_int_array(out.i.top) && alloc_int_array(out.i.right) && alloc_int_array(out.i.bottom); } return true; } void Render::FreeRemoteAction(RemoteAction& action) { if (action.type == ControlType::host_infomation) { for (size_t i = 0; i < action.i.display_num; ++i) { free(action.i.display_list[i]); } free(action.i.display_list); free(action.i.left); free(action.i.top); free(action.i.right); free(action.i.bottom); action.i.display_list = nullptr; action.i.left = action.i.top = action.i.right = action.i.bottom = nullptr; action.i.display_num = 0; } } SDL_HitTestResult Render::HitTestCallback(SDL_Window* window, const SDL_Point* area, void* data) { Render* render = (Render*)data; if (!render) { return SDL_HITTEST_NORMAL; } if (render->fullscreen_button_pressed_) { return SDL_HITTEST_NORMAL; } int window_width, window_height; SDL_GetWindowSize(window, &window_width, &window_height); if (area->y < 30 && area->y > MOUSE_GRAB_PADDING && area->x < window_width - 120 && area->x > MOUSE_GRAB_PADDING && !render->is_tab_bar_hovered_) { return SDL_HITTEST_DRAGGABLE; } // if (!render->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; } Render::Render() {} Render::~Render() {} int Render::SaveSettingsIntoCacheFile() { cd_cache_mutex_.lock(); std::ofstream cd_cache_file(cache_path_ + "/cache.cd", std::ios::binary); if (!cd_cache_file) { cd_cache_mutex_.unlock(); return -1; } memset(&cd_cache_.client_id_with_password, 0, sizeof(cd_cache_.client_id_with_password)); memcpy(cd_cache_.client_id_with_password, client_id_with_password_, sizeof(client_id_with_password_)); memcpy(&cd_cache_.language, &language_button_value_, sizeof(language_button_value_)); memcpy(&cd_cache_.video_quality, &video_quality_button_value_, sizeof(video_quality_button_value_)); memcpy(&cd_cache_.video_encode_format, &video_encode_format_button_value_, sizeof(video_encode_format_button_value_)); memcpy(&cd_cache_.enable_hardware_video_codec, &enable_hardware_video_codec_, sizeof(enable_hardware_video_codec_)); memcpy(&cd_cache_.enable_turn, &enable_turn_, sizeof(enable_turn_)); memcpy(&cd_cache_.key, &aes128_key_, sizeof(aes128_key_)); memcpy(&cd_cache_.iv, &aes128_iv_, sizeof(aes128_iv_)); cd_cache_file.write(reinterpret_cast(&cd_cache_), sizeof(CDCache)); cd_cache_file.close(); cd_cache_mutex_.unlock(); config_center_.SetLanguage((ConfigCenter::LANGUAGE)language_button_value_); config_center_.SetVideoQuality( (ConfigCenter::VIDEO_QUALITY)video_quality_button_value_); config_center_.SetVideoEncodeFormat( (ConfigCenter::VIDEO_ENCODE_FORMAT)video_encode_format_button_value_); config_center_.SetHardwareVideoCodec(enable_hardware_video_codec_); config_center_.SetTurn(enable_turn_); LOG_INFO("Save settings into cache file success"); return 0; } int Render::LoadSettingsFromCacheFile() { cd_cache_mutex_.lock(); std::ifstream cd_cache_file(cache_path_ + "/cache.cd", std::ios::binary); if (!cd_cache_file) { cd_cache_mutex_.unlock(); LOG_INFO("Init cache file by using default settings"); memset(password_saved_, 0, sizeof(password_saved_)); memset(aes128_key_, 0, sizeof(aes128_key_)); memset(aes128_iv_, 0, sizeof(aes128_iv_)); language_button_value_ = 0; video_quality_button_value_ = 0; video_encode_format_button_value_ = 1; enable_hardware_video_codec_ = false; enable_turn_ = false; config_center_.SetLanguage((ConfigCenter::LANGUAGE)language_button_value_); config_center_.SetVideoQuality( (ConfigCenter::VIDEO_QUALITY)video_quality_button_value_); config_center_.SetVideoEncodeFormat( (ConfigCenter::VIDEO_ENCODE_FORMAT)video_encode_format_button_value_); config_center_.SetHardwareVideoCodec(enable_hardware_video_codec_); config_center_.SetTurn(enable_turn_); thumbnail_.reset(); thumbnail_ = std::make_unique(cache_path_ + "/thumbnails/"); thumbnail_->GetKeyAndIv(aes128_key_, aes128_iv_); thumbnail_->DeleteAllFilesInDirectory(); SaveSettingsIntoCacheFile(); return -1; } cd_cache_file.read(reinterpret_cast(&cd_cache_), sizeof(CDCache)); cd_cache_file.close(); cd_cache_mutex_.unlock(); memset(&client_id_with_password_, 0, sizeof(client_id_with_password_)); memcpy(client_id_with_password_, cd_cache_.client_id_with_password, sizeof(client_id_with_password_)); if (strchr(client_id_with_password_, '@') != nullptr) { std::string id, password; const char* at_pos = strchr(client_id_with_password_, '@'); if (at_pos == nullptr) { id = client_id_with_password_; password.clear(); } else { id.assign(client_id_with_password_, at_pos - client_id_with_password_); password = at_pos + 1; } memset(&client_id_, 0, sizeof(client_id_)); strncpy(client_id_, id.c_str(), sizeof(client_id_) - 1); client_id_[sizeof(client_id_) - 1] = '\0'; memset(&password_saved_, 0, sizeof(password_saved_)); strncpy(password_saved_, password.c_str(), sizeof(password_saved_) - 1); password_saved_[sizeof(password_saved_) - 1] = '\0'; } memcpy(aes128_key_, cd_cache_.key, sizeof(cd_cache_.key)); memcpy(aes128_iv_, cd_cache_.iv, sizeof(cd_cache_.iv)); thumbnail_.reset(); thumbnail_ = std::make_unique(cache_path_ + "/thumbnails/", aes128_key_, aes128_iv_); language_button_value_ = cd_cache_.language; video_quality_button_value_ = cd_cache_.video_quality; video_encode_format_button_value_ = cd_cache_.video_encode_format; enable_hardware_video_codec_ = cd_cache_.enable_hardware_video_codec; enable_turn_ = cd_cache_.enable_turn; language_button_value_last_ = language_button_value_; video_quality_button_value_last_ = video_quality_button_value_; video_encode_format_button_value_last_ = video_encode_format_button_value_; enable_hardware_video_codec_last_ = enable_hardware_video_codec_; enable_turn_last_ = enable_turn_; config_center_.SetLanguage((ConfigCenter::LANGUAGE)language_button_value_); config_center_.SetVideoQuality( (ConfigCenter::VIDEO_QUALITY)video_quality_button_value_); config_center_.SetVideoEncodeFormat( (ConfigCenter::VIDEO_ENCODE_FORMAT)video_encode_format_button_value_); config_center_.SetHardwareVideoCodec(enable_hardware_video_codec_); config_center_.SetTurn(enable_turn_); LOG_INFO("Load settings from cache file"); return 0; } int Render::ScreenCapturerInit() { if (screen_capturer_) { LOG_INFO("Screen capturer already initialized"); return 0; } screen_capturer_ = (ScreenCapturer*)screen_capturer_factory_->Create(); last_frame_time_ = std::chrono::duration_cast( std::chrono::steady_clock::now().time_since_epoch()) .count(); int screen_capturer_init_ret = screen_capturer_->Init( 60, [this](unsigned char* data, int size, int width, int height, const char* display_name) -> void { auto now_time = std::chrono::duration_cast( std::chrono::steady_clock::now().time_since_epoch()) .count(); auto duration = now_time - last_frame_time_; if (duration >= 33) { XVideoFrame frame; frame.data = (const char*)data; frame.size = size; frame.width = width; frame.height = height; frame.captured_timestamp = GetSystemTimeMicros(peer_); SendVideoFrame(peer_, &frame, display_name); last_frame_time_ = now_time; } }); if (0 == screen_capturer_init_ret) { LOG_INFO("Init screen capturer success"); if (display_info_list_.empty()) { display_info_list_ = screen_capturer_->GetDisplayInfoList(); } return 0; } else { LOG_ERROR("Init screen capturer failed"); screen_capturer_->Destroy(); delete screen_capturer_; screen_capturer_ = nullptr; return -1; } } int Render::StartScreenCapturer() { if (screen_capturer_) { LOG_INFO("Start screen capturer"); screen_capturer_->Start(); } return 0; } int Render::StopScreenCapturer() { if (screen_capturer_) { LOG_INFO("Stop screen capturer"); screen_capturer_->Stop(); } return 0; } int Render::StartSpeakerCapturer() { if (!speaker_capturer_) { speaker_capturer_ = (SpeakerCapturer*)speaker_capturer_factory_->Create(); int speaker_capturer_init_ret = speaker_capturer_->Init([this](unsigned char* data, size_t size, const char* audio_name) -> void { SendAudioFrame(peer_, (const char*)data, size, audio_label_.c_str()); }); if (0 != speaker_capturer_init_ret) { speaker_capturer_->Destroy(); delete speaker_capturer_; speaker_capturer_ = nullptr; } } if (speaker_capturer_) { speaker_capturer_->Start(); } return 0; } int Render::StopSpeakerCapturer() { if (speaker_capturer_) { speaker_capturer_->Stop(); } return 0; } int Render::StartMouseController() { if (!device_controller_factory_) { LOG_INFO("Device controller factory is nullptr"); return -1; } mouse_controller_ = (MouseController*)device_controller_factory_->Create( DeviceControllerFactory::Device::Mouse); int mouse_controller_init_ret = mouse_controller_->Init(display_info_list_); if (0 != mouse_controller_init_ret) { LOG_INFO("Destroy mouse controller"); mouse_controller_->Destroy(); mouse_controller_ = nullptr; } return 0; } int Render::StopMouseController() { if (mouse_controller_) { mouse_controller_->Destroy(); delete mouse_controller_; mouse_controller_ = nullptr; } return 0; } int Render::StartKeyboardCapturer() { if (!keyboard_capturer_) { LOG_INFO("keyboard capturer is nullptr"); return -1; } int keyboard_capturer_init_ret = keyboard_capturer_->Hook( [](int key_code, bool is_down, void* user_ptr) { if (user_ptr) { Render* render = (Render*)user_ptr; render->SendKeyCommand(key_code, is_down); } }, this); if (0 != keyboard_capturer_init_ret) { LOG_ERROR("Start keyboard capturer failed"); } else { LOG_INFO("Start keyboard capturer"); } return 0; } int Render::StopKeyboardCapturer() { if (keyboard_capturer_) { keyboard_capturer_->Unhook(); LOG_INFO("Stop keyboard capturer"); } return 0; } int Render::CreateConnectionPeer() { params_.use_cfg_file = false; params_.signal_server_ip = "api.crossdesk.cn"; params_.signal_server_port = 9099; params_.stun_server_ip = "150.158.81.30"; params_.stun_server_port = 3478; params_.turn_server_ip = "150.158.81.30"; params_.turn_server_port = 3478; params_.turn_server_username = "dijunkun"; params_.turn_server_password = "dijunkunpw"; params_.tls_cert_path = std::filesystem::exists(cert_path_) ? cert_path_.c_str() : "certs/crossdesk.cn_root.crt"; params_.log_path = dll_log_path_.c_str(); params_.hardware_acceleration = config_center_.IsHardwareVideoCodec(); params_.av1_encoding = config_center_.GetVideoEncodeFormat() == ConfigCenter::VIDEO_ENCODE_FORMAT::AV1 ? true : false; params_.enable_turn = config_center_.IsEnableTurn(); params_.on_receive_video_buffer = nullptr; params_.on_receive_audio_buffer = OnReceiveAudioBufferCb; params_.on_receive_data_buffer = OnReceiveDataBufferCb; params_.on_receive_video_frame = OnReceiveVideoBufferCb; params_.on_signal_status = OnSignalStatusCb; params_.on_connection_status = OnConnectionStatusCb; params_.net_status_report = NetStatusReport; params_.user_id = client_id_with_password_; params_.user_data = this; peer_ = CreatePeer(¶ms_); if (peer_) { LOG_INFO("Create peer instance [{}] successful", client_id_); Init(peer_); LOG_INFO("Peer [{}] init finish", client_id_); } else { LOG_INFO("Create peer [{}] instance failed", client_id_); } if (0 == ScreenCapturerInit()) { for (auto& display_info : display_info_list_) { AddVideoStream(peer_, display_info.name.c_str()); } AddAudioStream(peer_, audio_label_.c_str()); AddDataStream(peer_, data_label_.c_str()); return 0; } else { return -1; } } int Render::AudioDeviceInit() { SDL_AudioSpec desired_in{}; desired_in.freq = 48000; desired_in.format = SDL_AUDIO_S16; desired_in.channels = 1; SDL_AudioSpec desired_out{}; desired_out.freq = 48000; desired_out.format = SDL_AUDIO_S16; desired_out.channels = 1; input_stream_ = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_RECORDING, &desired_in, nullptr, nullptr); if (!input_stream_) { LOG_ERROR("Failed to open input stream: {}", SDL_GetError()); return -1; } output_stream_ = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &desired_out, nullptr, nullptr); if (!output_stream_) { LOG_ERROR("Failed to open output stream: {}", SDL_GetError()); return -1; } SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(input_stream_)); SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(output_stream_)); return 0; } int Render::AudioDeviceDestroy() { if (input_stream_) { SDL_CloseAudioDevice(SDL_GetAudioStreamDevice(input_stream_)); SDL_DestroyAudioStream(input_stream_); input_stream_ = nullptr; } if (output_stream_) { SDL_CloseAudioDevice(SDL_GetAudioStreamDevice(output_stream_)); SDL_DestroyAudioStream(output_stream_); output_stream_ = nullptr; } return 0; } void Render::UpdateInteractions() { if (start_screen_capturer_ && !screen_capturer_is_started_) { StartScreenCapturer(); screen_capturer_is_started_ = true; } else if (!start_screen_capturer_ && screen_capturer_is_started_) { StopScreenCapturer(); screen_capturer_is_started_ = false; } if (start_mouse_controller_ && !mouse_controller_is_started_) { StartMouseController(); mouse_controller_is_started_ = true; } else if (!start_mouse_controller_ && mouse_controller_is_started_) { StopMouseController(); mouse_controller_is_started_ = false; } if (start_keyboard_capturer_ && foucs_on_stream_window_) { if (!keyboard_capturer_is_started_) { StartKeyboardCapturer(); keyboard_capturer_is_started_ = true; } } else if (keyboard_capturer_is_started_) { StopKeyboardCapturer(); keyboard_capturer_is_started_ = false; } } int Render::CreateMainWindow() { main_ctx_ = ImGui::CreateContext(); if (!main_ctx_) { LOG_ERROR("Main context is null"); return -1; } ImGui::SetCurrentContext(main_ctx_); if (!SDL_CreateWindowAndRenderer( "Remote Desk", (int)main_window_width_default_, (int)main_window_height_default_, SDL_WINDOW_HIGH_PIXEL_DENSITY | SDL_WINDOW_BORDERLESS | SDL_WINDOW_HIDDEN, &main_window_, &main_renderer_)) { LOG_ERROR("Error creating main_window_ and main_renderer_: {}", SDL_GetError()); return -1; } SDL_SetWindowResizable(main_window_, false); // for window region action SDL_SetWindowHitTest(main_window_, HitTestCallback, this); return 0; } int Render::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 Render::CreateStreamWindow() { if (stream_window_created_) { return 0; } stream_ctx_ = ImGui::CreateContext(); if (!stream_ctx_) { LOG_ERROR("Stream context is null"); return -1; } ImGui::SetCurrentContext(stream_ctx_); if (!SDL_CreateWindowAndRenderer( "Stream window", (int)stream_window_width_default_, (int)stream_window_height_default_, SDL_WINDOW_HIGH_PIXEL_DENSITY | SDL_WINDOW_BORDERLESS, &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); // 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; return 0; } int Render::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_); } if (stream_window_) { SDL_DestroyWindow(stream_window_); } stream_window_created_ = false; return 0; } int Render::SetupFontAndStyle() { // Setup Dear ImGui style ImGuiIO& io = ImGui::GetIO(); io.IniFilename = NULL; // disable imgui.ini io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking // Load Fonts ImFontConfig config; config.FontDataOwnedByAtlas = false; io.Fonts->AddFontFromMemoryTTF(OPPOSans_Regular_ttf, OPPOSans_Regular_ttf_len, 32.0f, &config, io.Fonts->GetGlyphRangesChineseFull()); 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, 30.0f, &config, icon_ranges); io.Fonts->Build(); ImGui::StyleColorsLight(); return 0; } int Render::SetupMainWindow() { if (!main_ctx_) { LOG_ERROR("Main context is null"); return -1; } ImGui::SetCurrentContext(main_ctx_); SetupFontAndStyle(); SDL_GetWindowSizeInPixels(main_window_, &main_window_width_real_, &main_window_height_real_); main_window_dpi_scaling_w_ = main_window_width_real_ / main_window_width_; main_window_dpi_scaling_h_ = main_window_width_real_ / main_window_width_; SDL_SetRenderScale(main_renderer_, main_window_dpi_scaling_w_, main_window_dpi_scaling_h_); LOG_INFO("Use dpi scaling [{}x{}] for main window", main_window_dpi_scaling_w_, main_window_dpi_scaling_h_); ImGui_ImplSDL3_InitForSDLRenderer(main_window_, main_renderer_); ImGui_ImplSDLRenderer3_Init(main_renderer_); return 0; } int Render::DestroyMainWindowContext() { ImGui::SetCurrentContext(main_ctx_); ImGui_ImplSDLRenderer3_Shutdown(); ImGui_ImplSDL3_Shutdown(); ImGui::DestroyContext(main_ctx_); return 0; } int Render::SetupStreamWindow() { if (stream_window_inited_) { return 0; } if (!stream_ctx_) { LOG_ERROR("Stream context is null"); return -1; } ImGui::SetCurrentContext(stream_ctx_); SetupFontAndStyle(); SDL_GetWindowSizeInPixels(stream_window_, &stream_window_width_real_, &stream_window_height_real_); stream_window_dpi_scaling_w_ = stream_window_width_real_ / stream_window_width_; stream_window_dpi_scaling_h_ = stream_window_width_real_ / stream_window_width_; SDL_SetRenderScale(stream_renderer_, stream_window_dpi_scaling_w_, stream_window_dpi_scaling_h_); LOG_INFO("Use dpi scaling [{}x{}] for stream window", stream_window_dpi_scaling_w_, stream_window_dpi_scaling_h_); ImGui_ImplSDL3_InitForSDLRenderer(stream_window_, stream_renderer_); ImGui_ImplSDLRenderer3_Init(stream_renderer_); stream_window_inited_ = true; LOG_INFO("Stream window inited"); return 0; } int Render::DestroyStreamWindowContext() { stream_window_inited_ = false; ImGui::SetCurrentContext(stream_ctx_); ImGui_ImplSDLRenderer3_Shutdown(); ImGui_ImplSDL3_Shutdown(); ImGui::DestroyContext(stream_ctx_); return 0; } int Render::DrawMainWindow() { if (!main_ctx_) { LOG_ERROR("Main context is null"); return -1; } ImGui::SetCurrentContext(main_ctx_); ImGui_ImplSDLRenderer3_NewFrame(); ImGui_ImplSDL3_NewFrame(); ImGui::NewFrame(); ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always); ImGui::SetNextWindowSize( ImVec2(main_window_width_, main_window_height_default_), ImGuiCond_Always); ImGui::Begin("MainRender", nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoBringToFrontOnFocus); ImGui::PopStyleColor(); TitleBar(true); MainWindow(); ImGui::End(); // Rendering ImGui::Render(); SDL_RenderClear(main_renderer_); ImGui_ImplSDLRenderer3_RenderDrawData(ImGui::GetDrawData(), main_renderer_); SDL_RenderPresent(main_renderer_); return 0; } int Render::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(); if (!fullscreen_button_pressed_) { ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always); ImGui::SetNextWindowSize( ImVec2(stream_window_width_, fullscreen_button_pressed_ ? 0 : title_bar_height_), ImGuiCond_Always); ImGui::Begin("StreamWindowTitleBar", nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoDocking); ImGui::PopStyleColor(); TitleBar(false); ImGui::End(); } // Rendering ImGui::Render(); SDL_RenderClear(stream_renderer_); for (auto& it : client_properties_) { auto props = it.second; if (props->tab_selected_) { SDL_FRect render_rect_f = { static_cast(props->stream_render_rect_.x), static_cast(props->stream_render_rect_.y), static_cast(props->stream_render_rect_.w), static_cast(props->stream_render_rect_.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 Render::Run() { path_manager_ = std::make_unique("CrossDesk"); if (path_manager_) { cert_path_ = (path_manager_->GetCertPath() / "crossdesk.cn_root.crt").string(); exec_log_path_ = path_manager_->GetLogPath().string(); dll_log_path_ = path_manager_->GetLogPath().string(); cache_path_ = path_manager_->GetCachePath().string(); } InitializeLogger(); InitializeSettings(); InitializeSDL(); InitializeModules(); InitializeMainWindow(); const int scaled_video_width_ = 160; const int scaled_video_height_ = 90; MainLoop(); Cleanup(); return 0; } void Render::InitializeLogger() { if (std::filesystem::exists(exec_log_path_)) { InitLogger(exec_log_path_); } else { InitLogger("logs"); } } void Render::InitializeSettings() { LoadSettingsFromCacheFile(); localization_language_ = (ConfigCenter::LANGUAGE)language_button_value_; localization_language_index_ = language_button_value_; if (localization_language_index_ != 0 && localization_language_index_ != 1) { localization_language_index_ = 0; LOG_ERROR("Invalid language index: [{}], use [0] by default", localization_language_index_); } } void Render::InitializeSDL() { 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; } STREAM_REFRESH_EVENT = SDL_RegisterEvents(1); if (STREAM_REFRESH_EVENT == (uint32_t)-1) { LOG_ERROR("Failed to register custom SDL event"); } LOG_INFO("Screen resolution: [{}x{}]", screen_width_, screen_height_); } void Render::InitializeModules() { if (!modules_inited_) { AudioDeviceInit(); screen_capturer_factory_ = new ScreenCapturerFactory(); speaker_capturer_factory_ = new SpeakerCapturerFactory(); device_controller_factory_ = new DeviceControllerFactory(); keyboard_capturer_ = (KeyboardCapturer*)device_controller_factory_->Create( DeviceControllerFactory::Device::Keyboard); CreateConnectionPeer(); modules_inited_ = true; } } void Render::InitializeMainWindow() { CreateMainWindow(); SetupMainWindow(); if (SDL_WINDOW_HIDDEN & SDL_GetWindowFlags(main_window_)) { SDL_ShowWindow(main_window_); } } void Render::MainLoop() { while (!exit_) { if (!peer_) { CreateConnectionPeer(); } UpdateLabels(); ProcessSdlEvent(); HandleRecentConnections(); HandleStreamWindow(); DrawMainWindow(); if (stream_window_inited_) { DrawStreamWindow(); } UpdateInteractions(); if (need_to_send_host_info_) { RemoteAction remote_action; remote_action.i.display_num = display_info_list_.size(); remote_action.i.display_list = (char**)malloc(remote_action.i.display_num * sizeof(char*)); remote_action.i.left = (int*)malloc(remote_action.i.display_num * sizeof(int)); remote_action.i.top = (int*)malloc(remote_action.i.display_num * sizeof(int)); remote_action.i.right = (int*)malloc(remote_action.i.display_num * sizeof(int)); remote_action.i.bottom = (int*)malloc(remote_action.i.display_num * sizeof(int)); for (int i = 0; i < remote_action.i.display_num; i++) { LOG_INFO("Local display [{}:{}]", i + 1, display_info_list_[i].name); remote_action.i.display_list[i] = (char*)malloc(display_info_list_[i].name.length() + 1); strncpy(remote_action.i.display_list[i], display_info_list_[i].name.c_str(), display_info_list_[i].name.length()); remote_action.i.display_list[i][display_info_list_[i].name.length()] = '\0'; remote_action.i.left[i] = display_info_list_[i].left; remote_action.i.top[i] = display_info_list_[i].top; remote_action.i.right[i] = display_info_list_[i].right; remote_action.i.bottom[i] = display_info_list_[i].bottom; } std::string host_name = GetHostName(); remote_action.type = ControlType::host_infomation; memcpy(&remote_action.i.host_name, host_name.data(), host_name.size()); remote_action.i.host_name[host_name.size()] = '\0'; remote_action.i.host_name_size = host_name.size(); std::vector serialized = SerializeRemoteAction(remote_action); int ret = SendDataFrame(peer_, serialized.data(), serialized.size(), data_label_.c_str()); FreeRemoteAction(remote_action); if (0 == ret) { need_to_send_host_info_ = false; } } } } void Render::UpdateLabels() { if (!label_inited_ || localization_language_index_last_ != localization_language_index_) { connect_button_label_ = connect_button_pressed_ ? localization::disconnect[localization_language_index_] : localization::connect[localization_language_index_]; label_inited_ = true; localization_language_index_last_ = localization_language_index_; } } void Render::HandleRecentConnections() { if (reload_recent_connections_ && main_renderer_) { uint32_t now_time = SDL_GetTicks(); if (now_time - recent_connection_image_save_time_ >= 50) { int ret = thumbnail_->LoadThumbnail(main_renderer_, recent_connections_, &recent_connection_image_width_, &recent_connection_image_height_); if (!ret) { LOG_INFO("Load recent connection thumbnails"); } reload_recent_connections_ = false; } } } void Render::HandleStreamWindow() { if (need_to_create_stream_window_) { CreateStreamWindow(); SetupStreamWindow(); 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 Render::Cleanup() { if (screen_capturer_) { screen_capturer_->Destroy(); delete screen_capturer_; screen_capturer_ = nullptr; } if (speaker_capturer_) { speaker_capturer_->Destroy(); delete speaker_capturer_; speaker_capturer_ = nullptr; } if (mouse_controller_) { mouse_controller_->Destroy(); delete mouse_controller_; mouse_controller_ = nullptr; } if (keyboard_capturer_) { delete keyboard_capturer_; keyboard_capturer_ = nullptr; } CleanupFactories(); CleanupPeers(); AudioDeviceDestroy(); DestroyMainWindowContext(); DestroyMainWindow(); SDL_Quit(); } void Render::CleanupFactories() { if (screen_capturer_factory_) { delete screen_capturer_factory_; screen_capturer_factory_ = nullptr; } if (speaker_capturer_factory_) { delete speaker_capturer_factory_; speaker_capturer_factory_ = nullptr; } if (device_controller_factory_) { delete device_controller_factory_; device_controller_factory_ = nullptr; } } void Render::CleanupPeer(std::shared_ptr props) { SDL_FlushEvent(STREAM_REFRESH_EVENT); if (props->dst_buffer_) { thumbnail_->SaveToThumbnail( (char*)props->dst_buffer_, props->video_width_, props->video_height_, props->remote_id_, props->remote_host_name_, props->remember_password_ ? props->remote_password_ : ""); } if (props->peer_) { LOG_INFO("[{}] Leave connection [{}]", props->local_id_, props->remote_id_); LeaveConnection(props->peer_, props->remote_id_.c_str()); LOG_INFO("Destroy peer [{}]", props->local_id_); DestroyPeer(&props->peer_); } } void Render::CleanupPeers() { if (peer_) { LOG_INFO("[{}] Leave connection [{}]", client_id_, client_id_); LeaveConnection(peer_, client_id_); is_client_mode_ = false; LOG_INFO("Destroy peer [{}]", client_id_); DestroyPeer(&peer_); } for (auto& it : client_properties_) { auto props = it.second; CleanupPeer(props); } client_properties_.clear(); } void Render::CleanSubStreamWindowProperties( std::shared_ptr props) { if (props->stream_texture_) { SDL_DestroyTexture(props->stream_texture_); props->stream_texture_ = nullptr; } if (props->dst_buffer_) { delete[] props->dst_buffer_; props->dst_buffer_ = nullptr; } } void Render::UpdateRenderRect() { for (auto& [_, props] : client_properties_) { 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_; if (render_area_width < render_area_height * video_ratio) { props->stream_render_rect_ = { (int)props->render_window_x_, (int)(abs(render_area_height - render_area_width * video_ratio_reverse) / 2 + (int)props->render_window_y_), (int)render_area_width, (int)(render_area_width * video_ratio_reverse)}; } else if (render_area_width > render_area_height * video_ratio) { props->stream_render_rect_ = { (int)abs(render_area_width - render_area_height * video_ratio) / 2, (int)props->render_window_y_, (int)(render_area_height * video_ratio), (int)render_area_height}; } else { props->stream_render_rect_ = { (int)props->render_window_x_, (int)props->render_window_y_, (int)render_area_width, (int)render_area_height}; } } } void Render::ProcessSdlEvent() { SDL_Event event; while (SDL_PollEvent(&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; } } switch (event.type) { case SDL_EVENT_QUIT: if (stream_window_inited_) { LOG_INFO("Destroy stream window"); SDL_SetWindowMouseGrab(stream_window_, false); DestroyStreamWindow(); DestroyStreamWindowContext(); for (auto& [host_name, props] : client_properties_) { thumbnail_->SaveToThumbnail( (char*)props->dst_buffer_, props->video_width_, props->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); } client_properties_.clear(); rejoin_ = false; is_client_mode_ = false; reload_recent_connections_ = true; fullscreen_button_pressed_ = 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 (event.window.windowID != SDL_GetWindowID(stream_window_)) { 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) { foucs_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) { foucs_on_stream_window_ = false; } else if (main_window_ && SDL_GetWindowID(main_window_) == event.window.windowID) { foucs_on_main_window_ = false; } break; case SDL_EVENT_MOUSE_MOTION: case SDL_EVENT_MOUSE_BUTTON_DOWN: case SDL_EVENT_MOUSE_BUTTON_UP: case SDL_EVENT_MOUSE_WHEEL: if (foucs_on_stream_window_) { ProcessMouseEvent(event); } break; } if (event.type == STREAM_REFRESH_EVENT) { auto* props = static_cast(event.user.data1); if (!props) { continue; } if (props->video_width_ <= 0 || props->video_height_ <= 0) { continue; } if (!props->dst_buffer_) { continue; } // use libyuv to convert NV12 to ARGB in order to fix SDL3 NV12 texture // display issue if (props->stream_texture_) { if (props->video_width_ != props->texture_width_ || props->video_height_ != props->texture_height_) { props->texture_width_ = props->video_width_; props->texture_height_ = props->video_height_; SDL_DestroyTexture(props->stream_texture_); props->stream_texture_ = SDL_CreateTexture(stream_renderer_, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, props->texture_width_, props->texture_height_); } } else { props->texture_width_ = props->video_width_; props->texture_height_ = props->video_height_; props->stream_texture_ = SDL_CreateTexture(stream_renderer_, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, props->texture_width_, props->texture_height_); } size_t argb_stride = props->texture_width_ * 4; size_t argb_size = argb_stride * props->texture_height_; if (!props->argb_buffer_ || props->argb_buffer_size_ != argb_size) { if (props->argb_buffer_) { delete[] props->argb_buffer_; } props->argb_buffer_ = new uint8_t[argb_size]; props->argb_buffer_size_ = argb_size; } uint8_t* src_y = reinterpret_cast(props->dst_buffer_); int src_stride_y = props->texture_width_; uint8_t* src_uv = src_y + src_stride_y * props->texture_height_; int src_stride_uv = props->texture_width_; libyuv::NV12ToARGB(src_y, src_stride_y, src_uv, src_stride_uv, props->argb_buffer_, static_cast(argb_stride), props->texture_width_, props->texture_height_); SDL_UpdateTexture(props->stream_texture_, NULL, props->argb_buffer_, static_cast(argb_stride)); break; } } }