[fix] keep stream menus open while suspending remote input

This commit is contained in:
dijunkun
2026-11-11 05:30:18 +08:00
parent 69d4394717
commit 14fa95beeb
4 changed files with 52 additions and 23 deletions
@@ -48,6 +48,7 @@
#include "features/gallery/gallery_workspace.h" #include "features/gallery/gallery_workspace.h"
#include "features/gallery/preview_bandwidth.h" #include "features/gallery/preview_bandwidth.h"
#include "features/gallery/preview_resolution.h" #include "features/gallery/preview_resolution.h"
#include "features/input/stream_keyboard_focus.h"
#include "filesystem_utf8.h" #include "filesystem_utf8.h"
#include "localization.h" #include "localization.h"
#include "platform.h" #include "platform.h"
@@ -3803,19 +3804,10 @@ void GuiApplication::SyncXWaylandWindowActivation() {
#endif #endif
void GuiApplication::SetStreamKeyboardFocus(bool focused) { void GuiApplication::SetStreamKeyboardFocus(bool focused) {
// Local dialogs own input even while the native window is active. const bool local_input_active = ui_ && ui_->stream &&
if (focused && ui_ && ui_->stream && (*ui_->stream)->get_local_input_active();
(*ui_->stream)->get_modal_visible()) { UpdateStreamKeyboardFocus(focused, local_input_active, focus_on_stream_window_,
focused = false; [this](bool preserve_local_ui) { ReleaseControl(preserve_local_ui); });
}
if (focus_on_stream_window_ == focused) {
return;
}
focus_on_stream_window_ = focused;
if (!focused) {
ReleaseControl();
}
} }
void GuiApplication::SyncStreamKeyboardFocus() { void GuiApplication::SyncStreamKeyboardFocus() {
@@ -5665,7 +5657,7 @@ void GuiApplication::SyncAudioPlayback() {
} }
} }
void GuiApplication::ReleasePressedInput() { void GuiApplication::ReleasePressedInput(bool preserve_local_ui) {
keyboard_.ForceReleasePressedKeys(); keyboard_.ForceReleasePressedKeys();
for (int button : ui_->pressed_mouse_buttons) { for (int button : ui_->pressed_mouse_buttons) {
RemoteAction action{}; RemoteAction action{};
@@ -5678,13 +5670,16 @@ void GuiApplication::ReleasePressedInput() {
SendRemoteInput(action); SendRemoteInput(action);
} }
ui_->pressed_mouse_buttons.clear(); ui_->pressed_mouse_buttons.clear();
if (ui_->stream) (*ui_->stream)->invoke_reset_input_state(); if (ui_->stream) {
if (preserve_local_ui) (*ui_->stream)->invoke_reset_pressed_input();
else (*ui_->stream)->invoke_reset_input_state();
}
} }
void GuiApplication::ReleaseControl() { void GuiApplication::ReleaseControl(bool preserve_local_ui) {
const auto gallery_target = ui_->gallery_control_id; const auto gallery_target = ui_->gallery_control_id;
devices_.StopKeyboardCapturer(); devices_.StopKeyboardCapturer();
ReleasePressedInput(); ReleasePressedInput(preserve_local_ui);
gallery_group_control_id_.clear(); gallery_group_control_id_.clear();
ui_->wall->set_gallery_group_controlling(false); ui_->wall->set_gallery_group_controlling(false);
ui_->wall->set_gallery_remote_cursor_shape( ui_->wall->set_gallery_remote_cursor_shape(
@@ -84,8 +84,8 @@ private:
void DeviceGroupAction(const std::string& action, const std::string& value, void DeviceGroupAction(const std::string& action, const std::string& value,
bool gallery_window); bool gallery_window);
bool SaveDeviceGroups(const std::string& new_group, bool gallery_window); bool SaveDeviceGroups(const std::string& new_group, bool gallery_window);
void ReleasePressedInput(); void ReleasePressedInput(bool preserve_local_ui = false);
void ReleaseControl(); void ReleaseControl(bool preserve_local_ui = false);
void SyncAudioPlayback(); void SyncAudioPlayback();
void SaveGalleryWindowState(); void SaveGalleryWindowState();
void SyncAnnouncements(); void SyncAnnouncements();
@@ -0,0 +1,26 @@
/*
* @Author: DI JUNKUN
* @Date: 2026-11-11
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _STREAM_KEYBOARD_FOCUS_H_
#define _STREAM_KEYBOARD_FOCUS_H_
namespace crossdesk {
// Native window focus and ownership of keyboard input are different: a local
// menu keeps the window active while temporarily suspending remote input.
// Publish the new state before releasing input, since UI callbacks can reenter.
template <typename Release>
void UpdateStreamKeyboardFocus(bool focused, bool local_input_active,
bool& remote_focused, Release release) {
focused = focused && !local_input_active;
if (remote_focused == focused) return;
remote_focused = focused;
if (!focused) release(local_input_active);
}
} // namespace crossdesk
#endif
+12 -4
View File
@@ -423,6 +423,10 @@ export component StreamWindow inherits Window {
private property <length> disconnect-menu-y: root.control-panel-y(root.disconnect-menu-height); private property <length> disconnect-menu-y: root.control-panel-y(root.disconnect-menu-height);
private property <bool> control-menu-open: private property <bool> control-menu-open:
root.display-menu-open || root.shortcut-menu-open || root.video-menu-open || root.disconnect-menu-open; root.display-menu-open || root.shortcut-menu-open || root.video-menu-open || root.disconnect-menu-open;
out property <bool> local-input-active: root.modal-visible || root.control-menu-open;
changed control-menu-open => {
root.keyboard-focus-changed(!root.control-menu-open && !root.modal-visible && input-focus.has-focus);
}
changed selected-tab => { changed selected-tab => {
root.close-control-menus(); root.close-control-menus();
root.cancel-remote-pointer(); root.cancel-remote-pointer();
@@ -589,10 +593,14 @@ export component StreamWindow inherits Window {
// Native focus/session changes release input in C++; clear the matching UI // Native focus/session changes release input in C++; clear the matching UI
// ownership too, including when replacing the only tab (same tab index). // ownership too, including when replacing the only tab (same tab index).
public function reset-input-state() { public function reset-pressed-input() {
root.remote-left-down = false; root.remote-left-down = false;
root.remote-right-down = false; root.remote-right-down = false;
root.remote-middle-down = false; root.remote-middle-down = false;
}
public function reset-input-state() {
root.reset-pressed-input();
root.close-control-menus(); root.close-control-menus();
root.control-drag-pending = false; root.control-drag-pending = false;
root.end-control-drag(); root.end-control-drag();
@@ -1039,10 +1047,10 @@ export component StreamWindow inherits Window {
input-focus := FocusScope { input-focus := FocusScope {
focus-on-click: true; focus-on-click: true;
focus-gained => { root.keyboard-focus-changed(true); } focus-gained => { root.keyboard-focus-changed(!root.control-menu-open && !root.modal-visible); }
focus-lost => { root.keyboard-focus-changed(false); } focus-lost => { root.keyboard-focus-changed(false); }
key-pressed(event) => { key-pressed(event) => {
if !root.modal-visible { if !root.modal-visible && !root.control-menu-open {
root.key-input(event.text, true, event.modifiers.control, event.modifiers.alt, event.modifiers.shift, event.modifiers.meta); root.key-input(event.text, true, event.modifiers.control, event.modifiers.alt, event.modifiers.shift, event.modifiers.meta);
} }
accept accept
@@ -1053,7 +1061,7 @@ export component StreamWindow inherits Window {
if event.text == Key.Escape || event.text == Key.Return { if event.text == Key.Escape || event.text == Key.Return {
if root.privacy-unlock-visible { root.dismiss-privacy-unlock(); } else { root.dismiss-remote-update(); } if root.privacy-unlock-visible { root.dismiss-privacy-unlock(); } else { root.dismiss-remote-update(); }
} }
} else { } else if !root.control-menu-open {
root.key-input(event.text, false, event.modifiers.control, event.modifiers.alt, event.modifiers.shift, event.modifiers.meta); root.key-input(event.text, false, event.modifiers.control, event.modifiers.alt, event.modifiers.shift, event.modifiers.meta);
} }
accept accept