mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-21 03:40:40 +08:00
[fix] preserve DeskHub previews and control during fullscreen changes
This commit is contained in:
@@ -1413,15 +1413,16 @@ void GuiApplication::BindMainCallbacks(bool gallery_window) {
|
||||
main->on_gallery_pointer([this](slint::PointerEventButton button,
|
||||
slint::PointerEventKind kind, float x,
|
||||
float y) {
|
||||
if (!ui_->gallery_control_id.empty())
|
||||
if (!ui_->gallery_control_id.empty() && !ui_->gallery_transition.Active())
|
||||
SendPointerInput(static_cast<int>(button), static_cast<int>(kind), x, y);
|
||||
});
|
||||
main->on_gallery_scroll([this](float dx, float dy, float x, float y) {
|
||||
if (!ui_->gallery_control_id.empty()) SendScrollInput(dx, dy, x, y);
|
||||
if (!ui_->gallery_control_id.empty() && !ui_->gallery_transition.Active())
|
||||
SendScrollInput(dx, dy, x, y);
|
||||
});
|
||||
main->on_gallery_key([this](slint::SharedString text, bool pressed,
|
||||
bool ctrl, bool alt, bool shift, bool meta) {
|
||||
if (!ui_->gallery_control_id.empty())
|
||||
if (!ui_->gallery_control_id.empty() && !ui_->gallery_transition.Active())
|
||||
SendKeyInput(std::string(text), pressed, ctrl, alt, shift, meta);
|
||||
});
|
||||
main->on_open_gallery([this] { ShowGalleryWindow(); });
|
||||
@@ -4381,9 +4382,7 @@ void GuiApplication::SyncAudioPlayback() {
|
||||
}
|
||||
}
|
||||
|
||||
void GuiApplication::ReleaseControl() {
|
||||
const auto gallery_target = ui_->gallery_control_id;
|
||||
devices_.StopKeyboardCapturer();
|
||||
void GuiApplication::ReleasePressedInput() {
|
||||
keyboard_.ForceReleasePressedKeys();
|
||||
if (auto props = FindRemoteSession(controlled_remote_id_);
|
||||
props && props->peer_) {
|
||||
@@ -4400,11 +4399,17 @@ void GuiApplication::ReleaseControl() {
|
||||
props->mouse_label_.c_str());
|
||||
}
|
||||
}
|
||||
ui_->pressed_mouse_buttons.clear();
|
||||
}
|
||||
|
||||
void GuiApplication::ReleaseControl() {
|
||||
const auto gallery_target = ui_->gallery_control_id;
|
||||
devices_.StopKeyboardCapturer();
|
||||
ReleasePressedInput();
|
||||
if (!ui_->gallery_control_id.empty()) {
|
||||
ui_->gallery_control_id.clear();
|
||||
ui_->wall->set_gallery_controlling(false);
|
||||
}
|
||||
ui_->pressed_mouse_buttons.clear();
|
||||
SyncAudioPlayback();
|
||||
std::shared_lock lock(remote_sessions_mutex_);
|
||||
for (const auto& [id, props] : remote_sessions_) {
|
||||
@@ -4476,7 +4481,6 @@ void GuiApplication::HideGalleryWindow() {
|
||||
SyncWindowGeometry(true);
|
||||
ui_->gallery_transition.Cancel();
|
||||
ui_->wall->set_gallery_transitioning(false);
|
||||
ui_->wall->set_gallery_content_visible(true);
|
||||
ui_->window_saved = {};
|
||||
SaveGalleryWindowState();
|
||||
#if defined(__APPLE__)
|
||||
@@ -4503,9 +4507,10 @@ void GuiApplication::ToggleGalleryFullscreen() {
|
||||
const bool target = !(ui_->gallery_window.Restoring()
|
||||
? ui_->gallery_window.wall_fullscreen : fullscreen);
|
||||
ui_->gallery_transition.Begin(target, std::chrono::steady_clock::now());
|
||||
ReleaseControl();
|
||||
// Fullscreen only changes presentation. End held gestures before blocking
|
||||
// input, but retain the controlled session and its keyboard focus.
|
||||
if (!ui_->gallery_control_id.empty()) ReleasePressedInput();
|
||||
ui_->wall->set_gallery_transitioning(true);
|
||||
ui_->wall->set_gallery_content_visible(false);
|
||||
ui_->gallery_synced = {};
|
||||
}
|
||||
|
||||
@@ -5096,7 +5101,8 @@ void GuiApplication::SyncGallery() {
|
||||
if (ui_->observed_gallery_size &&
|
||||
(current_size.width != ui_->observed_gallery_size->width ||
|
||||
current_size.height != ui_->observed_gallery_size->height)) {
|
||||
if (!ui_->gallery_control_id.empty()) ReleaseControl();
|
||||
if (!ui_->gallery_control_id.empty() && !ui_->gallery_transition.Active())
|
||||
ReleaseControl();
|
||||
// Native resize can redraw before the usual 200ms preview refresh.
|
||||
ui_->gallery_synced = {};
|
||||
}
|
||||
@@ -5112,7 +5118,8 @@ void GuiApplication::SyncGallery() {
|
||||
const bool fullscreen = window.is_fullscreen();
|
||||
#endif
|
||||
if (wall_mode && !placement.Restoring()) {
|
||||
if (fullscreen != ui_->wall->get_gallery_fullscreen()) ReleaseControl();
|
||||
if (fullscreen != ui_->wall->get_gallery_fullscreen() && !transition.Active())
|
||||
ReleaseControl();
|
||||
ui_->wall->set_gallery_fullscreen(fullscreen);
|
||||
}
|
||||
if (!ui_->gallery_control_id.empty()) {
|
||||
@@ -5125,13 +5132,13 @@ void GuiApplication::SyncGallery() {
|
||||
const auto action = transition.Advance(now, current_size.width, current_size.height,
|
||||
window.scale_factor(), fullscreen, placement.Restoring());
|
||||
if (action == gallery::FullscreenTransition::Action::Apply) {
|
||||
// Update custom chrome before native resizing; keep content hidden until
|
||||
// both the compositor geometry and the gallery model have settled.
|
||||
// Keep previews visible while native resizing and the gallery model catch
|
||||
// up. Only input and geometry persistence wait for the window to settle.
|
||||
ui_->wall->set_gallery_fullscreen(transition.Target());
|
||||
#if defined(__APPLE__)
|
||||
if (!SetGalleryWindowFullscreen(transition.Target())) {
|
||||
ui_->wall->set_gallery_fullscreen(fullscreen);
|
||||
transition.Reveal(now);
|
||||
transition.Cancel();
|
||||
} else
|
||||
#else
|
||||
window.set_fullscreen(transition.Target());
|
||||
@@ -5142,7 +5149,6 @@ void GuiApplication::SyncGallery() {
|
||||
ui_->gallery_synced = {};
|
||||
}
|
||||
ui_->wall->set_gallery_transitioning(transition.Active());
|
||||
ui_->wall->set_gallery_content_visible(transition.ContentVisible());
|
||||
if (!transition.Active() && now - ui_->gallery_synced <
|
||||
(visible ? 16ms : 200ms))
|
||||
return;
|
||||
|
||||
@@ -75,6 +75,7 @@ private:
|
||||
void DeviceGroupAction(const std::string& action, const std::string& value,
|
||||
bool gallery_window);
|
||||
bool SaveDeviceGroups(const std::string& new_group, bool gallery_window);
|
||||
void ReleasePressedInput();
|
||||
void ReleaseControl();
|
||||
void SyncAudioPlayback();
|
||||
void SaveGalleryWindowState();
|
||||
|
||||
@@ -12,17 +12,18 @@
|
||||
|
||||
namespace crossdesk::gallery {
|
||||
|
||||
// Guard input while native geometry settles, without hiding the live content.
|
||||
// Driven by the existing UI tick; never pumps events or creates nested timers.
|
||||
class FullscreenTransition {
|
||||
public:
|
||||
using Clock = std::chrono::steady_clock;
|
||||
enum class Action { Idle, Apply, Reveal, Finished };
|
||||
enum class Action { Idle, Apply, Finished };
|
||||
|
||||
bool Begin(bool fullscreen, Clock::time_point now) {
|
||||
if (Active())
|
||||
return false;
|
||||
target_ = fullscreen;
|
||||
phase_ = Phase::FadingOut;
|
||||
phase_ = Phase::Pending;
|
||||
phase_at_ = now;
|
||||
width_ = height_ = 0;
|
||||
scale_ = 0;
|
||||
@@ -33,13 +34,13 @@ public:
|
||||
Action Advance(Clock::time_point now, uint32_t width, uint32_t height,
|
||||
float scale, bool fullscreen, bool restoring) {
|
||||
using namespace std::chrono_literals;
|
||||
if (phase_ == Phase::FadingOut) {
|
||||
if (phase_ == Phase::Pending) {
|
||||
if (restoring && now - phase_at_ >= 1500ms) {
|
||||
Reveal(now);
|
||||
return Action::Reveal;
|
||||
Cancel();
|
||||
return Action::Finished;
|
||||
}
|
||||
// Slint fades out for 60ms; allow another frame before native resizing.
|
||||
if (now - phase_at_ < 80ms || restoring)
|
||||
// Apply on the next UI tick as soon as window restoration is complete.
|
||||
if (restoring)
|
||||
return Action::Idle;
|
||||
phase_ = Phase::Settling;
|
||||
phase_at_ = stable_at_ = now;
|
||||
@@ -65,30 +66,19 @@ public:
|
||||
(geometry_changed_ || now - phase_at_ >= 300ms) &&
|
||||
now - stable_at_ >= 32ms) ||
|
||||
now - phase_at_ >= 1500ms) {
|
||||
Reveal(now);
|
||||
return Action::Reveal;
|
||||
Cancel();
|
||||
return Action::Finished;
|
||||
}
|
||||
}
|
||||
if (phase_ == Phase::FadingIn && now - phase_at_ >= 80ms) {
|
||||
Cancel();
|
||||
return Action::Finished;
|
||||
}
|
||||
return Action::Idle;
|
||||
}
|
||||
|
||||
void Reveal(Clock::time_point now) {
|
||||
phase_ = Phase::FadingIn;
|
||||
phase_at_ = now;
|
||||
}
|
||||
void Cancel() { phase_ = Phase::Idle; }
|
||||
bool Active() const { return phase_ != Phase::Idle; }
|
||||
bool Target() const { return target_; }
|
||||
bool ContentVisible() const {
|
||||
return phase_ == Phase::Idle || phase_ == Phase::FadingIn;
|
||||
}
|
||||
|
||||
private:
|
||||
enum class Phase { Idle, FadingOut, Settling, FadingIn };
|
||||
enum class Phase { Idle, Pending, Settling };
|
||||
Phase phase_ = Phase::Idle;
|
||||
bool target_ = false;
|
||||
bool geometry_changed_ = false;
|
||||
|
||||
@@ -43,6 +43,7 @@ export struct GalleryActionDevice {
|
||||
|
||||
export component GalleryView inherits Rectangle {
|
||||
in property <bool> controlling: false;
|
||||
in property <bool> input-suspended: false;
|
||||
in property <bool> simplified: false;
|
||||
in property <bool> fullscreen: false;
|
||||
callback toggle-simplified;
|
||||
@@ -578,7 +579,7 @@ export component GalleryView inherits Rectangle {
|
||||
focus-on-click: true;
|
||||
focus-lost => { if root.controlling { root.action("release", ""); } }
|
||||
key-pressed(event) => {
|
||||
if root.controlling {
|
||||
if root.controlling && !root.input-suspended {
|
||||
if event.text == Key.Escape && event.modifiers.shift && (event.modifiers.control || event.modifiers.meta) {
|
||||
root.action("release", "");
|
||||
} else { root.key-input(event.text, true, event.modifiers.control, event.modifiers.alt, event.modifiers.shift, event.modifiers.meta); }
|
||||
@@ -586,16 +587,18 @@ export component GalleryView inherits Rectangle {
|
||||
accept
|
||||
}
|
||||
key-released(event) => {
|
||||
if root.controlling { root.key-input(event.text, false, event.modifiers.control, event.modifiers.alt, event.modifiers.shift, event.modifiers.meta); }
|
||||
if root.controlling && !root.input-suspended { root.key-input(event.text, false, event.modifiers.control, event.modifiers.alt, event.modifiers.shift, event.modifiers.meta); }
|
||||
accept
|
||||
}
|
||||
TouchArea {
|
||||
pointer-event(event) => {
|
||||
if event.kind == PointerEventKind.down { video-focus.focus(); }
|
||||
if root.controlling { root.pointer-input(event.button, event.kind, self.mouse-x, self.mouse-y); }
|
||||
if !root.input-suspended {
|
||||
if event.kind == PointerEventKind.down { video-focus.focus(); }
|
||||
if root.controlling { root.pointer-input(event.button, event.kind, self.mouse-x, self.mouse-y); }
|
||||
}
|
||||
}
|
||||
scroll-event(event) => {
|
||||
if root.controlling { root.scroll-input(event.delta-x, event.delta-y, self.mouse-x, self.mouse-y); }
|
||||
if root.controlling && !root.input-suspended { root.scroll-input(event.delta-x, event.delta-y, self.mouse-x, self.mouse-y); }
|
||||
accept
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,10 +299,10 @@ export component MainWindow inherits Window {
|
||||
}
|
||||
in property <bool> gallery-fullscreen: false;
|
||||
in property <bool> gallery-transitioning: false;
|
||||
in property <bool> gallery-content-visible: true;
|
||||
changed gallery-transitioning => {
|
||||
if root.gallery-transitioning {
|
||||
focus-sink.focus();
|
||||
// Preserve the preview's keyboard focus when only changing size.
|
||||
if !root.gallery-controlling { focus-sink.focus(); }
|
||||
root.menu-open = false;
|
||||
root.recent-tooltip-visible = false;
|
||||
}
|
||||
@@ -754,12 +754,6 @@ export component MainWindow inherits Window {
|
||||
width: root.width;
|
||||
height: root.height - self.y;
|
||||
clip: true;
|
||||
opacity: root.gallery-content-visible || !root.gallery-mode ? 1 : 0;
|
||||
animate opacity {
|
||||
duration: 60ms;
|
||||
easing: ease-in-out;
|
||||
enabled: root.gallery-mode;
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width;
|
||||
@@ -1325,6 +1319,7 @@ export component MainWindow inherits Window {
|
||||
tile-width <=> root.gallery-tile-width;
|
||||
network-open => { root.gallery-network-open = true; }
|
||||
controlling: root.gallery-controlling;
|
||||
input-suspended: root.gallery-transitioning;
|
||||
pointer-input(button, kind, x, y) => { root.gallery-pointer(button, kind, x, y); }
|
||||
scroll-input(dx, dy, x, y) => { root.gallery-scroll(dx, dy, x, y); }
|
||||
key-input(text, pressed, ctrl, alt, shift, meta) => { root.gallery-key(text, pressed, ctrl, alt, shift, meta); }
|
||||
|
||||
Reference in New Issue
Block a user