mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-11-07 06:39:10 +08:00
[fix] preserve macOS permissions and defer legacy migration to launch
This commit is contained in:
@@ -1173,6 +1173,9 @@ int GuiApplication::Run() {
|
||||
"announcement_reads");
|
||||
config_center_ = std::make_unique<ConfigCenter>(cache_path_ + "/config.ini");
|
||||
InitializeTrustedDevices(path_manager_->GetConfigPath() / "trusted-devices");
|
||||
#ifdef __APPLE__
|
||||
StartMacPermissionMigration(false);
|
||||
#endif
|
||||
|
||||
LOG_INFO("CrossDesk version: {} (Slint UI)", CROSSDESK_VERSION);
|
||||
|
||||
@@ -2080,6 +2083,26 @@ void GuiApplication::BindMainCallbacks(bool gallery_window) {
|
||||
props->rejoin_ = true;
|
||||
need_to_rejoin_ = true;
|
||||
});
|
||||
main->on_open_permissions([this, main] {
|
||||
#ifdef __APPLE__
|
||||
mac_permission_dialog_dismissed_ = false;
|
||||
mac_permission_dialog_requested_ = true;
|
||||
show_request_permission_window_ = true;
|
||||
if (main->get_gallery_mode()) ShowMainWindow();
|
||||
#endif
|
||||
});
|
||||
main->on_dismiss_permissions([this] {
|
||||
#ifdef __APPLE__
|
||||
mac_permission_dialog_dismissed_ = true;
|
||||
mac_permission_dialog_requested_ = false;
|
||||
show_request_permission_window_ = false;
|
||||
#endif
|
||||
});
|
||||
main->on_retry_permission_migration([this] {
|
||||
#ifdef __APPLE__
|
||||
StartMacPermissionMigration(true);
|
||||
#endif
|
||||
});
|
||||
main->on_request_screen_recording_permission([this] {
|
||||
#ifdef __APPLE__
|
||||
OpenScreenRecordingPreferences();
|
||||
@@ -3617,8 +3640,16 @@ void GuiApplication::SyncPlatformDialogs() {
|
||||
#endif
|
||||
#ifdef __APPLE__
|
||||
RefreshMacPermissionStatus(false);
|
||||
show_request_permission_window_ = !mac_screen_recording_permission_granted_ ||
|
||||
!mac_accessibility_permission_granted_;
|
||||
const bool migration_running = mac_permission_migration_task_.valid();
|
||||
const bool permissions_pending = migration_running ||
|
||||
mac_permission_migration_failed_ || !mac_screen_recording_permission_granted_ ||
|
||||
!mac_accessibility_permission_granted_;
|
||||
show_request_permission_window_ = !mac_permission_dialog_dismissed_ &&
|
||||
(mac_permission_dialog_requested_ || permissions_pending);
|
||||
ui_->main->set_mac_permission_settings_visible(true);
|
||||
ui_->wall->set_mac_permission_settings_visible(true);
|
||||
ui_->main->set_permission_migration_running(migration_running);
|
||||
ui_->main->set_permission_migration_failed(mac_permission_migration_failed_);
|
||||
ui_->main->set_permission_dialog_open(show_request_permission_window_);
|
||||
ui_->main->set_screen_recording_granted(
|
||||
mac_screen_recording_permission_granted_);
|
||||
|
||||
@@ -527,6 +527,11 @@ inline constexpr TranslationRow kTranslationRows[] = {
|
||||
{"打开目录", {"打开目录", "Open folder", "Открыть папку", "開啟目錄", "フォルダーを開く", "폴더 열기", "Ordner öffnen", "Ouvrir le dossier", "Abrir carpeta", "Abrir pasta"}},
|
||||
{"最小化时暂停视频", {"最小化时暂停视频", "Pause video when minimized", "Приостанавливать видео при сворачивании", "最小化時暫停視訊", "最小化時に映像を一時停止", "최소화 시 영상 일시 중지", "Video beim Minimieren pausieren", "Mettre la vidéo en pause à la réduction", "Pausar el vídeo al minimizar", "Pausar vídeo ao minimizar"}},
|
||||
{"控制窗口最小化时暂停接收视频,降低本机解码资源占用。", {"控制窗口最小化时暂停接收视频,降低本机解码资源占用。", "Pause video reception when the control window is minimized to reduce local decoding resource usage.", "При сворачивании окна управления приём видео приостанавливается, снижая нагрузку от декодирования на этом устройстве.", "控制視窗最小化時暫停接收視訊,降低本機解碼資源占用。", "操作ウィンドウの最小化時に映像の受信を一時停止し、この端末のデコード処理による負荷を軽減します。", "제어 창을 최소화하면 영상 수신을 일시 중지하여 이 기기의 디코딩 리소스 사용량을 줄입니다.", "Pausiert den Videoempfang, wenn das Steuerungsfenster minimiert ist, und reduziert so den lokalen Ressourcenverbrauch beim Dekodieren.", "Met en pause la réception vidéo lorsque la fenêtre de contrôle est réduite afin de diminuer les ressources de décodage utilisées sur cet appareil.", "Pausa la recepción de vídeo al minimizar la ventana de control para reducir el uso de recursos de decodificación en este dispositivo.", "Pausa a recepção de vídeo ao minimizar a janela de controle para reduzir o uso de recursos de decodificação neste dispositivo."}},
|
||||
{"系统权限", {"系统权限", "System permissions", "Системные разрешения", "系統權限", "システム権限", "시스템 권한", "Systemberechtigungen", "Autorisations système", "Permisos del sistema", "Permissões do sistema"}},
|
||||
{"正在迁移旧版权限…", {"正在迁移旧版权限…", "Migrating permissions…", "Перенос разрешений…", "正在遷移舊版權限…", "権限を移行中…", "권한 이전 중…", "Berechtigungen werden migriert…", "Migration des autorisations…", "Migrando permisos…", "Migrando permissões…"}},
|
||||
{"权限迁移未完成。请重试,或在系统设置中移除旧 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"}},
|
||||
{"稍后", {"稍后", "Later", "Позже", "稍後", "後で", "나중에", "Später", "Plus tard", "Más tarde", "Mais tarde"}},
|
||||
};
|
||||
|
||||
struct NamedTranslation { const char* name; const char* literal; };
|
||||
@@ -817,6 +822,11 @@ inline constexpr NamedTranslation kNamedTranslations[] = {
|
||||
{"shutdown_reason", "远程控制连接中,保留连接以确认或取消关机。"},
|
||||
{"pause_video_when_minimized", "最小化时暂停视频"},
|
||||
{"pause_video_when_minimized_hint", "控制窗口最小化时暂停接收视频,降低本机解码资源占用。"},
|
||||
{"system_permissions", "系统权限"},
|
||||
{"permission_migration_running", "正在迁移旧版权限…"},
|
||||
{"permission_migration_failed", "权限迁移未完成。请重试,或在系统设置中移除旧 CrossDesk 后重新添加。"},
|
||||
{"retry_permission_migration", "重试权限迁移"},
|
||||
{"permission_later", "稍后"},
|
||||
};
|
||||
inline constexpr const char* kSlintLiterals[] = {
|
||||
"此端口已被占用,",
|
||||
@@ -1318,7 +1328,12 @@ inline constexpr const char* kSlintLiterals[] = {
|
||||
X(exit_program) \
|
||||
X(shutdown_reason) \
|
||||
X(pause_video_when_minimized) \
|
||||
X(pause_video_when_minimized_hint)
|
||||
X(pause_video_when_minimized_hint) \
|
||||
X(system_permissions) \
|
||||
X(permission_migration_running) \
|
||||
X(permission_migration_failed) \
|
||||
X(retry_permission_migration) \
|
||||
X(permission_later)
|
||||
|
||||
} // namespace localization
|
||||
} // namespace crossdesk
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
@@ -28,6 +29,9 @@
|
||||
#include "runtime/peer_event_handler.h"
|
||||
#include "runtime/privacy_session_policy.h"
|
||||
#include "runtime/trusted_devices.h"
|
||||
#ifdef __APPLE__
|
||||
#include "platform/macos/gui/runtime/mac_permission_migration.h"
|
||||
#endif
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
@@ -180,6 +184,11 @@ class GuiRuntime : protected gui_detail::GuiState {
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
void StartMacPermissionMigration(bool retry);
|
||||
std::future<MacPermissionMigrationResult> mac_permission_migration_task_;
|
||||
bool mac_permission_migration_failed_ = false;
|
||||
bool mac_permission_dialog_dismissed_ = false;
|
||||
bool mac_permission_dialog_requested_ = false;
|
||||
bool CheckScreenRecordingPermission();
|
||||
bool CheckAccessibilityPermission();
|
||||
void OpenScreenRecordingPreferences();
|
||||
|
||||
@@ -120,6 +120,11 @@ export global UiStrings {
|
||||
in-out property <string> permission-required: "The application requires the following permissions:";
|
||||
in-out property <string> screen-recording-permission: "Screen recording permission";
|
||||
in-out property <string> accessibility-permission: "Accessibility permission";
|
||||
in-out property <string> system-permissions: "System permissions";
|
||||
in-out property <string> permission-migration-running: "Migrating permissions…";
|
||||
in-out property <string> permission-migration-failed;
|
||||
in-out property <string> retry-permission-migration: "Retry migration";
|
||||
in-out property <string> permission-later: "Later";
|
||||
in-out property <string> wayland-notice-title: "Wayland Session Notice";
|
||||
in-out property <string> wayland-notice-subtitle;
|
||||
in-out property <string> wayland-permission-title;
|
||||
@@ -625,6 +630,9 @@ export component MainWindow inherits Window {
|
||||
in-out property <string> connection-password: "";
|
||||
in-out property <bool> connection-remember-password: false;
|
||||
in property <bool> permission-dialog-open: false;
|
||||
in property <bool> mac-permission-settings-visible: false;
|
||||
in property <bool> permission-migration-running: false;
|
||||
in property <bool> permission-migration-failed: false;
|
||||
in property <bool> screen-recording-granted: true;
|
||||
in property <bool> accessibility-granted: true;
|
||||
in-out property <bool> wayland-notice-open: false;
|
||||
@@ -717,6 +725,9 @@ export component MainWindow inherits Window {
|
||||
callback reset-remote-id;
|
||||
callback request-screen-recording-permission;
|
||||
callback request-accessibility-permission;
|
||||
callback open-permissions;
|
||||
callback dismiss-permissions;
|
||||
callback retry-permission-migration;
|
||||
callback install-portable-service;
|
||||
callback cancel-portable-service;
|
||||
callback acknowledge-portable-service;
|
||||
@@ -2577,82 +2588,117 @@ export component MainWindow inherits Window {
|
||||
x: (parent.width - self.width) / 2;
|
||||
y: (parent.height - self.height) / 2;
|
||||
width: min(400px, parent.width - 40px);
|
||||
height: permission-content.preferred-height;
|
||||
height: min(permission-content.preferred-height, parent.height - 32px);
|
||||
background: #f8fafc; outline-color: UiSurface.edge;
|
||||
TouchArea { clicked => { focus-sink.focus(); } }
|
||||
permission-content := VerticalLayout {
|
||||
padding: 24px; spacing: 20px;
|
||||
VerticalLayout {
|
||||
spacing: 8px;
|
||||
Text {
|
||||
text: UiStrings.request-permissions;
|
||||
font-size: 20px; font-weight: 600; color: WorkbenchStyle.text;
|
||||
wrap: word-wrap;
|
||||
}
|
||||
Text {
|
||||
text: UiStrings.permission-required;
|
||||
font-size: 12px; color: WorkbenchStyle.muted; wrap: word-wrap;
|
||||
}
|
||||
}
|
||||
VerticalLayout {
|
||||
spacing: 12px;
|
||||
for permission[index] in [
|
||||
{label: UiStrings.accessibility-permission, granted: root.accessibility-granted,
|
||||
description: GalleryStrings.texts[108]},
|
||||
{label: UiStrings.screen-recording-permission, granted: root.screen-recording-granted,
|
||||
description: GalleryStrings.texts[109]},
|
||||
]: WorkbenchCard {
|
||||
height: max(80px, permission-labels.preferred-height + 32px);
|
||||
permission-labels := VerticalLayout {
|
||||
x: 16px; y: 16px; width: parent.width - permission-action.width - 48px;
|
||||
height: parent.height - 32px; spacing: 6px; alignment: center;
|
||||
permission-scroll := ScrollView {
|
||||
width: parent.width; height: parent.height;
|
||||
viewport-width: self.visible-width;
|
||||
viewport-height: permission-content.preferred-height;
|
||||
horizontal-scrollbar-policy: always-off;
|
||||
vertical-scrollbar-policy: as-needed;
|
||||
Rectangle {
|
||||
width: permission-scroll.viewport-width;
|
||||
height: permission-scroll.viewport-height;
|
||||
permission-content := VerticalLayout {
|
||||
padding: 24px; spacing: 20px;
|
||||
VerticalLayout {
|
||||
spacing: 8px;
|
||||
Text {
|
||||
text: permission.label; font-size: 13px; font-weight: 600;
|
||||
color: WorkbenchStyle.text; wrap: word-wrap;
|
||||
text: UiStrings.request-permissions;
|
||||
font-size: 20px; font-weight: 600; color: WorkbenchStyle.text;
|
||||
wrap: word-wrap;
|
||||
}
|
||||
Text {
|
||||
text: permission.description; font-size: 11px;
|
||||
color: WorkbenchStyle.muted; wrap: word-wrap;
|
||||
text: UiStrings.permission-required;
|
||||
font-size: 12px; color: WorkbenchStyle.muted; wrap: word-wrap;
|
||||
}
|
||||
}
|
||||
permission-action := Rectangle {
|
||||
x: parent.width - self.width - 16px; y: (parent.height - self.height) / 2;
|
||||
width: max(96px, permission-action-metrics.preferred-width + 24px);
|
||||
height: 36px;
|
||||
permission-action-metrics := Text {
|
||||
visible: false; font-size: 13px; font-weight: 600;
|
||||
text: GalleryStrings.texts[110];
|
||||
}
|
||||
if !permission.granted: WorkbenchButton {
|
||||
width: parent.width; height: parent.height; primary: true;
|
||||
text: permission-action-metrics.text;
|
||||
accessible-label: permission.label + ": " + self.text;
|
||||
clicked => {
|
||||
if index == 0 { root.request-accessibility-permission(); }
|
||||
else { root.request-screen-recording-permission(); }
|
||||
VerticalLayout {
|
||||
spacing: 12px;
|
||||
for permission[index] in [
|
||||
{label: UiStrings.accessibility-permission, granted: root.accessibility-granted,
|
||||
description: GalleryStrings.texts[108]},
|
||||
{label: UiStrings.screen-recording-permission, granted: root.screen-recording-granted,
|
||||
description: GalleryStrings.texts[109]},
|
||||
]: WorkbenchCard {
|
||||
height: max(80px, permission-labels.preferred-height + 32px);
|
||||
permission-labels := VerticalLayout {
|
||||
x: 16px; y: 16px; width: parent.width - permission-action.width - 48px;
|
||||
height: parent.height - 32px; spacing: 6px; alignment: center;
|
||||
Text {
|
||||
text: permission.label; font-size: 13px; font-weight: 600;
|
||||
color: WorkbenchStyle.text; wrap: word-wrap;
|
||||
}
|
||||
Text {
|
||||
text: permission.description; font-size: 11px;
|
||||
color: WorkbenchStyle.muted; wrap: word-wrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
if permission.granted: HorizontalLayout {
|
||||
alignment: center; spacing: 6px;
|
||||
Text {
|
||||
text: FontAwesomeIcons.check; font-family: "Font Awesome 6 Free";
|
||||
font-weight: 900; font-size: 12px; color: WorkbenchStyle.success;
|
||||
vertical-alignment: center; accessible-role: none;
|
||||
}
|
||||
Text {
|
||||
text: GalleryStrings.texts[111];
|
||||
font-size: 12px; color: WorkbenchStyle.success; vertical-alignment: center;
|
||||
permission-action := Rectangle {
|
||||
x: parent.width - self.width - 16px; y: (parent.height - self.height) / 2;
|
||||
width: max(96px, permission-action-metrics.preferred-width + 24px);
|
||||
height: 36px;
|
||||
permission-action-metrics := Text {
|
||||
visible: false; font-size: 13px; font-weight: 600;
|
||||
text: GalleryStrings.texts[110];
|
||||
}
|
||||
if !permission.granted: WorkbenchButton {
|
||||
width: parent.width; height: parent.height; primary: true;
|
||||
enabled: !root.permission-migration-running;
|
||||
text: permission-action-metrics.text;
|
||||
accessible-label: permission.label + ": " + self.text;
|
||||
clicked => {
|
||||
if index == 0 { root.request-accessibility-permission(); }
|
||||
else { root.request-screen-recording-permission(); }
|
||||
}
|
||||
}
|
||||
if permission.granted: HorizontalLayout {
|
||||
alignment: center; spacing: 6px;
|
||||
Text {
|
||||
text: FontAwesomeIcons.check; font-family: "Font Awesome 6 Free";
|
||||
font-weight: 900; font-size: 12px; color: WorkbenchStyle.success;
|
||||
vertical-alignment: center; accessible-role: none;
|
||||
}
|
||||
Text {
|
||||
text: GalleryStrings.texts[111];
|
||||
font-size: 12px; color: WorkbenchStyle.success; vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
VerticalLayout {
|
||||
spacing: 12px;
|
||||
SurfaceSeparator { height: 1px; outline-color: UiSurface.separator; }
|
||||
if root.permission-migration-running || root.permission-migration-failed: Text {
|
||||
text: root.permission-migration-running
|
||||
? UiStrings.permission-migration-running : UiStrings.permission-migration-failed;
|
||||
font-size: 11px; color: WorkbenchStyle.muted; wrap: word-wrap;
|
||||
}
|
||||
Text {
|
||||
text: GalleryStrings.texts[112];
|
||||
font-size: 11px; color: WorkbenchStyle.muted; wrap: word-wrap;
|
||||
}
|
||||
HorizontalLayout {
|
||||
alignment: end; spacing: 8px;
|
||||
WorkbenchButton {
|
||||
text: root.accessibility-granted && root.screen-recording-granted
|
||||
&& !root.permission-migration-running && !root.permission-migration-failed
|
||||
? UiStrings.ok : UiStrings.permission-later;
|
||||
clicked => { root.dismiss-permissions(); }
|
||||
}
|
||||
if root.permission-migration-failed: retry-migration := WorkbenchButton {
|
||||
width: max(112px, retry-migration-label.preferred-width + 24px);
|
||||
primary: true; text: UiStrings.retry-permission-migration;
|
||||
clicked => { root.retry-permission-migration(); }
|
||||
retry-migration-label := Text {
|
||||
visible: false; accessible-role: none;
|
||||
text: retry-migration.text; font-size: 13px; font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
VerticalLayout {
|
||||
spacing: 12px;
|
||||
SurfaceSeparator { height: 1px; outline-color: UiSurface.separator; }
|
||||
Text {
|
||||
text: GalleryStrings.texts[112];
|
||||
font-size: 11px; color: WorkbenchStyle.muted; wrap: word-wrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2794,13 +2840,14 @@ export component MainWindow inherits Window {
|
||||
}
|
||||
}
|
||||
SettingsGroup {
|
||||
title: GalleryStrings.texts[120]; rows: 5;
|
||||
title: GalleryStrings.texts[120];
|
||||
rows: root.mac-permission-settings-visible ? 6 : 5;
|
||||
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.lock-on-disconnect; checked <=> root.lock-on-disconnect-enabled; enabled: root.lock-on-disconnect-available; }
|
||||
SettingsToggleRow { label: TrustStrings.only; checked <=> root.trusted-devices-only; }
|
||||
SettingsRow {
|
||||
label: TrustStrings.title; divider: false;
|
||||
label: TrustStrings.title; divider: root.mac-permission-settings-visible;
|
||||
SettingsButton {
|
||||
x: parent.width - self.width - 12px; y: 4px;
|
||||
width: max(104px, self.label-width); height: 28px;
|
||||
@@ -2808,6 +2855,15 @@ export component MainWindow inherits Window {
|
||||
clicked => { root.trusted-devices-open = true; root.trust-action("open", "", ""); }
|
||||
}
|
||||
}
|
||||
if root.mac-permission-settings-visible: SettingsRow {
|
||||
label: UiStrings.system-permissions; divider: false;
|
||||
SettingsButton {
|
||||
x: parent.width - self.width - 12px; y: 4px;
|
||||
width: max(104px, self.label-width); height: 28px;
|
||||
text: TrustStrings.manage;
|
||||
clicked => { root.open-permissions(); }
|
||||
}
|
||||
}
|
||||
}
|
||||
Text {
|
||||
text: GalleryStrings.texts[121];
|
||||
|
||||
@@ -74,6 +74,11 @@ inline int ApplyMainWindowStrings(
|
||||
trust.set_remove(Text(localization::Literal("移除信任", language)));
|
||||
trust.set_close(Text(localization::message_close[language]));
|
||||
auto& strings = window->global<ui::UiStrings>();
|
||||
strings.set_system_permissions(Text(localization::system_permissions[language]));
|
||||
strings.set_permission_migration_running(Text(localization::permission_migration_running[language]));
|
||||
strings.set_permission_migration_failed(Text(localization::permission_migration_failed[language]));
|
||||
strings.set_retry_permission_migration(Text(localization::retry_permission_migration[language]));
|
||||
strings.set_permission_later(Text(localization::permission_later[language]));
|
||||
strings.set_window_minimize(Text(localization::window_minimize[language]));
|
||||
strings.set_window_close(Text(localization::window_close[language]));
|
||||
strings.set_save(Text(localization::save[language]));
|
||||
|
||||
Reference in New Issue
Block a user