[refactor] reorganize platform and wire sources

This commit is contained in:
dijunkun
2026-09-01 01:29:30 +08:00
parent fb0ab4f14e
commit 90184f5251
354 changed files with 2837 additions and 2456 deletions
@@ -0,0 +1,148 @@
#include <cstdlib>
#include <string>
#if defined(_WIN32)
#include <windows.h>
#endif
#include "application/gui_application.h"
#include "layout.h"
#include "localization.h"
#include "rd_log.h"
namespace crossdesk {
bool GuiApplication::OpenUrl(const std::string &url) {
#if defined(_WIN32)
int wide_len = MultiByteToWideChar(CP_UTF8, 0, url.c_str(), -1, nullptr, 0);
if (wide_len <= 0) {
return false;
}
std::wstring wide_url(static_cast<size_t>(wide_len), L'\0');
MultiByteToWideChar(CP_UTF8, 0, url.c_str(), -1, &wide_url[0], wide_len);
if (!wide_url.empty() && wide_url.back() == L'\0') {
wide_url.pop_back();
}
std::wstring cmd = L"cmd.exe /c start \"\" \"" + wide_url + L"\"";
STARTUPINFOW startup_info = {sizeof(startup_info)};
PROCESS_INFORMATION process_info = {};
if (!CreateProcessW(nullptr, &cmd[0], nullptr, nullptr, FALSE,
CREATE_NO_WINDOW, nullptr, nullptr, &startup_info,
&process_info)) {
return false;
}
CloseHandle(process_info.hThread);
CloseHandle(process_info.hProcess);
return true;
#elif defined(__APPLE__)
std::string cmd = "open " + url;
return system(cmd.c_str()) == 0;
#else
std::string cmd = "xdg-open " + url;
return system(cmd.c_str()) == 0;
#endif
}
void GuiApplication::Hyperlink(const std::string &label, const std::string &url,
const float window_width) {
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 255, 255));
ImGui::TextUnformatted(label.c_str());
ImGui::PopStyleColor();
if (ImGui::IsItemHovered()) {
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
ImGui::BeginTooltip();
ImGui::SetWindowFontScale(0.6f);
ImGui::TextUnformatted(url.c_str());
ImGui::SetWindowFontScale(1.0f);
ImGui::EndTooltip();
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
OpenUrl(url);
}
}
}
int GuiApplication::AboutWindow() {
if (show_about_window_) {
float about_window_width = title_bar_button_width_ * 7.5f;
float about_window_height = latest_version_.empty()
? title_bar_button_width_ * 4.0f
: title_bar_button_width_ * 4.9f;
const ImGuiViewport *viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(ImVec2(
(viewport->WorkSize.x - viewport->WorkPos.x - about_window_width) / 2,
(viewport->WorkSize.y - viewport->WorkPos.y - about_window_height) /
2));
ImGui::SetNextWindowSize(ImVec2(about_window_width, about_window_height));
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, window_rounding_);
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, window_rounding_ * 0.5f);
ImGui::SetWindowFontScale(0.5f);
ImGui::Begin(
localization::about[localization_language_index_].c_str(), nullptr,
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings);
ImGui::SetWindowFontScale(1.0f);
ImGui::SetWindowFontScale(0.5f);
std::string version;
#ifdef CROSSDESK_VERSION
version = CROSSDESK_VERSION;
#else
version = "Unknown";
#endif
std::string text = localization::version[localization_language_index_] +
": CrossDesk " + version;
ImGui::SetCursorPosX(about_window_width * 0.1f);
ImGui::Text("%s", text.c_str());
if (update_available_ && show_new_version_icon_in_menu_) {
std::string new_version_available =
localization::new_version_available[localization_language_index_] +
": ";
ImGui::SetCursorPosX(about_window_width * 0.1f);
ImGui::Text("%s", new_version_available.c_str());
std::string access_website =
localization::access_website[localization_language_index_];
ImGui::SetCursorPosX((about_window_width -
ImGui::CalcTextSize(latest_version_.c_str()).x) /
2.0f);
Hyperlink(latest_version_, "https://crossdesk.cn", about_window_width);
ImGui::Spacing();
} else {
ImGui::Text("%s", "");
}
std::string copyright_text = "© 2025 by JUNKUN DI. All rights reserved.";
std::string license_text = "Licensed under GNU GPL v3.";
ImGui::SetCursorPosX(about_window_width * 0.1f);
ImGui::Text("%s", copyright_text.c_str());
ImGui::SetCursorPosX(about_window_width * 0.1f);
ImGui::Text("%s", license_text.c_str());
ImGui::SetCursorPosX(about_window_width * 0.445f);
ImGui::SetCursorPosY(about_window_height * 0.8f);
// OK
if (ImGui::Button(localization::ok[localization_language_index_].c_str())) {
show_about_window_ = false;
}
ImGui::SetWindowFontScale(1.0f);
ImGui::SetWindowFontScale(0.5f);
ImGui::End();
ImGui::SetWindowFontScale(1.0f);
ImGui::PopStyleVar(3);
ImGui::PopStyleColor();
}
return 0;
}
} // namespace crossdesk
@@ -0,0 +1,209 @@
#include "application/gui_application.h"
#include "layout.h"
#include "localization.h"
#include "rd_log.h"
namespace crossdesk {
bool GuiApplication::ConnectionStatusWindow(
std::shared_ptr<RemoteSession> &props) {
ImGuiIO &io = ImGui::GetIO();
bool ret_flag = false;
ImGui::SetNextWindowPos(
ImVec2(io.DisplaySize.x * 0.33f, io.DisplaySize.y * 0.33f));
ImGui::SetNextWindowSize(
ImVec2(io.DisplaySize.x * 0.33f, io.DisplaySize.y * 0.33f));
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, window_rounding_ * 0.5f);
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, window_rounding_);
ImGui::Begin("ConnectionStatusWindow", nullptr,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoSavedSettings);
ImGui::PopStyleVar(2);
ImGui::PopStyleColor();
auto connection_status_window_width = ImGui::GetWindowSize().x;
auto connection_status_window_height = ImGui::GetWindowSize().y;
ImGui::SetWindowFontScale(0.5f);
std::string text;
const ConnectionStatus status = props->connection_status_.load();
if (ConnectionStatus::Connecting == status) {
text = localization::p2p_connecting[localization_language_index_];
ImGui::SetCursorPosX(connection_status_window_width * 0.43f);
ImGui::SetCursorPosY(connection_status_window_height * 0.67f);
// cancel
if (ImGui::Button(
localization::cancel[localization_language_index_].c_str()) ||
ImGui::IsKeyPressed(ImGuiKey_Escape)) {
show_connection_status_window_ = false;
re_enter_remote_id_ = true;
LOG_INFO("User cancelled connecting to [{}]", props->remote_id_);
if (props->peer_) {
LeaveConnection(props->peer_, props->remote_id_.c_str());
}
ret_flag = true;
}
} else if (ConnectionStatus::Gathering == status) {
text = localization::p2p_gathering[localization_language_index_];
ImGui::SetCursorPosX(connection_status_window_width * 0.43f);
ImGui::SetCursorPosY(connection_status_window_height * 0.67f);
// cancel
if (ImGui::Button(
localization::cancel[localization_language_index_].c_str()) ||
ImGui::IsKeyPressed(ImGuiKey_Escape)) {
show_connection_status_window_ = false;
re_enter_remote_id_ = true;
LOG_INFO("User cancelled connecting to [{}]", props->remote_id_);
if (props->peer_) {
LeaveConnection(props->peer_, props->remote_id_.c_str());
}
ret_flag = true;
}
} else if (ConnectionStatus::Connected == status) {
text = localization::p2p_connected[localization_language_index_];
ImGui::SetCursorPosX(connection_status_window_width * 0.43f);
ImGui::SetCursorPosY(connection_status_window_height * 0.67f);
// ok
if (ImGui::Button(localization::ok[localization_language_index_].c_str()) ||
ImGui::IsKeyPressed(ImGuiKey_Enter) ||
ImGui::IsKeyPressed(ImGuiKey_Escape)) {
show_connection_status_window_ = false;
}
} else if (ConnectionStatus::Disconnected == status) {
text = localization::p2p_disconnected[localization_language_index_];
ImGui::SetCursorPosX(connection_status_window_width * 0.43f);
ImGui::SetCursorPosY(connection_status_window_height * 0.67f);
// ok
if (ImGui::Button(localization::ok[localization_language_index_].c_str()) ||
ImGui::IsKeyPressed(ImGuiKey_Enter) ||
ImGui::IsKeyPressed(ImGuiKey_Escape)) {
show_connection_status_window_ = false;
}
} else if (ConnectionStatus::Failed == status) {
text = localization::p2p_failed[localization_language_index_];
ImGui::SetCursorPosX(connection_status_window_width * 0.43f);
ImGui::SetCursorPosY(connection_status_window_height * 0.67f);
// ok
if (ImGui::Button(localization::ok[localization_language_index_].c_str()) ||
ImGui::IsKeyPressed(ImGuiKey_Enter) ||
ImGui::IsKeyPressed(ImGuiKey_Escape)) {
show_connection_status_window_ = false;
}
} else if (ConnectionStatus::Closed == status) {
text = localization::p2p_closed[localization_language_index_];
ImGui::SetCursorPosX(connection_status_window_width * 0.43f);
ImGui::SetCursorPosY(connection_status_window_height * 0.67f);
// ok
if (ImGui::Button(localization::ok[localization_language_index_].c_str()) ||
ImGui::IsKeyPressed(ImGuiKey_Enter) ||
ImGui::IsKeyPressed(ImGuiKey_Escape)) {
show_connection_status_window_ = false;
}
} else if (ConnectionStatus::IncorrectPassword == status) {
if (!password_validating_) {
if (password_validating_time_ == 1) {
text = localization::input_password[localization_language_index_];
} else {
text = localization::reinput_password[localization_language_index_];
}
ImGui::SetCursorPosX(connection_status_window_width * 0.336f);
ImGui::SetCursorPosY(connection_status_window_height * 0.4f);
ImGui::SetNextItemWidth(connection_status_window_width * 0.33f);
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f);
if (focus_on_input_widget_) {
ImGui::SetKeyboardFocusHere();
focus_on_input_widget_ = false;
}
ImGui::InputText("##password", props->remote_password_,
IM_ARRAYSIZE(props->remote_password_),
ImGuiInputTextFlags_CharsNoBlank);
ImGui::SetWindowFontScale(0.4f);
ImVec2 text_size = ImGui::CalcTextSize(
localization::remember_password[localization_language_index_]
.c_str());
ImGui::SetCursorPosX((connection_status_window_width - text_size.x) *
0.45f);
ImGui::Checkbox(
localization::remember_password[localization_language_index_].c_str(),
&(props->remember_password_));
ImGui::SetWindowFontScale(0.5f);
ImGui::PopStyleVar();
ImGui::SetCursorPosX(connection_status_window_width * 0.325f);
ImGui::SetCursorPosY(connection_status_window_height * 0.75f);
// ok
if (ImGui::Button(
localization::ok[localization_language_index_].c_str()) ||
ImGui::IsKeyPressed(ImGuiKey_Enter)) {
show_connection_status_window_ = true;
password_validating_ = true;
props->rejoin_ = true;
need_to_rejoin_ = true;
focus_on_input_widget_ = true;
}
ImGui::SameLine();
// cancel
if (ImGui::Button(
localization::cancel[localization_language_index_].c_str()) ||
ImGui::IsKeyPressed(ImGuiKey_Escape)) {
memset(props->remote_password_, 0, sizeof(props->remote_password_));
show_connection_status_window_ = false;
focus_on_input_widget_ = true;
}
} else if (password_validating_) {
text = localization::validate_password[localization_language_index_];
ImGui::SetCursorPosX(connection_status_window_width * 0.43f);
ImGui::SetCursorPosY(connection_status_window_height * 0.67f);
}
} else if (ConnectionStatus::NoSuchTransmissionId == status) {
text = localization::no_such_id[localization_language_index_];
ImGui::SetCursorPosX(connection_status_window_width * 0.43f);
ImGui::SetCursorPosY(connection_status_window_height * 0.67f);
// ok
if (ImGui::Button(localization::ok[localization_language_index_].c_str()) ||
ImGui::IsKeyPressed(ImGuiKey_Enter)) {
show_connection_status_window_ = false;
re_enter_remote_id_ = true;
DestroyPeer(&props->peer_);
ret_flag = true;
}
} else if (ConnectionStatus::RemoteUnavailable == status) {
text = localization::device_offline[localization_language_index_];
ImGui::SetCursorPosX(connection_status_window_width * 0.43f);
ImGui::SetCursorPosY(connection_status_window_height * 0.67f);
// ok
if (ImGui::Button(localization::ok[localization_language_index_].c_str()) ||
ImGui::IsKeyPressed(ImGuiKey_Enter) ||
ImGui::IsKeyPressed(ImGuiKey_Escape)) {
show_connection_status_window_ = false;
re_enter_remote_id_ = true;
DestroyPeer(&props->peer_);
ret_flag = true;
}
}
auto text_width = ImGui::CalcTextSize(text.c_str()).x;
ImGui::SetCursorPosX((connection_status_window_width - text_width) * 0.5f);
ImGui::SetCursorPosY(connection_status_window_height * 0.2f);
ImGui::Text("%s", text.c_str());
ImGui::SetWindowFontScale(1.0f);
ImGui::End();
ImGui::PopStyleVar();
return ret_flag;
}
} // namespace crossdesk
@@ -0,0 +1,264 @@
#include "application/gui_application.h"
#include "rd_log.h"
namespace crossdesk {
int GuiApplication::ControlWindow(
std::shared_ptr<RemoteSession> &props) {
double time_duration =
ImGui::GetTime() - props->control_bar_button_pressed_time_;
if (props->control_window_width_is_changing_) {
if (props->control_bar_expand_) {
props->control_window_width_ =
(float)(props->control_window_min_width_ +
(props->control_window_max_width_ -
props->control_window_min_width_) *
4 * time_duration);
} else {
props->control_window_width_ =
(float)(props->control_window_max_width_ -
(props->control_window_max_width_ -
props->control_window_min_width_) *
4 * time_duration);
}
}
time_duration =
ImGui::GetTime() - props->net_traffic_stats_button_pressed_time_;
if (props->control_window_height_is_changing_) {
if (props->control_bar_expand_ &&
props->net_traffic_stats_button_pressed_) {
props->control_window_height_ =
(float)(props->control_window_min_height_ +
(props->control_window_max_height_ -
props->control_window_min_height_) *
4 * time_duration);
} else if (props->control_bar_expand_ &&
!props->net_traffic_stats_button_pressed_) {
props->control_window_height_ =
(float)(props->control_window_max_height_ -
(props->control_window_max_height_ -
props->control_window_min_height_) *
4 * time_duration);
}
}
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0));
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, window_rounding_ * 1.5f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, window_rounding_ * 1.5f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
float y_boundary = fullscreen_button_pressed_ ? 0.0f : title_bar_height_;
float container_x = 0.0f;
float container_y = y_boundary;
float container_w = stream_window_width_;
float container_h = stream_window_height_ - y_boundary;
ImGui::SetNextWindowSize(ImVec2(container_w, container_h), ImGuiCond_Always);
ImGui::SetNextWindowPos(ImVec2(container_x, container_y), ImGuiCond_Always);
float pos_x = 0;
float pos_y = 0;
std::string container_window_title =
props->remote_id_ + "ControlContainerWindow";
ImGui::Begin(container_window_title.c_str(), nullptr,
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoDocking |
ImGuiWindowFlags_NoBackground);
ImGui::PopStyleVar();
ImVec2 container_pos = ImGui::GetWindowPos();
if (ImGui::IsMouseDown(ImGuiMouseButton_Left) &&
props->control_bar_hovered_) {
float current_x_rel = props->control_window_pos_.x - container_pos.x;
float current_y_rel = props->control_window_pos_.y - container_pos.y;
ImVec2 delta = ImGui::GetIO().MouseDelta;
pos_x = current_x_rel + delta.x;
pos_y = current_y_rel + delta.y;
if (pos_x < 0.0f)
pos_x = 0.0f;
if (pos_y < 0.0f)
pos_y = 0.0f;
if (pos_x + props->control_window_width_ > container_w)
pos_x = container_w - props->control_window_width_;
if (pos_y + props->control_window_height_ > container_h)
pos_y = container_h - props->control_window_height_;
} else if (props->reset_control_bar_pos_) {
float new_cursor_pos_x = 0.0f;
float new_cursor_pos_y = 0.0f;
// set control window pos
float current_y_rel = props->control_window_pos_.y - container_pos.y;
if (current_y_rel + props->control_window_height_ > container_h) {
pos_y = container_h - props->control_window_height_;
} else if (current_y_rel < 0.0f) {
pos_y = 0.0f;
} else {
pos_y = current_y_rel;
}
if (props->is_control_bar_in_left_) {
pos_x = 0.0f;
} else {
pos_x = container_w - props->control_window_width_;
}
if (0 != props->mouse_diff_control_bar_pos_x_ &&
0 != props->mouse_diff_control_bar_pos_y_) {
// set cursor pos
new_cursor_pos_x =
container_pos.x + pos_x + props->mouse_diff_control_bar_pos_x_;
new_cursor_pos_y =
container_pos.y + pos_y + props->mouse_diff_control_bar_pos_y_;
SDL_WarpMouseInWindow(stream_window_, (int)new_cursor_pos_x,
(int)new_cursor_pos_y);
}
props->reset_control_bar_pos_ = false;
} else if (!props->reset_control_bar_pos_ &&
ImGui::IsMouseReleased(ImGuiMouseButton_Left) ||
props->control_window_width_is_changing_) {
float current_x_rel = props->control_window_pos_.x - container_pos.x;
float current_y_rel = props->control_window_pos_.y - container_pos.y;
if (current_x_rel <= container_w * 0.5f) {
pos_x = 0.0f;
if (current_y_rel + props->control_window_height_ > container_h) {
pos_y = container_h - props->control_window_height_;
} else {
pos_y = current_y_rel;
}
if (props->control_bar_expand_) {
if (props->control_window_width_ >= props->control_window_max_width_) {
props->control_window_width_ = props->control_window_max_width_;
props->control_window_width_is_changing_ = false;
} else {
props->control_window_width_is_changing_ = true;
}
} else {
if (props->control_window_width_ <= props->control_window_min_width_) {
props->control_window_width_ = props->control_window_min_width_;
props->control_window_width_is_changing_ = false;
} else {
props->control_window_width_is_changing_ = true;
}
}
props->is_control_bar_in_left_ = true;
} else if (current_x_rel > container_w * 0.5f) {
pos_x = container_w - props->control_window_width_;
pos_y = (current_y_rel >= 0.0f &&
current_y_rel <= container_h - props->control_window_height_)
? current_y_rel
: (current_y_rel < 0.0f
? 0.0f
: (container_h - props->control_window_height_));
if (props->control_bar_expand_) {
if (props->control_window_width_ >= props->control_window_max_width_) {
props->control_window_width_ = props->control_window_max_width_;
props->control_window_width_is_changing_ = false;
pos_x = container_w - props->control_window_max_width_;
} else {
props->control_window_width_is_changing_ = true;
pos_x = container_w - props->control_window_width_;
}
} else {
if (props->control_window_width_ <= props->control_window_min_width_) {
props->control_window_width_ = props->control_window_min_width_;
props->control_window_width_is_changing_ = false;
pos_x = container_w - props->control_window_min_width_;
} else {
props->control_window_width_is_changing_ = true;
pos_x = container_w - props->control_window_width_;
}
}
props->is_control_bar_in_left_ = false;
}
if (current_y_rel + props->control_window_height_ > container_h) {
pos_y = container_h - props->control_window_height_;
} else if (current_y_rel < 0.0f) {
pos_y = 0.0f;
}
} else {
float current_x_rel = props->control_window_pos_.x - container_pos.x;
float current_y_rel = props->control_window_pos_.y - container_pos.y;
pos_x = current_x_rel;
pos_y = current_y_rel;
if (pos_x < 0.0f)
pos_x = 0.0f;
if (pos_y < 0.0f)
pos_y = 0.0f;
if (pos_x + props->control_window_width_ > container_w)
pos_x = container_w - props->control_window_width_;
if (pos_y + props->control_window_height_ > container_h)
pos_y = container_h - props->control_window_height_;
}
if (props->control_bar_expand_ && props->control_window_height_is_changing_) {
if (props->net_traffic_stats_button_pressed_) {
if (props->control_window_height_ >= props->control_window_max_height_) {
props->control_window_height_ = props->control_window_max_height_;
props->control_window_height_is_changing_ = false;
} else {
props->control_window_height_is_changing_ = true;
}
} else {
if (props->control_window_height_ <= props->control_window_min_height_) {
props->control_window_height_ = props->control_window_min_height_;
props->control_window_height_is_changing_ = false;
} else {
props->control_window_height_is_changing_ = true;
}
}
}
std::string control_window_title = props->remote_id_ + "ControlWindow";
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
static bool a, b, c, d, e;
float child_cursor_x = pos_x;
float child_cursor_y = pos_y;
ImGui::SetCursorPos(ImVec2(child_cursor_x, child_cursor_y));
std::string control_child_window_title =
props->remote_id_ + "ControlChildWindow";
ImGui::BeginChild(
control_child_window_title.c_str(),
ImVec2(props->control_window_width_, props->control_window_height_),
ImGuiChildFlags_Borders, ImGuiWindowFlags_NoDecoration);
ImGui::PopStyleColor();
props->control_window_pos_ = ImGui::GetWindowPos();
SDL_GetMouseState(&props->mouse_pos_x_, &props->mouse_pos_y_);
props->mouse_diff_control_bar_pos_x_ =
props->mouse_pos_x_ - props->control_window_pos_.x;
props->mouse_diff_control_bar_pos_y_ =
props->mouse_pos_y_ - props->control_window_pos_.y;
if (props->control_window_pos_.y < container_pos.y ||
props->control_window_pos_.y + props->control_window_height_ >
(container_pos.y + container_h) ||
props->control_window_pos_.x < container_pos.x ||
props->control_window_pos_.x + props->control_window_width_ >
(container_pos.x + container_w)) {
ImGui::ClearActiveID();
props->reset_control_bar_pos_ = true;
props->mouse_diff_control_bar_pos_x_ = 0;
props->mouse_diff_control_bar_pos_y_ = 0;
}
ControlBar(props);
props->control_bar_hovered_ = ImGui::IsWindowHovered();
ImGui::EndChild();
ImGui::End();
ImGui::PopStyleVar(3);
ImGui::PopStyleColor();
return 0;
}
} // namespace crossdesk
@@ -0,0 +1,253 @@
#include <algorithm>
#include <cmath>
#include "IconsFontAwesome6.h"
#include "application/gui_application.h"
#include "layout.h"
#include "localization.h"
#include "rd_log.h"
namespace crossdesk {
namespace {
int CountDigits(int number) {
if (number == 0)
return 1;
return (int)std::floor(std::log10(std::abs(number))) + 1;
}
int BitrateDisplay(int bitrate) {
int num_of_digits = CountDigits(bitrate);
if (num_of_digits <= 3) {
ImGui::Text("%d bps", bitrate);
} else if (num_of_digits > 3 && num_of_digits <= 6) {
ImGui::Text("%d kbps", bitrate / 1000);
} else {
ImGui::Text("%.1f mbps", bitrate / 1000000.0f);
}
return 0;
}
} // namespace
int GuiApplication::FileTransferWindow(
std::shared_ptr<RemoteSession> &props) {
FileTransferState *state = &transfers_.state_for(props);
state->file_transfer_window_hovered_ = false;
// Only show window if there are files in transfer list or currently
// transferring
std::vector<RemoteSession::FileTransferInfo> file_list;
{
std::lock_guard<std::mutex> lock(state->file_transfer_list_mutex_);
file_list = state->file_transfer_list_;
}
// Sort file list: Sending first, then Completed, then Queued, then Failed
std::sort(
file_list.begin(), file_list.end(),
[](const RemoteSession::FileTransferInfo &a,
const RemoteSession::FileTransferInfo &b) {
// Priority: Sending > Completed > Queued > Failed
auto get_priority =
[](RemoteSession::FileTransferStatus status) {
switch (status) {
case RemoteSession::FileTransferStatus::Sending:
return 0;
case RemoteSession::FileTransferStatus::Completed:
return 1;
case RemoteSession::FileTransferStatus::Queued:
return 2;
case RemoteSession::FileTransferStatus::Failed:
return 3;
}
return 3;
};
return get_priority(a.status) < get_priority(b.status);
});
// Only show window if file_transfer_window_visible_ is true
// Window can be closed by user even during transfer
// It will be reopened automatically when:
// 1. A file transfer completes (via FileTransferManager)
// 2. FileTransferManager starts the next queued file.
if (!state->file_transfer_window_visible_) {
return 0;
}
// Position window at bottom-left of stream window
// Adjust window size based on number of files
float file_transfer_window_width = main_window_width_ * 0.6f;
float file_transfer_window_height =
main_window_height_ * 0.3f; // Dynamic height
float pos_x = file_transfer_window_width * 0.05f;
float pos_y = stream_window_height_ - file_transfer_window_height -
file_transfer_window_width * 0.05;
const ImVec2 mouse_pos = ImGui::GetMousePos();
const bool mouse_in_window_rect =
mouse_pos.x >= pos_x &&
mouse_pos.x <= pos_x + file_transfer_window_width &&
mouse_pos.y >= pos_y &&
mouse_pos.y <= pos_y + file_transfer_window_height;
ImGui::SetNextWindowPos(ImVec2(pos_x, pos_y), ImGuiCond_Always);
ImGui::SetNextWindowSize(
ImVec2(file_transfer_window_width, file_transfer_window_height),
ImGuiCond_Always);
if (mouse_in_window_rect) {
ImGui::SetNextWindowFocus();
}
// Set Chinese font for proper display
const bool has_chinese_font = stream_windows_system_chinese_font_ != nullptr;
if (has_chinese_font) {
ImGui::PushFont(stream_windows_system_chinese_font_);
}
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, window_rounding_ * 0.5f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 0.9f));
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.0f, 0.0f, 0.0f, 0.3f));
ImGui::PushStyleColor(ImGuiCol_TitleBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_TitleBgActive, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
ImGui::SetWindowFontScale(0.5f);
bool window_opened = true;
const bool show_contents = ImGui::Begin(
localization::file_transfer_progress[localization_language_index_]
.c_str(),
&window_opened,
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_NoScrollbar);
ImGui::PopStyleColor(4);
ImGui::PopStyleVar(2);
state->file_transfer_window_hovered_ =
mouse_in_window_rect ||
ImGui::IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows);
if (!window_opened) {
state->file_transfer_window_visible_ = false;
}
if (show_contents && window_opened) {
ImGui::SetWindowFontScale(1.0f);
ImGui::SetWindowFontScale(0.5f);
// Display file list
if (file_list.empty()) {
ImGui::Text("No files in transfer queue");
} else {
// Use a scrollable child window for the file list
ImGui::SetWindowFontScale(0.5f);
ImGui::BeginChild(
"FileList", ImVec2(0, file_transfer_window_height * 0.75f),
ImGuiChildFlags_Borders, ImGuiWindowFlags_HorizontalScrollbar);
ImGui::SetWindowFontScale(1.0f);
ImGui::SetWindowFontScale(0.5f);
for (size_t i = 0; i < file_list.size(); ++i) {
const auto &info = file_list[i];
ImGui::PushID(static_cast<int>(i));
// Status icon and file name
const char *status_icon = "";
ImVec4 status_color(0.5f, 0.5f, 0.5f, 1.0f);
const char *status_text = "";
switch (info.status) {
case RemoteSession::FileTransferStatus::Queued:
status_icon = ICON_FA_CLOCK;
status_color =
ImVec4(0.5f, 0.6f, 0.7f, 1.0f); // Common blue-gray for queued
status_text =
localization::queued[localization_language_index_].c_str();
break;
case RemoteSession::FileTransferStatus::Sending:
status_icon = ICON_FA_ARROW_UP;
status_color = ImVec4(0.2f, 0.6f, 1.0f, 1.0f);
status_text =
localization::sending[localization_language_index_].c_str();
break;
case RemoteSession::FileTransferStatus::Completed:
status_icon = ICON_FA_CHECK;
status_color = ImVec4(0.0f, 0.8f, 0.0f, 1.0f);
status_text =
localization::completed[localization_language_index_].c_str();
break;
case RemoteSession::FileTransferStatus::Failed:
status_icon = ICON_FA_XMARK;
status_color = ImVec4(1.0f, 0.2f, 0.2f, 1.0f);
status_text =
localization::failed[localization_language_index_].c_str();
break;
}
ImGui::TextColored(status_color, "%s", status_icon);
ImGui::SameLine();
ImGui::Text("%s", info.file_name.c_str());
ImGui::SameLine();
ImGui::TextColored(status_color, "%s", status_text);
// Progress bar for sending files
if (info.status ==
RemoteSession::FileTransferStatus::Sending &&
info.file_size > 0) {
float progress = static_cast<float>(info.sent_bytes) /
static_cast<float>(info.file_size);
progress = (std::max)(0.0f, (std::min)(1.0f, progress));
float text_height = ImGui::GetTextLineHeight();
ImGui::ProgressBar(
progress, ImVec2(file_transfer_window_width * 0.5f, text_height),
"");
ImGui::SameLine();
ImGui::Text("%.1f%%", progress * 100.0f);
ImGui::SameLine();
float speed_x_pos = file_transfer_window_width * 0.65f;
ImGui::SetCursorPosX(speed_x_pos);
BitrateDisplay(static_cast<int>(info.rate_bps));
} else if (info.status ==
RemoteSession::FileTransferStatus::Completed) {
// Show completed size
char size_str[64];
if (info.file_size < 1024) {
snprintf(size_str, sizeof(size_str), "%llu B",
(unsigned long long)info.file_size);
} else if (info.file_size < 1024 * 1024) {
snprintf(size_str, sizeof(size_str), "%.2f KB",
info.file_size / 1024.0f);
} else {
snprintf(size_str, sizeof(size_str), "%.2f MB",
info.file_size / (1024.0f * 1024.0f));
}
ImGui::Text("Size: %s", size_str);
}
ImGui::PopID();
ImGui::Spacing();
}
ImGui::SetWindowFontScale(1.0f);
ImGui::SetWindowFontScale(0.5f);
ImGui::EndChild();
ImGui::SetWindowFontScale(1.0f);
}
ImGui::SetWindowFontScale(1.0f);
ImGui::SetWindowFontScale(0.5f);
}
ImGui::End();
ImGui::SetWindowFontScale(1.0f);
if (has_chinese_font) {
ImGui::PopFont();
}
return 0;
}
} // namespace crossdesk
@@ -0,0 +1,670 @@
#include "application/gui_application.h"
#include "layout.h"
#include "localization.h"
#include "rd_log.h"
#include "tinyfiledialogs.h"
#if _WIN32 && CROSSDESK_PORTABLE
#include "service_host.h"
#endif
namespace crossdesk {
int GuiApplication::SettingWindow() {
ImGuiIO &io = ImGui::GetIO();
float portable_y_padding = 0.0f;
#if _WIN32 && CROSSDESK_PORTABLE
portable_y_padding = 0.05f;
#endif
if (show_settings_window_) {
if (settings_window_pos_reset_) {
const ImGuiViewport *viewport = ImGui::GetMainViewport();
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
#if (((defined(_WIN32) || defined(__linux__)) && !defined(__aarch64__) && \
!defined(__arm__) && USE_CUDA) || \
defined(__APPLE__))
ImGui::SetNextWindowPos(
ImVec2(io.DisplaySize.x * 0.343f, io.DisplaySize.y * 0.05f));
ImGui::SetNextWindowSize(
ImVec2(io.DisplaySize.x * 0.315f,
io.DisplaySize.y * (0.9f + portable_y_padding)));
#else
ImGui::SetNextWindowPos(
ImVec2(io.DisplaySize.x * 0.343f, io.DisplaySize.y * 0.08f));
ImGui::SetNextWindowSize(
ImVec2(io.DisplaySize.x * 0.315f,
io.DisplaySize.y * (0.85f + portable_y_padding)));
#endif
} else {
#if (((defined(_WIN32) || defined(__linux__)) && !defined(__aarch64__) && \
!defined(__arm__) && USE_CUDA) || \
defined(__APPLE__))
ImGui::SetNextWindowPos(
ImVec2(io.DisplaySize.x * 0.297f, io.DisplaySize.y * 0.05f));
ImGui::SetNextWindowSize(
ImVec2(io.DisplaySize.x * 0.42f,
io.DisplaySize.y * (0.9f + portable_y_padding)));
#else
ImGui::SetNextWindowPos(
ImVec2(io.DisplaySize.x * 0.297f, io.DisplaySize.y * 0.08f));
ImGui::SetNextWindowSize(
ImVec2(io.DisplaySize.x * 0.42f,
io.DisplaySize.y * (0.85f + portable_y_padding)));
#endif
}
settings_window_pos_reset_ = false;
}
// Settings
{
static int settings_items_padding = title_bar_button_width_ * 0.75f;
int settings_items_offset = 0;
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, window_rounding_);
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, window_rounding_ * 0.5f);
ImGui::Begin(localization::settings[localization_language_index_].c_str(),
nullptr,
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoSavedSettings);
ImGui::SetWindowFontScale(0.5f);
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f);
{
const auto &supported_languages = localization::GetSupportedLanguages();
language_button_value_ =
localization::detail::ClampLanguageIndex(language_button_value_);
settings_items_offset += settings_items_padding;
ImGui::SetCursorPosY(settings_items_offset);
ImGui::AlignTextToFramePadding();
ImGui::Text(
"%s", localization::language[localization_language_index_].c_str());
ImGui::SameLine();
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(title_bar_button_width_ * 3.0f);
} else {
ImGui::SetCursorPosX(title_bar_button_width_ * 4.7f);
}
ImGui::SetNextItemWidth(title_bar_button_width_ * 1.8f);
if (ImGui::BeginCombo("##language",
localization::GetSupportedLanguages()
[localization::detail::ClampLanguageIndex(
language_button_value_)]
.display_name.c_str())) {
ImGui::SetWindowFontScale(0.5f);
for (int i = 0; i < static_cast<int>(supported_languages.size());
++i) {
bool selected = (i == language_button_value_);
if (ImGui::Selectable(supported_languages[i].display_name.c_str(),
selected))
language_button_value_ = i;
if (selected) {
ImGui::SetItemDefaultFocus();
}
}
ImGui::EndCombo();
}
}
ImGui::Separator();
if (stream_window_inited_) {
ImGui::BeginDisabled();
}
{
const char *video_quality_items[] = {
localization::video_quality_low[localization_language_index_]
.c_str(),
localization::video_quality_medium[localization_language_index_]
.c_str(),
localization::video_quality_high[localization_language_index_]
.c_str()};
settings_items_offset += settings_items_padding;
ImGui::SetCursorPosY(settings_items_offset);
ImGui::AlignTextToFramePadding();
ImGui::Text(
"%s",
localization::video_quality[localization_language_index_].c_str());
ImGui::SameLine();
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(title_bar_button_width_ * 3.0f);
} else {
ImGui::SetCursorPosX(title_bar_button_width_ * 4.7f);
}
ImGui::SetNextItemWidth(title_bar_button_width_ * 1.8f);
if (ImGui::BeginCombo(
"##video_quality",
video_quality_items[video_quality_button_value_])) {
ImGui::SetWindowFontScale(0.5f);
for (int i = 0; i < IM_ARRAYSIZE(video_quality_items); i++) {
bool selected = (i == video_quality_button_value_);
if (ImGui::Selectable(video_quality_items[i], selected))
video_quality_button_value_ = i;
}
ImGui::EndCombo();
}
}
ImGui::Separator();
{
const char *video_frame_rate_items[] = {"30 fps", "60 fps"};
settings_items_offset += settings_items_padding;
ImGui::SetCursorPosY(settings_items_offset);
ImGui::AlignTextToFramePadding();
ImGui::Text("%s",
localization::video_frame_rate[localization_language_index_]
.c_str());
ImGui::SameLine();
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(title_bar_button_width_ * 3.0f);
} else {
ImGui::SetCursorPosX(title_bar_button_width_ * 4.7f);
}
ImGui::SetNextItemWidth(title_bar_button_width_ * 1.8f);
if (ImGui::BeginCombo(
"##video_frame_rate",
video_frame_rate_items[video_frame_rate_button_value_])) {
ImGui::SetWindowFontScale(0.5f);
for (int i = 0; i < IM_ARRAYSIZE(video_frame_rate_items); i++) {
bool selected = (i == video_frame_rate_button_value_);
if (ImGui::Selectable(video_frame_rate_items[i], selected))
video_frame_rate_button_value_ = i;
}
ImGui::EndCombo();
}
}
ImGui::Separator();
{
const char *video_encode_format_items[] = {
localization::h264[localization_language_index_].c_str(),
localization::av1[localization_language_index_].c_str()};
settings_items_offset += settings_items_padding;
ImGui::SetCursorPosY(settings_items_offset);
ImGui::AlignTextToFramePadding();
ImGui::Text(
"%s",
localization::video_encode_format[localization_language_index_]
.c_str());
ImGui::SameLine();
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(title_bar_button_width_ * 3.0f);
} else {
ImGui::SetCursorPosX(title_bar_button_width_ * 4.7f);
}
ImGui::SetNextItemWidth(title_bar_button_width_ * 1.8f);
if (ImGui::BeginCombo(
"##video_encode_format",
video_encode_format_items[video_encode_format_button_value_])) {
ImGui::SetWindowFontScale(0.5f);
for (int i = 0; i < IM_ARRAYSIZE(video_encode_format_items); i++) {
bool selected = (i == video_encode_format_button_value_);
if (ImGui::Selectable(video_encode_format_items[i], selected))
video_encode_format_button_value_ = i;
}
ImGui::EndCombo();
}
}
#if (((defined(_WIN32) || defined(__linux__)) && !defined(__aarch64__) && \
!defined(__arm__) && USE_CUDA) || \
defined(__APPLE__))
ImGui::Separator();
{
settings_items_offset += settings_items_padding;
ImGui::SetCursorPosY(settings_items_offset);
ImGui::AlignTextToFramePadding();
ImGui::Text("%s", localization::enable_hardware_video_codec
[localization_language_index_]
.c_str());
ImGui::SameLine();
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(title_bar_button_width_ * 4.275f);
} else {
ImGui::SetCursorPosX(title_bar_button_width_ * 5.955f);
}
ImGui::Checkbox("##enable_hardware_video_codec",
&enable_hardware_video_codec_);
}
#endif
ImGui::Separator();
{
settings_items_offset += settings_items_padding;
ImGui::SetCursorPosY(settings_items_offset);
ImGui::AlignTextToFramePadding();
ImGui::Text(
"%s",
localization::enable_turn[localization_language_index_].c_str());
ImGui::SameLine();
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(title_bar_button_width_ * 4.275f);
} else {
ImGui::SetCursorPosX(title_bar_button_width_ * 5.955f);
}
ImGui::Checkbox("##enable_turn", &enable_turn_);
}
ImGui::Separator();
{
settings_items_offset += settings_items_padding;
ImGui::SetCursorPosY(settings_items_offset);
ImGui::AlignTextToFramePadding();
ImGui::Text(
"%s",
localization::enable_srtp[localization_language_index_].c_str());
ImGui::SameLine();
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(title_bar_button_width_ * 4.275f);
} else {
ImGui::SetCursorPosX(title_bar_button_width_ * 5.955f);
}
ImGui::Checkbox("##enable_srtp", &enable_srtp_);
}
ImGui::Separator();
{
settings_items_offset += settings_items_padding;
ImGui::SetCursorPosY(settings_items_offset + 1);
ImGui::AlignTextToFramePadding();
if (ImGui::Button(localization::self_hosted_server_config
[localization_language_index_]
.c_str())) {
show_self_hosted_server_config_window_ = true;
}
ImGui::SameLine();
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(title_bar_button_width_ * 4.275f);
} else {
ImGui::SetCursorPosX(title_bar_button_width_ * 5.955f);
}
ImGui::Checkbox("##enable_self_hosted", &enable_self_hosted_);
}
ImGui::Separator();
{
settings_items_offset += settings_items_padding;
ImGui::SetCursorPosY(settings_items_offset);
ImGui::AlignTextToFramePadding();
ImGui::Text("%s",
localization::enable_autostart[localization_language_index_]
.c_str());
ImGui::SameLine();
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(title_bar_button_width_ * 4.275f);
} else {
ImGui::SetCursorPosX(title_bar_button_width_ * 5.955f);
}
ImGui::Checkbox("##enable_autostart_", &enable_autostart_);
}
ImGui::Separator();
{
settings_items_offset += settings_items_padding;
ImGui::SetCursorPosY(settings_items_offset);
ImGui::AlignTextToFramePadding();
ImGui::Text(
"%s",
localization::enable_daemon[localization_language_index_].c_str());
ImGui::SameLine();
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(title_bar_button_width_ * 4.275f);
} else {
ImGui::SetCursorPosX(title_bar_button_width_ * 5.955f);
}
ImGui::Checkbox("##enable_daemon_", &enable_daemon_);
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::SetWindowFontScale(0.5f);
ImGui::Text("%s", localization::takes_effect_after_restart
[localization_language_index_]
.c_str());
ImGui::SetWindowFontScale(1.0f);
ImGui::EndTooltip();
}
}
ImGui::Separator();
{
settings_items_offset += settings_items_padding;
ImGui::SetCursorPosY(settings_items_offset);
ImGui::AlignTextToFramePadding();
ImGui::Text(
"%s",
localization::file_transfer_save_path[localization_language_index_]
.c_str());
ImGui::SameLine();
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(title_bar_button_width_ * 2.82f);
} else {
ImGui::SetCursorPosX(title_bar_button_width_ * 4.5f);
}
std::string display_path =
strlen(file_transfer_save_path_buf_) > 0
? file_transfer_save_path_buf_
: localization::default_desktop[localization_language_index_];
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
ImVec4(0.95f, 0.95f, 0.95f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive,
ImVec4(0.9f, 0.9f, 0.9f, 1.0f));
ImGui::PushFont(main_windows_system_chinese_font_);
if (ImGui::Button(display_path.c_str(),
ImVec2(title_bar_button_width_ * 2.0f, 0))) {
const char *folder =
tinyfd_selectFolderDialog(localization::file_transfer_save_path
[localization_language_index_]
.c_str(),
strlen(file_transfer_save_path_buf_) > 0
? file_transfer_save_path_buf_
: nullptr);
if (folder) {
strncpy(file_transfer_save_path_buf_, folder,
sizeof(file_transfer_save_path_buf_) - 1);
file_transfer_save_path_buf_[sizeof(file_transfer_save_path_buf_) -
1] = '\0';
}
}
if (ImGui::IsItemHovered() &&
strlen(file_transfer_save_path_buf_) > 0) {
ImGui::BeginTooltip();
ImGui::SetWindowFontScale(0.5f);
ImGui::Text("%s", file_transfer_save_path_buf_);
ImGui::SetWindowFontScale(1.0f);
ImGui::EndTooltip();
}
ImGui::PopFont();
ImGui::PopStyleColor(3);
}
if (stream_window_inited_) {
ImGui::EndDisabled();
}
#if _WIN32 && CROSSDESK_PORTABLE
ImGui::Separator();
{
settings_items_offset += settings_items_padding;
ImGui::SetCursorPosY(settings_items_offset);
ImGui::AlignTextToFramePadding();
ImGui::Text("%s", localization::windows_service_settings_label
[localization_language_index_]
.c_str());
ImGui::SameLine();
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(title_bar_button_width_ * 4.0f);
} else if (ConfigCenter::LANGUAGE::ENGLISH == localization_language_) {
ImGui::SetCursorPosX(title_bar_button_width_ * 5.42f);
} else {
ImGui::SetCursorPosX(title_bar_button_width_ * 4.6f);
}
const PortableServiceInstallState state =
portable_service_install_state_.load(std::memory_order_acquire);
const bool service_installed =
IsCrossDeskServiceInstalled() ||
state == PortableServiceInstallState::succeeded;
if (service_installed) {
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(title_bar_button_width_ * 3.9f);
} else if (ConfigCenter::LANGUAGE::ENGLISH ==
localization_language_) {
ImGui::SetCursorPosX(title_bar_button_width_ * 5.32f);
} else {
ImGui::SetCursorPosX(title_bar_button_width_ * 4.6f);
}
ImGui::Text("%s", localization::windows_service_installed
[localization_language_index_]
.c_str());
} else {
if (state == PortableServiceInstallState::installing) {
ImGui::BeginDisabled();
}
if (ImGui::Button(localization::install_windows_service
[localization_language_index_]
.c_str())) {
StartPortableWindowsServiceInstall();
}
if (state == PortableServiceInstallState::installing) {
ImGui::EndDisabled();
ImGui::SameLine();
ImGui::Text("%s", localization::installing_windows_service
[localization_language_index_]
.c_str());
} else if (state == PortableServiceInstallState::failed) {
ImGui::SameLine();
ImGui::Text(
"%s",
localization::failed[localization_language_index_].c_str());
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::SetWindowFontScale(0.5f);
ImGui::PushTextWrapPos(title_bar_button_width_ * 10.0f);
ImGui::TextWrapped("%s",
localization::windows_service_install_failed
[localization_language_index_]
.c_str());
ImGui::PopTextWrapPos();
ImGui::SetWindowFontScale(1.0f);
ImGui::EndTooltip();
}
}
}
}
#endif
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(title_bar_button_width_ * 1.59f);
} else {
ImGui::SetCursorPosX(title_bar_button_width_ * 2.22f);
}
settings_items_offset +=
settings_items_padding + title_bar_button_width_ * 0.15f;
ImGui::SetCursorPosY(settings_items_offset);
ImGui::PopStyleVar();
// OK
if (ImGui::Button(
localization::ok[localization_language_index_].c_str())) {
show_settings_window_ = false;
show_self_hosted_server_config_window_ = false;
// Language
language_button_value_ =
localization::detail::ClampLanguageIndex(language_button_value_);
if (language_button_value_ == 0) {
localization_language_ = ConfigCenter::LANGUAGE::CHINESE;
} else if (language_button_value_ == 1) {
localization_language_ = ConfigCenter::LANGUAGE::ENGLISH;
} else {
localization_language_ = ConfigCenter::LANGUAGE::RUSSIAN;
}
config_center_->SetLanguage(localization_language_);
language_button_value_last_ = language_button_value_;
localization_language_index_ = language_button_value_;
LOG_INFO("Set localization language: {}",
localization::GetSupportedLanguages()
[localization::detail::ClampLanguageIndex(
localization_language_index_)]
.code.c_str());
// Video quality
if (video_quality_button_value_ == 0) {
config_center_->SetVideoQuality(ConfigCenter::VIDEO_QUALITY::LOW);
} else if (video_quality_button_value_ == 1) {
config_center_->SetVideoQuality(ConfigCenter::VIDEO_QUALITY::MEDIUM);
} else {
config_center_->SetVideoQuality(ConfigCenter::VIDEO_QUALITY::HIGH);
}
video_quality_button_value_last_ = video_quality_button_value_;
if (video_frame_rate_button_value_ == 0) {
config_center_->SetVideoFrameRate(
ConfigCenter::VIDEO_FRAME_RATE::FPS_30);
} else if (video_frame_rate_button_value_ == 1) {
config_center_->SetVideoFrameRate(
ConfigCenter::VIDEO_FRAME_RATE::FPS_60);
}
video_frame_rate_button_value_last_ = video_frame_rate_button_value_;
// Video encode format
if (video_encode_format_button_value_ == 0) {
config_center_->SetVideoEncodeFormat(
ConfigCenter::VIDEO_ENCODE_FORMAT::H264);
} else if (video_encode_format_button_value_ == 1) {
config_center_->SetVideoEncodeFormat(
ConfigCenter::VIDEO_ENCODE_FORMAT::AV1);
}
video_encode_format_button_value_last_ =
video_encode_format_button_value_;
// Hardware video codec
if (enable_hardware_video_codec_) {
config_center_->SetHardwareVideoCodec(true);
} else {
config_center_->SetHardwareVideoCodec(false);
}
enable_hardware_video_codec_last_ = enable_hardware_video_codec_;
// TURN mode
if (enable_turn_) {
config_center_->SetTurn(true);
} else {
config_center_->SetTurn(false);
}
enable_turn_last_ = enable_turn_;
// SRTP
if (enable_srtp_) {
config_center_->SetSrtp(true);
} else {
config_center_->SetSrtp(false);
}
enable_srtp_last_ = enable_srtp_;
if (enable_self_hosted_) {
config_center_->SetSelfHosted(true);
} else {
config_center_->SetSelfHosted(false);
}
enable_self_hosted_last_ = enable_self_hosted_;
if (enable_autostart_) {
config_center_->SetAutostart(true);
} else {
config_center_->SetAutostart(false);
}
enable_autostart_last_ = enable_autostart_;
if (enable_daemon_) {
config_center_->SetDaemon(true);
} else {
config_center_->SetDaemon(false);
}
enable_daemon_last_ = enable_daemon_;
// File transfer save path
config_center_->SetFileTransferSavePath(file_transfer_save_path_buf_);
file_transfer_save_path_last_ = file_transfer_save_path_buf_;
settings_window_pos_reset_ = true;
// Recreate peer instance
settings_.Load();
// Recreate peer instance
if (!stream_window_inited_) {
LOG_INFO("Recreate peer instance");
CloseAllRemoteSessions();
CreateConnectionPeer();
}
}
ImGui::SameLine();
// Cancel
if (ImGui::Button(
localization::cancel[localization_language_index_].c_str())) {
show_settings_window_ = false;
show_self_hosted_server_config_window_ = false;
if (language_button_value_ != language_button_value_last_) {
language_button_value_ = language_button_value_last_;
}
if (video_quality_button_value_ != video_quality_button_value_last_) {
video_quality_button_value_ = video_quality_button_value_last_;
}
if (video_frame_rate_button_value_ !=
video_frame_rate_button_value_last_) {
video_frame_rate_button_value_ = video_frame_rate_button_value_last_;
}
if (video_encode_format_button_value_ !=
video_encode_format_button_value_last_) {
video_encode_format_button_value_ =
video_encode_format_button_value_last_;
}
if (enable_hardware_video_codec_ != enable_hardware_video_codec_last_) {
enable_hardware_video_codec_ = enable_hardware_video_codec_last_;
}
if (enable_turn_ != enable_turn_last_) {
enable_turn_ = enable_turn_last_;
}
// Restore file transfer save path
strncpy(file_transfer_save_path_buf_,
file_transfer_save_path_last_.c_str(),
sizeof(file_transfer_save_path_buf_) - 1);
file_transfer_save_path_buf_[sizeof(file_transfer_save_path_buf_) - 1] =
'\0';
settings_window_pos_reset_ = true;
}
ImGui::SetWindowFontScale(0.5f);
ImGui::End();
ImGui::PopStyleVar(2);
ImGui::PopStyleColor();
}
}
return 0;
}
} // namespace crossdesk
@@ -0,0 +1,60 @@
#include "application/gui_application.h"
#include "layout_relative.h"
#include "localization.h"
#include "rd_log.h"
namespace crossdesk {
int GuiApplication::MainWindow() {
ImGuiIO &io = ImGui::GetIO();
float local_remote_window_width = io.DisplaySize.x;
float local_remote_window_height =
io.DisplaySize.y * (0.56f - TITLE_BAR_HEIGHT);
ImGui::SetNextWindowPos(ImVec2(0.0f, io.DisplaySize.y * (TITLE_BAR_HEIGHT)),
ImGuiCond_Always);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 0.0f));
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 1.0f, 1.0f, 0.0f));
ImGui::BeginChild(
"DeskWindow",
ImVec2(local_remote_window_width, local_remote_window_height),
ImGuiChildFlags_Borders,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoBringToFrontOnFocus);
ImGui::PopStyleVar();
ImGui::PopStyleColor(2);
LocalWindow();
ImDrawList *draw_list = ImGui::GetWindowDrawList();
draw_list->AddLine(ImVec2(io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.1f),
ImVec2(io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.53f),
IM_COL32(0, 0, 0, 122), 1.0f);
RemoteWindow();
ImGui::EndChild();
RecentConnectionsWindow();
StatusBar();
if (show_connection_status_window_) {
// std::unique_lock lock(remote_sessions_mutex_);
for (auto it = remote_sessions_.begin();
it != remote_sessions_.end();) {
auto &props = it->second;
if (focused_remote_id_ == props->remote_id_) {
if (ConnectionStatusWindow(props)) {
it = remote_sessions_.erase(it);
} else {
++it;
}
} else {
++it;
}
}
}
return 0;
}
} // namespace crossdesk
@@ -0,0 +1,359 @@
#include "application/gui_application.h"
#if _WIN32 && CROSSDESK_PORTABLE
#include <shellapi.h>
#include <algorithm>
#include <vector>
#include "localization.h"
#include "rd_log.h"
#include "service_host.h"
namespace crossdesk {
namespace {
std::filesystem::path GetCurrentExecutablePath() {
std::vector<wchar_t> buffer(MAX_PATH);
while (true) {
DWORD length = GetModuleFileNameW(nullptr, buffer.data(),
static_cast<DWORD>(buffer.size()));
if (length == 0) {
return {};
}
if (length < buffer.size()) {
return std::filesystem::path(buffer.data(), buffer.data() + length);
}
if (buffer.size() >= 32768) {
return {};
}
buffer.resize(buffer.size() * 2);
}
}
bool InstallServiceWithElevation() {
const std::filesystem::path executable_path = GetCurrentExecutablePath();
if (executable_path.empty()) {
LOG_ERROR("Portable service install failed: current executable not found");
return false;
}
const std::filesystem::path service_path =
executable_path.parent_path() / L"crossdesk_service.exe";
const std::filesystem::path helper_path =
executable_path.parent_path() / L"crossdesk_session_helper.exe";
if (!std::filesystem::exists(service_path) ||
!std::filesystem::exists(helper_path)) {
LOG_ERROR("Portable service install failed: service binaries missing, "
"service={}, "
"helper={}",
service_path.string(), helper_path.string());
return false;
}
std::wstring executable = executable_path.wstring();
std::wstring working_dir = executable_path.parent_path().wstring();
std::wstring parameters = L"--service-install";
SHELLEXECUTEINFOW execute_info{};
execute_info.cbSize = sizeof(execute_info);
execute_info.fMask = SEE_MASK_NOCLOSEPROCESS;
execute_info.hwnd = nullptr;
execute_info.lpVerb = L"runas";
execute_info.lpFile = executable.c_str();
execute_info.lpParameters = parameters.c_str();
execute_info.lpDirectory = working_dir.c_str();
execute_info.nShow = SW_HIDE;
if (!ShellExecuteExW(&execute_info)) {
LOG_ERROR("Portable service install failed: ShellExecuteExW error={}",
GetLastError());
return false;
}
DWORD wait_result = WaitForSingleObject(execute_info.hProcess, INFINITE);
DWORD exit_code = 1;
if (wait_result == WAIT_OBJECT_0) {
GetExitCodeProcess(execute_info.hProcess, &exit_code);
} else {
LOG_ERROR("Portable service install wait failed, result={}", wait_result);
}
CloseHandle(execute_info.hProcess);
if (exit_code != 0) {
LOG_ERROR("Portable service install command failed, exit_code={}",
exit_code);
return false;
}
const bool started = StartCrossDeskService();
if (!started) {
LOG_WARN("Portable service installed but start failed");
}
return IsCrossDeskServiceInstalled() && started;
}
} // namespace
void GuiApplication::CheckPortableWindowsService() {
if (portable_service_prompt_checked_) {
return;
}
portable_service_prompt_checked_ = true;
if (IsCrossDeskServiceInstalled()) {
return;
}
if (portable_service_prompt_suppressed_) {
return;
}
portable_service_install_state_.store(PortableServiceInstallState::idle,
std::memory_order_relaxed);
show_portable_service_install_window_ = true;
}
void GuiApplication::StartPortableWindowsServiceInstall() {
portable_service_do_not_remind_ = false;
PortableServiceInstallState expected = PortableServiceInstallState::idle;
if (!portable_service_install_state_.compare_exchange_strong(
expected, PortableServiceInstallState::installing,
std::memory_order_acq_rel)) {
if (expected != PortableServiceInstallState::failed) {
return;
}
portable_service_install_state_.store(
PortableServiceInstallState::installing, std::memory_order_release);
}
JoinPortableWindowsServiceInstallThread();
portable_service_install_thread_ = std::thread([this]() {
const bool installed = InstallServiceWithElevation();
portable_service_install_state_.store(
installed ? PortableServiceInstallState::succeeded
: PortableServiceInstallState::failed,
std::memory_order_release);
});
}
void GuiApplication::JoinPortableWindowsServiceInstallThread() {
if (portable_service_install_thread_.joinable()) {
portable_service_install_thread_.join();
}
}
int GuiApplication::PortableServiceInstallWindow() {
if (!show_portable_service_install_window_ &&
!show_portable_service_prompt_suppressed_window_) {
return 0;
}
const ImGuiViewport *viewport = ImGui::GetMainViewport();
const float window_width =
(std::min)(viewport->WorkSize.x * 0.6f, title_bar_button_width_ * 18.0f);
const float window_height =
(std::min)(viewport->WorkSize.y * 0.5f, title_bar_button_width_ * 8.0f);
if (show_portable_service_prompt_suppressed_window_) {
const float notice_width = window_width;
const float notice_height = (std::min)(viewport->WorkSize.y * 0.35f,
title_bar_button_width_ * 4.6f);
ImGui::SetNextWindowPos(
ImVec2(viewport->WorkPos.x +
(viewport->WorkSize.x - notice_width) / 2.0f,
viewport->WorkPos.y +
(viewport->WorkSize.y - notice_height) / 2.0f),
ImGuiCond_Appearing);
ImGui::SetNextWindowSize(ImVec2(notice_width, notice_height),
ImGuiCond_Always);
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, window_rounding_);
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, window_rounding_ * 0.5f);
ImGui::Begin(
localization::notification[localization_language_index_].c_str(),
nullptr,
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_NoTitleBar);
ImGui::Spacing();
ImGui::SetWindowFontScale(0.55f);
ImGui::SetCursorPosX(notice_width * 0.08f);
ImGui::Text(
"%s", localization::notification[localization_language_index_].c_str());
ImGui::SetWindowFontScale(0.5f);
ImGui::SetCursorPosX(notice_width * 0.06f);
ImGui::SetCursorPosY(notice_height * 0.28f);
ImGui::PushTextWrapPos(notice_width * 0.88f);
ImGui::TextWrapped("%s",
localization::windows_service_prompt_suppressed_message
[localization_language_index_]
.c_str());
ImGui::PopTextWrapPos();
const std::string ok_label = localization::ok[localization_language_index_];
const ImGuiStyle &style = ImGui::GetStyle();
const float ok_width =
ImGui::CalcTextSize(ok_label.c_str()).x + style.FramePadding.x * 2.0f;
ImGui::SetCursorPosX((notice_width - ok_width) * 0.5f);
ImGui::SetCursorPosY(notice_height * 0.75f);
if (ImGui::Button(ok_label.c_str())) {
show_portable_service_prompt_suppressed_window_ = false;
}
ImGui::SetWindowFontScale(1.0f);
ImGui::End();
ImGui::PopStyleVar(3);
ImGui::PopStyleColor();
}
if (!show_portable_service_install_window_) {
return 0;
}
ImGui::SetNextWindowPos(
ImVec2(viewport->WorkPos.x + (viewport->WorkSize.x - window_width) / 2.0f,
viewport->WorkPos.y +
(viewport->WorkSize.y - window_height) / 2.0f),
ImGuiCond_Appearing);
ImGui::SetNextWindowSize(ImVec2(window_width, window_height),
ImGuiCond_Always);
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, window_rounding_);
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, window_rounding_ * 0.5f);
ImGui::Begin(
localization::windows_service_setup_title[localization_language_index_]
.c_str(),
nullptr,
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_NoTitleBar);
ImGui::Spacing();
ImGui::SetWindowFontScale(0.55f);
ImGui::SetCursorPosX(window_width * 0.08f);
ImGui::Text(
"%s",
localization::windows_service_setup_title[localization_language_index_]
.c_str());
const PortableServiceInstallState state =
portable_service_install_state_.load(std::memory_order_acquire);
const char *status_text = nullptr;
if (state == PortableServiceInstallState::installing ||
state == PortableServiceInstallState::succeeded ||
state == PortableServiceInstallState::failed) {
status_text =
localization::installing_windows_service[localization_language_index_]
.c_str();
if (state == PortableServiceInstallState::succeeded) {
status_text = localization::windows_service_install_success
[localization_language_index_]
.c_str();
} else if (state == PortableServiceInstallState::failed) {
status_text = localization::windows_service_install_failed
[localization_language_index_]
.c_str();
}
}
ImGui::SetWindowFontScale(0.45f);
ImGui::SetCursorPosX(window_width * 0.04f);
ImGui::SetCursorPosY(window_height * 0.22f);
ImGui::BeginChild("PortableServiceInstallContent",
ImVec2(window_width * 0.92f, window_height * 0.45f),
ImGuiChildFlags_Borders, ImGuiWindowFlags_None);
ImGui::SetWindowFontScale(0.5f);
const float wrap_pos = ImGui::GetContentRegionAvail().x;
ImGui::PushTextWrapPos(wrap_pos);
ImGui::TextWrapped(
"%s",
localization::windows_service_setup_message[localization_language_index_]
.c_str());
if (status_text != nullptr) {
ImGui::Spacing();
ImGui::TextWrapped("%s", status_text);
}
ImGui::PopTextWrapPos();
ImGui::EndChild();
ImGui::SetWindowFontScale(0.5f);
ImGui::SetCursorPosX(window_width * 0.08f);
ImGui::SetCursorPosY(window_height * 0.71f);
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f);
ImGui::Checkbox(
localization::do_not_remind_again[localization_language_index_].c_str(),
&portable_service_do_not_remind_);
ImGui::PopStyleVar();
const float button_y = window_height * 0.84f;
const ImGuiStyle &style = ImGui::GetStyle();
const auto default_button_width = [&style](const std::string &label) {
return ImGui::CalcTextSize(label.c_str()).x + style.FramePadding.x * 2.0f;
};
const std::string install_label =
localization::install_windows_service[localization_language_index_];
const std::string cancel_label =
localization::cancel[localization_language_index_];
const std::string ok_label = localization::ok[localization_language_index_];
const float buttons_width = state == PortableServiceInstallState::succeeded
? default_button_width(ok_label)
: default_button_width(install_label) +
style.ItemSpacing.x +
default_button_width(cancel_label);
ImGui::SetCursorPosX((window_width - buttons_width) * 0.5f);
ImGui::SetCursorPosY(button_y);
if (state == PortableServiceInstallState::succeeded) {
if (ImGui::Button(ok_label.c_str())) {
show_portable_service_install_window_ = false;
JoinPortableWindowsServiceInstallThread();
}
} else {
if (state == PortableServiceInstallState::installing) {
ImGui::BeginDisabled();
}
if (ImGui::Button(install_label.c_str())) {
StartPortableWindowsServiceInstall();
}
if (state == PortableServiceInstallState::installing) {
ImGui::EndDisabled();
}
ImGui::SameLine();
if (state == PortableServiceInstallState::installing) {
ImGui::BeginDisabled();
}
if (ImGui::Button(cancel_label.c_str())) {
if (portable_service_do_not_remind_) {
portable_service_prompt_suppressed_ = true;
config_center_->SetPortableServicePromptSuppressed(true);
show_portable_service_prompt_suppressed_window_ = true;
}
show_portable_service_install_window_ = false;
}
if (state == PortableServiceInstallState::installing) {
ImGui::EndDisabled();
}
}
ImGui::SetWindowFontScale(1.0f);
ImGui::End();
ImGui::PopStyleVar(3);
ImGui::PopStyleColor();
return 0;
}
} // namespace crossdesk
#endif
@@ -0,0 +1,197 @@
#include <filesystem>
#include <vector>
#ifdef _WIN32
#include <windows.h>
#endif
#include "application/gui_application.h"
#include "layout.h"
#include "localization.h"
#include "rd_log.h"
namespace crossdesk {
std::vector<std::string> GetRootEntries() {
std::vector<std::string> roots;
#ifdef _WIN32
DWORD mask = GetLogicalDrives();
for (char letter = 'A'; letter <= 'Z'; ++letter) {
if (mask & 1) {
roots.push_back(std::string(1, letter) + ":\\");
}
mask >>= 1;
}
#else
roots.push_back("/");
#endif
return roots;
}
int GuiApplication::SelfHostedServerWindow() {
ImGuiIO &io = ImGui::GetIO();
if (show_self_hosted_server_config_window_) {
if (self_hosted_server_config_window_pos_reset_) {
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetNextWindowPos(
ImVec2(io.DisplaySize.x * 0.298f, io.DisplaySize.y * 0.25f));
ImGui::SetNextWindowSize(
ImVec2(io.DisplaySize.x * 0.407f, io.DisplaySize.y * 0.35f));
} else {
ImGui::SetNextWindowPos(
ImVec2(io.DisplaySize.x * 0.27f, io.DisplaySize.y * 0.3f));
ImGui::SetNextWindowSize(
ImVec2(io.DisplaySize.x * 0.465f, io.DisplaySize.y * 0.35f));
}
self_hosted_server_config_window_pos_reset_ = false;
}
// Settings
{
ImGui::SetWindowFontScale(0.5f);
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, window_rounding_);
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, window_rounding_ * 0.5f);
ImGui::Begin(localization::self_hosted_server_settings
[localization_language_index_]
.c_str(),
nullptr,
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoSavedSettings);
ImGui::SetWindowFontScale(1.0f);
ImGui::SetWindowFontScale(0.5f);
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f);
{
ImGui::AlignTextToFramePadding();
ImGui::Text("%s", localization::self_hosted_server_address
[localization_language_index_]
.c_str());
ImGui::SameLine();
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(title_bar_button_width_ * 2.5f);
} else {
ImGui::SetCursorPosX(title_bar_button_width_ * 3.43f);
}
ImGui::SetNextItemWidth(title_bar_button_width_ * 3.8f);
ImGui::InputText("##signal_server_ip_self_", signal_server_ip_self_,
IM_ARRAYSIZE(signal_server_ip_self_),
ImGuiInputTextFlags_AlwaysOverwrite);
}
ImGui::Separator();
{
ImGui::AlignTextToFramePadding();
ImGui::Text(
"%s",
localization::self_hosted_server_port[localization_language_index_]
.c_str());
ImGui::SameLine();
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(title_bar_button_width_ * 2.5f);
} else {
ImGui::SetCursorPosX(title_bar_button_width_ * 3.43f);
}
ImGui::SetNextItemWidth(title_bar_button_width_ * 3.8f);
ImGui::InputText("##signal_server_port_self_", signal_server_port_self_,
IM_ARRAYSIZE(signal_server_port_self_));
}
ImGui::Separator();
{
ImGui::AlignTextToFramePadding();
ImGui::Text("%s", localization::self_hosted_server_coturn_server_port
[localization_language_index_]
.c_str());
ImGui::SameLine();
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(title_bar_button_width_ * 2.5f);
} else {
ImGui::SetCursorPosX(title_bar_button_width_ * 3.43f);
}
ImGui::SetNextItemWidth(title_bar_button_width_ * 3.8f);
ImGui::InputText("##coturn_server_port_self_", coturn_server_port_self_,
IM_ARRAYSIZE(coturn_server_port_self_));
}
if (stream_window_inited_) {
ImGui::EndDisabled();
}
ImGui::Dummy(ImVec2(0.0f, title_bar_button_width_ * 0.25f));
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(title_bar_button_width_ * 2.32f);
} else {
ImGui::SetCursorPosX(title_bar_button_width_ * 2.7f);
}
ImGui::PopStyleVar();
// OK
if (ImGui::Button(
localization::ok[localization_language_index_].c_str())) {
show_self_hosted_server_config_window_ = false;
config_center_->SetServerHost(signal_server_ip_self_);
config_center_->SetServerPort(atoi(signal_server_port_self_));
config_center_->SetCoturnServerPort(atoi(coturn_server_port_self_));
strncpy(signal_server_ip_, signal_server_ip_self_,
sizeof(signal_server_ip_) - 1);
signal_server_ip_[sizeof(signal_server_ip_) - 1] = '\0';
strncpy(signal_server_port_, signal_server_port_self_,
sizeof(signal_server_port_) - 1);
signal_server_port_[sizeof(signal_server_port_) - 1] = '\0';
strncpy(coturn_server_port_, coturn_server_port_self_,
sizeof(coturn_server_port_) - 1);
coturn_server_port_[sizeof(coturn_server_port_) - 1] = '\0';
self_hosted_server_config_window_pos_reset_ = true;
}
ImGui::SameLine();
// Cancel
if (ImGui::Button(
localization::cancel[localization_language_index_].c_str())) {
show_self_hosted_server_config_window_ = false;
self_hosted_server_config_window_pos_reset_ = true;
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 = config_center_->GetSignalServerPort();
if (signal_port > 0) {
strncpy(signal_server_port_self_, std::to_string(signal_port).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';
}
int coturn_port = config_center_->GetCoturnServerPort();
if (coturn_port > 0) {
strncpy(coturn_server_port_self_, std::to_string(coturn_port).c_str(),
sizeof(coturn_server_port_self_) - 1);
coturn_server_port_self_[sizeof(coturn_server_port_self_) - 1] = '\0';
} else {
coturn_server_port_self_[0] = '\0';
}
}
ImGui::SetWindowFontScale(1.0f);
ImGui::SetWindowFontScale(0.5f);
ImGui::End();
ImGui::PopStyleVar(2);
ImGui::PopStyleColor();
ImGui::SetWindowFontScale(1.0f);
}
}
return 0;
}
} // namespace crossdesk
@@ -0,0 +1,388 @@
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <memory>
#include <shared_mutex>
#include <string>
#include <vector>
#include "application/gui_application.h"
#include "layout_relative.h"
#include "localization.h"
#include "rd_log.h"
#include "rounded_corner_button.h"
namespace crossdesk {
namespace {
int CountDigits(int number) {
if (number == 0)
return 1;
return (int)std::floor(std::log10(std::abs(number))) + 1;
}
void BitrateDisplay(uint32_t bitrate) {
const int num_of_digits = CountDigits(static_cast<int>(bitrate));
if (num_of_digits <= 3) {
ImGui::Text("%u bps", bitrate);
} else if (num_of_digits > 3 && num_of_digits <= 6) {
ImGui::Text("%u kbps", bitrate / 1000);
} else {
ImGui::Text("%.1f mbps", bitrate / 1000000.0f);
}
}
std::string FormatBytes(uint64_t bytes) {
char buf[64];
if (bytes < 1024ULL) {
std::snprintf(buf, sizeof(buf), "%llu B", (unsigned long long)bytes);
} else if (bytes < 1024ULL * 1024ULL) {
std::snprintf(buf, sizeof(buf), "%.2f KB", bytes / 1024.0);
} else if (bytes < 1024ULL * 1024ULL * 1024ULL) {
std::snprintf(buf, sizeof(buf), "%.2f MB", bytes / (1024.0 * 1024.0));
} else {
std::snprintf(buf, sizeof(buf), "%.2f GB",
bytes / (1024.0 * 1024.0 * 1024.0));
}
return std::string(buf);
}
} // namespace
int GuiApplication::ServerWindow() {
ImGui::SetNextWindowSize(ImVec2(server_window_width_, server_window_height_),
ImGuiCond_Always);
ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, window_rounding_);
ImGui::Begin("##server_window", nullptr,
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoScrollWithMouse);
ImGui::PopStyleVar();
server_window_title_bar_height_ = title_bar_height_;
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 0.0f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, window_rounding_);
ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always);
ImGui::BeginChild(
"ServerTitleBar",
ImVec2(server_window_width_, server_window_title_bar_height_),
ImGuiChildFlags_Borders,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoBringToFrontOnFocus);
float server_title_bar_button_width = server_window_title_bar_height_;
float server_title_bar_button_height = server_window_title_bar_height_;
// Collapse/expand toggle button (FontAwesome icon).
{
ImGui::SetCursorPos(ImVec2(0.0f, 0.0f));
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0, 0, 0, 0.1f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive,
ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
ImGui::SetWindowFontScale(0.5f);
const char *icon =
server_window_collapsed_ ? ICON_FA_ANGLE_DOWN : ICON_FA_ANGLE_UP;
std::string toggle_label = std::string(icon) + "##server_toggle";
bool toggle_clicked = RoundedCornerButton(
toggle_label.c_str(),
ImVec2(server_title_bar_button_width, server_title_bar_button_height),
8.5f, ImDrawFlags_RoundCornersTopLeft, true, IM_COL32(0, 0, 0, 0),
IM_COL32(0, 0, 0, 25), IM_COL32(255, 255, 255, 255));
if (toggle_clicked) {
if (server_window_) {
int w = 0;
int h = 0;
int x = 0;
int y = 0;
SDL_GetWindowSize(server_window_, &w, &h);
SDL_GetWindowPosition(server_window_, &x, &y);
if (server_window_collapsed_) {
const int normal_h = server_window_normal_height_;
SDL_SetWindowSize(server_window_, w, normal_h);
SDL_SetWindowPosition(server_window_, x, y);
server_window_collapsed_ = false;
} else {
const int collapsed_h = (int)server_window_title_bar_height_;
// Collapse upward: keep top edge stable.
SDL_SetWindowSize(server_window_, w, collapsed_h);
SDL_SetWindowPosition(server_window_, x, y);
server_window_collapsed_ = true;
}
}
}
ImGui::SetWindowFontScale(1.0f);
ImGui::PopStyleColor(3);
}
ImGui::EndChild();
ImGui::PopStyleVar(2);
ImGui::PopStyleColor();
RemoteClientInfoWindow();
ImGui::End();
return 0;
}
int GuiApplication::RemoteClientInfoWindow() {
float remote_client_info_window_width = server_window_width_ * 0.8f;
float remote_client_info_window_height =
(server_window_height_ - server_window_title_bar_height_) * 0.9f;
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 5.0f);
ImGui::BeginChild(
"RemoteClientInfoWindow",
ImVec2(remote_client_info_window_width, remote_client_info_window_height),
ImGuiChildFlags_Borders,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoBringToFrontOnFocus);
ImGui::PopStyleVar();
ImGui::PopStyleColor();
float font_scale = localization_language_index_ == 0 ? 0.5f : 0.45f;
std::vector<std::pair<std::string, std::string>> remote_entries;
{
std::shared_lock lock(connection_status_mutex_);
remote_entries.reserve(connection_status_.size());
for (const auto &kv : connection_status_) {
const auto host_it = connection_host_names_.find(kv.first);
const std::string display_name =
(host_it != connection_host_names_.end() && !host_it->second.empty())
? host_it->second
: kv.first;
remote_entries.emplace_back(kv.first, display_name);
}
}
auto find_display_name_by_remote_id =
[&remote_entries](const std::string &remote_id) -> std::string {
for (const auto &entry : remote_entries) {
if (entry.first == remote_id) {
return entry.second;
}
}
return {};
};
if (!selected_server_remote_id_.empty() &&
find_display_name_by_remote_id(selected_server_remote_id_).empty()) {
selected_server_remote_id_.clear();
selected_server_remote_hostname_.clear();
}
if (selected_server_remote_id_.empty() && !remote_entries.empty()) {
selected_server_remote_id_ = remote_entries.front().first;
}
if (!selected_server_remote_id_.empty()) {
selected_server_remote_hostname_ =
find_display_name_by_remote_id(selected_server_remote_id_);
}
ImGui::SetWindowFontScale(font_scale);
ImGui::AlignTextToFramePadding();
ImGui::Text("%s",
localization::controller[localization_language_index_].c_str());
ImGui::SameLine();
const char *selected_preview = "-";
if (!selected_server_remote_hostname_.empty()) {
selected_preview = selected_server_remote_hostname_.c_str();
} else if (!remote_client_id_.empty()) {
selected_preview = remote_client_id_.c_str();
}
ImGui::SetNextItemWidth(remote_client_info_window_width *
(localization_language_index_ == 0 ? 0.68f : 0.62f));
ImGui::SetWindowFontScale(localization_language_index_ == 0 ? 0.45f : 0.4f);
ImGui::AlignTextToFramePadding();
if (ImGui::BeginCombo("##server_remote_id", selected_preview)) {
ImGui::SetWindowFontScale(localization_language_index_ == 0 ? 0.45f : 0.4f);
for (int i = 0; i < static_cast<int>(remote_entries.size()); i++) {
const bool selected =
(remote_entries[i].first == selected_server_remote_id_);
if (ImGui::Selectable(remote_entries[i].second.c_str(), selected)) {
selected_server_remote_id_ = remote_entries[i].first;
selected_server_remote_hostname_ = remote_entries[i].second;
}
if (selected) {
ImGui::SetItemDefaultFocus();
}
}
ImGui::EndCombo();
}
ImGui::Separator();
ImGui::SetWindowFontScale(font_scale);
if (!selected_server_remote_id_.empty()) {
ConnectionStatus status = ConnectionStatus::Closed;
{
std::shared_lock lock(connection_status_mutex_);
auto it = connection_status_.find(selected_server_remote_id_);
if (it != connection_status_.end()) {
status = it->second;
}
}
ImGui::Text(
"%s",
localization::connection_status[localization_language_index_].c_str());
ImGui::SameLine();
switch (status) {
case ConnectionStatus::Connected:
ImGui::Text(
"%s",
localization::p2p_connected[localization_language_index_].c_str());
break;
case ConnectionStatus::Connecting:
ImGui::Text(
"%s",
localization::p2p_connecting[localization_language_index_].c_str());
break;
case ConnectionStatus::Disconnected:
ImGui::Text(
"%s",
localization::p2p_disconnected[localization_language_index_].c_str());
break;
case ConnectionStatus::Failed:
ImGui::Text(
"%s", localization::p2p_failed[localization_language_index_].c_str());
break;
case ConnectionStatus::Closed:
ImGui::Text(
"%s", localization::p2p_closed[localization_language_index_].c_str());
break;
default:
ImGui::Text(
"%s", localization::p2p_failed[localization_language_index_].c_str());
break;
}
}
ImGui::Separator();
ImGui::AlignTextToFramePadding();
ImGui::Text(
"%s", localization::file_transfer[localization_language_index_].c_str());
ImGui::SameLine();
if (ImGui::Button(
localization::select_file[localization_language_index_].c_str())) {
std::string title = localization::select_file[localization_language_index_];
std::string path = OpenFileDialog(title);
LOG_INFO("Selected file path: {}", path.c_str());
transfers_.ProcessSelectedFile(path, nullptr, file_label_,
selected_server_remote_id_);
}
auto &file_transfer = transfers_.global_state();
if (file_transfer.file_transfer_window_visible_) {
ImGui::SameLine();
const bool is_sending = file_transfer.file_sending_.load();
if (is_sending) {
// Simple animation: cycle icon every 0.5s while sending.
static const char *kFileTransferIcons[] = {ICON_FA_ANGLE_UP,
ICON_FA_ANGLES_UP};
const int icon_index = static_cast<int>(ImGui::GetTime() / 0.5) %
(static_cast<int>(sizeof(kFileTransferIcons) /
sizeof(kFileTransferIcons[0])));
ImGui::Text("%s", kFileTransferIcons[icon_index]);
} else {
// Completed.
ImGui::Text("%s", ICON_FA_CHECK);
}
if (ImGui::IsItemHovered()) {
const uint64_t sent_bytes = file_transfer.file_sent_bytes_.load();
const uint64_t total_bytes = file_transfer.file_total_bytes_.load();
const uint32_t rate_bps = file_transfer.file_send_rate_bps_.load();
float progress = 0.0f;
if (total_bytes > 0) {
progress =
static_cast<float>(sent_bytes) / static_cast<float>(total_bytes);
progress = (std::max)(0.0f, (std::min)(1.0f, progress));
}
std::string current_file_name;
const uint32_t current_file_id = file_transfer.current_file_id_.load();
if (current_file_id != 0) {
std::lock_guard<std::mutex> lock(
file_transfer.file_transfer_list_mutex_);
for (const auto &info : file_transfer.file_transfer_list_) {
if (info.file_id == current_file_id) {
current_file_name = info.file_name;
break;
}
}
}
ImGui::BeginTooltip();
if (server_windows_system_chinese_font_) {
ImGui::PushFont(server_windows_system_chinese_font_);
}
ImGui::SetWindowFontScale(0.5f);
if (!current_file_name.empty()) {
ImGui::Text("%s", current_file_name.c_str());
}
if (total_bytes > 0) {
const std::string sent_str = FormatBytes(sent_bytes);
const std::string total_str = FormatBytes(total_bytes);
ImGui::Text("%s / %s", sent_str.c_str(), total_str.c_str());
}
const float text_height = ImGui::GetTextLineHeight();
char overlay[32];
std::snprintf(overlay, sizeof(overlay), "%.1f%%", progress * 100.0f);
ImGui::ProgressBar(progress, ImVec2(180.0f, text_height), overlay);
BitrateDisplay(rate_bps);
ImGui::SetWindowFontScale(1.0f);
if (server_windows_system_chinese_font_) {
ImGui::PopFont();
}
ImGui::EndTooltip();
}
}
ImGui::SetWindowFontScale(1.0f);
ImGui::EndChild();
ImGui::SameLine();
float close_connection_button_width = server_window_width_ * 0.1f;
float close_connection_button_height = remote_client_info_window_height;
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1.0f, 0.0f, 0.0f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0f, 0.3f, 0.3f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1.0f, 0.5f, 0.5f, 1.0f));
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, window_rounding_);
ImGui::SetWindowFontScale(font_scale);
if (ImGui::Button(ICON_FA_XMARK, ImVec2(close_connection_button_width,
close_connection_button_height))) {
if (peer_ && !selected_server_remote_id_.empty()) {
LeaveConnection(peer_, selected_server_remote_id_.c_str());
}
}
ImGui::SetWindowFontScale(1.0f);
ImGui::PopStyleColor(3);
ImGui::PopStyleVar();
return 0;
}
} // namespace crossdesk
@@ -0,0 +1,311 @@
#include "application/gui_application.h"
#include "localization.h"
#include "rd_log.h"
namespace crossdesk {
void GuiApplication::DrawConnectionStatusText(
std::shared_ptr<RemoteSession> &props) {
std::string text;
switch (props->connection_status_.load()) {
case ConnectionStatus::Disconnected:
text = localization::p2p_disconnected[localization_language_index_];
break;
case ConnectionStatus::Failed:
text = localization::p2p_failed[localization_language_index_];
break;
case ConnectionStatus::Closed:
text = localization::p2p_closed[localization_language_index_];
break;
default:
break;
}
if (!text.empty()) {
ImVec2 size = ImGui::GetWindowSize();
ImVec2 text_size = ImGui::CalcTextSize(text.c_str());
ImGui::SetCursorPos(
ImVec2((size.x - text_size.x) * 0.5f,
(size.y - text_size.y - title_bar_height_) * 0.5f));
ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 1.0f), "%s", text.c_str());
}
}
void GuiApplication::DrawReceivingScreenText(
std::shared_ptr<RemoteSession> &props) {
if (!props->connection_established_ ||
props->connection_status_.load() != ConnectionStatus::Connected) {
return;
}
bool has_valid_frame = false;
{
std::lock_guard<std::mutex> lock(props->video_frame_mutex_);
has_valid_frame = props->stream_texture_ != nullptr &&
props->video_width_ > 0 && props->video_height_ > 0 &&
props->front_frame_ && !props->front_frame_->empty();
}
if (has_valid_frame) {
return;
}
const std::string &text =
localization::receiving_screen[localization_language_index_];
ImVec2 size = ImGui::GetWindowSize();
ImVec2 text_size = ImGui::CalcTextSize(text.c_str());
ImGui::SetCursorPos(
ImVec2((size.x - text_size.x) * 0.5f, (size.y - text_size.y) * 0.5f));
ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 0.92f), "%s", text.c_str());
}
void GuiApplication::CloseTab(decltype(remote_sessions_)::iterator &it) {
// std::unique_lock lock(remote_sessions_mutex_);
if (it != remote_sessions_.end()) {
CloseRemoteSession(it->second);
it = remote_sessions_.erase(it);
if (remote_sessions_.empty()) {
SDL_Event event;
event.type = SDL_EVENT_QUIT;
SDL_PushEvent(&event);
}
}
}
int GuiApplication::StreamWindow() {
ImGui::SetNextWindowPos(
ImVec2(0, fullscreen_button_pressed_ ? 0 : title_bar_height_),
ImGuiCond_Always);
ImGui::SetNextWindowSize(ImVec2(stream_window_width_, stream_window_height_),
ImGuiCond_Always);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0));
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0));
bool video_bg_opened = ImGui::Begin(
"VideoBg", nullptr,
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration |
ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoDocking);
ImGui::PopStyleColor(2);
ImGui::PopStyleVar();
if (!video_bg_opened) {
return 0;
}
ImGuiWindowFlags stream_window_flag =
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoDecoration |
ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoMove;
if (!fullscreen_button_pressed_) {
ImGui::SetNextWindowPos(
ImVec2(title_bar_button_width_ * 0.8f, title_bar_button_width_ * 0.1f),
ImGuiCond_Always);
ImGui::SetNextWindowSize(ImVec2(0, title_bar_button_width_ * 0.8f),
ImGuiCond_Always);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0.0f));
ImGui::Begin("TabBar", nullptr,
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration |
ImGuiWindowFlags_NoBringToFrontOnFocus |
ImGuiWindowFlags_NoDocking);
ImGui::PopStyleColor();
ImGui::PopStyleVar(2);
if (ImGui::BeginTabBar("StreamTabBar",
ImGuiTabBarFlags_Reorderable |
ImGuiTabBarFlags_AutoSelectNewTabs)) {
is_tab_bar_hovered_ = ImGui::IsWindowHovered();
// std::shared_lock lock(remote_sessions_mutex_);
for (auto it = remote_sessions_.begin();
it != remote_sessions_.end();) {
auto &props = it->second;
if (!props->tab_opened_) {
std::string remote_id_to_close = props->remote_id_;
// lock.unlock();
{
// std::unique_lock unique_lock(remote_sessions_mutex_);
auto close_it = remote_sessions_.find(remote_id_to_close);
if (close_it != remote_sessions_.end()) {
CloseTab(close_it);
}
}
// lock.lock();
it = remote_sessions_.begin();
continue;
}
std::string tab_label =
enable_srtp_
? std::string(ICON_FA_SHIELD_HALVED) + " " + props->remote_id_
: props->remote_id_;
if (ImGui::BeginTabItem(tab_label.c_str(), &props->tab_opened_)) {
props->tab_selected_ = true;
ImGui::SetWindowFontScale(0.6f);
ImGui::SetNextWindowSize(
ImVec2(stream_window_width_,
stream_window_height_ -
(fullscreen_button_pressed_ ? 0 : title_bar_height_)),
ImGuiCond_Always);
ImGui::SetNextWindowPos(
ImVec2(0, fullscreen_button_pressed_ ? 0 : title_bar_height_),
ImGuiCond_Always);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0.0f));
ImGui::Begin(props->remote_id_.c_str(), nullptr, stream_window_flag);
ImGui::PopStyleColor();
ImGui::PopStyleVar(2);
ImVec2 pos = ImGui::GetWindowPos();
ImVec2 size = ImGui::GetWindowSize();
props->render_window_x_ = pos.x;
props->render_window_y_ = pos.y;
props->render_window_width_ = size.x;
props->render_window_height_ = size.y;
UpdateRenderRect();
ControlWindow(props);
// Show file transfer window if needed
FileTransferWindow(props);
DrawReceivingScreenText(props);
focused_remote_id_ = props->remote_id_;
if (!props->peer_) {
std::string remote_id_to_erase = props->remote_id_;
// lock.unlock();
{
// std::unique_lock unique_lock(remote_sessions_mutex_);
auto erase_it = remote_sessions_.find(remote_id_to_erase);
if (erase_it != remote_sessions_.end()) {
// Ensure we flush pending STREAM_REFRESH_EVENT events and
// clean up peer resources before erasing the entry, otherwise
// SDL events may still hold raw pointers to freed
// RemoteSession (including video_frame_mutex_),
// leading to std::system_error when locking.
CloseTab(erase_it);
}
}
// lock.lock();
ImGui::End();
ImGui::EndTabItem();
it = remote_sessions_.begin();
continue;
} else {
DrawConnectionStatusText(props);
++it;
}
ImGui::End();
ImGui::EndTabItem();
} else {
props->tab_selected_ = false;
if (!props->tab_opened_) {
std::string remote_id_to_close = props->remote_id_;
// lock.unlock();
{
// std::unique_lock unique_lock(remote_sessions_mutex_);
auto close_it = remote_sessions_.find(remote_id_to_close);
if (close_it != remote_sessions_.end()) {
CloseTab(close_it);
}
}
// lock.lock();
it = remote_sessions_.begin();
continue;
}
++it;
}
}
ImGui::EndTabBar();
}
ImGui::End(); // End TabBar
} else {
// std::shared_lock lock(remote_sessions_mutex_);
for (auto it = remote_sessions_.begin();
it != remote_sessions_.end();) {
auto &props = it->second;
if (!props->tab_opened_) {
std::string remote_id_to_close = props->remote_id_;
// lock.unlock();
{
// std::unique_lock unique_lock(remote_sessions_mutex_);
auto close_it = remote_sessions_.find(remote_id_to_close);
if (close_it != remote_sessions_.end()) {
CloseTab(close_it);
}
}
// lock.lock();
it = remote_sessions_.begin();
continue;
}
if (props->tab_selected_) {
ImGui::SetNextWindowSize(
ImVec2(stream_window_width_,
stream_window_height_ -
(fullscreen_button_pressed_ ? 0 : title_bar_height_)),
ImGuiCond_Always);
ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0.0f));
ImGui::Begin(props->remote_id_.c_str(), nullptr, stream_window_flag);
ImGui::PopStyleColor();
ImGui::PopStyleVar(2);
ImVec2 pos = ImGui::GetWindowPos();
ImVec2 size = ImGui::GetWindowSize();
props->render_window_x_ = pos.x;
props->render_window_y_ = pos.y;
props->render_window_width_ = size.x;
props->render_window_height_ = size.y;
UpdateRenderRect();
ControlWindow(props);
// Show file transfer window if needed
FileTransferWindow(props);
DrawReceivingScreenText(props);
ImGui::End();
if (!props->peer_) {
fullscreen_button_pressed_ = false;
SDL_SetWindowFullscreen(stream_window_, false);
std::string remote_id_to_erase = props->remote_id_;
// lock.unlock();
{
// std::unique_lock unique_lock(remote_sessions_mutex_);
auto erase_it = remote_sessions_.find(remote_id_to_erase);
if (erase_it != remote_sessions_.end()) {
CloseTab(erase_it);
}
}
// lock.lock();
it = remote_sessions_.begin();
continue;
} else {
DrawConnectionStatusText(props);
++it;
}
} else {
++it;
}
}
}
// UpdateRenderRect();
ImGui::End(); // End VideoBg
return 0;
}
} // namespace crossdesk
@@ -0,0 +1,163 @@
#include <string>
#include "application/gui_application.h"
#include "layout.h"
#include "localization.h"
#include "rd_log.h"
namespace crossdesk {
int GuiApplication::UpdateNotificationWindow() {
if (show_update_notification_window_ && update_available_) {
const ImGuiViewport* viewport = ImGui::GetMainViewport();
float update_notification_window_width = title_bar_button_width_ * 10.0f;
float update_notification_window_height = title_bar_button_width_ * 8.0f;
// #ifdef __APPLE__
// float font_scale = 0.3f;
// #else
// float font_scale = 0.5f;
// #endif
ImGui::SetNextWindowPos(ImVec2((viewport->WorkSize.x - viewport->WorkPos.x -
update_notification_window_width) /
2,
(viewport->WorkSize.y - viewport->WorkPos.y -
update_notification_window_height) /
2),
ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSize(ImVec2(update_notification_window_width,
update_notification_window_height));
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, window_rounding_);
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, window_rounding_ * 0.5f);
ImGui::Begin(
localization::notification[localization_language_index_].c_str(),
nullptr,
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoTitleBar);
ImGui::SetCursorPosY(ImGui::GetCursorPosY() +
update_notification_window_height * 0.05f);
// title: new version available
ImGui::SetCursorPosX(update_notification_window_width * 0.1f);
ImGui::SetWindowFontScale(0.55f);
std::string title =
localization::new_version_available[localization_language_index_] +
": " + latest_version_;
ImGui::Text("%s", title.c_str());
ImGui::SetWindowFontScale(0.1f);
ImGui::Spacing();
// website link
std::string download_text =
localization::access_website[localization_language_index_] +
"https://crossdesk.cn";
ImGui::SetWindowFontScale(0.5f);
ImGui::SetCursorPosX(update_notification_window_width * 0.1f);
Hyperlink(download_text, "https://crossdesk.cn",
update_notification_window_width);
ImGui::SetWindowFontScale(1.0f);
ImGui::Spacing();
float scrollable_height =
update_notification_window_height - UPDATE_NOTIFICATION_RESERVED_HEIGHT;
if (main_windows_system_chinese_font_ != nullptr) {
ImGui::PushFont(main_windows_system_chinese_font_);
}
// scrollable content area
ImGui::SetCursorPosX(update_notification_window_width * 0.05f);
ImGui::BeginChild(
"ScrollableContent",
ImVec2(update_notification_window_width * 0.9f, scrollable_height),
ImGuiChildFlags_Borders, ImGuiWindowFlags_None);
ImGui::SetWindowFontScale(0.5f);
// set text wrap position to current available width (accounts for
// scrollbar)
float wrap_pos = ImGui::GetContentRegionAvail().x;
ImGui::PushTextWrapPos(wrap_pos);
// release name
if (latest_version_info_.contains("releaseName") &&
latest_version_info_["releaseName"].is_string() &&
!latest_version_info_["releaseName"].empty()) {
ImGui::SetCursorPosX(update_notification_window_width * 0.05f);
std::string release_name =
latest_version_info_["releaseName"].get<std::string>();
ImGui::TextWrapped("%s", release_name.c_str());
ImGui::Spacing();
}
// release notes
if (!release_notes_.empty()) {
ImGui::SetCursorPosX(update_notification_window_width * 0.05f);
ImGui::TextWrapped("%s", release_notes_.c_str());
ImGui::Spacing();
}
// release date
if (latest_version_info_.contains("releaseDate") &&
latest_version_info_["releaseDate"].is_string() &&
!latest_version_info_["releaseDate"].empty()) {
ImGui::SetCursorPosX(update_notification_window_width * 0.05f);
std::string date_label =
localization::release_date[localization_language_index_];
std::string release_date = latest_version_info_["releaseDate"];
std::string date_text = date_label + release_date;
ImGui::Text("%s", date_text.c_str());
ImGui::Spacing();
}
// pop text wrap position
ImGui::PopTextWrapPos();
ImGui::EndChild();
// pop system font
if (main_windows_system_chinese_font_ != nullptr) {
ImGui::PopFont();
}
ImGui::Spacing();
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(update_notification_window_width * 0.407f);
} else {
ImGui::SetCursorPosX(update_notification_window_width * 0.367f);
}
ImGui::SetWindowFontScale(0.5f);
// update button
if (ImGui::Button(
localization::update[localization_language_index_].c_str())) {
// open download page
std::string url = "https://crossdesk.cn";
OpenUrl(url);
show_update_notification_window_ = false;
}
ImGui::SameLine();
if (ImGui::Button(
localization::cancel[localization_language_index_].c_str())) {
show_update_notification_window_ = false;
}
ImGui::SetWindowFontScale(1.0f);
ImGui::End();
ImGui::PopStyleVar(3);
ImGui::PopStyleColor();
}
return 0;
}
} // namespace crossdesk