mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-11-07 23:09:21 +08:00
[fix] repair portable service paths after upgrades, refs #146
This commit is contained in:
@@ -70,6 +70,7 @@ void PrintServiceCliUsage() {
|
|||||||
std::cout
|
std::cout
|
||||||
<< "CrossDesk service management commands\n"
|
<< "CrossDesk service management commands\n"
|
||||||
<< " --service-install Install the sibling crossdesk_service.exe\n"
|
<< " --service-install Install the sibling crossdesk_service.exe\n"
|
||||||
|
<< " --service-repair Update the service path and restart it\n"
|
||||||
<< " --service-uninstall Remove the installed Windows service\n"
|
<< " --service-uninstall Remove the installed Windows service\n"
|
||||||
<< " --service-start Start the Windows service\n"
|
<< " --service-start Start the Windows service\n"
|
||||||
<< " --service-stop Stop the Windows service\n"
|
<< " --service-stop Stop the Windows service\n"
|
||||||
@@ -111,6 +112,7 @@ bool IsServiceCliCommand(const char* arg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return std::strcmp(arg, "--service-install") == 0 ||
|
return std::strcmp(arg, "--service-install") == 0 ||
|
||||||
|
std::strcmp(arg, "--service-repair") == 0 ||
|
||||||
std::strcmp(arg, "--unattended-enable") == 0 ||
|
std::strcmp(arg, "--unattended-enable") == 0 ||
|
||||||
std::strcmp(arg, "--unattended-disable") == 0 ||
|
std::strcmp(arg, "--unattended-disable") == 0 ||
|
||||||
std::strcmp(arg, "--unattended-status") == 0 ||
|
std::strcmp(arg, "--unattended-status") == 0 ||
|
||||||
@@ -233,7 +235,7 @@ int HandleServiceCliCommand(const std::string& command) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
if (command == "--service-install") {
|
if (command == "--service-install" || command == "--service-repair") {
|
||||||
std::filesystem::path service_path = GetSiblingServiceExecutablePath();
|
std::filesystem::path service_path = GetSiblingServiceExecutablePath();
|
||||||
if (service_path.empty()) {
|
if (service_path.empty()) {
|
||||||
std::cerr << "Failed to locate crossdesk_service.exe" << std::endl;
|
std::cerr << "Failed to locate crossdesk_service.exe" << std::endl;
|
||||||
@@ -245,7 +247,9 @@ int HandleServiceCliCommand(const std::string& command) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool success = crossdesk::InstallCrossDeskService(service_path.wstring());
|
bool success = command == "--service-repair"
|
||||||
|
? crossdesk::RepairCrossDeskService(service_path.wstring())
|
||||||
|
: crossdesk::InstallCrossDeskService(service_path.wstring());
|
||||||
std::cout << (success ? "install ok" : "install failed") << std::endl;
|
std::cout << (success ? "install ok" : "install failed") << std::endl;
|
||||||
return success ? 0 : 1;
|
return success ? 0 : 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2115,6 +2115,21 @@ void GuiApplication::BindMainCallbacks(bool gallery_window) {
|
|||||||
OpenAccessibilityPreferences();
|
OpenAccessibilityPreferences();
|
||||||
mac_accessibility_permission_requested_ = true;
|
mac_accessibility_permission_requested_ = true;
|
||||||
RefreshMacPermissionStatus(true);
|
RefreshMacPermissionStatus(true);
|
||||||
|
#endif
|
||||||
|
});
|
||||||
|
main->on_open_portable_service([this] {
|
||||||
|
#if _WIN32 && CROSSDESK_PORTABLE
|
||||||
|
if (portable_service_install_state_.load(std::memory_order_acquire) ==
|
||||||
|
PortableServiceInstallState::installing) return;
|
||||||
|
JoinPortableWindowsServiceInstallThread();
|
||||||
|
portable_service_prompt_checked_ = false;
|
||||||
|
CheckPortableWindowsService();
|
||||||
|
portable_service_needs_repair_ =
|
||||||
|
portable_service_needs_repair_ || portable_service_installed_;
|
||||||
|
portable_service_install_state_.store(PortableServiceInstallState::idle,
|
||||||
|
std::memory_order_release);
|
||||||
|
show_portable_service_install_window_ = true;
|
||||||
|
ShowMainWindow();
|
||||||
#endif
|
#endif
|
||||||
});
|
});
|
||||||
main->on_install_portable_service([this, main] {
|
main->on_install_portable_service([this, main] {
|
||||||
@@ -2133,7 +2148,7 @@ void GuiApplication::BindMainCallbacks(bool gallery_window) {
|
|||||||
}
|
}
|
||||||
portable_service_do_not_remind_ =
|
portable_service_do_not_remind_ =
|
||||||
main->get_portable_service_do_not_remind();
|
main->get_portable_service_do_not_remind();
|
||||||
if (portable_service_do_not_remind_) {
|
if (portable_service_do_not_remind_ && !portable_service_needs_repair_) {
|
||||||
portable_service_prompt_suppressed_ = true;
|
portable_service_prompt_suppressed_ = true;
|
||||||
config_center_->SetPortableServicePromptSuppressed(true);
|
config_center_->SetPortableServicePromptSuppressed(true);
|
||||||
show_portable_service_prompt_suppressed_window_ = true;
|
show_portable_service_prompt_suppressed_window_ = true;
|
||||||
@@ -3605,6 +3620,8 @@ void GuiApplication::SyncPlatformDialogs() {
|
|||||||
ui_->main->set_portable_service_installed(portable_service_installed_);
|
ui_->main->set_portable_service_installed(portable_service_installed_);
|
||||||
ui_->wall->set_portable_service_settings_visible(true);
|
ui_->wall->set_portable_service_settings_visible(true);
|
||||||
ui_->wall->set_portable_service_installed(portable_service_installed_);
|
ui_->wall->set_portable_service_installed(portable_service_installed_);
|
||||||
|
ui_->main->set_portable_service_needs_repair(portable_service_needs_repair_);
|
||||||
|
ui_->wall->set_portable_service_needs_repair(portable_service_needs_repair_);
|
||||||
ui_->wall->set_portable_service_installing(
|
ui_->wall->set_portable_service_installing(
|
||||||
state == PortableServiceInstallState::installing);
|
state == PortableServiceInstallState::installing);
|
||||||
ui_->main->set_portable_service_installing(
|
ui_->main->set_portable_service_installing(
|
||||||
@@ -3613,14 +3630,17 @@ void GuiApplication::SyncPlatformDialogs() {
|
|||||||
state == PortableServiceInstallState::succeeded);
|
state == PortableServiceInstallState::succeeded);
|
||||||
const std::string* status = nullptr;
|
const std::string* status = nullptr;
|
||||||
if (state == PortableServiceInstallState::installing) {
|
if (state == PortableServiceInstallState::installing) {
|
||||||
status =
|
status = &(portable_service_needs_repair_
|
||||||
&localization::installing_windows_service[localization_language_index_];
|
? localization::windows_service_repairing
|
||||||
|
: localization::installing_windows_service)[localization_language_index_];
|
||||||
} else if (state == PortableServiceInstallState::succeeded) {
|
} else if (state == PortableServiceInstallState::succeeded) {
|
||||||
status = &localization::windows_service_install_success
|
status = &(portable_service_needs_repair_
|
||||||
[localization_language_index_];
|
? localization::windows_service_repair_success
|
||||||
|
: localization::windows_service_install_success)[localization_language_index_];
|
||||||
} else if (state == PortableServiceInstallState::failed) {
|
} else if (state == PortableServiceInstallState::failed) {
|
||||||
status = &localization::windows_service_install_failed
|
status = &(portable_service_needs_repair_
|
||||||
[localization_language_index_];
|
? localization::windows_service_repair_failed
|
||||||
|
: localization::windows_service_install_failed)[localization_language_index_];
|
||||||
}
|
}
|
||||||
ui_->main->set_portable_service_status(status ? UiText(*status)
|
ui_->main->set_portable_service_status(status ? UiText(*status)
|
||||||
: slint::SharedString{});
|
: slint::SharedString{});
|
||||||
@@ -3952,6 +3972,12 @@ void GuiApplication::SyncStreamWindow() {
|
|||||||
}
|
}
|
||||||
(*ui_->stream)->set_remote_update_visible(remote_update_available);
|
(*ui_->stream)->set_remote_update_visible(remote_update_available);
|
||||||
(*ui_->stream)->set_secure_attention_supported(remote_platform == HostPlatform::Windows);
|
(*ui_->stream)->set_secure_attention_supported(remote_platform == HostPlatform::Windows);
|
||||||
|
(*ui_->stream)->set_service_warning(UiText(
|
||||||
|
status == MiniRtcConnectionStatus::MiniRtcConnectionConnected &&
|
||||||
|
remote_platform == HostPlatform::Windows &&
|
||||||
|
GetRemoteUnlockState(*props) == RemoteUnlockState::service_unavailable
|
||||||
|
? localization::remote_service_unavailable_message[localization_language_index_]
|
||||||
|
: std::string{}));
|
||||||
if (remote_update_available) {
|
if (remote_update_available) {
|
||||||
SetStreamKeyboardFocus(false);
|
SetStreamKeyboardFocus(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -532,6 +532,13 @@ inline constexpr TranslationRow kTranslationRows[] = {
|
|||||||
{"权限迁移未完成。请重试,或在系统设置中移除旧 CrossDesk 后重新添加。", {"权限迁移未完成。请重试,或在系统设置中移除旧 CrossDesk 后重新添加。", "Permission migration needs attention. Retry, or remove and re-add CrossDesk in System Settings.", "Не удалось завершить перенос разрешений. Повторите попытку или удалите и заново добавьте CrossDesk в системных настройках.", "權限遷移未完成。請重試,或在系統設定中移除舊 CrossDesk 後重新加入。", "権限の移行を完了できませんでした。再試行するか、システム設定でCrossDeskを削除して追加し直してください。", "권한 이전이 완료되지 않았습니다. 다시 시도하거나 시스템 설정에서 CrossDesk를 제거한 후 다시 추가하세요.", "Die Migration der Berechtigungen ist unvollständig. Wiederholen Sie den Vorgang oder entfernen Sie CrossDesk in den Systemeinstellungen und fügen Sie es erneut hinzu.", "La migration des autorisations est incomplète. Réessayez, ou supprimez puis ajoutez à nouveau CrossDesk dans les réglages système.", "La migración de permisos está incompleta. Reinténtelo o elimine y vuelva a añadir CrossDesk en Ajustes del Sistema.", "A migração de permissões está incompleta. Tente novamente ou remova e adicione o CrossDesk novamente nos Ajustes do Sistema."}},
|
{"权限迁移未完成。请重试,或在系统设置中移除旧 CrossDesk 后重新添加。", {"权限迁移未完成。请重试,或在系统设置中移除旧 CrossDesk 后重新添加。", "Permission migration needs attention. Retry, or remove and re-add CrossDesk in System Settings.", "Не удалось завершить перенос разрешений. Повторите попытку или удалите и заново добавьте CrossDesk в системных настройках.", "權限遷移未完成。請重試,或在系統設定中移除舊 CrossDesk 後重新加入。", "権限の移行を完了できませんでした。再試行するか、システム設定でCrossDeskを削除して追加し直してください。", "권한 이전이 완료되지 않았습니다. 다시 시도하거나 시스템 설정에서 CrossDesk를 제거한 후 다시 추가하세요.", "Die Migration der Berechtigungen ist unvollständig. Wiederholen Sie den Vorgang oder entfernen Sie CrossDesk in den Systemeinstellungen und fügen Sie es erneut hinzu.", "La migration des autorisations est incomplète. Réessayez, ou supprimez puis ajoutez à nouveau CrossDesk dans les réglages système.", "La migración de permisos está incompleta. Reinténtelo o elimine y vuelva a añadir CrossDesk en Ajustes del Sistema.", "A migração de permissões está incompleta. Tente novamente ou remova e adicione o CrossDesk novamente nos Ajustes do Sistema."}},
|
||||||
{"重试权限迁移", {"重试权限迁移", "Retry migration", "Повторить перенос", "重試權限遷移", "移行を再試行", "권한 이전 재시도", "Migration wiederholen", "Réessayer la migration", "Reintentar migración", "Repetir migração"}},
|
{"重试权限迁移", {"重试权限迁移", "Retry migration", "Повторить перенос", "重試權限遷移", "移行を再試行", "권한 이전 재시도", "Migration wiederholen", "Réessayer la migration", "Reintentar migración", "Repetir migração"}},
|
||||||
{"稍后", {"稍后", "Later", "Позже", "稍後", "後で", "나중에", "Später", "Plus tard", "Más tarde", "Mais tarde"}},
|
{"稍后", {"稍后", "Later", "Позже", "稍後", "後で", "나중에", "Später", "Plus tard", "Más tarde", "Mais tarde"}},
|
||||||
|
{"修复", {"修复", "Repair", "Восстановить", "修復", "修復", "복구", "Reparieren", "Réparer", "Reparar", "Reparar"}},
|
||||||
|
{"修复 CrossDesk 服务", {"修复 CrossDesk 服务", "Repair CrossDesk Service", "Восстановление службы CrossDesk", "修復 CrossDesk 服務", "CrossDesk サービスの修復", "CrossDesk 서비스 복구", "CrossDesk-Dienst reparieren", "Réparer le service CrossDesk", "Reparar el servicio CrossDesk", "Reparar o serviço CrossDesk"}},
|
||||||
|
{"升级或移动 CrossDesk 后,锁屏控制服务可能不可用。修复将以管理员权限更新服务路径并重启服务。请确认当前目录包含完整的便携版文件。", {"升级或移动 CrossDesk 后,锁屏控制服务可能不可用。修复将以管理员权限更新服务路径并重启服务。请确认当前目录包含完整的便携版文件。", "After updating or moving CrossDesk, lock-screen control may be unavailable. Repair updates the service path and restarts the service with administrator permission. Keep all portable package files in the current folder.", "После обновления или перемещения CrossDesk управление экраном блокировки может быть недоступно. Восстановление обновит путь и перезапустит службу с правами администратора. Все файлы портативной версии должны находиться в текущей папке.", "升級或移動 CrossDesk 後,鎖定畫面控制服務可能無法使用。修復將以管理員權限更新服務路徑並重新啟動服務。請確認目前目錄包含完整的可攜版檔案。", "CrossDesk の更新や移動後は、ロック画面を操作できない場合があります。修復すると管理者権限でサービスのパスを更新し、再起動します。ポータブル版の全ファイルを現在のフォルダーに配置してください。", "CrossDesk를 업데이트하거나 이동한 후 잠금 화면 제어가 작동하지 않을 수 있습니다. 복구하면 관리자 권한으로 서비스 경로를 갱신하고 서비스를 다시 시작합니다. 현재 폴더에 포터블 버전의 모든 파일이 있어야 합니다.", "Nach dem Aktualisieren oder Verschieben von CrossDesk ist die Steuerung des Sperrbildschirms möglicherweise nicht verfügbar. Die Reparatur aktualisiert den Dienstpfad und startet den Dienst mit Administratorrechten neu. Alle Dateien der portablen Version müssen im aktuellen Ordner liegen.", "Après une mise à jour ou un déplacement de CrossDesk, le contrôle de l’écran verrouillé peut être indisponible. La réparation met à jour le chemin du service et le redémarre avec les droits administrateur. Conservez tous les fichiers de la version portable dans le dossier actuel.", "Tras actualizar o mover CrossDesk, el control de la pantalla de bloqueo puede no estar disponible. La reparación actualiza la ruta y reinicia el servicio con permisos de administrador. Mantenga todos los archivos de la versión portátil en la carpeta actual.", "Após atualizar ou mover o CrossDesk, o controle da tela de bloqueio pode ficar indisponível. O reparo atualiza o caminho e reinicia o serviço com permissão de administrador. Mantenha todos os arquivos da versão portátil na pasta atual."}},
|
||||||
|
{"正在修复 Windows 服务…", {"正在修复 Windows 服务…", "Repairing Windows service…", "Восстановление службы Windows…", "正在修復 Windows 服務…", "Windows サービスを修復中…", "Windows 서비스 복구 중…", "Windows-Dienst wird repariert…", "Réparation du service Windows…", "Reparando el servicio de Windows…", "Reparando o serviço do Windows…"}},
|
||||||
|
{"服务已修复并启动", {"服务已修复并启动", "Service repaired and started", "Служба восстановлена и запущена", "服務已修復並啟動", "サービスを修復し、起動しました", "서비스를 복구하고 시작했습니다", "Dienst repariert und gestartet", "Service réparé et démarré", "Servicio reparado e iniciado", "Serviço reparado e iniciado"}},
|
||||||
|
{"修复失败,请检查管理员权限和便携版文件后重试", {"修复失败,请检查管理员权限和便携版文件后重试", "Repair failed. Check administrator permission and portable package files, then retry.", "Восстановление не удалось. Проверьте права администратора и файлы портативной версии, затем повторите попытку.", "修復失敗,請檢查管理員權限和可攜版檔案後重試", "修復に失敗しました。管理者権限とポータブル版のファイルを確認して再試行してください。", "복구하지 못했습니다. 관리자 권한과 포터블 버전 파일을 확인한 후 다시 시도하세요.", "Reparatur fehlgeschlagen. Prüfen Sie die Administratorrechte und die Dateien der portablen Version und versuchen Sie es erneut.", "Échec de la réparation. Vérifiez les droits administrateur et les fichiers de la version portable, puis réessayez.", "Error al reparar. Compruebe los permisos de administrador y los archivos de la versión portátil e inténtelo de nuevo.", "Falha no reparo. Verifique a permissão de administrador e os arquivos da versão portátil e tente novamente."}},
|
||||||
|
{"远端 Windows 服务不可用,锁屏时无法操作。请在远端安装或修复 CrossDesk 服务。", {"远端 Windows 服务不可用,锁屏时无法操作。请在远端安装或修复 CrossDesk 服务。", "The remote Windows service is unavailable. Lock-screen control will not work. Install or repair the CrossDesk service on the remote device.", "Служба Windows на удалённом устройстве недоступна. Управление экраном блокировки не работает. Установите или восстановите службу CrossDesk на удалённом устройстве.", "遠端 Windows 服務無法使用,鎖定畫面時無法操作。請在遠端安裝或修復 CrossDesk 服務。", "リモートの Windows サービスが利用できないため、ロック画面を操作できません。リモート端末で CrossDesk サービスをインストールまたは修復してください。", "원격 Windows 서비스를 사용할 수 없어 잠금 화면을 제어할 수 없습니다. 원격 장치에서 CrossDesk 서비스를 설치하거나 복구하세요.", "Der Windows-Dienst auf dem entfernten Gerät ist nicht verfügbar. Der Sperrbildschirm kann nicht gesteuert werden. Installieren oder reparieren Sie dort den CrossDesk-Dienst.", "Le service Windows distant est indisponible. Le contrôle de l’écran verrouillé ne fonctionnera pas. Installez ou réparez le service CrossDesk sur l’appareil distant.", "El servicio de Windows remoto no está disponible. No se podrá controlar la pantalla de bloqueo. Instale o repare el servicio CrossDesk en el dispositivo remoto.", "O serviço do Windows remoto está indisponível. Não será possível controlar a tela de bloqueio. Instale ou repare o serviço CrossDesk no dispositivo remoto."}},
|
||||||
};
|
};
|
||||||
|
|
||||||
struct NamedTranslation { const char* name; const char* literal; };
|
struct NamedTranslation { const char* name; const char* literal; };
|
||||||
@@ -827,6 +834,13 @@ inline constexpr NamedTranslation kNamedTranslations[] = {
|
|||||||
{"permission_migration_failed", "权限迁移未完成。请重试,或在系统设置中移除旧 CrossDesk 后重新添加。"},
|
{"permission_migration_failed", "权限迁移未完成。请重试,或在系统设置中移除旧 CrossDesk 后重新添加。"},
|
||||||
{"retry_permission_migration", "重试权限迁移"},
|
{"retry_permission_migration", "重试权限迁移"},
|
||||||
{"permission_later", "稍后"},
|
{"permission_later", "稍后"},
|
||||||
|
{"repair_windows_service", "修复"},
|
||||||
|
{"windows_service_repair_title", "修复 CrossDesk 服务"},
|
||||||
|
{"windows_service_repair_message", "升级或移动 CrossDesk 后,锁屏控制服务可能不可用。修复将以管理员权限更新服务路径并重启服务。请确认当前目录包含完整的便携版文件。"},
|
||||||
|
{"windows_service_repairing", "正在修复 Windows 服务…"},
|
||||||
|
{"windows_service_repair_success", "服务已修复并启动"},
|
||||||
|
{"windows_service_repair_failed", "修复失败,请检查管理员权限和便携版文件后重试"},
|
||||||
|
{"remote_service_unavailable_message", "远端 Windows 服务不可用,锁屏时无法操作。请在远端安装或修复 CrossDesk 服务。"},
|
||||||
};
|
};
|
||||||
inline constexpr const char* kSlintLiterals[] = {
|
inline constexpr const char* kSlintLiterals[] = {
|
||||||
"此端口已被占用,",
|
"此端口已被占用,",
|
||||||
@@ -1333,7 +1347,14 @@ inline constexpr const char* kSlintLiterals[] = {
|
|||||||
X(permission_migration_running) \
|
X(permission_migration_running) \
|
||||||
X(permission_migration_failed) \
|
X(permission_migration_failed) \
|
||||||
X(retry_permission_migration) \
|
X(retry_permission_migration) \
|
||||||
X(permission_later)
|
X(permission_later) \
|
||||||
|
X(repair_windows_service) \
|
||||||
|
X(windows_service_repair_title) \
|
||||||
|
X(windows_service_repair_message) \
|
||||||
|
X(windows_service_repairing) \
|
||||||
|
X(windows_service_repair_success) \
|
||||||
|
X(windows_service_repair_failed) \
|
||||||
|
X(remote_service_unavailable_message)
|
||||||
|
|
||||||
} // namespace localization
|
} // namespace localization
|
||||||
} // namespace crossdesk
|
} // namespace crossdesk
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ struct PlatformIntegrationState {
|
|||||||
bool portable_service_do_not_remind_ = false;
|
bool portable_service_do_not_remind_ = false;
|
||||||
bool portable_service_prompt_suppressed_ = false;
|
bool portable_service_prompt_suppressed_ = false;
|
||||||
bool portable_service_installed_ = false;
|
bool portable_service_installed_ = false;
|
||||||
|
bool portable_service_needs_repair_ = false;
|
||||||
std::atomic<PortableServiceInstallState> portable_service_install_state_{
|
std::atomic<PortableServiceInstallState> portable_service_install_state_{
|
||||||
PortableServiceInstallState::idle};
|
PortableServiceInstallState::idle};
|
||||||
std::thread portable_service_install_thread_;
|
std::thread portable_service_install_thread_;
|
||||||
|
|||||||
@@ -137,6 +137,9 @@ export global UiStrings {
|
|||||||
in-out property <string> service-settings-label: "Lock-screen control service";
|
in-out property <string> service-settings-label: "Lock-screen control service";
|
||||||
in-out property <string> install-service: "Install";
|
in-out property <string> install-service: "Install";
|
||||||
in-out property <string> service-installed: "Installed";
|
in-out property <string> service-installed: "Installed";
|
||||||
|
in-out property <string> repair-service: "Repair";
|
||||||
|
in-out property <string> service-repair-title: "Repair CrossDesk Service";
|
||||||
|
in-out property <string> service-repair-message;
|
||||||
in-out property <string> do-not-remind: "Do not remind again";
|
in-out property <string> do-not-remind: "Do not remind again";
|
||||||
in-out property <string> notification: "Notification";
|
in-out property <string> notification: "Notification";
|
||||||
in-out property <string> service-suppressed-message: "You will not be reminded again. You can install the service later in Settings.";
|
in-out property <string> service-suppressed-message: "You will not be reminded again. You can install the service later in Settings.";
|
||||||
@@ -219,6 +222,92 @@ component DialogPromptText inherits Rectangle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Service installation, repair and reminder notices share the settings style.
|
||||||
|
// Keep actions visible while long translations can scroll within the window.
|
||||||
|
component ServicePromptDialog inherits DialogSurface {
|
||||||
|
in property <string> title;
|
||||||
|
in property <string> message;
|
||||||
|
in property <string> status;
|
||||||
|
in property <string> primary-text;
|
||||||
|
in property <string> secondary-text;
|
||||||
|
in property <string> opt-out-label;
|
||||||
|
in property <bool> busy: false;
|
||||||
|
in property <bool> succeeded: false;
|
||||||
|
in property <bool> show-opt-out: false;
|
||||||
|
in-out property <bool> opt-out: false;
|
||||||
|
in property <length> available-height;
|
||||||
|
callback primary-action;
|
||||||
|
callback secondary-action;
|
||||||
|
height: min(root.available-height, content.preferred-height + 113px);
|
||||||
|
background: #f8fafc;
|
||||||
|
outline-color: UiSurface.edge;
|
||||||
|
TouchArea { scroll-event(event) => { accept } }
|
||||||
|
ScrollView {
|
||||||
|
x: 24px; y: 24px; width: parent.width - 40px;
|
||||||
|
height: parent.height - 113px;
|
||||||
|
horizontal-scrollbar-policy: always-off;
|
||||||
|
vertical-scrollbar-policy: as-needed;
|
||||||
|
viewport-width: self.visible-width;
|
||||||
|
viewport-height: content.preferred-height;
|
||||||
|
content := VerticalLayout {
|
||||||
|
// Reserve the scrollbar gutter independently of its visibility.
|
||||||
|
x: 0px; y: 0px;
|
||||||
|
width: root.width - 64px; height: self.preferred-height;
|
||||||
|
padding: 0px; spacing: 16px;
|
||||||
|
Text {
|
||||||
|
text: root.title; color: WorkbenchStyle.text;
|
||||||
|
font-size: 20px; font-weight: 600; wrap: word-wrap;
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
text: root.message; color: WorkbenchStyle.text;
|
||||||
|
font-size: 14px; wrap: word-wrap;
|
||||||
|
font-family: "CrossDesk Message Spacing";
|
||||||
|
}
|
||||||
|
if root.status != "": Text {
|
||||||
|
text: root.status; font-size: 12px; wrap: word-wrap;
|
||||||
|
font-family: "CrossDesk Message Spacing";
|
||||||
|
color: root.succeeded ? WorkbenchStyle.success
|
||||||
|
: root.busy ? WorkbenchStyle.muted : UiSurface.error;
|
||||||
|
}
|
||||||
|
if root.show-opt-out: HorizontalLayout {
|
||||||
|
spacing: 12px;
|
||||||
|
Text {
|
||||||
|
text: root.opt-out-label; color: WorkbenchStyle.muted;
|
||||||
|
font-size: 12px; wrap: word-wrap;
|
||||||
|
vertical-alignment: center; horizontal-stretch: 1;
|
||||||
|
}
|
||||||
|
ToggleSwitch {
|
||||||
|
checked <=> root.opt-out; enabled: !root.busy;
|
||||||
|
accessible-role: switch;
|
||||||
|
accessible-label: root.opt-out-label;
|
||||||
|
accessible-checked: root.opt-out;
|
||||||
|
accessible-enabled: !root.busy;
|
||||||
|
accessible-action-default => { if !root.busy { root.opt-out = !root.opt-out; } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SurfaceSeparator {
|
||||||
|
x: 24px; y: parent.height - 73px; width: parent.width - 48px;
|
||||||
|
outline-color: UiSurface.separator;
|
||||||
|
}
|
||||||
|
HorizontalLayout {
|
||||||
|
x: 24px; y: parent.height - 56px;
|
||||||
|
width: parent.width - 48px; height: 32px;
|
||||||
|
alignment: end; spacing: 8px;
|
||||||
|
if root.secondary-text != "": SettingsButton {
|
||||||
|
width: max(80px, self.label-width);
|
||||||
|
text: root.secondary-text; enabled: !root.busy;
|
||||||
|
clicked => { root.secondary-action(); }
|
||||||
|
}
|
||||||
|
SettingsButton {
|
||||||
|
width: max(80px, self.label-width); primary: true;
|
||||||
|
text: root.primary-text; enabled: !root.busy;
|
||||||
|
clicked => { root.primary-action(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Use the same plain-text field for connection and password changes.
|
// Use the same plain-text field for connection and password changes.
|
||||||
component PasswordInput inherits FlatSurface {
|
component PasswordInput inherits FlatSurface {
|
||||||
in-out property <string> text;
|
in-out property <string> text;
|
||||||
@@ -640,6 +729,7 @@ export component MainWindow inherits Window {
|
|||||||
in property <bool> portable-service-dialog-open: false;
|
in property <bool> portable-service-dialog-open: false;
|
||||||
in property <bool> portable-service-suppressed-notice-open: false;
|
in property <bool> portable-service-suppressed-notice-open: false;
|
||||||
in property <bool> portable-service-installed: false;
|
in property <bool> portable-service-installed: false;
|
||||||
|
in property <bool> portable-service-needs-repair: false;
|
||||||
in property <bool> portable-service-installing: false;
|
in property <bool> portable-service-installing: false;
|
||||||
in property <bool> portable-service-succeeded: false;
|
in property <bool> portable-service-succeeded: false;
|
||||||
in property <string> portable-service-status: "";
|
in property <string> portable-service-status: "";
|
||||||
@@ -729,6 +819,7 @@ export component MainWindow inherits Window {
|
|||||||
callback dismiss-permissions;
|
callback dismiss-permissions;
|
||||||
callback retry-permission-migration;
|
callback retry-permission-migration;
|
||||||
callback install-portable-service;
|
callback install-portable-service;
|
||||||
|
callback open-portable-service;
|
||||||
callback cancel-portable-service;
|
callback cancel-portable-service;
|
||||||
callback acknowledge-portable-service;
|
callback acknowledge-portable-service;
|
||||||
callback acknowledge-portable-service-suppressed;
|
callback acknowledge-portable-service-suppressed;
|
||||||
@@ -2841,13 +2932,14 @@ export component MainWindow inherits Window {
|
|||||||
}
|
}
|
||||||
SettingsGroup {
|
SettingsGroup {
|
||||||
title: GalleryStrings.texts[120];
|
title: GalleryStrings.texts[120];
|
||||||
rows: root.mac-permission-settings-visible ? 6 : 5;
|
rows: 5 + (root.mac-permission-settings-visible ? 1 : 0)
|
||||||
|
+ (root.portable-service-settings-visible ? 1 : 0);
|
||||||
SettingsToggleRow { label: UiStrings.privacy-on-connect; checked <=> root.privacy-on-connect-enabled; enabled: root.privacy-setting-available; }
|
SettingsToggleRow { label: UiStrings.privacy-on-connect; checked <=> root.privacy-on-connect-enabled; enabled: root.privacy-setting-available; }
|
||||||
SettingsToggleRow { label: UiStrings.privacy-local-unlock; checked <=> root.privacy-local-unlock-allowed; enabled: root.privacy-setting-available; }
|
SettingsToggleRow { label: UiStrings.privacy-local-unlock; checked <=> root.privacy-local-unlock-allowed; enabled: root.privacy-setting-available; }
|
||||||
SettingsToggleRow { label: UiStrings.lock-on-disconnect; checked <=> root.lock-on-disconnect-enabled; enabled: root.lock-on-disconnect-available; }
|
SettingsToggleRow { label: UiStrings.lock-on-disconnect; checked <=> root.lock-on-disconnect-enabled; enabled: root.lock-on-disconnect-available; }
|
||||||
SettingsToggleRow { label: TrustStrings.only; checked <=> root.trusted-devices-only; }
|
SettingsToggleRow { label: TrustStrings.only; checked <=> root.trusted-devices-only; }
|
||||||
SettingsRow {
|
SettingsRow {
|
||||||
label: TrustStrings.title; divider: root.mac-permission-settings-visible;
|
label: TrustStrings.title; divider: root.mac-permission-settings-visible || root.portable-service-settings-visible;
|
||||||
SettingsButton {
|
SettingsButton {
|
||||||
x: parent.width - self.width - 12px; y: 4px;
|
x: parent.width - self.width - 12px; y: 4px;
|
||||||
width: max(104px, self.label-width); height: 28px;
|
width: max(104px, self.label-width); height: 28px;
|
||||||
@@ -2864,6 +2956,18 @@ export component MainWindow inherits Window {
|
|||||||
clicked => { root.open-permissions(); }
|
clicked => { root.open-permissions(); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if root.portable-service-settings-visible: SettingsRow {
|
||||||
|
label: UiStrings.service-settings-label; divider: false;
|
||||||
|
label-reserve: service-button.width + 24px;
|
||||||
|
service-button := SettingsButton {
|
||||||
|
x: parent.width - self.width - 12px; y: 4px;
|
||||||
|
width: max(104px, self.label-width); height: 28px;
|
||||||
|
text: root.portable-service-installed || root.portable-service-needs-repair
|
||||||
|
? UiStrings.repair-service : UiStrings.install-service;
|
||||||
|
enabled: !root.portable-service-installing;
|
||||||
|
clicked => { root.open-portable-service(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Text {
|
Text {
|
||||||
text: GalleryStrings.texts[121];
|
text: GalleryStrings.texts[121];
|
||||||
@@ -3607,72 +3711,46 @@ export component MainWindow inherits Window {
|
|||||||
|
|
||||||
// Portable Windows builds prompt for the optional lock-screen service.
|
// Portable Windows builds prompt for the optional lock-screen service.
|
||||||
if root.portable-service-dialog-open: Rectangle {
|
if root.portable-service-dialog-open: Rectangle {
|
||||||
background: #00000052;
|
background: #15223835;
|
||||||
z: 50;
|
z: 50;
|
||||||
TouchArea { clicked => { focus-sink.focus(); } }
|
TouchArea { clicked => { focus-sink.focus(); } }
|
||||||
DialogSurface {
|
ServicePromptDialog {
|
||||||
x: (parent.width - self.width) / 2; y: (parent.height - self.height) / 2; width: 480px; height: 300px;
|
x: (parent.width - self.width) / 2;
|
||||||
VerticalLayout {
|
y: (parent.height - self.height) / 2;
|
||||||
padding: 24px; spacing: 14px;
|
width: min(480px, parent.width - 32px);
|
||||||
DialogPromptText {
|
available-height: parent.height - 32px;
|
||||||
prompt: UiStrings.service-setup-title;
|
title: root.portable-service-needs-repair ? UiStrings.service-repair-title : UiStrings.service-setup-title;
|
||||||
text-color: #242832;
|
message: root.portable-service-needs-repair ? UiStrings.service-repair-message : UiStrings.service-setup-message;
|
||||||
text-size: ImGuiFontStyle.prominent;
|
status: root.portable-service-status;
|
||||||
text-weight: 600;
|
busy: root.portable-service-installing;
|
||||||
}
|
succeeded: root.portable-service-succeeded;
|
||||||
FlatSurface {
|
show-opt-out: !root.portable-service-succeeded && !root.portable-service-needs-repair;
|
||||||
background: #f7f8fa; outline-color: UiSurface.edge; border-radius: UiRadius.surface;
|
opt-out-label: UiStrings.do-not-remind;
|
||||||
Text { x: 14px; y: 22px; width: parent.width - 28px; height: parent.height - 24px; text: UiStrings.service-setup-message; color: #3d434d; wrap: word-wrap; vertical-alignment: center; }
|
opt-out <=> root.portable-service-do-not-remind;
|
||||||
}
|
primary-text: root.portable-service-succeeded ? UiStrings.ok
|
||||||
if root.portable-service-status != "": Text {
|
: root.portable-service-needs-repair ? UiStrings.repair-service : UiStrings.install-service;
|
||||||
text: root.portable-service-status;
|
secondary-text: root.portable-service-succeeded ? "" : UiStrings.cancel;
|
||||||
color: root.portable-service-succeeded ? #25874b : root.portable-service-installing ? #68707d : #b23b3b;
|
primary-action => {
|
||||||
horizontal-alignment: center;
|
if root.portable-service-succeeded { root.acknowledge-portable-service(); }
|
||||||
wrap: word-wrap;
|
else { root.install-portable-service(); }
|
||||||
}
|
|
||||||
if !root.portable-service-succeeded: HorizontalLayout {
|
|
||||||
height: 26px; alignment: center; spacing: 8px;
|
|
||||||
Text { text: UiStrings.do-not-remind; vertical-alignment: center; color: #4a505a; }
|
|
||||||
ToggleSwitch { checked <=> root.portable-service-do-not-remind; enabled: !root.portable-service-installing; }
|
|
||||||
}
|
|
||||||
HorizontalLayout {
|
|
||||||
height: 34px; alignment: center; spacing: 8px;
|
|
||||||
if root.portable-service-succeeded: PrimaryButton { text: UiStrings.ok; clicked => { root.acknowledge-portable-service(); } }
|
|
||||||
if !root.portable-service-succeeded: PrimaryButton {
|
|
||||||
text: UiStrings.install-service;
|
|
||||||
enabled: !root.portable-service-installing;
|
|
||||||
clicked => { root.install-portable-service(); }
|
|
||||||
}
|
|
||||||
if !root.portable-service-succeeded: SecondaryButton {
|
|
||||||
text: UiStrings.cancel;
|
|
||||||
enabled: !root.portable-service-installing;
|
|
||||||
clicked => { root.cancel-portable-service(); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
secondary-action => { root.cancel-portable-service(); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if root.portable-service-suppressed-notice-open: Rectangle {
|
if root.portable-service-suppressed-notice-open: Rectangle {
|
||||||
background: #00000052;
|
background: #15223835;
|
||||||
z: 51;
|
z: 51;
|
||||||
TouchArea { clicked => { focus-sink.focus(); } }
|
TouchArea { clicked => { focus-sink.focus(); } }
|
||||||
DialogSurface {
|
ServicePromptDialog {
|
||||||
x: (parent.width - self.width) / 2; y: (parent.height - self.height) / 2; width: 440px; height: 190px;
|
x: (parent.width - self.width) / 2;
|
||||||
VerticalLayout {
|
y: (parent.height - self.height) / 2;
|
||||||
padding: 24px; spacing: 16px;
|
width: min(440px, parent.width - 32px);
|
||||||
DialogPromptText {
|
available-height: parent.height - 32px;
|
||||||
prompt: UiStrings.notification;
|
title: UiStrings.notification;
|
||||||
text-color: #242832;
|
message: UiStrings.service-suppressed-message;
|
||||||
text-size: ImGuiFontStyle.prominent;
|
primary-text: UiStrings.ok;
|
||||||
text-weight: 600;
|
primary-action => { root.acknowledge-portable-service-suppressed(); }
|
||||||
}
|
|
||||||
DialogPromptText {
|
|
||||||
prompt: UiStrings.service-suppressed-message;
|
|
||||||
text-color: #3d434d;
|
|
||||||
}
|
|
||||||
PrimaryButton { text: UiStrings.ok; clicked => { root.acknowledge-portable-service-suppressed(); } }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -301,6 +301,7 @@ export component StreamWindow inherits Window {
|
|||||||
in property <bool> has-frame: false;
|
in property <bool> has-frame: false;
|
||||||
in property <string> status-text: "";
|
in property <string> status-text: "";
|
||||||
in property <string> receiving-text: "";
|
in property <string> receiving-text: "";
|
||||||
|
in property <string> service-warning: "";
|
||||||
in property <bool> remote-update-visible: false;
|
in property <bool> remote-update-visible: false;
|
||||||
in property <bool> privacy-unlock-visible: false;
|
in property <bool> privacy-unlock-visible: false;
|
||||||
in property <bool> privacy-can-lock: false;
|
in property <bool> privacy-can-lock: false;
|
||||||
@@ -1765,6 +1766,21 @@ export component StreamWindow inherits Window {
|
|||||||
scroll-event(event) => { accept }
|
scroll-event(event) => { accept }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if root.service-warning != "" && !root.modal-visible: FlatSurface {
|
||||||
|
x: (parent.width - self.width) / 2;
|
||||||
|
y: 12px;
|
||||||
|
width: min(600px, parent.width - 32px);
|
||||||
|
height: service-warning-text.preferred-height + 20px;
|
||||||
|
background: #fff4d6;
|
||||||
|
outline-color: #e8c56a;
|
||||||
|
border-radius: UiRadius.surface;
|
||||||
|
z: 12;
|
||||||
|
service-warning-text := Text {
|
||||||
|
x: 12px; y: 10px; width: parent.width - 24px;
|
||||||
|
text: root.service-warning; color: #805b12;
|
||||||
|
font-size: 12px; wrap: word-wrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
if root.privacy-unlock-visible: DialogSurface {
|
if root.privacy-unlock-visible: DialogSurface {
|
||||||
x: (parent.width - self.width) / 2;
|
x: (parent.width - self.width) / 2;
|
||||||
y: max(16px, (parent.height - self.height) / 2);
|
y: max(16px, (parent.height - self.height) / 2);
|
||||||
|
|||||||
@@ -219,6 +219,12 @@ inline int ApplyMainWindowStrings(
|
|||||||
Text(localization::install_windows_service[language]));
|
Text(localization::install_windows_service[language]));
|
||||||
strings.set_service_installed(
|
strings.set_service_installed(
|
||||||
Text(localization::windows_service_installed[language]));
|
Text(localization::windows_service_installed[language]));
|
||||||
|
strings.set_repair_service(
|
||||||
|
Text(localization::repair_windows_service[language]));
|
||||||
|
strings.set_service_repair_title(
|
||||||
|
Text(localization::windows_service_repair_title[language]));
|
||||||
|
strings.set_service_repair_message(
|
||||||
|
Text(localization::windows_service_repair_message[language]));
|
||||||
strings.set_do_not_remind(
|
strings.set_do_not_remind(
|
||||||
Text(localization::do_not_remind_again[language]));
|
Text(localization::do_not_remind_again[language]));
|
||||||
strings.set_notification(Text(localization::notification[language]));
|
strings.set_notification(Text(localization::notification[language]));
|
||||||
|
|||||||
+29
-10
@@ -43,8 +43,9 @@ bool InstallServiceWithElevation() {
|
|||||||
executable_path.parent_path() / L"crossdesk_service.exe";
|
executable_path.parent_path() / L"crossdesk_service.exe";
|
||||||
const std::filesystem::path helper_path =
|
const std::filesystem::path helper_path =
|
||||||
executable_path.parent_path() / L"crossdesk_session_helper.exe";
|
executable_path.parent_path() / L"crossdesk_session_helper.exe";
|
||||||
if (!std::filesystem::exists(service_path) ||
|
std::error_code error;
|
||||||
!std::filesystem::exists(helper_path)) {
|
if (!std::filesystem::is_regular_file(service_path, error) ||
|
||||||
|
!std::filesystem::is_regular_file(helper_path, error)) {
|
||||||
LOG_ERROR("Portable service install failed: service binaries missing, "
|
LOG_ERROR("Portable service install failed: service binaries missing, "
|
||||||
"service={}, helper={}",
|
"service={}, helper={}",
|
||||||
service_path.string(), helper_path.string());
|
service_path.string(), helper_path.string());
|
||||||
@@ -53,7 +54,7 @@ bool InstallServiceWithElevation() {
|
|||||||
|
|
||||||
const std::wstring executable = executable_path.wstring();
|
const std::wstring executable = executable_path.wstring();
|
||||||
const std::wstring working_dir = executable_path.parent_path().wstring();
|
const std::wstring working_dir = executable_path.parent_path().wstring();
|
||||||
constexpr wchar_t parameters[] = L"--service-install";
|
constexpr wchar_t parameters[] = L"--service-repair";
|
||||||
|
|
||||||
SHELLEXECUTEINFOW execute_info{};
|
SHELLEXECUTEINFOW execute_info{};
|
||||||
execute_info.cbSize = sizeof(execute_info);
|
execute_info.cbSize = sizeof(execute_info);
|
||||||
@@ -86,11 +87,8 @@ bool InstallServiceWithElevation() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool started = StartCrossDeskService();
|
return QueryCrossDeskServiceRegistration(service_path.wstring()) ==
|
||||||
if (!started) {
|
ServiceRegistrationState::current;
|
||||||
LOG_WARN("Portable service installed but start failed");
|
|
||||||
}
|
|
||||||
return IsCrossDeskServiceInstalled() && started;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
@@ -100,14 +98,34 @@ void GuiApplication::CheckPortableWindowsService() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
portable_service_prompt_checked_ = true;
|
portable_service_prompt_checked_ = true;
|
||||||
portable_service_installed_ = IsCrossDeskServiceInstalled();
|
const auto executable_path = GetCurrentExecutablePath();
|
||||||
if (portable_service_installed_ || portable_service_prompt_suppressed_) {
|
DWORD error = ERROR_SUCCESS;
|
||||||
|
const auto registration = QueryCrossDeskServiceRegistration(
|
||||||
|
executable_path.empty() ? std::wstring{}
|
||||||
|
: (executable_path.parent_path() /
|
||||||
|
L"crossdesk_service.exe").wstring(),
|
||||||
|
&error);
|
||||||
|
portable_service_installed_ = registration == ServiceRegistrationState::current;
|
||||||
|
portable_service_needs_repair_ =
|
||||||
|
registration == ServiceRegistrationState::repair_required ||
|
||||||
|
registration == ServiceRegistrationState::query_failed;
|
||||||
|
if (registration == ServiceRegistrationState::query_failed) {
|
||||||
|
LOG_WARN("Could not validate portable service registration, error={}", error);
|
||||||
|
}
|
||||||
|
// Suppressing an optional first-install prompt must never hide a broken
|
||||||
|
// registration left behind by a portable upgrade.
|
||||||
|
if (portable_service_installed_ ||
|
||||||
|
(!portable_service_needs_repair_ && portable_service_prompt_suppressed_)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
portable_service_install_state_.store(PortableServiceInstallState::idle,
|
portable_service_install_state_.store(PortableServiceInstallState::idle,
|
||||||
std::memory_order_relaxed);
|
std::memory_order_relaxed);
|
||||||
show_portable_service_install_window_ = true;
|
show_portable_service_install_window_ = true;
|
||||||
|
if (portable_service_needs_repair_) {
|
||||||
|
portable_service_do_not_remind_ = false;
|
||||||
|
ShowMainWindow();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GuiApplication::StartPortableWindowsServiceInstall() {
|
void GuiApplication::StartPortableWindowsServiceInstall() {
|
||||||
@@ -124,6 +142,7 @@ void GuiApplication::StartPortableWindowsServiceInstall() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
JoinPortableWindowsServiceInstallThread();
|
JoinPortableWindowsServiceInstallThread();
|
||||||
|
portable_service_installed_ = false;
|
||||||
portable_service_install_thread_ = std::thread([this]() {
|
portable_service_install_thread_ = std::thread([this]() {
|
||||||
const bool installed = InstallServiceWithElevation();
|
const bool installed = InstallServiceWithElevation();
|
||||||
portable_service_install_state_.store(
|
portable_service_install_state_.store(
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include <Userenv.h>
|
#include <Userenv.h>
|
||||||
#include <WtsApi32.h>
|
#include <WtsApi32.h>
|
||||||
#include <sddl.h>
|
#include <sddl.h>
|
||||||
|
#include <shellapi.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
@@ -242,7 +243,8 @@ bool GrantCrossDeskServiceStartAccessToAuthenticatedUsers(SC_HANDLE service) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
EXPLICIT_ACCESSW access{};
|
EXPLICIT_ACCESSW access{};
|
||||||
access.grfAccessPermissions = SERVICE_START | SERVICE_QUERY_STATUS;
|
access.grfAccessPermissions =
|
||||||
|
SERVICE_START | SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG;
|
||||||
access.grfAccessMode = SET_ACCESS;
|
access.grfAccessMode = SET_ACCESS;
|
||||||
access.grfInheritance = NO_INHERITANCE;
|
access.grfInheritance = NO_INHERITANCE;
|
||||||
access.Trustee.TrusteeForm = TRUSTEE_IS_SID;
|
access.Trustee.TrusteeForm = TRUSTEE_IS_SID;
|
||||||
@@ -2637,6 +2639,75 @@ std::string CrossDeskServiceHost::SendSecureDesktopTextInput(
|
|||||||
BuildSecureTextInputCommand(text, target.stage, target.desktop), 1000);
|
BuildSecureTextInputCommand(text, target.stage, target.desktop), 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ServiceRegistrationState QueryCrossDeskServiceRegistration(
|
||||||
|
const std::wstring& binary_path, DWORD* error_code) {
|
||||||
|
if (error_code) *error_code = ERROR_SUCCESS;
|
||||||
|
const auto failed = [error_code](DWORD error) {
|
||||||
|
if (error_code) *error_code = error;
|
||||||
|
return ServiceRegistrationState::query_failed;
|
||||||
|
};
|
||||||
|
SC_HANDLE manager = OpenSCManagerW(nullptr, nullptr, SC_MANAGER_CONNECT);
|
||||||
|
if (!manager) return failed(GetLastError());
|
||||||
|
SC_HANDLE service =
|
||||||
|
OpenServiceW(manager, kCrossDeskServiceName, SERVICE_QUERY_CONFIG);
|
||||||
|
const DWORD open_error = service ? ERROR_SUCCESS : GetLastError();
|
||||||
|
CloseServiceHandle(manager);
|
||||||
|
if (!service) {
|
||||||
|
return open_error == ERROR_SERVICE_DOES_NOT_EXIST
|
||||||
|
? ServiceRegistrationState::not_installed
|
||||||
|
: failed(open_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryServiceConfig documents an 8 KiB maximum. A single query also avoids
|
||||||
|
// a size-query race with an installer changing the registration.
|
||||||
|
alignas(QUERY_SERVICE_CONFIGW) BYTE buffer[8192]{};
|
||||||
|
auto* config = reinterpret_cast<QUERY_SERVICE_CONFIGW*>(buffer);
|
||||||
|
DWORD needed = 0;
|
||||||
|
const bool queried = QueryServiceConfigW(service, config, sizeof(buffer),
|
||||||
|
&needed) != FALSE;
|
||||||
|
const DWORD query_error = queried ? ERROR_SUCCESS : GetLastError();
|
||||||
|
CloseServiceHandle(service);
|
||||||
|
if (!queried) return failed(query_error);
|
||||||
|
|
||||||
|
if (!config->lpBinaryPathName || binary_path.empty() ||
|
||||||
|
config->dwStartType == SERVICE_DISABLED) {
|
||||||
|
return ServiceRegistrationState::repair_required;
|
||||||
|
}
|
||||||
|
const DWORD expanded_size =
|
||||||
|
ExpandEnvironmentStringsW(config->lpBinaryPathName, nullptr, 0);
|
||||||
|
if (!expanded_size) return failed(GetLastError());
|
||||||
|
std::vector<wchar_t> expanded(expanded_size);
|
||||||
|
const DWORD expanded_length = ExpandEnvironmentStringsW(
|
||||||
|
config->lpBinaryPathName, expanded.data(), expanded_size);
|
||||||
|
if (!expanded_length) return failed(GetLastError());
|
||||||
|
if (expanded_length > expanded_size) return failed(ERROR_INSUFFICIENT_BUFFER);
|
||||||
|
const std::wstring command(expanded.data());
|
||||||
|
const auto first = command.find_first_not_of(L" \t");
|
||||||
|
if (first == std::wstring::npos)
|
||||||
|
return ServiceRegistrationState::repair_required;
|
||||||
|
int argc = 0;
|
||||||
|
LPWSTR* argv = CommandLineToArgvW(command.c_str() + first, &argc);
|
||||||
|
if (!argv) return failed(GetLastError());
|
||||||
|
const bool valid_command = argc == 2 && std::wstring(argv[1]) == L"--service";
|
||||||
|
const std::filesystem::path registered = argc > 0 ? argv[0] : L"";
|
||||||
|
LocalFree(argv);
|
||||||
|
const std::filesystem::path expected(binary_path);
|
||||||
|
const auto registered_path = registered.lexically_normal().make_preferred().wstring();
|
||||||
|
const auto expected_path = expected.lexically_normal().make_preferred().wstring();
|
||||||
|
if (!valid_command ||
|
||||||
|
CompareStringOrdinal(registered_path.c_str(), -1, expected_path.c_str(),
|
||||||
|
-1, TRUE) != CSTR_EQUAL) {
|
||||||
|
return ServiceRegistrationState::repair_required;
|
||||||
|
}
|
||||||
|
std::error_code error;
|
||||||
|
if (!std::filesystem::is_regular_file(expected, error) ||
|
||||||
|
!std::filesystem::is_regular_file(
|
||||||
|
expected.parent_path() / L"crossdesk_session_helper.exe", error)) {
|
||||||
|
return ServiceRegistrationState::repair_required;
|
||||||
|
}
|
||||||
|
return ServiceRegistrationState::current;
|
||||||
|
}
|
||||||
|
|
||||||
bool InstallCrossDeskService(const std::wstring& binary_path) {
|
bool InstallCrossDeskService(const std::wstring& binary_path) {
|
||||||
SC_HANDLE manager = OpenSCManagerW(nullptr, nullptr, SC_MANAGER_ALL_ACCESS);
|
SC_HANDLE manager = OpenSCManagerW(nullptr, nullptr, SC_MANAGER_ALL_ACCESS);
|
||||||
if (manager == nullptr) {
|
if (manager == nullptr) {
|
||||||
@@ -2734,6 +2805,37 @@ bool IsCrossDeskServiceInstalled() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool RepairCrossDeskService(const std::wstring& binary_path) {
|
||||||
|
const std::filesystem::path service_path(binary_path);
|
||||||
|
std::error_code error;
|
||||||
|
if (!std::filesystem::is_regular_file(service_path, error) ||
|
||||||
|
!std::filesystem::is_regular_file(
|
||||||
|
service_path.parent_path() / L"crossdesk_session_helper.exe", error)) {
|
||||||
|
LOG_ERROR("Service repair failed: service binaries missing");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Updating ImagePath does not replace an already running process. Keep the
|
||||||
|
// existing service identity and unattended profile, then restart it so the
|
||||||
|
// service and its helpers all come from the current directory.
|
||||||
|
if (!InstallCrossDeskService(binary_path) || !StopCrossDeskService() ||
|
||||||
|
!StartCrossDeskService()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const ULONGLONG deadline = GetTickCount64() + 10000;
|
||||||
|
while (GetTickCount64() < deadline) {
|
||||||
|
const auto response = Json::parse(QueryCrossDeskService("ping", 500),
|
||||||
|
nullptr, false);
|
||||||
|
if (response.is_object() && response.value("ok", false) &&
|
||||||
|
response.value("reply", std::string{}) == "pong") {
|
||||||
|
return QueryCrossDeskServiceRegistration(binary_path) ==
|
||||||
|
ServiceRegistrationState::current;
|
||||||
|
}
|
||||||
|
Sleep(100);
|
||||||
|
}
|
||||||
|
LOG_ERROR("Service repair failed: service did not become ready");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool StartCrossDeskService() {
|
bool StartCrossDeskService() {
|
||||||
SC_HANDLE manager = OpenSCManagerW(nullptr, nullptr, SC_MANAGER_CONNECT);
|
SC_HANDLE manager = OpenSCManagerW(nullptr, nullptr, SC_MANAGER_CONNECT);
|
||||||
if (manager == nullptr) {
|
if (manager == nullptr) {
|
||||||
|
|||||||
@@ -186,7 +186,16 @@ class CrossDeskServiceHost {
|
|||||||
};
|
};
|
||||||
|
|
||||||
bool IsCrossDeskServiceInstalled();
|
bool IsCrossDeskServiceInstalled();
|
||||||
|
enum class ServiceRegistrationState {
|
||||||
|
not_installed,
|
||||||
|
current,
|
||||||
|
repair_required,
|
||||||
|
query_failed,
|
||||||
|
};
|
||||||
|
ServiceRegistrationState QueryCrossDeskServiceRegistration(
|
||||||
|
const std::wstring& binary_path, DWORD* error_code = nullptr);
|
||||||
bool InstallCrossDeskService(const std::wstring& binary_path);
|
bool InstallCrossDeskService(const std::wstring& binary_path);
|
||||||
|
bool RepairCrossDeskService(const std::wstring& binary_path);
|
||||||
bool UninstallCrossDeskService();
|
bool UninstallCrossDeskService();
|
||||||
bool StartCrossDeskService();
|
bool StartCrossDeskService();
|
||||||
bool StopCrossDeskService(DWORD timeout_ms = 20000);
|
bool StopCrossDeskService(DWORD timeout_ms = 20000);
|
||||||
|
|||||||
+100
-2
@@ -10727,6 +10727,90 @@
|
|||||||
"fr": "Plus tard",
|
"fr": "Plus tard",
|
||||||
"es": "Más tarde",
|
"es": "Más tarde",
|
||||||
"pt-BR": "Mais tarde"
|
"pt-BR": "Mais tarde"
|
||||||
|
},
|
||||||
|
"修复": {
|
||||||
|
"zh-Hans": "修复",
|
||||||
|
"en": "Repair",
|
||||||
|
"ru": "Восстановить",
|
||||||
|
"zh-Hant": "修復",
|
||||||
|
"ja": "修復",
|
||||||
|
"ko": "복구",
|
||||||
|
"de": "Reparieren",
|
||||||
|
"fr": "Réparer",
|
||||||
|
"es": "Reparar",
|
||||||
|
"pt-BR": "Reparar"
|
||||||
|
},
|
||||||
|
"修复 CrossDesk 服务": {
|
||||||
|
"zh-Hans": "修复 CrossDesk 服务",
|
||||||
|
"en": "Repair CrossDesk Service",
|
||||||
|
"ru": "Восстановление службы CrossDesk",
|
||||||
|
"zh-Hant": "修復 CrossDesk 服務",
|
||||||
|
"ja": "CrossDesk サービスの修復",
|
||||||
|
"ko": "CrossDesk 서비스 복구",
|
||||||
|
"de": "CrossDesk-Dienst reparieren",
|
||||||
|
"fr": "Réparer le service CrossDesk",
|
||||||
|
"es": "Reparar el servicio CrossDesk",
|
||||||
|
"pt-BR": "Reparar o serviço CrossDesk"
|
||||||
|
},
|
||||||
|
"升级或移动 CrossDesk 后,锁屏控制服务可能不可用。修复将以管理员权限更新服务路径并重启服务。请确认当前目录包含完整的便携版文件。": {
|
||||||
|
"zh-Hans": "升级或移动 CrossDesk 后,锁屏控制服务可能不可用。修复将以管理员权限更新服务路径并重启服务。请确认当前目录包含完整的便携版文件。",
|
||||||
|
"en": "After updating or moving CrossDesk, lock-screen control may be unavailable. Repair updates the service path and restarts the service with administrator permission. Keep all portable package files in the current folder.",
|
||||||
|
"ru": "После обновления или перемещения CrossDesk управление экраном блокировки может быть недоступно. Восстановление обновит путь и перезапустит службу с правами администратора. Все файлы портативной версии должны находиться в текущей папке.",
|
||||||
|
"zh-Hant": "升級或移動 CrossDesk 後,鎖定畫面控制服務可能無法使用。修復將以管理員權限更新服務路徑並重新啟動服務。請確認目前目錄包含完整的可攜版檔案。",
|
||||||
|
"ja": "CrossDesk の更新や移動後は、ロック画面を操作できない場合があります。修復すると管理者権限でサービスのパスを更新し、再起動します。ポータブル版の全ファイルを現在のフォルダーに配置してください。",
|
||||||
|
"ko": "CrossDesk를 업데이트하거나 이동한 후 잠금 화면 제어가 작동하지 않을 수 있습니다. 복구하면 관리자 권한으로 서비스 경로를 갱신하고 서비스를 다시 시작합니다. 현재 폴더에 포터블 버전의 모든 파일이 있어야 합니다.",
|
||||||
|
"de": "Nach dem Aktualisieren oder Verschieben von CrossDesk ist die Steuerung des Sperrbildschirms möglicherweise nicht verfügbar. Die Reparatur aktualisiert den Dienstpfad und startet den Dienst mit Administratorrechten neu. Alle Dateien der portablen Version müssen im aktuellen Ordner liegen.",
|
||||||
|
"fr": "Après une mise à jour ou un déplacement de CrossDesk, le contrôle de l’écran verrouillé peut être indisponible. La réparation met à jour le chemin du service et le redémarre avec les droits administrateur. Conservez tous les fichiers de la version portable dans le dossier actuel.",
|
||||||
|
"es": "Tras actualizar o mover CrossDesk, el control de la pantalla de bloqueo puede no estar disponible. La reparación actualiza la ruta y reinicia el servicio con permisos de administrador. Mantenga todos los archivos de la versión portátil en la carpeta actual.",
|
||||||
|
"pt-BR": "Após atualizar ou mover o CrossDesk, o controle da tela de bloqueio pode ficar indisponível. O reparo atualiza o caminho e reinicia o serviço com permissão de administrador. Mantenha todos os arquivos da versão portátil na pasta atual."
|
||||||
|
},
|
||||||
|
"正在修复 Windows 服务…": {
|
||||||
|
"zh-Hans": "正在修复 Windows 服务…",
|
||||||
|
"en": "Repairing Windows service…",
|
||||||
|
"ru": "Восстановление службы Windows…",
|
||||||
|
"zh-Hant": "正在修復 Windows 服務…",
|
||||||
|
"ja": "Windows サービスを修復中…",
|
||||||
|
"ko": "Windows 서비스 복구 중…",
|
||||||
|
"de": "Windows-Dienst wird repariert…",
|
||||||
|
"fr": "Réparation du service Windows…",
|
||||||
|
"es": "Reparando el servicio de Windows…",
|
||||||
|
"pt-BR": "Reparando o serviço do Windows…"
|
||||||
|
},
|
||||||
|
"服务已修复并启动": {
|
||||||
|
"zh-Hans": "服务已修复并启动",
|
||||||
|
"en": "Service repaired and started",
|
||||||
|
"ru": "Служба восстановлена и запущена",
|
||||||
|
"zh-Hant": "服務已修復並啟動",
|
||||||
|
"ja": "サービスを修復し、起動しました",
|
||||||
|
"ko": "서비스를 복구하고 시작했습니다",
|
||||||
|
"de": "Dienst repariert und gestartet",
|
||||||
|
"fr": "Service réparé et démarré",
|
||||||
|
"es": "Servicio reparado e iniciado",
|
||||||
|
"pt-BR": "Serviço reparado e iniciado"
|
||||||
|
},
|
||||||
|
"修复失败,请检查管理员权限和便携版文件后重试": {
|
||||||
|
"zh-Hans": "修复失败,请检查管理员权限和便携版文件后重试",
|
||||||
|
"en": "Repair failed. Check administrator permission and portable package files, then retry.",
|
||||||
|
"ru": "Восстановление не удалось. Проверьте права администратора и файлы портативной версии, затем повторите попытку.",
|
||||||
|
"zh-Hant": "修復失敗,請檢查管理員權限和可攜版檔案後重試",
|
||||||
|
"ja": "修復に失敗しました。管理者権限とポータブル版のファイルを確認して再試行してください。",
|
||||||
|
"ko": "복구하지 못했습니다. 관리자 권한과 포터블 버전 파일을 확인한 후 다시 시도하세요.",
|
||||||
|
"de": "Reparatur fehlgeschlagen. Prüfen Sie die Administratorrechte und die Dateien der portablen Version und versuchen Sie es erneut.",
|
||||||
|
"fr": "Échec de la réparation. Vérifiez les droits administrateur et les fichiers de la version portable, puis réessayez.",
|
||||||
|
"es": "Error al reparar. Compruebe los permisos de administrador y los archivos de la versión portátil e inténtelo de nuevo.",
|
||||||
|
"pt-BR": "Falha no reparo. Verifique a permissão de administrador e os arquivos da versão portátil e tente novamente."
|
||||||
|
},
|
||||||
|
"远端 Windows 服务不可用,锁屏时无法操作。请在远端安装或修复 CrossDesk 服务。": {
|
||||||
|
"zh-Hans": "远端 Windows 服务不可用,锁屏时无法操作。请在远端安装或修复 CrossDesk 服务。",
|
||||||
|
"en": "The remote Windows service is unavailable. Lock-screen control will not work. Install or repair the CrossDesk service on the remote device.",
|
||||||
|
"ru": "Служба Windows на удалённом устройстве недоступна. Управление экраном блокировки не работает. Установите или восстановите службу CrossDesk на удалённом устройстве.",
|
||||||
|
"zh-Hant": "遠端 Windows 服務無法使用,鎖定畫面時無法操作。請在遠端安裝或修復 CrossDesk 服務。",
|
||||||
|
"ja": "リモートの Windows サービスが利用できないため、ロック画面を操作できません。リモート端末で CrossDesk サービスをインストールまたは修復してください。",
|
||||||
|
"ko": "원격 Windows 서비스를 사용할 수 없어 잠금 화면을 제어할 수 없습니다. 원격 장치에서 CrossDesk 서비스를 설치하거나 복구하세요.",
|
||||||
|
"de": "Der Windows-Dienst auf dem entfernten Gerät ist nicht verfügbar. Der Sperrbildschirm kann nicht gesteuert werden. Installieren oder reparieren Sie dort den CrossDesk-Dienst.",
|
||||||
|
"fr": "Le service Windows distant est indisponible. Le contrôle de l’écran verrouillé ne fonctionnera pas. Installez ou réparez le service CrossDesk sur l’appareil distant.",
|
||||||
|
"es": "El servicio de Windows remoto no está disponible. No se podrá controlar la pantalla de bloqueo. Instale o repare el servicio CrossDesk en el dispositivo remoto.",
|
||||||
|
"pt-BR": "O serviço do Windows remoto está indisponível. Não será possível controlar a tela de bloqueio. Instale ou repare o serviço CrossDesk no dispositivo remoto."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"desktop": {
|
"desktop": {
|
||||||
@@ -11020,7 +11104,14 @@
|
|||||||
"permission_migration_running": "正在迁移旧版权限…",
|
"permission_migration_running": "正在迁移旧版权限…",
|
||||||
"permission_migration_failed": "权限迁移未完成。请重试,或在系统设置中移除旧 CrossDesk 后重新添加。",
|
"permission_migration_failed": "权限迁移未完成。请重试,或在系统设置中移除旧 CrossDesk 后重新添加。",
|
||||||
"retry_permission_migration": "重试权限迁移",
|
"retry_permission_migration": "重试权限迁移",
|
||||||
"permission_later": "稍后"
|
"permission_later": "稍后",
|
||||||
|
"repair_windows_service": "修复",
|
||||||
|
"windows_service_repair_title": "修复 CrossDesk 服务",
|
||||||
|
"windows_service_repair_message": "升级或移动 CrossDesk 后,锁屏控制服务可能不可用。修复将以管理员权限更新服务路径并重启服务。请确认当前目录包含完整的便携版文件。",
|
||||||
|
"windows_service_repairing": "正在修复 Windows 服务…",
|
||||||
|
"windows_service_repair_success": "服务已修复并启动",
|
||||||
|
"windows_service_repair_failed": "修复失败,请检查管理员权限和便携版文件后重试",
|
||||||
|
"remote_service_unavailable_message": "远端 Windows 服务不可用,锁屏时无法操作。请在远端安装或修复 CrossDesk 服务。"
|
||||||
},
|
},
|
||||||
"slint": [
|
"slint": [
|
||||||
"此端口已被占用,",
|
"此端口已被占用,",
|
||||||
@@ -11754,7 +11845,14 @@
|
|||||||
"正在迁移旧版权限…",
|
"正在迁移旧版权限…",
|
||||||
"权限迁移未完成。请重试,或在系统设置中移除旧 CrossDesk 后重新添加。",
|
"权限迁移未完成。请重试,或在系统设置中移除旧 CrossDesk 后重新添加。",
|
||||||
"重试权限迁移",
|
"重试权限迁移",
|
||||||
"稍后"
|
"稍后",
|
||||||
|
"修复",
|
||||||
|
"修复 CrossDesk 服务",
|
||||||
|
"升级或移动 CrossDesk 后,锁屏控制服务可能不可用。修复将以管理员权限更新服务路径并重启服务。请确认当前目录包含完整的便携版文件。",
|
||||||
|
"正在修复 Windows 服务…",
|
||||||
|
"服务已修复并启动",
|
||||||
|
"修复失败,请检查管理员权限和便携版文件后重试",
|
||||||
|
"远端 Windows 服务不可用,锁屏时无法操作。请在远端安装或修复 CrossDesk 服务。"
|
||||||
],
|
],
|
||||||
"android": [
|
"android": [
|
||||||
" · 已完成",
|
" · 已完成",
|
||||||
|
|||||||
Reference in New Issue
Block a user