mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-02-17 14:07:28 +08:00
Fix/linux shortcuts inhibit (#14302)
* feat: Inhibit system shortcuts on Linux Fixes #13013. Signed-off-by: Max von Forell <max@vonforell.de> * fix(linux): shortcuts inhibit Signed-off-by: fufesou <linlong1266@gmail.com> --------- Signed-off-by: Max von Forell <max@vonforell.de> Signed-off-by: fufesou <linlong1266@gmail.com> Co-authored-by: Max von Forell <max@vonforell.de>
This commit is contained in:
@@ -55,6 +55,7 @@
|
|||||||
],
|
],
|
||||||
"finish-args": [
|
"finish-args": [
|
||||||
"--share=ipc",
|
"--share=ipc",
|
||||||
|
"--socket=wayland",
|
||||||
"--socket=x11",
|
"--socket=x11",
|
||||||
"--share=network",
|
"--share=network",
|
||||||
"--filesystem=home",
|
"--filesystem=home",
|
||||||
|
|||||||
@@ -2538,6 +2538,49 @@ class WaylandCard extends StatefulWidget {
|
|||||||
|
|
||||||
class _WaylandCardState extends State<WaylandCard> {
|
class _WaylandCardState extends State<WaylandCard> {
|
||||||
final restoreTokenKey = 'wayland-restore-token';
|
final restoreTokenKey = 'wayland-restore-token';
|
||||||
|
static const _kClearShortcutsInhibitorEventKey =
|
||||||
|
'clear-gnome-shortcuts-inhibitor-permission-res';
|
||||||
|
final _clearShortcutsInhibitorFailedMsg = ''.obs;
|
||||||
|
// Don't show the shortcuts permission reset button for now.
|
||||||
|
// Users can change it manually:
|
||||||
|
// "Settings" -> "Apps" -> "RustDesk" -> "Permissions" -> "Inhibit Shortcuts".
|
||||||
|
// For resetting(clearing) the permission from the portal permission store, you can
|
||||||
|
// use (replace <desktop-id> with the RustDesk desktop file ID):
|
||||||
|
// busctl --user call org.freedesktop.impl.portal.PermissionStore \
|
||||||
|
// /org/freedesktop/impl/portal/PermissionStore org.freedesktop.impl.portal.PermissionStore \
|
||||||
|
// DeletePermission sss "gnome" "shortcuts-inhibitor" "<desktop-id>"
|
||||||
|
// On a native install this is typically "rustdesk.desktop"; on Flatpak it is usually
|
||||||
|
// the exported desktop ID derived from the Flatpak app-id (e.g. "com.rustdesk.RustDesk.desktop").
|
||||||
|
//
|
||||||
|
// We may add it back in the future if needed.
|
||||||
|
final showResetInhibitorPermission = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
if (showResetInhibitorPermission) {
|
||||||
|
platformFFI.registerEventHandler(
|
||||||
|
_kClearShortcutsInhibitorEventKey, _kClearShortcutsInhibitorEventKey,
|
||||||
|
(evt) async {
|
||||||
|
if (!mounted) return;
|
||||||
|
if (evt['success'] == true) {
|
||||||
|
setState(() {});
|
||||||
|
} else {
|
||||||
|
_clearShortcutsInhibitorFailedMsg.value =
|
||||||
|
evt['msg'] as String? ?? 'Unknown error';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
if (showResetInhibitorPermission) {
|
||||||
|
platformFFI.unregisterEventHandler(
|
||||||
|
_kClearShortcutsInhibitorEventKey, _kClearShortcutsInhibitorEventKey);
|
||||||
|
}
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -2545,9 +2588,16 @@ class _WaylandCardState extends State<WaylandCard> {
|
|||||||
future: bind.mainHandleWaylandScreencastRestoreToken(
|
future: bind.mainHandleWaylandScreencastRestoreToken(
|
||||||
key: restoreTokenKey, value: "get"),
|
key: restoreTokenKey, value: "get"),
|
||||||
hasData: (restoreToken) {
|
hasData: (restoreToken) {
|
||||||
|
final hasShortcutsPermission = showResetInhibitorPermission &&
|
||||||
|
bind.mainGetCommonSync(
|
||||||
|
key: "has-gnome-shortcuts-inhibitor-permission") ==
|
||||||
|
"true";
|
||||||
|
|
||||||
final children = [
|
final children = [
|
||||||
if (restoreToken.isNotEmpty)
|
if (restoreToken.isNotEmpty)
|
||||||
_buildClearScreenSelection(context, restoreToken),
|
_buildClearScreenSelection(context, restoreToken),
|
||||||
|
if (hasShortcutsPermission)
|
||||||
|
_buildClearShortcutsInhibitorPermission(context),
|
||||||
];
|
];
|
||||||
return Offstage(
|
return Offstage(
|
||||||
offstage: children.isEmpty,
|
offstage: children.isEmpty,
|
||||||
@@ -2592,6 +2642,50 @@ class _WaylandCardState extends State<WaylandCard> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildClearShortcutsInhibitorPermission(BuildContext context) {
|
||||||
|
onConfirm() {
|
||||||
|
_clearShortcutsInhibitorFailedMsg.value = '';
|
||||||
|
bind.mainSetCommon(
|
||||||
|
key: "clear-gnome-shortcuts-inhibitor-permission", value: "");
|
||||||
|
gFFI.dialogManager.dismissAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
showConfirmMsgBox() => msgBoxCommon(
|
||||||
|
gFFI.dialogManager,
|
||||||
|
'Confirmation',
|
||||||
|
Text(
|
||||||
|
translate('confirm-clear-shortcuts-inhibitor-permission-tip'),
|
||||||
|
),
|
||||||
|
[
|
||||||
|
dialogButton('OK', onPressed: onConfirm),
|
||||||
|
dialogButton('Cancel',
|
||||||
|
onPressed: () => gFFI.dialogManager.dismissAll())
|
||||||
|
]);
|
||||||
|
|
||||||
|
return Column(children: [
|
||||||
|
Obx(
|
||||||
|
() => _clearShortcutsInhibitorFailedMsg.value.isEmpty
|
||||||
|
? Offstage()
|
||||||
|
: Align(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: Text(_clearShortcutsInhibitorFailedMsg.value,
|
||||||
|
style: DefaultTextStyle.of(context)
|
||||||
|
.style
|
||||||
|
.copyWith(color: Colors.red))
|
||||||
|
.marginOnly(bottom: 10.0)),
|
||||||
|
),
|
||||||
|
_Button(
|
||||||
|
'Reset keyboard shortcuts permission',
|
||||||
|
showConfirmMsgBox,
|
||||||
|
tip: 'clear-shortcuts-inhibitor-permission-tip',
|
||||||
|
style: ButtonStyle(
|
||||||
|
backgroundColor: MaterialStateProperty.all<Color>(
|
||||||
|
Theme.of(context).colorScheme.error.withOpacity(0.75)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Project-level configuration.
|
# Project-level configuration.
|
||||||
cmake_minimum_required(VERSION 3.10)
|
cmake_minimum_required(VERSION 3.10)
|
||||||
project(runner LANGUAGES CXX)
|
project(runner LANGUAGES C CXX)
|
||||||
|
|
||||||
# The name of the executable created for the application. Change this to change
|
# The name of the executable created for the application. Change this to change
|
||||||
# the on-disk name of your application.
|
# the on-disk name of your application.
|
||||||
@@ -54,6 +54,55 @@ add_subdirectory(${FLUTTER_MANAGED_DIR})
|
|||||||
find_package(PkgConfig REQUIRED)
|
find_package(PkgConfig REQUIRED)
|
||||||
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
|
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
|
||||||
|
|
||||||
|
# Wayland protocol for keyboard shortcuts inhibit
|
||||||
|
pkg_check_modules(WAYLAND_CLIENT IMPORTED_TARGET wayland-client)
|
||||||
|
pkg_check_modules(WAYLAND_PROTOCOLS_PKG QUIET wayland-protocols)
|
||||||
|
pkg_check_modules(WAYLAND_SCANNER_PKG QUIET wayland-scanner)
|
||||||
|
|
||||||
|
if(WAYLAND_PROTOCOLS_PKG_FOUND)
|
||||||
|
pkg_get_variable(WAYLAND_PROTOCOLS_DIR wayland-protocols pkgdatadir)
|
||||||
|
endif()
|
||||||
|
if(WAYLAND_SCANNER_PKG_FOUND)
|
||||||
|
pkg_get_variable(WAYLAND_SCANNER wayland-scanner wayland_scanner)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(WAYLAND_CLIENT_FOUND AND WAYLAND_PROTOCOLS_DIR AND WAYLAND_SCANNER)
|
||||||
|
set(KEYBOARD_SHORTCUTS_INHIBIT_PROTOCOL
|
||||||
|
"${WAYLAND_PROTOCOLS_DIR}/unstable/keyboard-shortcuts-inhibit/keyboard-shortcuts-inhibit-unstable-v1.xml")
|
||||||
|
|
||||||
|
if(EXISTS ${KEYBOARD_SHORTCUTS_INHIBIT_PROTOCOL})
|
||||||
|
set(WAYLAND_GENERATED_DIR "${CMAKE_CURRENT_BINARY_DIR}/wayland-protocols")
|
||||||
|
file(MAKE_DIRECTORY ${WAYLAND_GENERATED_DIR})
|
||||||
|
|
||||||
|
# Generate client header
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT "${WAYLAND_GENERATED_DIR}/keyboard-shortcuts-inhibit-unstable-v1-client-protocol.h"
|
||||||
|
COMMAND ${WAYLAND_SCANNER} client-header
|
||||||
|
${KEYBOARD_SHORTCUTS_INHIBIT_PROTOCOL}
|
||||||
|
"${WAYLAND_GENERATED_DIR}/keyboard-shortcuts-inhibit-unstable-v1-client-protocol.h"
|
||||||
|
DEPENDS ${KEYBOARD_SHORTCUTS_INHIBIT_PROTOCOL}
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
|
||||||
|
# Generate protocol code
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT "${WAYLAND_GENERATED_DIR}/keyboard-shortcuts-inhibit-unstable-v1-protocol.c"
|
||||||
|
COMMAND ${WAYLAND_SCANNER} private-code
|
||||||
|
${KEYBOARD_SHORTCUTS_INHIBIT_PROTOCOL}
|
||||||
|
"${WAYLAND_GENERATED_DIR}/keyboard-shortcuts-inhibit-unstable-v1-protocol.c"
|
||||||
|
DEPENDS ${KEYBOARD_SHORTCUTS_INHIBIT_PROTOCOL}
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
|
||||||
|
set(WAYLAND_PROTOCOL_SOURCES
|
||||||
|
"${WAYLAND_GENERATED_DIR}/keyboard-shortcuts-inhibit-unstable-v1-client-protocol.h"
|
||||||
|
"${WAYLAND_GENERATED_DIR}/keyboard-shortcuts-inhibit-unstable-v1-protocol.c"
|
||||||
|
)
|
||||||
|
|
||||||
|
set(HAS_KEYBOARD_SHORTCUTS_INHIBIT TRUE)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
|
add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
|
||||||
|
|
||||||
# Define the application target. To change its name, change BINARY_NAME above,
|
# Define the application target. To change its name, change BINARY_NAME above,
|
||||||
@@ -63,9 +112,11 @@ add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
|
|||||||
add_executable(${BINARY_NAME}
|
add_executable(${BINARY_NAME}
|
||||||
"main.cc"
|
"main.cc"
|
||||||
"my_application.cc"
|
"my_application.cc"
|
||||||
|
"wayland_shortcuts_inhibit.cc"
|
||||||
"bump_mouse.cc"
|
"bump_mouse.cc"
|
||||||
"bump_mouse_x11.cc"
|
"bump_mouse_x11.cc"
|
||||||
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
|
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
|
||||||
|
${WAYLAND_PROTOCOL_SOURCES}
|
||||||
)
|
)
|
||||||
|
|
||||||
# Apply the standard set of build settings. This can be removed for applications
|
# Apply the standard set of build settings. This can be removed for applications
|
||||||
@@ -78,6 +129,13 @@ target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
|
|||||||
target_link_libraries(${BINARY_NAME} PRIVATE ${CMAKE_DL_LIBS})
|
target_link_libraries(${BINARY_NAME} PRIVATE ${CMAKE_DL_LIBS})
|
||||||
# target_link_libraries(${BINARY_NAME} PRIVATE librustdesk)
|
# target_link_libraries(${BINARY_NAME} PRIVATE librustdesk)
|
||||||
|
|
||||||
|
# Wayland support for keyboard shortcuts inhibit
|
||||||
|
if(HAS_KEYBOARD_SHORTCUTS_INHIBIT)
|
||||||
|
target_compile_definitions(${BINARY_NAME} PRIVATE HAS_KEYBOARD_SHORTCUTS_INHIBIT)
|
||||||
|
target_include_directories(${BINARY_NAME} PRIVATE ${WAYLAND_GENERATED_DIR})
|
||||||
|
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::WAYLAND_CLIENT)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Run the Flutter tool portions of the build. This must not be removed.
|
# Run the Flutter tool portions of the build. This must not be removed.
|
||||||
add_dependencies(${BINARY_NAME} flutter_assemble)
|
add_dependencies(${BINARY_NAME} flutter_assemble)
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,11 @@
|
|||||||
#ifdef GDK_WINDOWING_X11
|
#ifdef GDK_WINDOWING_X11
|
||||||
#include <gdk/gdkx.h>
|
#include <gdk/gdkx.h>
|
||||||
#endif
|
#endif
|
||||||
|
#if defined(GDK_WINDOWING_WAYLAND) && defined(HAS_KEYBOARD_SHORTCUTS_INHIBIT)
|
||||||
|
#include "wayland_shortcuts_inhibit.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <desktop_multi_window/desktop_multi_window_plugin.h>
|
||||||
|
|
||||||
#include "flutter/generated_plugin_registrant.h"
|
#include "flutter/generated_plugin_registrant.h"
|
||||||
|
|
||||||
@@ -91,6 +96,13 @@ static void my_application_activate(GApplication* application) {
|
|||||||
gtk_widget_show(GTK_WIDGET(window));
|
gtk_widget_show(GTK_WIDGET(window));
|
||||||
gtk_widget_show(GTK_WIDGET(view));
|
gtk_widget_show(GTK_WIDGET(view));
|
||||||
|
|
||||||
|
#if defined(GDK_WINDOWING_WAYLAND) && defined(HAS_KEYBOARD_SHORTCUTS_INHIBIT)
|
||||||
|
// Register callback for sub-windows created by desktop_multi_window plugin
|
||||||
|
// Only sub-windows (remote windows) need keyboard shortcuts inhibition
|
||||||
|
desktop_multi_window_plugin_set_window_created_callback(
|
||||||
|
(WindowCreatedCallback)wayland_shortcuts_inhibit_init_for_subwindow);
|
||||||
|
#endif
|
||||||
|
|
||||||
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
|
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
|
||||||
|
|
||||||
g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
|
g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
|
||||||
|
|||||||
244
flutter/linux/wayland_shortcuts_inhibit.cc
Normal file
244
flutter/linux/wayland_shortcuts_inhibit.cc
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
// Wayland keyboard shortcuts inhibit implementation
|
||||||
|
// Uses the zwp_keyboard_shortcuts_inhibit_manager_v1 protocol to request
|
||||||
|
// the compositor to disable system shortcuts for specific windows.
|
||||||
|
|
||||||
|
#include "wayland_shortcuts_inhibit.h"
|
||||||
|
|
||||||
|
#if defined(GDK_WINDOWING_WAYLAND) && defined(HAS_KEYBOARD_SHORTCUTS_INHIBIT)
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <gdk/gdkwayland.h>
|
||||||
|
#include <wayland-client.h>
|
||||||
|
#include "keyboard-shortcuts-inhibit-unstable-v1-client-protocol.h"
|
||||||
|
|
||||||
|
// Data structure to hold inhibitor state for each window
|
||||||
|
typedef struct {
|
||||||
|
struct zwp_keyboard_shortcuts_inhibit_manager_v1* manager;
|
||||||
|
struct zwp_keyboard_shortcuts_inhibitor_v1* inhibitor;
|
||||||
|
} ShortcutsInhibitData;
|
||||||
|
|
||||||
|
// Cleanup function for ShortcutsInhibitData
|
||||||
|
static void shortcuts_inhibit_data_free(gpointer data) {
|
||||||
|
ShortcutsInhibitData* inhibit_data = static_cast<ShortcutsInhibitData*>(data);
|
||||||
|
if (inhibit_data->inhibitor != NULL) {
|
||||||
|
zwp_keyboard_shortcuts_inhibitor_v1_destroy(inhibit_data->inhibitor);
|
||||||
|
}
|
||||||
|
if (inhibit_data->manager != NULL) {
|
||||||
|
zwp_keyboard_shortcuts_inhibit_manager_v1_destroy(inhibit_data->manager);
|
||||||
|
}
|
||||||
|
g_free(inhibit_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wayland registry handler to find the shortcuts inhibit manager
|
||||||
|
static void registry_handle_global(void* data, struct wl_registry* registry,
|
||||||
|
uint32_t name, const char* interface,
|
||||||
|
uint32_t /*version*/) {
|
||||||
|
ShortcutsInhibitData* inhibit_data = static_cast<ShortcutsInhibitData*>(data);
|
||||||
|
if (strcmp(interface,
|
||||||
|
zwp_keyboard_shortcuts_inhibit_manager_v1_interface.name) == 0) {
|
||||||
|
inhibit_data->manager =
|
||||||
|
static_cast<zwp_keyboard_shortcuts_inhibit_manager_v1*>(wl_registry_bind(
|
||||||
|
registry, name, &zwp_keyboard_shortcuts_inhibit_manager_v1_interface,
|
||||||
|
1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void registry_handle_global_remove(void* /*data*/, struct wl_registry* /*registry*/,
|
||||||
|
uint32_t /*name*/) {
|
||||||
|
// Not needed for this use case
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct wl_registry_listener registry_listener = {
|
||||||
|
registry_handle_global,
|
||||||
|
registry_handle_global_remove,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Inhibitor event handlers
|
||||||
|
static void inhibitor_active(void* /*data*/,
|
||||||
|
struct zwp_keyboard_shortcuts_inhibitor_v1* /*inhibitor*/) {
|
||||||
|
// Inhibitor is now active, shortcuts are being captured
|
||||||
|
}
|
||||||
|
|
||||||
|
static void inhibitor_inactive(void* /*data*/,
|
||||||
|
struct zwp_keyboard_shortcuts_inhibitor_v1* /*inhibitor*/) {
|
||||||
|
// Inhibitor is now inactive, shortcuts restored to compositor
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct zwp_keyboard_shortcuts_inhibitor_v1_listener inhibitor_listener = {
|
||||||
|
inhibitor_active,
|
||||||
|
inhibitor_inactive,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Forward declaration
|
||||||
|
static void uninhibit_keyboard_shortcuts(GtkWindow* window);
|
||||||
|
|
||||||
|
// Inhibit keyboard shortcuts on Wayland for a specific window
|
||||||
|
static void inhibit_keyboard_shortcuts(GtkWindow* window) {
|
||||||
|
GdkDisplay* display = gtk_widget_get_display(GTK_WIDGET(window));
|
||||||
|
if (!GDK_IS_WAYLAND_DISPLAY(display)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if already inhibited for this window
|
||||||
|
if (g_object_get_data(G_OBJECT(window), "shortcuts-inhibit-data") != NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ShortcutsInhibitData* inhibit_data = g_new0(ShortcutsInhibitData, 1);
|
||||||
|
|
||||||
|
struct wl_display* wl_display = gdk_wayland_display_get_wl_display(display);
|
||||||
|
if (wl_display == NULL) {
|
||||||
|
shortcuts_inhibit_data_free(inhibit_data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct wl_registry* registry = wl_display_get_registry(wl_display);
|
||||||
|
if (registry == NULL) {
|
||||||
|
shortcuts_inhibit_data_free(inhibit_data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
wl_registry_add_listener(registry, ®istry_listener, inhibit_data);
|
||||||
|
wl_display_roundtrip(wl_display);
|
||||||
|
|
||||||
|
if (inhibit_data->manager == NULL) {
|
||||||
|
wl_registry_destroy(registry);
|
||||||
|
shortcuts_inhibit_data_free(inhibit_data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window));
|
||||||
|
if (gdk_window == NULL) {
|
||||||
|
wl_registry_destroy(registry);
|
||||||
|
shortcuts_inhibit_data_free(inhibit_data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct wl_surface* surface = gdk_wayland_window_get_wl_surface(gdk_window);
|
||||||
|
if (surface == NULL) {
|
||||||
|
wl_registry_destroy(registry);
|
||||||
|
shortcuts_inhibit_data_free(inhibit_data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GdkSeat* gdk_seat = gdk_display_get_default_seat(display);
|
||||||
|
if (gdk_seat == NULL) {
|
||||||
|
wl_registry_destroy(registry);
|
||||||
|
shortcuts_inhibit_data_free(inhibit_data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct wl_seat* seat = gdk_wayland_seat_get_wl_seat(gdk_seat);
|
||||||
|
if (seat == NULL) {
|
||||||
|
wl_registry_destroy(registry);
|
||||||
|
shortcuts_inhibit_data_free(inhibit_data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
inhibit_data->inhibitor =
|
||||||
|
zwp_keyboard_shortcuts_inhibit_manager_v1_inhibit_shortcuts(
|
||||||
|
inhibit_data->manager, surface, seat);
|
||||||
|
|
||||||
|
if (inhibit_data->inhibitor == NULL) {
|
||||||
|
wl_registry_destroy(registry);
|
||||||
|
shortcuts_inhibit_data_free(inhibit_data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add listener to monitor active/inactive state
|
||||||
|
zwp_keyboard_shortcuts_inhibitor_v1_add_listener(
|
||||||
|
inhibit_data->inhibitor, &inhibitor_listener, window);
|
||||||
|
|
||||||
|
wl_display_roundtrip(wl_display);
|
||||||
|
wl_registry_destroy(registry);
|
||||||
|
|
||||||
|
// Associate the inhibit data with the window for cleanup on destroy
|
||||||
|
g_object_set_data_full(G_OBJECT(window), "shortcuts-inhibit-data",
|
||||||
|
inhibit_data, shortcuts_inhibit_data_free);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove keyboard shortcuts inhibitor from a window
|
||||||
|
static void uninhibit_keyboard_shortcuts(GtkWindow* window) {
|
||||||
|
ShortcutsInhibitData* inhibit_data = static_cast<ShortcutsInhibitData*>(
|
||||||
|
g_object_get_data(G_OBJECT(window), "shortcuts-inhibit-data"));
|
||||||
|
|
||||||
|
if (inhibit_data == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This will trigger shortcuts_inhibit_data_free via g_object_set_data
|
||||||
|
g_object_set_data(G_OBJECT(window), "shortcuts-inhibit-data", NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Focus event handlers for dynamic inhibitor management
|
||||||
|
static gboolean on_window_focus_in(GtkWidget* widget, GdkEventFocus* /*event*/, gpointer /*user_data*/) {
|
||||||
|
if (GTK_IS_WINDOW(widget)) {
|
||||||
|
inhibit_keyboard_shortcuts(GTK_WINDOW(widget));
|
||||||
|
}
|
||||||
|
return FALSE; // Continue event propagation
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean on_window_focus_out(GtkWidget* widget, GdkEventFocus* /*event*/, gpointer /*user_data*/) {
|
||||||
|
if (GTK_IS_WINDOW(widget)) {
|
||||||
|
uninhibit_keyboard_shortcuts(GTK_WINDOW(widget));
|
||||||
|
}
|
||||||
|
return FALSE; // Continue event propagation
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key for marking window as having focus handlers connected
|
||||||
|
static const char* const kFocusHandlersConnectedKey = "shortcuts-inhibit-focus-handlers-connected";
|
||||||
|
// Key for marking window as having a pending realize handler
|
||||||
|
static const char* const kRealizeHandlerConnectedKey = "shortcuts-inhibit-realize-handler-connected";
|
||||||
|
|
||||||
|
// Callback when window is realized (mapped to screen)
|
||||||
|
// Sets up focus-based inhibitor management
|
||||||
|
static void on_window_realize(GtkWidget* widget, gpointer /*user_data*/) {
|
||||||
|
if (GTK_IS_WINDOW(widget)) {
|
||||||
|
// Check if focus handlers are already connected to avoid duplicates
|
||||||
|
if (g_object_get_data(G_OBJECT(widget), kFocusHandlersConnectedKey) != NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect focus events for dynamic inhibitor management
|
||||||
|
g_signal_connect(widget, "focus-in-event",
|
||||||
|
G_CALLBACK(on_window_focus_in), NULL);
|
||||||
|
g_signal_connect(widget, "focus-out-event",
|
||||||
|
G_CALLBACK(on_window_focus_out), NULL);
|
||||||
|
|
||||||
|
// Mark as connected to prevent duplicate connections
|
||||||
|
g_object_set_data(G_OBJECT(widget), kFocusHandlersConnectedKey, GINT_TO_POINTER(1));
|
||||||
|
|
||||||
|
// If window already has focus, create inhibitor now
|
||||||
|
if (gtk_window_has_toplevel_focus(GTK_WINDOW(widget))) {
|
||||||
|
inhibit_keyboard_shortcuts(GTK_WINDOW(widget));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public API: Initialize shortcuts inhibit for a sub-window
|
||||||
|
void wayland_shortcuts_inhibit_init_for_subwindow(void* view) {
|
||||||
|
GtkWidget* widget = GTK_WIDGET(view);
|
||||||
|
GtkWidget* toplevel = gtk_widget_get_toplevel(widget);
|
||||||
|
|
||||||
|
if (toplevel != NULL && GTK_IS_WINDOW(toplevel)) {
|
||||||
|
// Check if already initialized to avoid duplicate realize handlers
|
||||||
|
if (g_object_get_data(G_OBJECT(toplevel), kFocusHandlersConnectedKey) != NULL ||
|
||||||
|
g_object_get_data(G_OBJECT(toplevel), kRealizeHandlerConnectedKey) != NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gtk_widget_get_realized(toplevel)) {
|
||||||
|
// Window is already realized, set up focus handlers now
|
||||||
|
on_window_realize(toplevel, NULL);
|
||||||
|
} else {
|
||||||
|
// Mark realize handler as connected to prevent duplicate connections
|
||||||
|
// if called again before window is realized
|
||||||
|
g_object_set_data(G_OBJECT(toplevel), kRealizeHandlerConnectedKey, GINT_TO_POINTER(1));
|
||||||
|
// Wait for window to be realized
|
||||||
|
g_signal_connect(toplevel, "realize",
|
||||||
|
G_CALLBACK(on_window_realize), NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // defined(GDK_WINDOWING_WAYLAND) && defined(HAS_KEYBOARD_SHORTCUTS_INHIBIT)
|
||||||
22
flutter/linux/wayland_shortcuts_inhibit.h
Normal file
22
flutter/linux/wayland_shortcuts_inhibit.h
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
// Wayland keyboard shortcuts inhibit support
|
||||||
|
// This module provides functionality to inhibit system keyboard shortcuts
|
||||||
|
// on Wayland compositors, allowing remote desktop windows to capture all
|
||||||
|
// key events including Super, Alt+Tab, etc.
|
||||||
|
|
||||||
|
#ifndef WAYLAND_SHORTCUTS_INHIBIT_H_
|
||||||
|
#define WAYLAND_SHORTCUTS_INHIBIT_H_
|
||||||
|
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
|
||||||
|
#if defined(GDK_WINDOWING_WAYLAND) && defined(HAS_KEYBOARD_SHORTCUTS_INHIBIT)
|
||||||
|
|
||||||
|
// Initialize shortcuts inhibit for a sub-window created by desktop_multi_window plugin.
|
||||||
|
// This sets up focus-based inhibitor management: inhibitor is created when
|
||||||
|
// the window gains focus and destroyed when it loses focus.
|
||||||
|
//
|
||||||
|
// @param view The FlView of the sub-window
|
||||||
|
void wayland_shortcuts_inhibit_init_for_subwindow(void* view);
|
||||||
|
|
||||||
|
#endif // defined(GDK_WINDOWING_WAYLAND) && defined(HAS_KEYBOARD_SHORTCUTS_INHIBIT)
|
||||||
|
|
||||||
|
#endif // WAYLAND_SHORTCUTS_INHIBIT_H_
|
||||||
@@ -2759,6 +2759,11 @@ pub fn main_get_common(key: String) -> String {
|
|||||||
None => "",
|
None => "",
|
||||||
}
|
}
|
||||||
.to_string();
|
.to_string();
|
||||||
|
} else if key == "has-gnome-shortcuts-inhibitor-permission" {
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
return crate::platform::linux::has_gnome_shortcuts_inhibitor_permission().to_string();
|
||||||
|
#[cfg(not(target_os = "linux"))]
|
||||||
|
return false.to_string();
|
||||||
} else {
|
} else {
|
||||||
if key.starts_with("download-data-") {
|
if key.starts_with("download-data-") {
|
||||||
let id = key.replace("download-data-", "");
|
let id = key.replace("download-data-", "");
|
||||||
@@ -2920,6 +2925,29 @@ pub fn main_set_common(_key: String, _value: String) {
|
|||||||
} else if _key == "cancel-downloader" {
|
} else if _key == "cancel-downloader" {
|
||||||
crate::hbbs_http::downloader::cancel(&_value);
|
crate::hbbs_http::downloader::cancel(&_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
if _key == "clear-gnome-shortcuts-inhibitor-permission" {
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
let (success, msg) =
|
||||||
|
match crate::platform::linux::clear_gnome_shortcuts_inhibitor_permission() {
|
||||||
|
Ok(_) => (true, "".to_owned()),
|
||||||
|
Err(e) => (false, e.to_string()),
|
||||||
|
};
|
||||||
|
let data = HashMap::from([
|
||||||
|
(
|
||||||
|
"name",
|
||||||
|
serde_json::json!("clear-gnome-shortcuts-inhibitor-permission-res"),
|
||||||
|
),
|
||||||
|
("success", serde_json::json!(success)),
|
||||||
|
("msg", serde_json::json!(msg)),
|
||||||
|
]);
|
||||||
|
let _res = flutter::push_global_event(
|
||||||
|
flutter::APP_TYPE_MAIN,
|
||||||
|
serde_json::ser::to_string(&data).unwrap_or("".to_owned()),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_get_common_sync(
|
pub fn session_get_common_sync(
|
||||||
|
|||||||
@@ -220,7 +220,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("default_proxy_tip", "Default protocol and port are Socks5 and 1080"),
|
("default_proxy_tip", "Default protocol and port are Socks5 and 1080"),
|
||||||
("no_audio_input_device_tip", "No audio input device found."),
|
("no_audio_input_device_tip", "No audio input device found."),
|
||||||
("clear_Wayland_screen_selection_tip", "After clearing the screen selection, you can reselect the screen to share."),
|
("clear_Wayland_screen_selection_tip", "After clearing the screen selection, you can reselect the screen to share."),
|
||||||
("confirm_clear_Wayland_screen_selection_tip", "Are you sure to clear the Wayland screen selection?"),
|
("confirm_clear_Wayland_screen_selection_tip", "Are you sure you want to clear the Wayland screen selection?"),
|
||||||
("android_new_voice_call_tip", "A new voice call request was received. If you accept, the audio will switch to voice communication."),
|
("android_new_voice_call_tip", "A new voice call request was received. If you accept, the audio will switch to voice communication."),
|
||||||
("texture_render_tip", "Use texture rendering to make the pictures smoother. You could try disabling this option if you encounter rendering issues."),
|
("texture_render_tip", "Use texture rendering to make the pictures smoother. You could try disabling this option if you encounter rendering issues."),
|
||||||
("floating_window_tip", "It helps to keep RustDesk background service"),
|
("floating_window_tip", "It helps to keep RustDesk background service"),
|
||||||
|
|||||||
@@ -2088,3 +2088,122 @@ pub fn is_selinux_enforcing() -> bool {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the app ID for shortcuts inhibitor permission.
|
||||||
|
/// Returns different ID based on whether running in Flatpak or native.
|
||||||
|
/// The ID must match the installed .desktop filename, as GNOME Shell's
|
||||||
|
/// inhibitShortcutsDialog uses `Shell.WindowTracker.get_window_app(window).get_id()`.
|
||||||
|
fn get_shortcuts_inhibitor_app_id() -> String {
|
||||||
|
if is_flatpak() {
|
||||||
|
// In Flatpak, FLATPAK_ID is set automatically by the runtime to the app ID
|
||||||
|
// (e.g., "com.rustdesk.RustDesk"). This is the most reliable source.
|
||||||
|
// Fall back to constructing from app name if not available.
|
||||||
|
match std::env::var("FLATPAK_ID") {
|
||||||
|
Ok(id) if !id.is_empty() => format!("{}.desktop", id),
|
||||||
|
_ => {
|
||||||
|
let app_name = crate::get_app_name();
|
||||||
|
format!("com.{}.{}.desktop", app_name.to_lowercase(), app_name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
format!("{}.desktop", crate::get_app_name().to_lowercase())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const PERMISSION_STORE_DEST: &str = "org.freedesktop.impl.portal.PermissionStore";
|
||||||
|
const PERMISSION_STORE_PATH: &str = "/org/freedesktop/impl/portal/PermissionStore";
|
||||||
|
const PERMISSION_STORE_IFACE: &str = "org.freedesktop.impl.portal.PermissionStore";
|
||||||
|
|
||||||
|
/// Clear GNOME shortcuts inhibitor permission via D-Bus.
|
||||||
|
/// This allows the permission dialog to be shown again.
|
||||||
|
pub fn clear_gnome_shortcuts_inhibitor_permission() -> ResultType<()> {
|
||||||
|
let app_id = get_shortcuts_inhibitor_app_id();
|
||||||
|
log::info!(
|
||||||
|
"Clearing shortcuts inhibitor permission for app_id: {}, is_flatpak: {}",
|
||||||
|
app_id,
|
||||||
|
is_flatpak()
|
||||||
|
);
|
||||||
|
|
||||||
|
let conn = dbus::blocking::Connection::new_session()?;
|
||||||
|
let proxy = conn.with_proxy(
|
||||||
|
PERMISSION_STORE_DEST,
|
||||||
|
PERMISSION_STORE_PATH,
|
||||||
|
std::time::Duration::from_secs(3),
|
||||||
|
);
|
||||||
|
|
||||||
|
// DeletePermission(s table, s id, s app) -> ()
|
||||||
|
let result: Result<(), dbus::Error> = proxy.method_call(
|
||||||
|
PERMISSION_STORE_IFACE,
|
||||||
|
"DeletePermission",
|
||||||
|
("gnome", "shortcuts-inhibitor", app_id.as_str()),
|
||||||
|
);
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(()) => {
|
||||||
|
log::info!("Successfully cleared GNOME shortcuts inhibitor permission");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
let err_name = e.name().unwrap_or("");
|
||||||
|
// If the permission doesn't exist, that's also fine
|
||||||
|
if err_name == "org.freedesktop.portal.Error.NotFound"
|
||||||
|
|| err_name == "org.freedesktop.DBus.Error.UnknownObject"
|
||||||
|
|| err_name == "org.freedesktop.DBus.Error.ServiceUnknown"
|
||||||
|
{
|
||||||
|
log::info!("GNOME shortcuts inhibitor permission was not set ({})", err_name);
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
bail!("Failed to clear permission: {}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if GNOME shortcuts inhibitor permission exists.
|
||||||
|
pub fn has_gnome_shortcuts_inhibitor_permission() -> bool {
|
||||||
|
let app_id = get_shortcuts_inhibitor_app_id();
|
||||||
|
|
||||||
|
let conn = match dbus::blocking::Connection::new_session() {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => {
|
||||||
|
log::debug!("Failed to connect to session bus: {}", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let proxy = conn.with_proxy(
|
||||||
|
PERMISSION_STORE_DEST,
|
||||||
|
PERMISSION_STORE_PATH,
|
||||||
|
std::time::Duration::from_secs(3),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Lookup(s table, s id) -> (a{sas} permissions, v data)
|
||||||
|
// We only need the permissions dict; check if app_id is a key.
|
||||||
|
let result: Result<
|
||||||
|
(
|
||||||
|
std::collections::HashMap<String, Vec<String>>,
|
||||||
|
dbus::arg::Variant<Box<dyn dbus::arg::RefArg>>,
|
||||||
|
),
|
||||||
|
dbus::Error,
|
||||||
|
> = proxy.method_call(
|
||||||
|
PERMISSION_STORE_IFACE,
|
||||||
|
"Lookup",
|
||||||
|
("gnome", "shortcuts-inhibitor"),
|
||||||
|
);
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok((permissions, _)) => {
|
||||||
|
let found = permissions.contains_key(&app_id);
|
||||||
|
log::debug!(
|
||||||
|
"Shortcuts inhibitor permission lookup: app_id={}, found={}, keys={:?}",
|
||||||
|
app_id,
|
||||||
|
found,
|
||||||
|
permissions.keys().collect::<Vec<_>>()
|
||||||
|
);
|
||||||
|
found
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::debug!("Failed to query shortcuts inhibitor permission: {}", e);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user