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:
fufesou
2026-02-11 16:11:47 +08:00
committed by GitHub
parent 6c541f7bfd
commit 2842315b1d
9 changed files with 580 additions and 2 deletions

View File

@@ -55,6 +55,7 @@
],
"finish-args": [
"--share=ipc",
"--socket=wayland",
"--socket=x11",
"--share=network",
"--filesystem=home",

View File

@@ -2538,6 +2538,49 @@ class WaylandCard extends StatefulWidget {
class _WaylandCardState extends State<WaylandCard> {
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
Widget build(BuildContext context) {
@@ -2545,9 +2588,16 @@ class _WaylandCardState extends State<WaylandCard> {
future: bind.mainHandleWaylandScreencastRestoreToken(
key: restoreTokenKey, value: "get"),
hasData: (restoreToken) {
final hasShortcutsPermission = showResetInhibitorPermission &&
bind.mainGetCommonSync(
key: "has-gnome-shortcuts-inhibitor-permission") ==
"true";
final children = [
if (restoreToken.isNotEmpty)
_buildClearScreenSelection(context, restoreToken),
if (hasShortcutsPermission)
_buildClearShortcutsInhibitorPermission(context),
];
return Offstage(
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

View File

@@ -1,6 +1,6 @@
# Project-level configuration.
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 on-disk name of your application.
@@ -54,6 +54,55 @@ add_subdirectory(${FLUTTER_MANAGED_DIR})
find_package(PkgConfig REQUIRED)
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}")
# 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}
"main.cc"
"my_application.cc"
"wayland_shortcuts_inhibit.cc"
"bump_mouse.cc"
"bump_mouse_x11.cc"
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
${WAYLAND_PROTOCOL_SOURCES}
)
# 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 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.
add_dependencies(${BINARY_NAME} flutter_assemble)

View File

@@ -6,6 +6,11 @@
#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
#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"
@@ -91,6 +96,13 @@ static void my_application_activate(GApplication* application) {
gtk_widget_show(GTK_WIDGET(window));
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));
g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();

View 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, &registry_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)

View 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_

View File

@@ -2759,6 +2759,11 @@ pub fn main_get_common(key: String) -> String {
None => "",
}
.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 {
if key.starts_with("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" {
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(

View File

@@ -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"),
("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."),
("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."),
("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"),

View File

@@ -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
}
}
}