tauri-apps / tauri

Build smaller, faster, and more secure desktop and mobile applications with a web frontend.
https://tauri.app
Apache License 2.0
85.95k stars 2.6k forks source link

[feat] Disabling standard browser keyboard shortcuts #7418

Open f1nked opened 1 year ago

f1nked commented 1 year ago

Describe the problem

I didn't find a way to do this in tauri.conf.json. Perhaps this can be done in some other way.

List of browser keyboard shortcuts:

https://support.microsoft.com/en-us/microsoft-edge/keyboard-shortcuts-in-microsoft-edge-50d3edab-30d9-c7e4-21ce-37fe2713cfad

Describe the solution you'd like

The ability to set up a keyboard shortcut in tauri.conf.json seems to me the most convenient way to solve the problem.

Alternatives considered

For now, I'm assuming that this can only be achieved with addEventListener per combination.

Additional context

No response

prestomation commented 1 year ago

For windows, check out this discord thread:

https://discord.com/channels/616186924390023171/1126997012647264306/1128359828453085274

MohammadElokour commented 10 months ago

@f1nked any findings on this matter? @prestomation I can't seem to open the thread link, could you share the discord server its coming from? I might need to join it first

FabianLars commented 10 months ago

It links to tauri's discord https://discord.com/invite/tauri

FabianLars commented 10 months ago

Here's a copy of said thread:

Disable methods that create extra webview2 windows

prestomation OP - 08.07.2023 00:03
On Windows there are a number of ways to easily open standalone webview2 windows that are not tauri-controlled.

    'ctrl-u' will always open a webview2 window with a 'view-source' URI. it doesn't actually work because it doesn't understand https://tauri.localhost/. But from here you can also open the dev tools and navigate to other websites.
    The right-click 'Share' menu will open a webview2 window if you select a web target like 'gmail'.

There may be others.

Is there anyway for Tauri to disable these windows whole sale?

Is there a workaround to disable ctrl-u and remove these context menus we don't want?

FabianLars - 08.07.2023 11:17
for ctrl-u there's this setting which unfortunetely disables many potentially useful shortcuts depending on your app: https://learn.microsoft.com/en-us/dotnet/api/microsoft.web.webview2.core.corewebview2settings.arebrowseracceleratorkeysenabled?view=webview2-dotnet-1.0.1823.32.

The easiest solution for the share menu is to just disable the context menu, though the webview2 team also added an api to remove specific entries of it https://learn.microsoft.com/en-us/microsoft-edge/webview2/how-to/context-menus?tabs=csharp#example-removing-menu-items-from-a-default-context-menu

Also, there's a "NewWindowRequested" event which in theory sounds exactly like what you asked for but last time i tested it it didn't work reliably for requests from outside the webview contents :/

prestomation OP - 10.07.2023 21:08
thanks for the pointers! For anyone stumbling across this later, I found sample code for NewWindowRequested in this previous thread: https://discord.com/channels/616186924390023171/1050063362085625906/1050063362085625906

prestomation OP - 11.07.2023 18:19
I ended up disabling acceleratorkeys. some code:
Weirdly, this works for my main window but ctrl-u still works on some secondary windows. I can't figure out why.  The same looping for adjusting the context menu works for all

pub fn configure_webview_shortcuts(app: &AppHandle) {
    for (_, window) in app.windows().into_iter() {      
        window
            .with_webview(|webview| unsafe {
                let core_webview: webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2 =
                    webview.controller().CoreWebView2().unwrap().cast::<ICoreWebView2>().unwrap();
                let settings = core_webview.Settings().unwrap().cast::<ICoreWebView2Settings3>().unwrap();
                settings.SetAreBrowserAcceleratorKeysEnabled(false).unwrap();

            }).unwrap();
    }
    Ok(())
}
burgil commented 4 months ago

(Solved already below thanks.) Tried the example code in the thread but I was having some issues, anyone have any clue? image I tried to import it inside the scope and in global scope, it complains about it even without the use line in there I'm on tauri V2 notice I had to switch app.windows with app.webview_windows() Thanks

Here is another attempt I've made: image

I have the windows-core crate on version 0.58.0 without any features, trying the windows crate instead which is on the same version with some features:

Ok I managed to get it working...

Here is how I did it:

1) Find out the windows version tauri uses by looking for name = "windows" and picking the latest there because there's two https://github.com/tauri-apps/tauri/blob/dev/Cargo.lock https://crates.io/crates/windows https://crates.io/crates/windows-core

As of writing it the latest is 0.58.0 but tauri uses 0.57.0

2) Added the following to the Cargo.toml:

[target.'cfg(target_os = "windows")'.dependencies]
webview2-com = "0.31.0"
windows-core = "0.57.0"
windows = { version = "0.57.0", features = ["Win32_Foundation", "Win32_System_Com"] }

3) Imported that in lib.rs:

#[allow(unused_imports)]
use windows::core::Interface; // Dependency
use webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2Settings4; // Dependency

4) Finally, here is my setup: (finally running without errors and seems to be working)

        .setup(move |app| {
            // Get the window
            let Some(window) = app.get_webview_window("main") else {
                return Ok(());
            };
            #[cfg(windows)]
            let _ = window.with_webview(|webview| unsafe {
              webview.controller().SetZoomFactor(1.).unwrap();

              let icore_webview2 = webview.controller().CoreWebView2().unwrap();
              let icore_webview2_settings = icore_webview2.Settings().unwrap();

              let icore_webview2_settings4 = icore_webview2_settings.cast::<ICoreWebView2Settings4>().unwrap();
              let _ = icore_webview2_settings4.SetIsGeneralAutofillEnabled(false);
              let _ = icore_webview2_settings4.SetIsPasswordAutosaveEnabled(false);
            });

Notice that this should let me finally access:

let _ = icore_webview2_settings3.SetAreBrowserAcceleratorKeysEnabled(false);

I hope it will work, I will need to cast it to Settings3 instead full list of settings: https://docs.rs/webview2/latest/webview2/struct.Settings5.html

So to sum it up I just modified it as follow and it worked

use webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2Settings3; // Dependency
use webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2Settings4; // Dependency
...
              let icore_webview2_settings3: ICoreWebView2Settings3 = icore_webview2_settings.cast::<ICoreWebView2Settings3>().unwrap();
              let _ = icore_webview2_settings3.SetAreBrowserAcceleratorKeysEnabled(false);
edelvarden commented 4 months ago

@f1nked, using frontend prevention is not ideal because if you want to use this hotkey, it won't work. I found a solution how to do this without losing functionality.

  1. Create a module to register/unregister hotkeys and emit the "shortcut-pressed" event to listen for it on the frontend:

shortcut_manager.rs

use tauri::{AppHandle, GlobalShortcutManager, Manager};

const SHORTCUTS: &[&str] = &[
    // Redefine default MS Edge shortcuts which described in https://support.microsoft.com/en-us/microsoft-edge/keyboard-shortcuts-in-microsoft-edge-50d3edab-30d9-c7e4-21ce-37fe2713cfad#ID0EBD=Windows
    "CmdOrCtrl+Shift+B",
    "Alt+Shift+B",
    "CmdOrCtrl+D",
    "CmdOrCtrl+Shift+D",
    "CmdOrCtrl+Shift+E",
    "Alt+D",
    "CmdOrCtrl+E",
    "Alt+E",
    "CmdOrCtrl+F",
    "Alt+F",
    "CmdOrCtrl+G",
    "CmdOrCtrl+Shift+G",
    "CmdOrCtrl+H",
    "CmdOrCtrl+Shift+I",
    "Alt+Shift+I",
    "CmdOrCtrl+J",
    "CmdOrCtrl+K",
    "CmdOrCtrl+Shift+K",
    "CmdOrCtrl+L",
    "CmdOrCtrl+Shift+L",
    "CmdOrCtrl+M",
    "CmdOrCtrl+Shift+M",
    "CmdOrCtrl+N",
    "CmdOrCtrl+Shift+N",
    "CmdOrCtrl+O",
    "CmdOrCtrl+Shift+O",
    "CmdOrCtrl+P",
    "CmdOrCtrl+Shift+P",
    "CmdOrCtrl+R",
    "CmdOrCtrl+Shift+R",
    "CmdOrCtrl+S",
    "CmdOrCtrl+T",
    "CmdOrCtrl+Shift+T",
    "Alt+Shift+T",
    "CmdOrCtrl+U",
    "CmdOrCtrl+Shift+U",
    "CmdOrCtrl+Shift+V",
    "CmdOrCtrl+W",
    "CmdOrCtrl+Shift+W",
    "CmdOrCtrl+Shift+Y",
    "CmdOrCtrl+0",
    "CmdOrCtrl+1", "CmdOrCtrl+2", "CmdOrCtrl+3", "CmdOrCtrl+4", "CmdOrCtrl+5", "CmdOrCtrl+6", "CmdOrCtrl+7", "CmdOrCtrl+8", "CmdOrCtrl+9",
    "CmdOrCtrl+Enter",
    "CmdOrCtrl+Tab",
    "CmdOrCtrl+Shift+Tab",
    "CmdOrCtrl+Backslash",
    "CmdOrCtrl+[",
    "CmdOrCtrl+]",
    "CmdOrCtrl+Shift+Delete",
    "Alt+ArrowLeft",
    "Alt+ArrowRight",
    "Alt+Home",
    "Shift+Space",
    "PageDown",
    "CmdOrCtrl+PageDown",
    "PageUp",
    "CmdOrCtrl+PageUp",
    "Shift+Tab"
];

pub fn register_shortcuts(app_handle: &AppHandle) {
    let mut shortcut_manager = app_handle.global_shortcut_manager();

    for &shortcut in SHORTCUTS {

        if let Err(err) = shortcut_manager.register(shortcut, {
            let app_handle = app_handle.clone();
            let shortcut = shortcut.to_string();
            move || {
                println!("{} pressed", shortcut);
                if let Err(err) = app_handle.emit_all("shortcut-pressed", &shortcut) {
                    println!("Error emitting shortcut event: {}", err);
                }
            }
        }) {
            println!("Error registering shortcut {}: {}", shortcut, err);
        }
    }
}

pub fn unregister_shortcuts(app_handle: &AppHandle) {
    let mut shortcut_manager = app_handle.global_shortcut_manager();

    for &shortcut in SHORTCUTS {
        if let Err(err) = shortcut_manager.unregister(shortcut) {
            println!("Error unregistering shortcut {}: {}", shortcut, err);
        }
    }
}
  1. Add shortcut_manager.rs module to main.rs, making shortcuts available only when the app window is focused and unregistering it on blur:

main.rs

...
        .on_window_event(|event| {
            let app_handle = event.window().app_handle();
            let window_id = event.window().label();

            if window_id == "main" {
                match event.event() {
                    // Dynamicly register shortcut to make it available only from focused main window and unregister on blur
                    tauri::WindowEvent::Focused(true) => {
                        shortcut_manager::register_shortcuts(&app_handle);
                    }
                    tauri::WindowEvent::Focused(false) => {
                        shortcut_manager::unregister_shortcuts(&app_handle);
                    }
                    _ => {}
                }
            }
        })
...
  1. Then, you can listen for shortcut presses in any frontend part (using SolidJS in the example below):

App.tsx

import { createEffect, onCleanup } from 'solid-js'
import { Event, listen } from '@tauri-apps/api/event'
...
  // Handler function for shortcut events
  const handleShortcutPressed = (event: Event<any>) => {
    const payload = event.payload as string
    console.log(`${payload} was pressed`)
    if (payload === 'CmdOrCtrl+P') {
      // Action on Ctrl + P
    }
    if (payload === 'CmdOrCtrl+Shift+P') {
      // Action on Ctrl + Shift + P
    }
  }

  createEffect(() => {
    // Register the event listener
    const unlisten = listen("shortcut-pressed", handleShortcutPressed)

    // Cleanup the listener on effect cleanup
    onCleanup(() => {
      unlisten.then(fn => fn())
    })
  })
...

P. S. I'll leave this here, it might be useful for those who encounter the same issue.