diff --git a/scripts/windows/crossdesk_portable.manifest b/scripts/windows/crossdesk_portable.manifest
new file mode 100644
index 0000000..158968e
--- /dev/null
+++ b/scripts/windows/crossdesk_portable.manifest
@@ -0,0 +1,32 @@
+
+
+
+
+
+ CrossDesk Portable Application
+
+
+
+
+
+
+
+
+
+
+
+ true/pm
+ PerMonitorV2
+
+
+
+
+
+
+
+
+
+
diff --git a/scripts/windows/crossdesk_portable.rc b/scripts/windows/crossdesk_portable.rc
new file mode 100644
index 0000000..a391fb5
--- /dev/null
+++ b/scripts/windows/crossdesk_portable.rc
@@ -0,0 +1,8 @@
+// Portable build resource. The app itself runs as the current user; service
+// installation raises a separate UAC prompt only when the user chooses it.
+IDI_ICON1 ICON "..\\..\\icons\\windows\\crossdesk.ico"
+
+#define CREATEPROCESS_MANIFEST_RESOURCE_ID 1
+#define RT_MANIFEST 24
+
+CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "crossdesk_portable.manifest"
diff --git a/src/config_center/config_center.cpp b/src/config_center/config_center.cpp
index 3a6260e..3e7cbd5 100644
--- a/src/config_center/config_center.cpp
+++ b/src/config_center/config_center.cpp
@@ -79,6 +79,9 @@ int ConfigCenter::Load() {
enable_daemon_ = ini_.GetBoolValue(section_, "enable_daemon", enable_daemon_);
enable_minimize_to_tray_ = ini_.GetBoolValue(
section_, "enable_minimize_to_tray", enable_minimize_to_tray_);
+ portable_service_prompt_suppressed_ =
+ ini_.GetBoolValue(section_, "portable_service_prompt_suppressed",
+ portable_service_prompt_suppressed_);
const char* file_transfer_save_path_value =
ini_.GetValue(section_, "file_transfer_save_path", nullptr);
@@ -118,6 +121,8 @@ int ConfigCenter::Save() {
ini_.SetBoolValue(section_, "enable_daemon", enable_daemon_);
ini_.SetBoolValue(section_, "enable_minimize_to_tray",
enable_minimize_to_tray_);
+ ini_.SetBoolValue(section_, "portable_service_prompt_suppressed",
+ portable_service_prompt_suppressed_);
ini_.SetValue(section_, "file_transfer_save_path",
file_transfer_save_path_.c_str());
@@ -325,6 +330,18 @@ int ConfigCenter::SetDaemon(bool enable_daemon) {
return 0;
}
+int ConfigCenter::SetPortableServicePromptSuppressed(bool suppressed) {
+ portable_service_prompt_suppressed_ = suppressed;
+ ini_.SetBoolValue(section_, "portable_service_prompt_suppressed",
+ portable_service_prompt_suppressed_);
+ SI_Error rc = ini_.SaveFile(config_path_.c_str());
+ if (rc < 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
// getters
ConfigCenter::LANGUAGE ConfigCenter::GetLanguage() const { return language_; }
@@ -377,6 +394,10 @@ bool ConfigCenter::IsEnableAutostart() const { return enable_autostart_; }
bool ConfigCenter::IsEnableDaemon() const { return enable_daemon_; }
+bool ConfigCenter::IsPortableServicePromptSuppressed() const {
+ return portable_service_prompt_suppressed_;
+}
+
int ConfigCenter::SetFileTransferSavePath(const std::string& path) {
file_transfer_save_path_ = path;
ini_.SetValue(section_, "file_transfer_save_path",
diff --git a/src/config_center/config_center.h b/src/config_center/config_center.h
index 2258f71..acb2a5c 100644
--- a/src/config_center/config_center.h
+++ b/src/config_center/config_center.h
@@ -39,6 +39,7 @@ class ConfigCenter {
int SetMinimizeToTray(bool enable_minimize_to_tray);
int SetAutostart(bool enable_autostart);
int SetDaemon(bool enable_daemon);
+ int SetPortableServicePromptSuppressed(bool suppressed);
int SetFileTransferSavePath(const std::string& path);
// read config
@@ -60,6 +61,7 @@ class ConfigCenter {
bool IsMinimizeToTray() const;
bool IsEnableAutostart() const;
bool IsEnableDaemon() const;
+ bool IsPortableServicePromptSuppressed() const;
std::string GetFileTransferSavePath() const;
int Load();
@@ -87,6 +89,7 @@ class ConfigCenter {
bool enable_minimize_to_tray_ = false;
bool enable_autostart_ = false;
bool enable_daemon_ = false;
+ bool portable_service_prompt_suppressed_ = false;
std::string file_transfer_save_path_ = "";
};
} // namespace crossdesk
diff --git a/src/gui/assets/localization/localization_data.h b/src/gui/assets/localization/localization_data.h
index 160f032..b78bc65 100644
--- a/src/gui/assets/localization/localization_data.h
+++ b/src/gui/assets/localization/localization_data.h
@@ -19,176 +19,203 @@ struct TranslationRow {
};
// Single source of truth for all UI strings.
-#define CROSSDESK_LOCALIZATION_ALL(X) \
- X(local_desktop, u8"本桌面", "Local Desktop", u8"Локальный рабочий стол") \
- X(local_id, u8"本机ID", "Local ID", u8"Локальный ID") \
- X(local_id_copied_to_clipboard, u8"已复制到剪贴板", "Copied to clipboard", \
- u8"Скопировано в буфер обмена") \
- X(password, u8"密码", "Password", u8"Пароль") \
- X(max_password_len, u8"最大6个字符", "Max 6 chars", u8"Макс. 6 символов") \
- X(remote_desktop, u8"远程桌面", "Remote Desktop", \
- u8"Удаленный рабочий стол") \
- X(remote_id, u8"对端ID", "Remote ID", u8"Удаленный ID") \
- X(connect, u8"连接", "Connect", u8"Подключиться") \
- X(recent_connections, u8"近期连接", "Recent Connections", \
- u8"Недавние подключения") \
- X(disconnect, u8"断开连接", "Disconnect", u8"Отключить") \
- X(select_display, u8"选择显示器", "Select Display", u8"Выбрать дисплей") \
- X(expand_control_bar, u8"展开控制栏", "Expand Control Bar", \
- u8"Развернуть панель управления") \
- X(collapse_control_bar, u8"收起控制栏", "Collapse Control Bar", \
- u8"Свернуть панель управления") \
- X(fullscreen, u8"全屏", " Fullscreen", u8"Полный экран") \
- X(show_net_traffic_stats, u8"显示网络状态", "Show Net Traffic Stats", \
- u8"Показать статистику трафика") \
- X(hide_net_traffic_stats, u8"隐藏网络状态", "Hide Net Traffic Stats", \
- u8"Скрыть статистику трафика") \
- X(video, u8"视频", "Video", u8"Видео") \
- X(audio, u8"音频", "Audio", u8"Аудио") \
- X(data, u8"数据", "Data", u8"Данные") \
- X(total, u8"总计", "Total", u8"Итого") \
- X(in, u8"输入", "In", u8"Вход") \
- X(out, u8"输出", "Out", u8"Выход") \
- X(loss_rate, u8"丢包率", "Loss Rate", u8"Потери пакетов") \
- X(exit_fullscreen, u8"退出全屏", "Exit fullscreen", \
- u8"Выйти из полноэкранного режима") \
- X(control_mouse, u8"控制鼠标", "Control Mouse", u8"Управление мышью") \
- X(release_mouse, u8"释放鼠标", "Release Mouse", u8"Освободить мышь") \
- X(audio_capture, u8"播放声音", "Audio Capture", u8"Воспроизведение звука") \
- X(mute, u8" 静音", " Mute", u8"Без звука") \
- X(send_shortcut, u8"发送组合键", "Send Shortcut", u8"Сочетания клавиш") \
- X(send_sas, u8"发送SAS", "Send SAS", u8"Отправить SAS") \
- X(lock_remote, u8"锁定远端", "Lock Remote", u8"Заблокировать") \
- X(remote_password_box_visible, u8"远端密码框已出现", \
- "Remote password box visible", u8"Окно ввода пароля видно") \
- X(remote_lock_screen_hint, u8"远端处于锁屏封面,可发送SAS", \
- "Remote lock screen visible, send SAS", \
- u8"Видна блокировка, отправьте SAS") \
- X(remote_secure_desktop_active, u8"远端已进入安全桌面", \
- "Remote secure desktop active", u8"Активен защищенный рабочий стол") \
- X(remote_service_unavailable, u8"远端Windows服务不可用", \
- "Remote Windows service unavailable", \
- u8"Служба Windows на удаленной стороне недоступна") \
- X(windows_service_setup_title, u8"安装 CrossDesk Service", \
- "Install CrossDesk Service", u8"Установить CrossDesk Service") \
- X(windows_service_setup_message, \
- u8"便携版需要安装本机Windows服务,以便在锁屏/登录界面/安全桌面下完整控制此电脑。检测到服务尚未安装,可点击安装并允许相关系统权限。", \
- "The portable version needs the local Windows service for full control on the lock screen, sign-in UI, and secure desktop. The service is not installed. Click Install and approve the system prompt.", \
- u8"Портативной версии нужна локальная служба Windows для полного управления на экране блокировки, входа и защищенном рабочем столе. Служба не установлена. Нажмите Установить и подтвердите системный запрос.") \
- X(install_windows_service, u8"安装", "Install", \
- u8"Установить службу") \
- X(installing_windows_service, u8"正在安装服务...", "Installing service...", \
- u8"Установка службы...") \
- X(windows_service_install_success, u8"服务已安装并启动", \
- "Service installed and started", u8"Служба установлена и запущена") \
- X(windows_service_install_failed, u8"服务安装失败,请确认便携目录内服务文件完整,并允许管理员权限。", \
- "Service installation failed. Check that the portable folder contains all service files and approve administrator permission.", \
- u8"Не удалось установить службу. Проверьте файлы службы в папке портативной версии и подтвердите права администратора.") \
- X(remote_unlock_requires_secure_desktop, \
- u8"当前仍需要安全桌面专用采集/输入", \
- "Secure desktop capture/input is still required", \
- u8"По-прежнему нужен отдельный захват/ввод для защищенного рабочего " \
- u8"стола") \
- X(settings, u8"设置", "Settings", u8"Настройки") \
- X(language, u8"语言:", "Language:", u8"Язык:") \
- X(video_quality, u8"视频质量:", "Video Quality:", u8"Качество видео:") \
- X(video_frame_rate, u8"画面采集帧率:", \
- "Video Capture Frame Rate:", u8"Частота захвата видео:") \
- X(video_quality_high, u8"高", "High", u8"Высокое") \
- X(video_quality_medium, u8"中", "Medium", u8"Среднее") \
- X(video_quality_low, u8"低", "Low", u8"Низкое") \
- X(video_encode_format, u8"视频编码格式:", \
- "Video Encode Format:", u8"Формат кодека видео:") \
- X(av1, u8"AV1", "AV1", "AV1") \
- X(h264, u8"H.264", "H.264", "H.264") \
- X(enable_hardware_video_codec, u8"启用硬件编解码器:", \
- "Enable Hardware Video Codec:", u8"Использовать аппаратный кодек:") \
- X(enable_turn, u8"启用中继服务:", \
- "Enable TURN Service:", u8"Включить TURN-сервис:") \
- X(enable_srtp, u8"启用SRTP:", "Enable SRTP:", u8"Включить SRTP:") \
- X(self_hosted_server_config, u8"自托管配置", "Self-Hosted Config", \
- u8"Конфигурация self-hosted") \
- X(self_hosted_server_settings, u8"自托管设置", "Self-Hosted Settings", \
- u8"Настройки self-hosted") \
- X(self_hosted_server_address, u8"服务器地址:", \
- "Server Address:", u8"Адрес сервера:") \
- X(self_hosted_server_port, u8"信令服务端口:", \
- "Signal Service Port:", u8"Порт сигнального сервиса:") \
- X(self_hosted_server_coturn_server_port, u8"中继服务端口:", \
- "Relay Service Port:", u8"Порт реле-сервиса:") \
- X(ok, u8"确认", "OK", u8"ОК") \
- X(cancel, u8"取消", "Cancel", u8"Отмена") \
- X(new_password, u8"请输入六位密码:", \
- "Please input a six-char password:", u8"Введите шестизначный пароль:") \
- X(input_password, u8"请输入密码:", \
- "Please input password:", u8"Введите пароль:") \
- X(validate_password, u8"验证密码中...", "Validate password ...", \
- u8"Проверка пароля...") \
- X(reinput_password, u8"请重新输入密码", "Please input password again", \
- u8"Повторно введите пароль") \
- X(remember_password, u8"记住密码", "Remember password", \
- u8"Запомнить пароль") \
- X(signal_connected, u8"已连接服务器", "Connected", u8"Подключено к серверу") \
- X(signal_disconnected, u8"未连接服务器", "Disconnected", \
- u8"Нет подключения к серверу") \
- X(p2p_connected, u8"对等连接已建立", "P2P Connected", u8"P2P подключено") \
- X(p2p_disconnected, u8"对等连接已断开", "P2P Disconnected", \
- u8"P2P отключено") \
- X(p2p_connecting, u8"正在建立对等连接...", "P2P Connecting ...", \
- u8"Подключение P2P...") \
- X(receiving_screen, u8"画面接收中...", "Receiving screen...", \
- u8"Получение изображения...") \
- X(p2p_failed, u8"对等连接失败", "P2P Failed", u8"Сбой P2P") \
- X(p2p_closed, u8"对等连接已关闭", "P2P closed", u8"P2P закрыто") \
- X(no_such_id, u8"无此ID", "No such ID", u8"ID не найден") \
- X(about, u8"关于", "About", u8"О программе") \
- X(notification, u8"通知", "Notification", u8"Уведомление") \
- X(new_version_available, u8"新版本可用", "New Version Available", \
- u8"Доступна новая версия") \
- X(version, u8"版本", "Version", u8"Версия") \
- X(release_date, u8"发布日期: ", "Release Date: ", u8"Дата релиза: ") \
- X(access_website, u8"访问官网: ", \
- "Access Website: ", u8"Официальный сайт: ") \
- X(update, u8"更新", "Update", u8"Обновить") \
- X(confirm_delete_connection, u8"确认删除此连接", \
- "Confirm to delete this connection", u8"Удалить это подключение?") \
- X(enable_autostart, u8"开机自启:", "Auto Start:", u8"Автозапуск:") \
- X(enable_daemon, u8"启用守护进程:", "Enable Daemon:", u8"Включить демон:") \
- X(takes_effect_after_restart, u8"重启后生效", "Takes effect after restart", \
- u8"Вступит в силу после перезапуска") \
- X(select_file, u8"选择文件发送", "Select File to Send", \
- u8"Выбрать файл для отправки") \
- X(file_transfer_progress, u8"文件传输进度", "File Transfer Progress", \
- u8"Прогресс передачи файлов") \
- X(queued, u8"队列中", "Queued", u8"В очереди") \
- X(sending, u8"正在传输", "Sending", u8"Передача") \
- X(completed, u8"已完成", "Completed", u8"Завершено") \
- X(failed, u8"失败", "Failed", u8"Ошибка") \
- X(controller, u8"控制端:", "Controller:", u8"Контроллер:") \
- X(file_transfer, u8"文件传输:", "File Transfer:", u8"Передача файлов:") \
- X(connection_status, u8"连接状态:", \
- "Connection Status:", u8"Состояние соединения:") \
- X(file_transfer_save_path, u8"文件接收保存路径:", \
- "File Transfer Save Path:", u8"Путь сохранения файлов:") \
- X(default_desktop, u8"桌面", "Desktop", u8"Рабочий стол") \
- X(minimize_to_tray, u8"退出时最小化到系统托盘:", \
- "Minimize on Exit:", u8"Сворачивать в трей при выходе:") \
- X(resolution, u8"分辨率", "Res", u8"Разрешение") \
- X(connection_mode, u8"连接模式", "Mode", u8"Режим") \
- X(connection_mode_direct, u8"直连", "Direct", u8"Прямой") \
- X(connection_mode_relay, u8"中继", "Relay", u8"Релейный") \
- X(online, u8"在线", "Online", u8"Онлайн") \
- X(offline, u8"离线", "Offline", u8"Офлайн") \
- X(device_offline, u8"设备离线", "Device Offline", u8"Устройство офлайн") \
- X(request_permissions, u8"权限请求", "Request Permissions", \
- u8"Запрос разрешений") \
- X(screen_recording_permission, u8"屏幕录制权限", \
- "Screen Recording Permission", u8"Разрешение на запись экрана") \
- X(accessibility_permission, u8"辅助功能权限", "Accessibility Permission", \
- u8"Разрешение специальных возможностей") \
- X(permission_required_message, u8"该应用需要授权以下权限:", \
- "The application requires the following permissions:", \
- u8"Для работы приложения требуются следующие разрешения:") \
+#define CROSSDESK_LOCALIZATION_ALL(X) \
+ X(local_desktop, u8"本桌面", "Local Desktop", u8"Локальный рабочий стол") \
+ X(local_id, u8"本机ID", "Local ID", u8"Локальный ID") \
+ X(local_id_copied_to_clipboard, u8"已复制到剪贴板", "Copied to clipboard", \
+ u8"Скопировано в буфер обмена") \
+ X(password, u8"密码", "Password", u8"Пароль") \
+ X(max_password_len, u8"最大6个字符", "Max 6 chars", u8"Макс. 6 символов") \
+ X(remote_desktop, u8"远程桌面", "Remote Desktop", \
+ u8"Удаленный рабочий стол") \
+ X(remote_id, u8"对端ID", "Remote ID", u8"Удаленный ID") \
+ X(connect, u8"连接", "Connect", u8"Подключиться") \
+ X(recent_connections, u8"近期连接", "Recent Connections", \
+ u8"Недавние подключения") \
+ X(disconnect, u8"断开连接", "Disconnect", u8"Отключить") \
+ X(select_display, u8"选择显示器", "Select Display", u8"Выбрать дисплей") \
+ X(expand_control_bar, u8"展开控制栏", "Expand Control Bar", \
+ u8"Развернуть панель управления") \
+ X(collapse_control_bar, u8"收起控制栏", "Collapse Control Bar", \
+ u8"Свернуть панель управления") \
+ X(fullscreen, u8"全屏", " Fullscreen", u8"Полный экран") \
+ X(show_net_traffic_stats, u8"显示网络状态", "Show Net Traffic Stats", \
+ u8"Показать статистику трафика") \
+ X(hide_net_traffic_stats, u8"隐藏网络状态", "Hide Net Traffic Stats", \
+ u8"Скрыть статистику трафика") \
+ X(video, u8"视频", "Video", u8"Видео") \
+ X(audio, u8"音频", "Audio", u8"Аудио") \
+ X(data, u8"数据", "Data", u8"Данные") \
+ X(total, u8"总计", "Total", u8"Итого") \
+ X(in, u8"输入", "In", u8"Вход") \
+ X(out, u8"输出", "Out", u8"Выход") \
+ X(loss_rate, u8"丢包率", "Loss Rate", u8"Потери пакетов") \
+ X(exit_fullscreen, u8"退出全屏", "Exit fullscreen", \
+ u8"Выйти из полноэкранного режима") \
+ X(control_mouse, u8"控制鼠标", "Control Mouse", u8"Управление мышью") \
+ X(release_mouse, u8"释放鼠标", "Release Mouse", u8"Освободить мышь") \
+ X(audio_capture, u8"播放声音", "Audio Capture", u8"Воспроизведение звука") \
+ X(mute, u8" 静音", " Mute", u8"Без звука") \
+ X(send_shortcut, u8"发送组合键", "Send Shortcut", u8"Сочетания клавиш") \
+ X(send_sas, u8"发送SAS", "Send SAS", u8"Отправить SAS") \
+ X(lock_remote, u8"锁定远端", "Lock Remote", u8"Заблокировать") \
+ X(remote_password_box_visible, u8"远端密码框已出现", \
+ "Remote password box visible", u8"Окно ввода пароля видно") \
+ X(remote_lock_screen_hint, u8"远端处于锁屏封面,可发送SAS", \
+ "Remote lock screen visible, send SAS", \
+ u8"Видна блокировка, отправьте SAS") \
+ X(remote_secure_desktop_active, u8"远端已进入安全桌面", \
+ "Remote secure desktop active", u8"Активен защищенный рабочий стол") \
+ X(remote_service_unavailable, u8"远端Windows服务不可用", \
+ "Remote Windows service unavailable", \
+ u8"Служба Windows на удаленной стороне недоступна") \
+ X(windows_service_setup_title, u8"安装 CrossDesk Service", \
+ "Install CrossDesk Service", u8"Установить CrossDesk Service") \
+ X(windows_service_setup_message, \
+ u8"为支持该设备在锁屏状态下被远程控制,需要以管理员权限安装 CrossDesk " \
+ u8"Service。\n未安装该服务不影响 CrossDesk " \
+ u8"正常使用,仅无法在锁屏状态下控制本机。", \
+ "To support remote control of this device while it is locked, CrossDesk " \
+ "Service must be installed with administrator permission.\nWithout this " \
+ "service, CrossDesk still works normally; only lock-screen control of " \
+ "this computer is unavailable.", \
+ u8"Чтобы поддерживать удаленное управление этим устройством на экране " \
+ u8"блокировки, необходимо установить CrossDesk Service с правами " \
+ u8"администратора.\nБез этой службы CrossDesk продолжит работать " \
+ u8"нормально; будет недоступно только управление этим компьютером на " \
+ u8"экране блокировки.") \
+ X(install_windows_service, u8"安装", "Install", u8"Установить") \
+ X(windows_service_settings_label, u8"锁屏控制服务:", \
+ "Lock Screen Service:", u8"Служба блокировки экрана:") \
+ X(windows_service_installed, u8"已安装", "Installed", u8"Установлена") \
+ X(do_not_remind_again, u8"不再提醒", "Do not remind again", \
+ u8"Больше не напоминать") \
+ X(windows_service_prompt_suppressed_message, \
+ u8"已不再提醒。后续如需启用锁屏状态下被远程控制,可在设置中点击“安装”。", \
+ "You will not be reminded again. To enable remote control while locked " \
+ "later, click Install in Settings.", \
+ u8"Напоминание отключено. Чтобы позже включить удаленное управление на " \
+ u8"экране блокировки, нажмите «Установить» в настройках.") \
+ X(installing_windows_service, u8"正在安装服务...", "Installing service...", \
+ u8"Установка службы...") \
+ X(windows_service_install_success, u8"服务已安装并启动", \
+ "Service installed and started", u8"Служба установлена и запущена") \
+ X(windows_service_install_failed, \
+ u8"服务安装失败。请确认 " \
+ u8"CrossDesk.exe、crossdesk_service.exe、crossdesk_session_helper.exe " \
+ u8"位于同一便携目录中,并在系统弹窗中允许管理员权限。", \
+ "Service installation failed. Make sure CrossDesk.exe, " \
+ "crossdesk_service.exe, and crossdesk_session_helper.exe are in the same " \
+ "portable folder, then approve the administrator prompt.", \
+ u8"Не удалось установить службу. Убедитесь, что CrossDesk.exe, " \
+ u8"crossdesk_service.exe и crossdesk_session_helper.exe находятся в " \
+ u8"одной папке портативной версии, затем подтвердите запрос прав " \
+ u8"администратора.") \
+ X(remote_unlock_requires_secure_desktop, \
+ u8"当前仍需要安全桌面专用采集/输入", \
+ "Secure desktop capture/input is still required", \
+ u8"По-прежнему нужен отдельный захват/ввод для защищенного рабочего " \
+ u8"стола") \
+ X(settings, u8"设置", "Settings", u8"Настройки") \
+ X(language, u8"语言:", "Language:", u8"Язык:") \
+ X(video_quality, u8"视频质量:", "Video Quality:", u8"Качество видео:") \
+ X(video_frame_rate, u8"画面采集帧率:", \
+ "Video Capture Frame Rate:", u8"Частота захвата видео:") \
+ X(video_quality_high, u8"高", "High", u8"Высокое") \
+ X(video_quality_medium, u8"中", "Medium", u8"Среднее") \
+ X(video_quality_low, u8"低", "Low", u8"Низкое") \
+ X(video_encode_format, u8"视频编码格式:", \
+ "Video Encode Format:", u8"Формат кодека видео:") \
+ X(av1, u8"AV1", "AV1", "AV1") \
+ X(h264, u8"H.264", "H.264", "H.264") \
+ X(enable_hardware_video_codec, u8"启用硬件编解码器:", \
+ "Enable Hardware Video Codec:", u8"Использовать аппаратный кодек:") \
+ X(enable_turn, u8"启用中继服务:", \
+ "Enable TURN Service:", u8"Включить TURN-сервис:") \
+ X(enable_srtp, u8"启用SRTP:", "Enable SRTP:", u8"Включить SRTP:") \
+ X(self_hosted_server_config, u8"自托管配置", "Self-Hosted Config", \
+ u8"Конфигурация self-hosted") \
+ X(self_hosted_server_settings, u8"自托管设置", "Self-Hosted Settings", \
+ u8"Настройки self-hosted") \
+ X(self_hosted_server_address, u8"服务器地址:", \
+ "Server Address:", u8"Адрес сервера:") \
+ X(self_hosted_server_port, u8"信令服务端口:", \
+ "Signal Service Port:", u8"Порт сигнального сервиса:") \
+ X(self_hosted_server_coturn_server_port, u8"中继服务端口:", \
+ "Relay Service Port:", u8"Порт реле-сервиса:") \
+ X(ok, u8"确认", "OK", u8"ОК") \
+ X(cancel, u8"取消", "Cancel", u8"Отмена") \
+ X(new_password, u8"请输入六位密码:", \
+ "Please input a six-char password:", u8"Введите шестизначный пароль:") \
+ X(input_password, u8"请输入密码:", \
+ "Please input password:", u8"Введите пароль:") \
+ X(validate_password, u8"验证密码中...", "Validate password ...", \
+ u8"Проверка пароля...") \
+ X(reinput_password, u8"请重新输入密码", "Please input password again", \
+ u8"Повторно введите пароль") \
+ X(remember_password, u8"记住密码", "Remember password", \
+ u8"Запомнить пароль") \
+ X(signal_connected, u8"已连接服务器", "Connected", u8"Подключено к серверу") \
+ X(signal_disconnected, u8"未连接服务器", "Disconnected", \
+ u8"Нет подключения к серверу") \
+ X(p2p_connected, u8"对等连接已建立", "P2P Connected", u8"P2P подключено") \
+ X(p2p_disconnected, u8"对等连接已断开", "P2P Disconnected", \
+ u8"P2P отключено") \
+ X(p2p_connecting, u8"正在建立对等连接...", "P2P Connecting ...", \
+ u8"Подключение P2P...") \
+ X(receiving_screen, u8"画面接收中...", "Receiving screen...", \
+ u8"Получение изображения...") \
+ X(p2p_failed, u8"对等连接失败", "P2P Failed", u8"Сбой P2P") \
+ X(p2p_closed, u8"对等连接已关闭", "P2P closed", u8"P2P закрыто") \
+ X(no_such_id, u8"无此ID", "No such ID", u8"ID не найден") \
+ X(about, u8"关于", "About", u8"О программе") \
+ X(notification, u8"通知", "Notification", u8"Уведомление") \
+ X(new_version_available, u8"新版本可用", "New Version Available", \
+ u8"Доступна новая версия") \
+ X(version, u8"版本", "Version", u8"Версия") \
+ X(release_date, u8"发布日期: ", "Release Date: ", u8"Дата релиза: ") \
+ X(access_website, u8"访问官网: ", \
+ "Access Website: ", u8"Официальный сайт: ") \
+ X(update, u8"更新", "Update", u8"Обновить") \
+ X(confirm_delete_connection, u8"确认删除此连接", \
+ "Confirm to delete this connection", u8"Удалить это подключение?") \
+ X(enable_autostart, u8"开机自启:", "Auto Start:", u8"Автозапуск:") \
+ X(enable_daemon, u8"启用守护进程:", "Enable Daemon:", u8"Включить демон:") \
+ X(takes_effect_after_restart, u8"重启后生效", "Takes effect after restart", \
+ u8"Вступит в силу после перезапуска") \
+ X(select_file, u8"选择文件发送", "Select File to Send", \
+ u8"Выбрать файл для отправки") \
+ X(file_transfer_progress, u8"文件传输进度", "File Transfer Progress", \
+ u8"Прогресс передачи файлов") \
+ X(queued, u8"队列中", "Queued", u8"В очереди") \
+ X(sending, u8"正在传输", "Sending", u8"Передача") \
+ X(completed, u8"已完成", "Completed", u8"Завершено") \
+ X(failed, u8"失败", "Failed", u8"Ошибка") \
+ X(controller, u8"控制端:", "Controller:", u8"Контроллер:") \
+ X(file_transfer, u8"文件传输:", "File Transfer:", u8"Передача файлов:") \
+ X(connection_status, u8"连接状态:", \
+ "Connection Status:", u8"Состояние соединения:") \
+ X(file_transfer_save_path, u8"文件接收保存路径:", \
+ "File Transfer Save Path:", u8"Путь сохранения файлов:") \
+ X(default_desktop, u8"桌面", "Desktop", u8"Рабочий стол") \
+ X(minimize_to_tray, u8"退出时最小化到系统托盘:", \
+ "Minimize on Exit:", u8"Сворачивать в трей при выходе:") \
+ X(resolution, u8"分辨率", "Res", u8"Разрешение") \
+ X(connection_mode, u8"连接模式", "Mode", u8"Режим") \
+ X(connection_mode_direct, u8"直连", "Direct", u8"Прямой") \
+ X(connection_mode_relay, u8"中继", "Relay", u8"Релейный") \
+ X(online, u8"在线", "Online", u8"Онлайн") \
+ X(offline, u8"离线", "Offline", u8"Офлайн") \
+ X(device_offline, u8"设备离线", "Device Offline", u8"Устройство офлайн") \
+ X(request_permissions, u8"权限请求", "Request Permissions", \
+ u8"Запрос разрешений") \
+ X(screen_recording_permission, u8"屏幕录制权限", \
+ "Screen Recording Permission", u8"Разрешение на запись экрана") \
+ X(accessibility_permission, u8"辅助功能权限", "Accessibility Permission", \
+ u8"Разрешение специальных возможностей") \
+ X(permission_required_message, u8"该应用需要授权以下权限:", \
+ "The application requires the following permissions:", \
+ u8"Для работы приложения требуются следующие разрешения:") \
X(exit_program, u8"退出", "Exit", u8"Выход")
inline constexpr TranslationRow kTranslationRows[] = {
diff --git a/src/gui/render.cpp b/src/gui/render.cpp
index b708693..4bcf822 100644
--- a/src/gui/render.cpp
+++ b/src/gui/render.cpp
@@ -609,6 +609,11 @@ int Render::LoadSettingsFromCacheFile() {
enable_autostart_ = config_center_->IsEnableAutostart();
enable_daemon_ = config_center_->IsEnableDaemon();
enable_minimize_to_tray_ = config_center_->IsMinimizeToTray();
+#if _WIN32 && CROSSDESK_PORTABLE
+ portable_service_prompt_suppressed_ =
+ config_center_->IsPortableServicePromptSuppressed();
+ portable_service_do_not_remind_ = portable_service_prompt_suppressed_;
+#endif
// File transfer save path
{
diff --git a/src/gui/render.h b/src/gui/render.h
index 72d2651..674c58c 100644
--- a/src/gui/render.h
+++ b/src/gui/render.h
@@ -567,6 +567,9 @@ class Render {
#if CROSSDESK_PORTABLE
bool portable_service_prompt_checked_ = false;
bool show_portable_service_install_window_ = false;
+ bool show_portable_service_prompt_suppressed_window_ = false;
+ bool portable_service_do_not_remind_ = false;
+ bool portable_service_prompt_suppressed_ = false;
std::atomic portable_service_install_state_{
PortableServiceInstallState::idle};
std::thread portable_service_install_thread_;
diff --git a/src/gui/windows/main_settings_window.cpp b/src/gui/windows/main_settings_window.cpp
index 691badc..e014712 100644
--- a/src/gui/windows/main_settings_window.cpp
+++ b/src/gui/windows/main_settings_window.cpp
@@ -4,10 +4,19 @@
#include "render.h"
#include "tinyfiledialogs.h"
+#if _WIN32 && CROSSDESK_PORTABLE
+#include "service_host.h"
+#endif
+
namespace crossdesk {
int Render::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();
@@ -18,12 +27,14 @@ int Render::SettingWindow() {
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));
+ 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));
+ ImVec2(io.DisplaySize.x * 0.315f,
+ io.DisplaySize.y * (0.85f + portable_y_padding)));
#endif
} else {
#if (((defined(_WIN32) || defined(__linux__)) && !defined(__aarch64__) && \
@@ -32,12 +43,14 @@ int Render::SettingWindow() {
ImGui::SetNextWindowPos(
ImVec2(io.DisplaySize.x * 0.297f, io.DisplaySize.y * 0.05f));
ImGui::SetNextWindowSize(
- ImVec2(io.DisplaySize.x * 0.407f, io.DisplaySize.y * 0.9f));
+ 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.407f, io.DisplaySize.y * 0.85f));
+ ImVec2(io.DisplaySize.x * 0.42f,
+ io.DisplaySize.y * (0.85f + portable_y_padding)));
#endif
}
@@ -73,23 +86,21 @@ int Render::SettingWindow() {
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(title_bar_button_width_ * 3.0f);
} else {
- ImGui::SetCursorPosX(title_bar_button_width_ * 4.5f);
+ 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())) {
+ 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(supported_languages.size());
++i) {
bool selected = (i == language_button_value_);
- if (ImGui::Selectable(
- supported_languages[i].display_name.c_str(), selected))
+ if (ImGui::Selectable(supported_languages[i].display_name.c_str(),
+ selected))
language_button_value_ = i;
if (selected) {
ImGui::SetItemDefaultFocus();
@@ -125,7 +136,7 @@ int Render::SettingWindow() {
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(title_bar_button_width_ * 3.0f);
} else {
- ImGui::SetCursorPosX(title_bar_button_width_ * 4.5f);
+ ImGui::SetCursorPosX(title_bar_button_width_ * 4.7f);
}
ImGui::SetNextItemWidth(title_bar_button_width_ * 1.8f);
@@ -158,7 +169,7 @@ int Render::SettingWindow() {
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(title_bar_button_width_ * 3.0f);
} else {
- ImGui::SetCursorPosX(title_bar_button_width_ * 4.5f);
+ ImGui::SetCursorPosX(title_bar_button_width_ * 4.7f);
}
ImGui::SetNextItemWidth(title_bar_button_width_ * 1.8f);
@@ -194,7 +205,7 @@ int Render::SettingWindow() {
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(title_bar_button_width_ * 3.0f);
} else {
- ImGui::SetCursorPosX(title_bar_button_width_ * 4.5f);
+ ImGui::SetCursorPosX(title_bar_button_width_ * 4.7f);
}
ImGui::SetNextItemWidth(title_bar_button_width_ * 1.8f);
@@ -228,7 +239,7 @@ int Render::SettingWindow() {
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(title_bar_button_width_ * 4.275f);
} else {
- ImGui::SetCursorPosX(title_bar_button_width_ * 5.755f);
+ ImGui::SetCursorPosX(title_bar_button_width_ * 5.955f);
}
ImGui::Checkbox("##enable_hardware_video_codec",
@@ -249,7 +260,7 @@ int Render::SettingWindow() {
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(title_bar_button_width_ * 4.275f);
} else {
- ImGui::SetCursorPosX(title_bar_button_width_ * 5.755f);
+ ImGui::SetCursorPosX(title_bar_button_width_ * 5.955f);
}
ImGui::Checkbox("##enable_turn", &enable_turn_);
@@ -268,7 +279,7 @@ int Render::SettingWindow() {
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(title_bar_button_width_ * 4.275f);
} else {
- ImGui::SetCursorPosX(title_bar_button_width_ * 5.755f);
+ ImGui::SetCursorPosX(title_bar_button_width_ * 5.955f);
}
ImGui::Checkbox("##enable_srtp", &enable_srtp_);
@@ -289,7 +300,7 @@ int Render::SettingWindow() {
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(title_bar_button_width_ * 4.275f);
} else {
- ImGui::SetCursorPosX(title_bar_button_width_ * 5.755f);
+ ImGui::SetCursorPosX(title_bar_button_width_ * 5.955f);
}
ImGui::Checkbox("##enable_self_hosted", &enable_self_hosted_);
@@ -308,7 +319,7 @@ int Render::SettingWindow() {
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(title_bar_button_width_ * 4.275f);
} else {
- ImGui::SetCursorPosX(title_bar_button_width_ * 5.755f);
+ ImGui::SetCursorPosX(title_bar_button_width_ * 5.955f);
}
ImGui::Checkbox("##enable_autostart_", &enable_autostart_);
@@ -327,7 +338,7 @@ int Render::SettingWindow() {
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(title_bar_button_width_ * 4.275f);
} else {
- ImGui::SetCursorPosX(title_bar_button_width_ * 5.755f);
+ ImGui::SetCursorPosX(title_bar_button_width_ * 5.955f);
}
ImGui::Checkbox("##enable_daemon_", &enable_daemon_);
@@ -359,7 +370,7 @@ int Render::SettingWindow() {
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(title_bar_button_width_ * 4.275f);
} else {
- ImGui::SetCursorPosX(title_bar_button_width_ * 5.755f);
+ ImGui::SetCursorPosX(title_bar_button_width_ * 5.955f);
}
ImGui::Checkbox("##enable_minimize_to_tray_",
@@ -384,7 +395,7 @@ int Render::SettingWindow() {
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(title_bar_button_width_ * 2.82f);
} else {
- ImGui::SetCursorPosX(title_bar_button_width_ * 4.3f);
+ ImGui::SetCursorPosX(title_bar_button_width_ * 4.5f);
}
std::string display_path =
@@ -429,6 +440,80 @@ int Render::SettingWindow() {
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 {
@@ -463,9 +548,8 @@ int Render::SettingWindow() {
LOG_INFO("Set localization language: {}",
localization::GetSupportedLanguages()
[localization::detail::ClampLanguageIndex(
- localization_language_index_)]
- .code
- .c_str());
+ localization_language_index_)]
+ .code.c_str());
// Video quality
if (video_quality_button_value_ == 0) {
diff --git a/src/gui/windows/portable_service_install_window.cpp b/src/gui/windows/portable_service_install_window.cpp
index bfc59d6..84669e7 100644
--- a/src/gui/windows/portable_service_install_window.cpp
+++ b/src/gui/windows/portable_service_install_window.cpp
@@ -4,6 +4,7 @@
#include
+#include
#include
#include "localization.h"
@@ -16,9 +17,8 @@ namespace {
std::filesystem::path GetCurrentExecutablePath() {
std::vector buffer(MAX_PATH);
while (true) {
- DWORD length =
- GetModuleFileNameW(nullptr, buffer.data(),
- static_cast(buffer.size()));
+ DWORD length = GetModuleFileNameW(nullptr, buffer.data(),
+ static_cast(buffer.size()));
if (length == 0) {
return {};
}
@@ -46,7 +46,8 @@ bool InstallServiceWithElevation() {
if (!std::filesystem::exists(service_path) ||
!std::filesystem::exists(helper_path)) {
LOG_ERROR(
- "Portable service install failed: service binaries missing, service={}, "
+ "Portable service install failed: service binaries missing, "
+ "service={}, "
"helper={}",
service_path.string(), helper_path.string());
return false;
@@ -106,12 +107,17 @@ void Render::CheckPortableWindowsService() {
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 Render::StartPortableWindowsServiceInstall() {
+ portable_service_do_not_remind_ = false;
PortableServiceInstallState expected = PortableServiceInstallState::idle;
if (!portable_service_install_state_.compare_exchange_strong(
expected, PortableServiceInstallState::installing,
@@ -140,18 +146,82 @@ void Render::JoinPortableWindowsServiceInstallThread() {
}
int Render::PortableServiceInstallWindow() {
- if (!show_portable_service_install_window_) {
+ if (!show_portable_service_install_window_ &&
+ !show_portable_service_prompt_suppressed_window_) {
return 0;
}
const ImGuiViewport* viewport = ImGui::GetMainViewport();
- const float window_width = title_bar_button_width_ * 12.0f;
- const float window_height = title_bar_button_width_ * 4.0f;
+ 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->WorkSize.x - viewport->WorkPos.x - window_width) /
- 2.0f,
- (viewport->WorkSize.y - viewport->WorkPos.y - window_height) /
- 2.0f),
+ 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);
@@ -187,15 +257,13 @@ int Render::PortableServiceInstallWindow() {
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();
+ 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();
+ status_text = localization::windows_service_install_failed
+ [localization_language_index_]
+ .c_str();
}
}
@@ -203,7 +271,7 @@ int Render::PortableServiceInstallWindow() {
ImGui::SetCursorPosX(window_width * 0.04f);
ImGui::SetCursorPosY(window_height * 0.22f);
ImGui::BeginChild("PortableServiceInstallContent",
- ImVec2(window_width * 0.92f, window_height * 0.5f),
+ ImVec2(window_width * 0.92f, window_height * 0.45f),
ImGuiChildFlags_Borders, ImGuiWindowFlags_None);
ImGui::SetWindowFontScale(0.5f);
const float wrap_pos = ImGui::GetContentRegionAvail().x;
@@ -220,7 +288,15 @@ int Render::PortableServiceInstallWindow() {
ImGui::EndChild();
ImGui::SetWindowFontScale(0.5f);
- const float button_y = window_height * 0.76f;
+ 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;
@@ -259,6 +335,11 @@ int Render::PortableServiceInstallWindow() {
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) {
diff --git a/tests/windows_manifest_resource_test.cpp b/tests/windows_manifest_resource_test.cpp
index f63e68b..3caeea8 100644
--- a/tests/windows_manifest_resource_test.cpp
+++ b/tests/windows_manifest_resource_test.cpp
@@ -82,16 +82,27 @@ int main() {
}
const std::string rc = ReadFile(repo_root / "scripts/windows/crossdesk.rc");
+ const std::string portable_rc =
+ ReadFile(repo_root / "scripts/windows/crossdesk_portable.rc");
const std::string manifest =
ReadFile(repo_root / "scripts/windows/crossdesk.manifest");
const std::string debug_manifest =
ReadFile(repo_root / "scripts/windows/crossdesk_debug.manifest");
+ const std::string portable_manifest =
+ ReadFile(repo_root / "scripts/windows/crossdesk_portable.manifest");
+ const std::string targets = ReadFile(repo_root / "xmake/targets.lua");
bool ok = true;
ok &= ExpectContains("crossdesk.rc", rc, "crossdesk.manifest");
ok &= ExpectContains("crossdesk.rc", rc, "crossdesk_debug.manifest");
ok &= ExpectContains("crossdesk.rc", rc, "CROSSDESK_DEBUG");
ok &= ExpectContains("crossdesk.rc", rc, "RT_MANIFEST");
+ ok &= ExpectContains("crossdesk_portable.rc", portable_rc,
+ "crossdesk_portable.manifest");
+ ok &= ExpectContains("crossdesk_portable.rc", portable_rc, "RT_MANIFEST");
+ ok &= ExpectContains("xmake/targets.lua", targets,
+ "scripts/windows/crossdesk_portable.rc");
+ ok &= ExpectContains("xmake/targets.lua", targets, "CROSSDESK_PORTABLE");
ok &= ExpectContains("crossdesk.manifest", manifest,
"level=\"requireAdministrator\"");
ok &= ExpectContains("crossdesk.manifest", manifest,
@@ -108,10 +119,22 @@ int main() {
"http://schemas.microsoft.com/SMI/2016/WindowsSettings");
ok &= ExpectNotContains("crossdesk_debug.manifest", debug_manifest,
"processorArchitecture=\"*\"");
+ ok &= ExpectContains("crossdesk_portable.manifest", portable_manifest,
+ "level=\"asInvoker\"");
+ ok &= ExpectNotContains("crossdesk_portable.manifest", portable_manifest,
+ "level=\"requireAdministrator\"");
+ ok &= ExpectContains("crossdesk_portable.manifest", portable_manifest,
+ "http://schemas.microsoft.com/SMI/2005/WindowsSettings");
+ ok &= ExpectContains("crossdesk_portable.manifest", portable_manifest,
+ "http://schemas.microsoft.com/SMI/2016/WindowsSettings");
+ ok &= ExpectNotContains("crossdesk_portable.manifest", portable_manifest,
+ "processorArchitecture=\"*\"");
#ifdef _WIN32
ok &= ExpectActivationContext(repo_root / "scripts/windows/crossdesk.manifest");
ok &= ExpectActivationContext(
repo_root / "scripts/windows/crossdesk_debug.manifest");
+ ok &= ExpectActivationContext(
+ repo_root / "scripts/windows/crossdesk_portable.manifest");
#endif
return ok ? 0 : 1;
}
diff --git a/xmake/targets.lua b/xmake/targets.lua
index 371e0b4..f9bddc9 100644
--- a/xmake/targets.lua
+++ b/xmake/targets.lua
@@ -3,6 +3,11 @@ function setup_targets()
includes("submodules", "thirdparty")
+ local crossdesk_windows_resource = "scripts/windows/crossdesk.rc"
+ if is_config("CROSSDESK_PORTABLE", true) then
+ crossdesk_windows_resource = "scripts/windows/crossdesk_portable.rc"
+ end
+
target("rd_log")
set_kind("object")
add_packages("spdlog")
@@ -65,14 +70,6 @@ function setup_targets()
set_default(false)
add_files("tests/display_popup_hover_state_test.cpp")
- target("version_checker_test")
- set_kind("binary")
- set_default(false)
- add_packages("cpp-httplib", "nlohmann_json")
- add_includedirs("src/version_checker")
- add_files("tests/version_checker_test.cpp",
- "src/version_checker/version_checker.cpp")
-
target("screen_capturer")
set_kind("object")
add_deps("rd_log", "common")
@@ -231,7 +228,7 @@ function setup_targets()
add_deps("rd_log", "path_manager")
add_links("Advapi32", "User32", "Wtsapi32", "Gdi32")
add_files("src/service/windows/session_helper_main.cpp")
- add_files("scripts/windows/crossdesk.rc")
+ add_files(crossdesk_windows_resource)
add_includedirs("src/service/windows", {public = true})
end
@@ -245,6 +242,6 @@ function setup_targets()
add_includedirs("src/service/windows", {public = true})
add_links("Advapi32", "Wtsapi32", "Ole32", "Userenv")
add_deps("wgc_plugin", "crossdesk_service", "crossdesk_session_helper")
- add_files("scripts/windows/crossdesk.rc")
+ add_files(crossdesk_windows_resource)
end
end