diff --git a/apps/android/app/src/main/java/cn/crossdesk/mobile/MainActivity.java b/apps/android/app/src/main/java/cn/crossdesk/mobile/MainActivity.java index c7b2fc14..5f0640ed 100644 --- a/apps/android/app/src/main/java/cn/crossdesk/mobile/MainActivity.java +++ b/apps/android/app/src/main/java/cn/crossdesk/mobile/MainActivity.java @@ -338,7 +338,7 @@ public final class MainActivity extends Activity implements NativeSession.Listen public void mute(){muted=!muted;if(session!=null)session.control(2,muted?0:1);controls.setMuted(muted);} public void mouse(){boolean relative=!preferences.getBoolean("relativeMouse",true);preferences.edit().putBoolean("relativeMouse",relative).apply();video.setRelative(relative);controls.setRelative(relative);} public void videoSettings(int field,int value){RemoteVideoSettings.Values old=MainActivity.this.videoSettings.selection;updateVideoSettings(new RemoteVideoSettings.Values(field==0?value:old.quality,field==1?value:old.frameRate,field==2?value:old.preference));} - });controls.setRelative(preferences.getBoolean("relativeMouse",true));controls.setMuted(muted);controls.videoSettings(videoSettings);root.addView(controls,new LinearLayout.LayoutParams(-1,-1));getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + });controls.setHostPlatform(hostPlatform);controls.setRelative(preferences.getBoolean("relativeMouse",true));controls.setMuted(muted);controls.videoSettings(videoSettings);root.addView(controls,new LinearLayout.LayoutParams(-1,-1));getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); controls.attachMouseBar(canvas,new VirtualMouseBar(ui,video.mouseInput)); privacyChanged(); video.remoteFocusChanged=()->{if(controls!=null)controls.resetKeyboardContext();}; @@ -496,8 +496,8 @@ public final class MainActivity extends Activity implements NativeSession.Listen if(!ready)return;int count=Math.min(displays.length(),8);if(count==0){toast("等待远端屏幕信息");return;}String[] labels=new String[count];for(int i=0;i{if(session!=null){if(w!=selectedDisplay&&video!=null)video.resetViewport();selectedDisplay=w;session.control(4,w);if(controls!=null)controls.resetVideoStatistics();}}).show(); } private void secureAttention(){ - if(!ready||session==null)return; - if(!remoteServiceAvailable){controlFeedback(hostPlatform.equals("windows")?"请在远端启用 CrossDesk 系统服务后重试":"Ctrl+Alt+Del 需要远端 Windows 系统服务支持");return;} + if(!ready||session==null||!hostPlatform.equals("windows"))return; + if(!remoteServiceAvailable){controlFeedback("请在远端启用 CrossDesk 系统服务后重试");return;} session.secureAttention(); } private void chooseFile(){ @@ -650,6 +650,7 @@ public final class MainActivity extends Activity implements NativeSession.Listen JSONObject info=message.optJSONObject("host_info"); if(info!=null){ hostPlatform=info.optString("platform","");JSONArray list=info.optJSONArray("displays");if(list!=null)displays=list;history.metadata(serverKey(),connectedRemote,info.optString("host_name",connectedRemote),hostPlatform); + if(controls!=null)controls.setHostPlatform(hostPlatform); boolean wasSupported=videoSettings.supported;videoSettings.setSupported(info.optBoolean("supports_video_settings",false)); if(!videoSettings.supported)cancelVideoSettingsTimeout(); if(controls!=null)controls.videoSettings(videoSettings); @@ -739,7 +740,7 @@ public final class MainActivity extends Activity implements NativeSession.Listen videoSettings=retained.videoSettings;ready=true;retained.attach(this); if(rebuild)sessionScreen(retained.autoLandscape); if(retained.width>0&&retained.height>0)videoSize(retained.width,retained.height); - if(controls!=null){controls.setMuted(muted);controls.videoSettings(videoSettings);controls.fileStatus(retained.fileStatus,receivedFile!=null);} + if(controls!=null){controls.setHostPlatform(hostPlatform);controls.setMuted(muted);controls.videoSettings(videoSettings);controls.fileStatus(retained.fileStatus,receivedFile!=null);} retained.setBackground(false);signaling(signalState,localIdentity); return true; } diff --git a/apps/android/app/src/main/java/cn/crossdesk/mobile/SessionControls.java b/apps/android/app/src/main/java/cn/crossdesk/mobile/SessionControls.java index 7dde0c16..bf3ca2e3 100644 --- a/apps/android/app/src/main/java/cn/crossdesk/mobile/SessionControls.java +++ b/apps/android/app/src/main/java/cn/crossdesk/mobile/SessionControls.java @@ -51,7 +51,7 @@ final class SessionControls extends FrameLayout { private LinearLayout[] trafficRows; private TextView[] networkDetails; private View encryptionLock,encryptionOpen; - private boolean muted,relative=true,orbPositioned; + private boolean muted,relative=true,orbPositioned,secureAttentionVisible; SessionControls(Context context,MobileUi ui,Actions actions){ super(context);this.ui=ui;this.actions=actions; keyboard=new RemoteKeyboard(context,ui,actions,()->{requestLayout();positionMouse();}); @@ -66,6 +66,12 @@ final class SessionControls extends FrameLayout { drag(orb,orb,()->{if(menu==null)openMenu("controls");else closeMenu();}); } void setRelative(boolean value){relative=value;if(menu!=null&&menuPage.equals("controls"))openMenu("controls");} + void setHostPlatform(String platform){ + boolean visible="windows".equals(platform); + if(secureAttentionVisible==visible)return; + secureAttentionVisible=visible; + if(menu!=null&&menuPage.equals("controls"))openMenu("controls"); + } void attachMouseBar(FrameLayout host, VirtualMouseBar bar) { mouseHost=host;mouseBar=bar; // A direct, tightly bounded sibling of the video is essential: otherwise @@ -130,8 +136,13 @@ final class SessionControls extends FrameLayout { String[] icons={"keyboard","mouse","mouse","display","sliders","audio","folder","chart","lock","clipboard","lock"}; Runnable[] clicks={()->{closeMenu();toggleKeyboard();},this::toggleMouse,actions::mouse,actions::display,()->openMenu("video"),actions::mute,actions::sendFile,()->openMenu("network"),actions::secureAttention,actions::clipboard,actions::togglePrivacy}; menuColumns=controlColumns(); - int controlCount=privacy.desktopAvailable?labels.length:labels.length-1; - for(int row=0;row<(controlCount+menuColumns-1)/menuColumns;row++){LinearLayout line=ui.row();for(int col=0;col=controlCount){View spacer=new View(getContext());LinearLayout.LayoutParams p=new LinearLayout.LayoutParams(0,ui.dp(52),1);if(col>0)p.leftMargin=ui.dp(7);line.addView(spacer,p);continue;}LinearLayout control=ui.column();control.setGravity(Gravity.CENTER);control.setBackground(ui.background(0xFFF0F0F2,10));control.addView(ui.icon(icons[index],INK),ui.size(19,19));ui.gap(control,4);TextView caption=ui.text(labels[index],10,false);caption.setSingleLine();caption.setGravity(Gravity.CENTER);control.addView(caption);control.setContentDescription(labels[index]);control.setClickable(true);control.setFocusable(true);control.setOnClickListener(v->clicks[index].run()); + java.util.List visibleControls=new java.util.ArrayList<>(); + for(int index=0;index=controlCount){View spacer=new View(getContext());LinearLayout.LayoutParams p=new LinearLayout.LayoutParams(0,ui.dp(52),1);if(col>0)p.leftMargin=ui.dp(7);line.addView(spacer,p);continue;}int index=visibleControls.get(position);LinearLayout control=ui.column();control.setGravity(Gravity.CENTER);control.setBackground(ui.background(0xFFF0F0F2,10));control.addView(ui.icon(icons[index],INK),ui.size(19,19));ui.gap(control,4);TextView caption=ui.text(labels[index],10,false);caption.setSingleLine();caption.setGravity(Gravity.CENTER);control.addView(caption);control.setContentDescription(labels[index]);control.setClickable(true);control.setFocusable(true);control.setOnClickListener(v->clicks[index].run()); if(index==10){privacyButton=control;privacyCaption=caption;} LinearLayout.LayoutParams p=new LinearLayout.LayoutParams(0,ui.dp(52),1);if(col>0)p.leftMargin=ui.dp(7);line.addView(control,p); }body.addView(line);if((row+1)*menuColumnsremote_version_mutex_); - if (name == "Ctrl+Alt+Del" && props->remote_platform_ == HostPlatform::HarmonyOS) return; + if (props->remote_platform_ != HostPlatform::Windows) return; } RemoteAction action{}; action.type = ControlType::service_command; @@ -3796,9 +3796,7 @@ void GuiApplication::SyncStreamWindow() { } } (*ui_->stream)->set_remote_update_visible(remote_update_available); - // All desktop clients use the same menu for a Harmony host. Windows SAS is - // not an ordinary key chord and has no public Harmony implementation. - (*ui_->stream)->set_secure_attention_supported(remote_platform != HostPlatform::HarmonyOS); + (*ui_->stream)->set_secure_attention_supported(remote_platform == HostPlatform::Windows); if (remote_update_available) { SetStreamKeyboardFocus(false); } diff --git a/apps/desktop/src/gui/ui/stream_window.slint b/apps/desktop/src/gui/ui/stream_window.slint index 66504cba..0987c500 100644 --- a/apps/desktop/src/gui/ui/stream_window.slint +++ b/apps/desktop/src/gui/ui/stream_window.slint @@ -335,10 +335,8 @@ export component StreamWindow inherits Window { callback reorder-tab(int, length, length); callback close-tab(string); callback switch-display(int); - in property secure-attention-supported: true; - private property <[string]> secure-shortcut-items: ["Ctrl+Alt+Del", "Win+L"]; - private property <[string]> ordinary-shortcut-items: ["Win+L"]; - private property <[string]> shortcut-items: root.secure-attention-supported ? root.secure-shortcut-items : root.ordinary-shortcut-items; + in property secure-attention-supported: false; + private property <[string]> shortcut-items: ["Ctrl+Alt+Del", "Win+L"]; private property shortcut-menu-height: root.shortcut-items.length * 30px + 16px; callback send-shortcut(string); callback toggle-mouse-control; @@ -364,6 +362,7 @@ export component StreamWindow inherits Window { private property control-visual-expanded: false; private property control-toolbar-height: 38px; private property control-button-step: 32px; + private property control-shortcut-width: root.secure-attention-supported ? root.control-button-step : 0px; private property control-privacy-width: root.privacy-visible ? root.control-button-step : 0px; private property control-disconnect-gap: 8px; private property control-buttons-x: root.control-docked-left ? 43px : 7px; @@ -416,6 +415,9 @@ export component StreamWindow inherits Window { root.close-control-menus(); root.cancel-remote-pointer(); } + changed secure-attention-supported => { + if !root.secure-attention-supported { root.shortcut-menu-open = false; } + } changed control-expanded => { control-reveal-timer.running = false; control-collapse-timer.running = false; @@ -517,7 +519,7 @@ export component StreamWindow inherits Window { if logo-button.has-hover { return logo-button.tooltip-info; } if root.control-expanded && root.control-content-visible { if display-button.has-hover { return display-button.tooltip-info; } - if shortcut-button.has-hover { return shortcut-button.tooltip-info; } + if root.secure-attention-supported && shortcut-button.has-hover { return shortcut-button.tooltip-info; } if mouse-button.has-hover { return mouse-button.tooltip-info; } if root.privacy-visible && privacy-button.has-hover { return privacy-button.tooltip-info; } if video-button.has-hover { return video-button.tooltip-info; } @@ -715,7 +717,7 @@ export component StreamWindow inherits Window { && pointer-y >= root.display-menu-y && pointer-y <= root.display-menu-y + root.display-menu-height; let shortcut-menu-x = control.x + shortcut-button.x; - let over-shortcut-menu = root.shortcut-menu-open + let over-shortcut-menu = root.secure-attention-supported && root.shortcut-menu-open && pointer-x >= shortcut-menu-x && pointer-x <= shortcut-menu-x + 180px && pointer-y >= root.shortcut-menu-y @@ -1085,7 +1087,7 @@ export component StreamWindow inherits Window { control := Rectangle { x: root.control-dragging ? root.control-drag-x : root.control-docked-left ? 0px : parent.width - self.width; y: min(max(0px, root.control-pos-y), parent.height - self.height); - width: root.control-visual-expanded ? min(80px + 8 * root.control-button-step + root.control-privacy-width + root.control-disconnect-gap, parent.width) : root.control-toolbar-height; + width: root.control-visual-expanded ? min(80px + 7 * root.control-button-step + root.control-shortcut-width + root.control-privacy-width + root.control-disconnect-gap, parent.width) : root.control-toolbar-height; height: root.control-toolbar-height; z: 10; @@ -1130,8 +1132,8 @@ export component StreamWindow inherits Window { } shortcut-button := ControlBarButton { reveal-progress: root.control-content-progress; - visible: root.control-content-visible; - action-enabled: root.control-expanded; + visible: root.control-content-visible && root.secure-attention-supported; + action-enabled: root.control-expanded && root.secure-attention-supported; x: root.control-buttons-x + root.control-button-step; y: (parent.height - self.height) / 2; icon: FontAwesomeIcons.keyboard; tooltip: StreamStrings.send-shortcut; @@ -1149,7 +1151,7 @@ export component StreamWindow inherits Window { reveal-progress: root.control-content-progress; visible: root.control-content-visible; action-enabled: root.control-expanded; - x: root.control-buttons-x + 2 * root.control-button-step; + x: root.control-buttons-x + root.control-button-step + root.control-shortcut-width; y: (parent.height - self.height) / 2; icon: FontAwesomeIcons.computer-mouse; slashed: !root.mouse-control-enabled; tooltip: root.mouse-control-enabled ? StreamStrings.release-mouse : StreamStrings.control-mouse; @@ -1159,7 +1161,7 @@ export component StreamWindow inherits Window { reveal-progress: root.control-content-progress; visible: root.control-content-visible; action-enabled: root.control-expanded; - x: root.control-buttons-x + 3 * root.control-button-step + root.control-privacy-width; + x: root.control-buttons-x + 2 * root.control-button-step + root.control-shortcut-width + root.control-privacy-width; y: (parent.height - self.height) / 2; icon: FontAwesomeIcons.volume-high; slashed: !root.audio-enabled; tooltip: root.audio-enabled ? StreamStrings.mute : StreamStrings.audio; @@ -1169,7 +1171,7 @@ export component StreamWindow inherits Window { reveal-progress: root.control-content-progress; visible: root.control-content-visible; action-enabled: root.control-expanded; - x: root.control-buttons-x + 4 * root.control-button-step + root.control-privacy-width; + x: root.control-buttons-x + 3 * root.control-button-step + root.control-shortcut-width + root.control-privacy-width; y: (parent.height - self.height) / 2; icon: FontAwesomeIcons.folder-open; tooltip: StreamStrings.select-file; clicked => { root.close-control-menus(); root.select-file(); } @@ -1178,7 +1180,7 @@ export component StreamWindow inherits Window { reveal-progress: root.control-content-progress; visible: root.control-content-visible; action-enabled: root.control-expanded; - x: root.control-buttons-x + 5 * root.control-button-step + root.control-privacy-width; + x: root.control-buttons-x + 4 * root.control-button-step + root.control-shortcut-width + root.control-privacy-width; y: (parent.height - self.height) / 2; icon: "\u{f1de}"; selected: root.video-menu-open; tooltip: StreamStrings.video-settings; @@ -1193,7 +1195,7 @@ export component StreamWindow inherits Window { reveal-progress: root.control-content-progress; visible: root.control-content-visible; action-enabled: root.control-expanded; - x: root.control-buttons-x + 6 * root.control-button-step + root.control-privacy-width; + x: root.control-buttons-x + 5 * root.control-button-step + root.control-shortcut-width + root.control-privacy-width; y: (parent.height - self.height) / 2; icon: FontAwesomeIcons.signal; selected: root.stats-visible; tooltip: root.stats-visible ? StreamStrings.hide-stats : StreamStrings.show-stats; @@ -1203,7 +1205,7 @@ export component StreamWindow inherits Window { reveal-progress: root.control-content-progress; visible: root.control-content-visible; action-enabled: root.control-expanded; - x: root.control-buttons-x + 7 * root.control-button-step + root.control-privacy-width; + x: root.control-buttons-x + 6 * root.control-button-step + root.control-shortcut-width + root.control-privacy-width; y: (parent.height - self.height) / 2; icon: root.fullscreen-enabled ? FontAwesomeIcons.compress : FontAwesomeIcons.expand; tooltip: root.fullscreen-enabled ? StreamStrings.exit-fullscreen : StreamStrings.fullscreen; @@ -1219,7 +1221,7 @@ export component StreamWindow inherits Window { reveal-progress: root.control-content-progress; visible: root.control-content-visible; action-enabled: root.control-expanded; - x: root.control-buttons-x + 8 * root.control-button-step + root.control-privacy-width + root.control-disconnect-gap; + x: root.control-buttons-x + 7 * root.control-button-step + root.control-shortcut-width + root.control-privacy-width + root.control-disconnect-gap; y: (parent.height - self.height) / 2; icon: FontAwesomeIcons.xmark; tooltip: StreamStrings.disconnect; danger: true; @@ -1255,7 +1257,7 @@ export component StreamWindow inherits Window { privacy-button := ControlBarButton { reveal-progress: root.control-content-progress; visible: root.control-content-visible && root.privacy-visible; - x: root.control-buttons-x + 3 * root.control-button-step; + x: root.control-buttons-x + 2 * root.control-button-step + root.control-shortcut-width; y: (parent.height - self.height) / 2; icon: FontAwesomeIcons.user-secret; slashed: root.privacy-off && !root.privacy-unsupported; @@ -1698,7 +1700,7 @@ export component StreamWindow inherits Window { } } - if root.shortcut-menu-open: StreamControlSurface { + if root.secure-attention-supported && root.shortcut-menu-open: StreamControlSurface { x: control.x + shortcut-button.x; y: root.shortcut-menu-y; width: 180px; height: root.shortcut-menu-height; z: 11; TouchArea { mouse-cursor: default; scroll-event(event) => { accept } } diff --git a/apps/harmony/desktop-port/README.md b/apps/harmony/desktop-port/README.md index 8ac7bffb..82e1f8a2 100644 --- a/apps/harmony/desktop-port/README.md +++ b/apps/harmony/desktop-port/README.md @@ -86,7 +86,7 @@ python3 apps/harmony/scripts/build_slint_desktop.py --desktop --reuse-runtime \ `--desktop-permissions` 生成权限声明、用途说明和授权流程开关;`--signing-config` 生成签名配置并选择 `entry-default-signed.hap` 安装。生成的配置仅位于忽略的 `.native` 缓存,文件权限为 `0600`。无签名参数时仍输出普通 `entry-default-unsigned.hap`。可单独用 `--desktop-permissions` 做带权限声明的编译检查,不能据此认为已获得权限或可安装到真机。 -远程 `lock_workstation` 命令已接到经过采集/输入授权检查的 Meta+L 路径,先释放残留键鼠,发送后配对释放,失败保留待清理状态。Windows `send_sas` 没有等价的鸿蒙公开接口,明确返回不支持;所有 PC 客户端共用的 Slint 快捷键菜单在目标为鸿蒙时不显示 Ctrl+Alt+Del,仍保留 Win+L。普通 Ctrl/Alt/Meta 组合键由原生钩子按原顺序转发,系统最终允许拦截哪些保留键仍需授权后的真机验证。 +远程 `lock_workstation` 命令已接到经过采集/输入授权检查的 Meta+L 路径,先释放残留键鼠,发送后配对释放,失败保留待清理状态。Windows `send_sas` 没有等价的鸿蒙公开接口,明确返回不支持;PC 和移动端仅在被控端为 Windows 时显示系统组合键按钮,目标为鸿蒙或其他系统时隐藏。普通 Ctrl/Alt/Meta 组合键由原生钩子按原顺序转发,系统最终允许拦截哪些保留键仍需授权后的真机验证。 本轮验证:普通 HAP 与带权限声明的 HAP 构建通过;普通 HAP 安装本机 2in1 模拟器后,全局钩子实际返回 `201`,无授权弹窗循环,仍收到共享窗口回退发送的 `keyboard` 数据流。真实 Ctrl+A/C、End、Ctrl+V 把 `246` 复制为 `246 246`。生产 C++/ArkTS 的模拟 SDK 回归覆盖 Alt+Tab、Meta+L、重复键、小键盘 Enter、取消、失焦、撤权、重复注册、卸载失败、后台通知、缓存失效、读取中变更/撤权,以及权限拒绝后不重复引导。macOS 公共 Slint 冒烟测试新增快捷键菜单的真实点击回归,验证移除 SAS 后首项为锁屏、弹层命中范围随菜单缩小、切回 Windows 菜单仍正常。成功授权的钩子和后台跨应用读取没有在无 ACL 包中冒充验证通过。 diff --git a/apps/harmony/entry/src/main/ets/mobile/MobileController.ets b/apps/harmony/entry/src/main/ets/mobile/MobileController.ets index 1a8c98e3..a01197c5 100644 --- a/apps/harmony/entry/src/main/ets/mobile/MobileController.ets +++ b/apps/harmony/entry/src/main/ets/mobile/MobileController.ets @@ -311,7 +311,8 @@ export class MobileController { catch { this.toast('复制失败,请重试'); } } sas(): void { - if (!this.serviceAvailable) { this.toast(this.state.platform === 'windows' ? '请在远端启用 CrossDesk 系统服务后重试' : 'Ctrl+Alt+Del 需要远端 Windows 系统服务支持'); return; } + if (this.state.platform !== 'windows') return; + if (!this.serviceAvailable) { this.toast('请在远端启用 CrossDesk 系统服务后重试'); return; } this.send({ op: 'sas' }); } async chooseFile(): Promise { diff --git a/apps/harmony/entry/src/main/ets/mobile/MobileRemoteView.ets b/apps/harmony/entry/src/main/ets/mobile/MobileRemoteView.ets index f007c6df..6a1140f7 100644 --- a/apps/harmony/entry/src/main/ets/mobile/MobileRemoteView.ets +++ b/apps/harmony/entry/src/main/ets/mobile/MobileRemoteView.ets @@ -136,9 +136,11 @@ export struct MobileRemoteView { if (index === 10) this.controller?.togglePrivacy(); } private labels(): string[] { - const labels: string[] = ['键盘', this.state.relative ? '相对鼠标' : '绝对鼠标', this.mouseVisible ? '收起鼠标' : '虚拟鼠标', '显示器', '画面设置', this.state.muted ? '静音' : '声音', '发送文件', '网络状态', 'Ctrl+Alt+Del', '剪贴板']; - if (this.state.privacyVisible) labels.push(this.state.privacyButtonTitle); - return labels; + return ['键盘', this.state.relative ? '相对鼠标' : '绝对鼠标', this.mouseVisible ? '收起鼠标' : '虚拟鼠标', '显示器', '画面设置', this.state.muted ? '静音' : '声音', '发送文件', '网络状态', 'Ctrl+Alt+Del', '剪贴板', this.state.privacyButtonTitle]; + } + private controlIds(): number[] { + return [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10].filter((index: number) => + (index !== 8 || this.state.platform === 'windows') && (index !== 10 || this.state.privacyVisible)); } private icons(): string[] { return ['keyboard', 'mouse', 'mouse', 'display', 'sliders', this.state.muted ? 'muted' : 'audio', 'folder', 'chart', 'lock', 'clipboard', 'lock']; } private columns(): number { return this.viewHeight < 400 && this.viewWidth >= 400 ? 4 : 3; } @@ -215,20 +217,20 @@ export struct MobileRemoteView { Column({ space: 8 }) { if (this.menu === 'controls') { GridRow({ columns: this.columns(), gutter: 7 }) { - ForEach(this.labels(), (label: string, index: number) => { + ForEach(this.controlIds(), (index: number) => { GridCol() { Column({ space: 4 }) { MobileIcon({ name: this.icons()[index], iconExtent: 19 }) - Text(label).fontSize(10).fontColor(MobileTheme.ink).maxLines(1) + Text(this.labels()[index]).fontSize(10).fontColor(MobileTheme.ink).maxLines(1) } .height(52).width('100%').justifyContent(FlexAlign.Center) .backgroundColor('#F0F0F2').borderRadius(10) .enabled(index !== 10 || this.state.ready && this.state.privacyCanToggle) .opacity(index === 10 && (!this.state.ready || !this.state.privacyCanToggle) ? 0.45 : 1) .accessibilitySelected(index === 10 && this.state.privacyEnabled) - .accessibilityText(index === 10 ? `${label},${this.state.privacyStatus}` : label).accessibilityLevel('yes').onClick(() => this.action(index)) + .accessibilityText(index === 10 ? `${this.labels()[index]},${this.state.privacyStatus}` : this.labels()[index]).accessibilityLevel('yes').onClick(() => this.action(index)) } - }, (label: string) => label) + }, (index: number) => index.toString()) } if (this.state.privacyFeedback) { Text(this.state.privacyFeedback).fontSize(11).fontColor(MobileTheme.secondary).width('100%') } if (this.state.transfer) { Text(this.state.transfer).fontSize(11).fontColor(MobileTheme.secondary).width('100%') } diff --git a/apps/ios/CrossDeskMobile/RemoteSessionModel.swift b/apps/ios/CrossDeskMobile/RemoteSessionModel.swift index ea3651e9..aed2dc7d 100644 --- a/apps/ios/CrossDeskMobile/RemoteSessionModel.swift +++ b/apps/ios/CrossDeskMobile/RemoteSessionModel.swift @@ -1125,7 +1125,7 @@ final class RemoteSessionModel: NSObject, ObservableObject, CrossDeskRTCBridgeDe } func sendSecureAttention() { - guard hasNetworkConsent, isConnected else { return } + guard hasNetworkConsent, isConnected, remotePlatform == .windows else { return } guard remoteServiceAvailable else { controlStatus = "Ctrl+Alt+Del 需要远端启用 Windows 系统服务" return diff --git a/apps/ios/CrossDeskMobile/RemoteSessionView.swift b/apps/ios/CrossDeskMobile/RemoteSessionView.swift index 063b6f18..47b1c2f3 100644 --- a/apps/ios/CrossDeskMobile/RemoteSessionView.swift +++ b/apps/ios/CrossDeskMobile/RemoteSessionView.swift @@ -1245,9 +1245,11 @@ private struct FloatingSessionMenu: View { FloatingControlButton(title: "网络状态", symbol: "chart.bar.xaxis") { page = .networkStats } - FloatingControlButton(title: "Ctrl+Alt+Del", - symbol: "lock.trianglebadge.exclamationmark", - action: session.sendSecureAttention) + if session.remotePlatform == .windows { + FloatingControlButton(title: "Ctrl+Alt+Del", + symbol: "lock.trianglebadge.exclamationmark", + action: session.sendSecureAttention) + } FloatingControlButton(title: "剪贴板", symbol: "doc.on.clipboard", action: session.sendClipboard) if session.privacyScreen.desktopAvailable {