mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-04-20 01:13:19 +03:00
Compare commits
6 Commits
copilot/fi
...
2842315b1d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2842315b1d | ||
|
|
6c541f7bfd | ||
|
|
067fab2b73 | ||
|
|
de6bf9dc7e | ||
|
|
54eae37038 | ||
|
|
0118e16132 |
2
build.py
2
build.py
@@ -299,7 +299,7 @@ Version: %s
|
|||||||
Architecture: %s
|
Architecture: %s
|
||||||
Maintainer: rustdesk <info@rustdesk.com>
|
Maintainer: rustdesk <info@rustdesk.com>
|
||||||
Homepage: https://rustdesk.com
|
Homepage: https://rustdesk.com
|
||||||
Depends: libgtk-3-0, libxcb-randr0, libxdo3, libxfixes3, libxcb-shape0, libxcb-xfixes0, libasound2, libsystemd0, curl, libva2, libva-drm2, libva-x11-2, libgstreamer-plugins-base1.0-0, libpam0g, gstreamer1.0-pipewire%s
|
Depends: libgtk-3-0, libxcb-randr0, libxdo3 | libxdo4, libxfixes3, libxcb-shape0, libxcb-xfixes0, libasound2, libsystemd0, curl, libva2, libva-drm2, libva-x11-2, libgstreamer-plugins-base1.0-0, libpam0g, gstreamer1.0-pipewire%s
|
||||||
Recommends: libayatana-appindicator3-1
|
Recommends: libayatana-appindicator3-1
|
||||||
Description: A remote control software.
|
Description: A remote control software.
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ class _RemotePageState extends State<RemotePage> with WidgetsBindingObserver {
|
|||||||
double _viewInsetsBottom = 0;
|
double _viewInsetsBottom = 0;
|
||||||
final _uniqueKey = UniqueKey();
|
final _uniqueKey = UniqueKey();
|
||||||
Timer? _timerDidChangeMetrics;
|
Timer? _timerDidChangeMetrics;
|
||||||
|
Timer? _iosKeyboardWorkaroundTimer;
|
||||||
|
|
||||||
final _blockableOverlayState = BlockableOverlayState();
|
final _blockableOverlayState = BlockableOverlayState();
|
||||||
|
|
||||||
@@ -140,6 +141,7 @@ class _RemotePageState extends State<RemotePage> with WidgetsBindingObserver {
|
|||||||
await gFFI.close();
|
await gFFI.close();
|
||||||
_timer?.cancel();
|
_timer?.cancel();
|
||||||
_timerDidChangeMetrics?.cancel();
|
_timerDidChangeMetrics?.cancel();
|
||||||
|
_iosKeyboardWorkaroundTimer?.cancel();
|
||||||
gFFI.dialogManager.dismissAll();
|
gFFI.dialogManager.dismissAll();
|
||||||
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
|
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
|
||||||
overlays: SystemUiOverlay.values);
|
overlays: SystemUiOverlay.values);
|
||||||
@@ -206,7 +208,24 @@ class _RemotePageState extends State<RemotePage> with WidgetsBindingObserver {
|
|||||||
gFFI.ffiModel.pi.version.isNotEmpty) {
|
gFFI.ffiModel.pi.version.isNotEmpty) {
|
||||||
gFFI.invokeMethod("enable_soft_keyboard", false);
|
gFFI.invokeMethod("enable_soft_keyboard", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Workaround for iOS: physical keyboard input fails after virtual keyboard is hidden
|
||||||
|
// https://github.com/flutter/flutter/issues/39900
|
||||||
|
// https://github.com/rustdesk/rustdesk/discussions/11843#discussioncomment-13499698 - Virtual keyboard issue
|
||||||
|
if (isIOS) {
|
||||||
|
_iosKeyboardWorkaroundTimer?.cancel();
|
||||||
|
_iosKeyboardWorkaroundTimer = Timer(Duration(milliseconds: 100), () {
|
||||||
|
if (!mounted) return;
|
||||||
|
_physicalFocusNode.unfocus();
|
||||||
|
_iosKeyboardWorkaroundTimer = Timer(Duration(milliseconds: 50), () {
|
||||||
|
if (!mounted) return;
|
||||||
|
_physicalFocusNode.requestFocus();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
_iosKeyboardWorkaroundTimer?.cancel();
|
||||||
|
_iosKeyboardWorkaroundTimer = null;
|
||||||
_timer?.cancel();
|
_timer?.cancel();
|
||||||
_timer = Timer(kMobileDelaySoftKeyboardFocus, () {
|
_timer = Timer(kMobileDelaySoftKeyboardFocus, () {
|
||||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
|
||||||
|
|||||||
@@ -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"),
|
||||||
|
|||||||
@@ -738,6 +738,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Changelog", "변경 기록"),
|
("Changelog", "변경 기록"),
|
||||||
("keep-awake-during-outgoing-sessions-label", "발신 세션 중 화면 켜짐 유지"),
|
("keep-awake-during-outgoing-sessions-label", "발신 세션 중 화면 켜짐 유지"),
|
||||||
("keep-awake-during-incoming-sessions-label", "수신 세션 중 화면 켜짐 유지"),
|
("keep-awake-during-incoming-sessions-label", "수신 세션 중 화면 켜짐 유지"),
|
||||||
("Continue with {}", "{} (으)로 계속"),
|
("Continue with {}", "{}(으)로 계속"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -673,21 +673,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("dont-show-again-tip", "Não mostrar novamente"),
|
("dont-show-again-tip", "Não mostrar novamente"),
|
||||||
("Take screenshot", "Capturar de tela"),
|
("Take screenshot", "Capturar de tela"),
|
||||||
("Taking screenshot", "Capturando tela"),
|
("Taking screenshot", "Capturando tela"),
|
||||||
("screenshot-merged-screen-not-supported-tip", ""),
|
("screenshot-merged-screen-not-supported-tip", "Mesclar a captura de tela de múltiplos monitores não é suportada no momento. Por favor, alterne para um único monitor e tente novamente."),
|
||||||
("screenshot-action-tip", ""),
|
("screenshot-action-tip", "Por favor, selecione como seguir com a captura de tela."),
|
||||||
("Save as", "Salvar como"),
|
("Save as", "Salvar como"),
|
||||||
("Copy to clipboard", "Copiar para área de transferência"),
|
("Copy to clipboard", "Copiar para área de transferência"),
|
||||||
("Enable remote printer", "Habilitar impressora remota"),
|
("Enable remote printer", "Habilitar impressora remota"),
|
||||||
("Downloading {}", ""),
|
("Downloading {}", "Baixando {}"),
|
||||||
("{} Update", ""),
|
("{} Update", "Atualização do {}"),
|
||||||
("{}-to-update-tip", ""),
|
("{}-to-update-tip", "{} será fechado agora para instalar a nova versão."),
|
||||||
("download-new-version-failed-tip", "Falha no download. Você pode tentar novamente ou clicar no botão \"Download\" para baixar da página releases e atualizar manualmente."),
|
("download-new-version-failed-tip", "Falha no download. Você pode tentar novamente ou clicar no botão \"Download\" para baixar da página releases e atualizar manualmente."),
|
||||||
("Auto update", "Atualização automática"),
|
("Auto update", "Atualização automática"),
|
||||||
("update-failed-check-msi-tip", "Falha na verificação do método de instalação. Clique no botão \"Download\" para baixar da página releases e atualizar manualmente."),
|
("update-failed-check-msi-tip", "Falha na verificação do método de instalação. Clique no botão \"Download\" para baixar da página releases e atualizar manualmente."),
|
||||||
("websocket_tip", "Usando WebSocket, apenas conexões via relay são suportadas."),
|
("websocket_tip", "Usando WebSocket, apenas conexões via relay são suportadas."),
|
||||||
("Use WebSocket", "Usar WebSocket"),
|
("Use WebSocket", "Usar WebSocket"),
|
||||||
("Trackpad speed", "Velocidade do trackpad"),
|
("Trackpad speed", "Velocidade do trackpad"),
|
||||||
("Default trackpad speed", ""),
|
("Default trackpad speed", "Velocidade padrão do trackpad"),
|
||||||
("Numeric one-time password", "Senha numérica de uso único"),
|
("Numeric one-time password", "Senha numérica de uso único"),
|
||||||
("Enable IPv6 P2P connection", "Habilitar conexão IPv6 P2P"),
|
("Enable IPv6 P2P connection", "Habilitar conexão IPv6 P2P"),
|
||||||
("Enable UDP hole punching", "Habilitar UDP hole punching"),
|
("Enable UDP hole punching", "Habilitar UDP hole punching"),
|
||||||
@@ -717,11 +717,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Virtual mouse size", "Tamanho do mouse virtual"),
|
("Virtual mouse size", "Tamanho do mouse virtual"),
|
||||||
("Small", "Pequeno"),
|
("Small", "Pequeno"),
|
||||||
("Large", "Grande"),
|
("Large", "Grande"),
|
||||||
("Show virtual joystick", ""),
|
("Show virtual joystick", "Mostrar joystick virtual"),
|
||||||
("Edit note", "Editar nota"),
|
("Edit note", "Editar nota"),
|
||||||
("Alias", "Apelido"),
|
("Alias", "Apelido"),
|
||||||
("ScrollEdge", "Rolagem nas bordas"),
|
("ScrollEdge", "Rolagem nas bordas"),
|
||||||
("Allow insecure TLS fallback", ""),
|
("Allow insecure TLS fallback", "Permitir fallback TLS inseguro"),
|
||||||
("allow-insecure-tls-fallback-tip", "Por padrão, o RustDesk verifica o certificado do servidor para protocolos que usam TLS.\nCom esta opção habilitada, o RustDesk ignorará a verificação e prosseguirá em caso de falha."),
|
("allow-insecure-tls-fallback-tip", "Por padrão, o RustDesk verifica o certificado do servidor para protocolos que usam TLS.\nCom esta opção habilitada, o RustDesk ignorará a verificação e prosseguirá em caso de falha."),
|
||||||
("Disable UDP", "Desabilitar UDP"),
|
("Disable UDP", "Desabilitar UDP"),
|
||||||
("disable-udp-tip", "Controla se deve usar somente TCP.\nCom esta opção habilitada, o RustDesk não usará mais UDP 21116, TCP 21116 será usado no lugar."),
|
("disable-udp-tip", "Controla se deve usar somente TCP.\nCom esta opção habilitada, o RustDesk não usará mais UDP 21116, TCP 21116 será usado no lugar."),
|
||||||
|
|||||||
@@ -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