mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-30 01:42:49 +08:00
[fix] prevent remote mouse input from popup menus
This commit is contained in:
@@ -172,6 +172,10 @@ export component StreamWindow inherits Window {
|
|||||||
private property <bool> display-menu-animation-enabled: false;
|
private property <bool> display-menu-animation-enabled: false;
|
||||||
private property <float> display-menu-progress: 0.0;
|
private property <float> display-menu-progress: 0.0;
|
||||||
private property <bool> shortcut-menu-open: false;
|
private property <bool> shortcut-menu-open: false;
|
||||||
|
// A popup can remove itself from the scene in its clicked callback before
|
||||||
|
// the video input layer observes the same pointer release. Remember that
|
||||||
|
// the press belonged to local UI so that release cannot reach the peer.
|
||||||
|
private property <bool> suppress-next-remote-pointer-release: false;
|
||||||
private property <bool> control-docked-left: true;
|
private property <bool> control-docked-left: true;
|
||||||
private property <bool> control-dragging: false;
|
private property <bool> control-dragging: false;
|
||||||
private property <length> control-drag-x: 0px;
|
private property <length> control-drag-x: 0px;
|
||||||
@@ -554,7 +558,14 @@ export component StreamWindow inherits Window {
|
|||||||
&& self.mouse-y <= control.y + control.height;
|
&& self.mouse-y <= control.y + control.height;
|
||||||
let over-local-ui = root.is-local-control-area(
|
let over-local-ui = root.is-local-control-area(
|
||||||
self.mouse-x, self.mouse-y);
|
self.mouse-x, self.mouse-y);
|
||||||
|
let suppress-remote-release =
|
||||||
|
root.suppress-next-remote-pointer-release
|
||||||
|
&& (event.kind == PointerEventKind.up
|
||||||
|
|| event.kind == PointerEventKind.cancel);
|
||||||
if event.kind == PointerEventKind.down && !over-local-ui {
|
if event.kind == PointerEventKind.down && !over-local-ui {
|
||||||
|
// Discard a stale marker if a popup consumed the
|
||||||
|
// whole previous gesture, including its release.
|
||||||
|
root.suppress-next-remote-pointer-release = false;
|
||||||
input-focus.focus();
|
input-focus.focus();
|
||||||
}
|
}
|
||||||
if event.kind == PointerEventKind.down
|
if event.kind == PointerEventKind.down
|
||||||
@@ -568,9 +579,13 @@ export component StreamWindow inherits Window {
|
|||||||
// The control bar overlays the remote video. Events in
|
// The control bar overlays the remote video. Events in
|
||||||
// it and its attached menus (including padding and gaps)
|
// it and its attached menus (including padding and gaps)
|
||||||
// are local UI input and must never reach the peer.
|
// are local UI input and must never reach the peer.
|
||||||
} else if !root.control-dragging && !over-local-ui {
|
} else if !root.control-dragging && !over-local-ui
|
||||||
|
&& !suppress-remote-release {
|
||||||
root.pointer-input(event.button, event.kind, self.mouse-x, self.mouse-y);
|
root.pointer-input(event.button, event.kind, self.mouse-x, self.mouse-y);
|
||||||
}
|
}
|
||||||
|
if suppress-remote-release {
|
||||||
|
root.suppress-next-remote-pointer-release = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
moved => {
|
moved => {
|
||||||
if root.control-dragging {
|
if root.control-dragging {
|
||||||
@@ -871,6 +886,11 @@ export component StreamWindow inherits Window {
|
|||||||
// hover stable while still consuming padding, gaps and scroll
|
// hover stable while still consuming padding, gaps and scroll
|
||||||
// events locally instead of forwarding them to the peer.
|
// events locally instead of forwarding them to the peer.
|
||||||
display-touch := TouchArea {
|
display-touch := TouchArea {
|
||||||
|
pointer-event(event) => {
|
||||||
|
if event.kind == PointerEventKind.down {
|
||||||
|
root.suppress-next-remote-pointer-release = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
clicked => {
|
clicked => {
|
||||||
let index = floor((self.mouse-y - 4px) / 30px);
|
let index = floor((self.mouse-y - 4px) / 30px);
|
||||||
let row-y = self.mouse-y - 4px - index * 30px;
|
let row-y = self.mouse-y - 4px - index * 30px;
|
||||||
@@ -896,7 +916,14 @@ export component StreamWindow inherits Window {
|
|||||||
for shortcut in ["Ctrl+Alt+Del", "Win+L"]: Rectangle {
|
for shortcut in ["Ctrl+Alt+Del", "Win+L"]: Rectangle {
|
||||||
height: 29px; background: shortcut-touch.has-hover ? #e8eef9 : transparent;
|
height: 29px; background: shortcut-touch.has-hover ? #e8eef9 : transparent;
|
||||||
Text { x: 8px; text: shortcut; color: #30343b; font-size: ImGuiFontStyle.body; vertical-alignment: center; }
|
Text { x: 8px; text: shortcut; color: #30343b; font-size: ImGuiFontStyle.body; vertical-alignment: center; }
|
||||||
shortcut-touch := TouchArea { clicked => { root.send-shortcut(shortcut); root.shortcut-menu-open = false; } }
|
shortcut-touch := TouchArea {
|
||||||
|
pointer-event(event) => {
|
||||||
|
if event.kind == PointerEventKind.down {
|
||||||
|
root.suppress-next-remote-pointer-release = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
clicked => { root.send-shortcut(shortcut); root.shortcut-menu-open = false; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,139 +0,0 @@
|
|||||||
#include <filesystem>
|
|
||||||
#include <fstream>
|
|
||||||
#include <iostream>
|
|
||||||
#include <sstream>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
std::filesystem::path FindRepoRoot() {
|
|
||||||
std::filesystem::path current = std::filesystem::current_path();
|
|
||||||
while (!current.empty()) {
|
|
||||||
if (std::filesystem::exists(current / "xmake.lua") &&
|
|
||||||
std::filesystem::exists(current / "src/gui/ui/stream_window.slint")) {
|
|
||||||
return current;
|
|
||||||
}
|
|
||||||
current = current.parent_path();
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string ReadFile(const std::filesystem::path& path) {
|
|
||||||
std::ifstream file(path, std::ios::binary);
|
|
||||||
if (!file) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
std::ostringstream stream;
|
|
||||||
stream << file.rdbuf();
|
|
||||||
return stream.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ExpectContains(const char* name, const std::string& value,
|
|
||||||
const std::string& expected) {
|
|
||||||
if (value.find(expected) != std::string::npos) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
std::cerr << name << " missing expected text: " << expected << "\n";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ExpectNotContains(const char* name, const std::string& value,
|
|
||||||
const std::string& unexpected) {
|
|
||||||
if (value.find(unexpected) == std::string::npos) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
std::cerr << name << " contains unexpected text: " << unexpected << "\n";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ExpectContainsAtLeast(const char* name, const std::string& value,
|
|
||||||
const std::string& expected, size_t min_count) {
|
|
||||||
size_t count = 0;
|
|
||||||
size_t pos = 0;
|
|
||||||
while ((pos = value.find(expected, pos)) != std::string::npos) {
|
|
||||||
++count;
|
|
||||||
pos += expected.size();
|
|
||||||
}
|
|
||||||
if (count >= min_count) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
std::cerr << name << " expected at least " << min_count
|
|
||||||
<< " occurrences of: " << expected << ", found " << count << "\n";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
const std::filesystem::path repo_root = FindRepoRoot();
|
|
||||||
if (repo_root.empty()) {
|
|
||||||
std::cerr << "failed to locate repository root\n";
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string stream =
|
|
||||||
ReadFile(repo_root / "src/gui/ui/stream_window.slint");
|
|
||||||
const std::string common = ReadFile(repo_root / "src/gui/ui/common.slint");
|
|
||||||
const std::string application =
|
|
||||||
ReadFile(repo_root / "src/gui/application/gui_application.cpp");
|
|
||||||
|
|
||||||
bool ok = true;
|
|
||||||
ok &= ExpectContains("stream_window.slint", stream,
|
|
||||||
"private property <bool> display-menu-open: false;");
|
|
||||||
ok &= ExpectContains("stream_window.slint", stream,
|
|
||||||
"root.shortcut-menu-open = false;");
|
|
||||||
ok &= ExpectContains("stream_window.slint", stream,
|
|
||||||
"if root.display-menu-open: Rectangle");
|
|
||||||
ok &=
|
|
||||||
ExpectContains("stream_window.slint", stream, "display-touch.has-hover");
|
|
||||||
ok &= ExpectContains("stream_window.slint", stream,
|
|
||||||
"root.switch-display(index)");
|
|
||||||
ok &= ExpectContains("stream_window.slint", stream,
|
|
||||||
"if root.shortcut-menu-open: Rectangle");
|
|
||||||
ok &=
|
|
||||||
ExpectContains("stream_window.slint", stream, "shortcut-touch.has-hover");
|
|
||||||
ok &= ExpectContains("stream_window.slint", stream,
|
|
||||||
"} else if !root.control-dragging && !over-control {");
|
|
||||||
|
|
||||||
ok &= ExpectContains("common.slint", common,
|
|
||||||
"if touch.has-hover && root.tooltip != \"\": Rectangle");
|
|
||||||
ok &= ExpectContainsAtLeast("stream_window.slint", stream, "tooltip:", 9);
|
|
||||||
ok &= ExpectContains("stream_window.slint", stream,
|
|
||||||
"tooltip: StreamStrings.select-display");
|
|
||||||
ok &= ExpectContains("stream_window.slint", stream,
|
|
||||||
"tooltip: StreamStrings.send-shortcut");
|
|
||||||
ok &=
|
|
||||||
ExpectContains("stream_window.slint", stream,
|
|
||||||
"root.mouse-control-enabled ? StreamStrings.release-mouse "
|
|
||||||
": StreamStrings.control-mouse");
|
|
||||||
ok &= ExpectContains(
|
|
||||||
"stream_window.slint", stream,
|
|
||||||
"root.audio-enabled ? StreamStrings.mute : StreamStrings.audio");
|
|
||||||
ok &= ExpectContains("stream_window.slint", stream,
|
|
||||||
"tooltip: StreamStrings.select-file");
|
|
||||||
ok &= ExpectContains("stream_window.slint", stream,
|
|
||||||
"root.stats-visible ? StreamStrings.hide-stats : "
|
|
||||||
"StreamStrings.show-stats");
|
|
||||||
ok &=
|
|
||||||
ExpectContains("stream_window.slint", stream,
|
|
||||||
"root.fullscreen-enabled ? StreamStrings.exit-fullscreen "
|
|
||||||
": StreamStrings.fullscreen");
|
|
||||||
ok &= ExpectContains("stream_window.slint", stream,
|
|
||||||
"tooltip: StreamStrings.disconnect");
|
|
||||||
ok &= ExpectContains("stream_window.slint", stream,
|
|
||||||
"root.control-expanded ? StreamStrings.collapse-control "
|
|
||||||
": StreamStrings.expand-control");
|
|
||||||
|
|
||||||
ok &=
|
|
||||||
ExpectContains("gui_application.cpp", application, "set_select_display(");
|
|
||||||
ok &= ExpectContains("gui_application.cpp", application, "set_show_stats(");
|
|
||||||
ok &=
|
|
||||||
ExpectContains("gui_application.cpp", application, "set_expand_control(");
|
|
||||||
ok &= ExpectContains("gui_application.cpp", application,
|
|
||||||
"set_collapse_control(");
|
|
||||||
ok &= ExpectContains("gui_application.cpp", application,
|
|
||||||
"(*ui_->stream)->global<ui::StreamStrings>()");
|
|
||||||
ok &=
|
|
||||||
ExpectNotContains("gui_application.cpp", application, "#include <imgui");
|
|
||||||
return ok ? 0 : 1;
|
|
||||||
}
|
|
||||||
@@ -100,11 +100,6 @@ function setup_targets()
|
|||||||
add_includedirs("src/service/windows")
|
add_includedirs("src/service/windows")
|
||||||
add_files("tests/windows_sas_guard_test.cpp")
|
add_files("tests/windows_sas_guard_test.cpp")
|
||||||
|
|
||||||
target("display_popup_hover_state_test")
|
|
||||||
set_kind("binary")
|
|
||||||
set_default(false)
|
|
||||||
add_files("tests/display_popup_hover_state_test.cpp")
|
|
||||||
|
|
||||||
target("slint_ui_smoke_test")
|
target("slint_ui_smoke_test")
|
||||||
set_kind("binary")
|
set_kind("binary")
|
||||||
set_languages("c++20")
|
set_languages("c++20")
|
||||||
|
|||||||
Reference in New Issue
Block a user