├── .gitignore
├── .changes
├── msrv.md
├── config.json
└── readme.md
├── renovate.json
├── .github
└── workflows
│ ├── covector-status.yml
│ ├── audit.yml
│ ├── test.yml
│ ├── clippy-fmt.yml
│ └── covector-version-or-publish.yml
├── src
├── platform_impl
│ ├── mod.rs
│ ├── no-op.rs
│ ├── macos
│ │ ├── ffi.rs
│ │ └── mod.rs
│ ├── windows
│ │ └── mod.rs
│ └── x11
│ │ └── mod.rs
├── error.rs
├── lib.rs
└── hotkey.rs
├── LICENSE.spdx
├── LICENSE-MIT
├── README.md
├── Cargo.toml
├── examples
├── tao.rs
├── egui.rs
├── winit.rs
└── iced.rs
├── CHANGELOG.md
└── LICENSE-APACHE
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | /.vscode
--------------------------------------------------------------------------------
/.changes/msrv.md:
--------------------------------------------------------------------------------
1 | ---
2 | global-hotkey: minor
3 | ---
4 |
5 | Increased MSRV to `1.77`.
6 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "extends": ["config:base", ":disableDependencyDashboard"]
4 | }
--------------------------------------------------------------------------------
/.github/workflows/covector-status.yml:
--------------------------------------------------------------------------------
1 | # Copyright 2022-2022 Tauri Programme within The Commons Conservancy
2 | # SPDX-License-Identifier: Apache-2.0
3 | # SPDX-License-Identifier: MIT
4 |
5 | name: covector status
6 | on: [pull_request]
7 |
8 | jobs:
9 | covector:
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - uses: actions/checkout@v4
14 | - name: covector status
15 | uses: jbolda/covector/packages/action@covector-v0
16 | id: covector
17 | with:
18 | command: "status"
19 | token: ${{ secrets.GITHUB_TOKEN }}
20 | comment: true
--------------------------------------------------------------------------------
/.github/workflows/audit.yml:
--------------------------------------------------------------------------------
1 | # Copyright 2022-2022 Tauri Programme within The Commons Conservancy
2 | # SPDX-License-Identifier: Apache-2.0
3 | # SPDX-License-Identifier: MIT
4 |
5 | name: audit
6 |
7 | on:
8 | workflow_dispatch:
9 | schedule:
10 | - cron: '0 0 * * *'
11 | push:
12 | branches:
13 | - dev
14 | paths:
15 | - 'Cargo.lock'
16 | - 'Cargo.toml'
17 | pull_request:
18 | paths:
19 | - 'Cargo.lock'
20 | - 'Cargo.toml'
21 |
22 | concurrency:
23 | group: ${{ github.workflow }}-${{ github.ref }}
24 | cancel-in-progress: true
25 |
26 | jobs:
27 | audit:
28 | runs-on: ubuntu-latest
29 | steps:
30 | - uses: actions/checkout@v4
31 | - uses: rustsec/audit-check@v2
32 | with:
33 | token: ${{ secrets.GITHUB_TOKEN }}
34 |
--------------------------------------------------------------------------------
/src/platform_impl/mod.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2022 Tauri Programme within The Commons Conservancy
2 | // SPDX-License-Identifier: Apache-2.0
3 | // SPDX-License-Identifier: MIT
4 |
5 | #[cfg(target_os = "windows")]
6 | #[path = "windows/mod.rs"]
7 | mod platform;
8 | #[cfg(any(
9 | target_os = "linux",
10 | target_os = "dragonfly",
11 | target_os = "freebsd",
12 | target_os = "openbsd",
13 | target_os = "netbsd"
14 | ))]
15 | #[path = "x11/mod.rs"]
16 | mod platform;
17 | #[cfg(target_os = "macos")]
18 | #[path = "macos/mod.rs"]
19 | mod platform;
20 |
21 | #[cfg(not(any(
22 | target_os = "windows",
23 | target_os = "linux",
24 | target_os = "dragonfly",
25 | target_os = "freebsd",
26 | target_os = "openbsd",
27 | target_os = "netbsd",
28 | target_os = "macos"
29 | )))]
30 | #[path = "no-op.rs"]
31 | mod platform;
32 |
33 | pub(crate) use self::platform::*;
34 |
--------------------------------------------------------------------------------
/LICENSE.spdx:
--------------------------------------------------------------------------------
1 | SPDXVersion: SPDX-2.1
2 | DataLicense: CC0-1.0
3 | PackageName: global-hotkey
4 | DataFormat: SPDXRef-1
5 | PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy
6 | PackageHomePage: https://tauri.app
7 | PackageLicenseDeclared: Apache-2.0
8 | PackageLicenseDeclared: MIT
9 | PackageCopyrightText: 2020-2022, The Tauri Programme in the Commons Conservancy
10 | PackageSummary: Menu Utilities for Desktop Applications.
11 |
12 | PackageComment: The package includes the following libraries; see
13 | Relationship information.
14 |
15 | Created: 2022-12-05T09:00:00Z
16 | PackageDownloadLocation: git://github.com/tauri-apps/global-hotkey
17 | PackageDownloadLocation: git+https://github.com/tauri-apps/global-hotkey.git
18 | PackageDownloadLocation: git+ssh://github.com/tauri-apps/global-hotkey.git
19 | Creator: Person: Daniel Thompson-Yvetot
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | # Copyright 2022-2022 Tauri Programme within The Commons Conservancy
2 | # SPDX-License-Identifier: Apache-2.0
3 | # SPDX-License-Identifier: MIT
4 |
5 | name: test
6 |
7 | on:
8 | push:
9 | branches:
10 | - dev
11 | pull_request:
12 |
13 | env:
14 | RUST_BACKTRACE: 1
15 |
16 | concurrency:
17 | group: ${{ github.workflow }}-${{ github.ref }}
18 | cancel-in-progress: true
19 |
20 | jobs:
21 | test:
22 | strategy:
23 | fail-fast: false
24 | matrix:
25 | platform: ["windows-latest", "macos-latest", "ubuntu-latest"]
26 |
27 | runs-on: ${{ matrix.platform }}
28 |
29 | steps:
30 | - uses: actions/checkout@v4
31 |
32 | - name: install system deps
33 | if: matrix.platform == 'ubuntu-latest'
34 | run: |
35 | sudo apt-get update
36 | sudo apt-get install -y libgtk-3-dev libxdo-dev
37 |
38 | - uses: dtolnay/rust-toolchain@1.77
39 | - run: cargo build
40 |
41 | - uses: dtolnay/rust-toolchain@stable
42 | - run: cargo test --all-features
43 |
--------------------------------------------------------------------------------
/src/platform_impl/no-op.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2022 Tauri Programme within The Commons Conservancy
2 | // SPDX-License-Identifier: Apache-2.0
3 | // SPDX-License-Identifier: MIT
4 | // Copyright 2022-2022 Tauri Programme within The Commons Conservancy
5 | // SPDX-License-Identifier: Apache-2.0
6 | // SPDX-License-Identifier: MIT
7 |
8 | use crate::hotkey::HotKey;
9 |
10 | pub struct GlobalHotKeyManager {}
11 |
12 | impl GlobalHotKeyManager {
13 | pub fn new() -> crate::Result {
14 | Ok(Self {})
15 | }
16 |
17 | pub fn register(&self, hotkey: HotKey) -> crate::Result<()> {
18 | Ok(())
19 | }
20 |
21 | pub fn unregister(&self, hotkey: HotKey) -> crate::Result<()> {
22 | Ok(())
23 | }
24 |
25 | pub fn register_all(&self, hotkeys: &[HotKey]) -> crate::Result<()> {
26 | for hotkey in hotkeys {
27 | self.register(*hotkey)?;
28 | }
29 | Ok(())
30 | }
31 |
32 | pub fn unregister_all(&self, hotkeys: &[HotKey]) -> crate::Result<()> {
33 | for hotkey in hotkeys {
34 | self.unregister(*hotkey)?;
35 | }
36 | Ok(())
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/LICENSE-MIT:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022-2022 Tauri Programme within The Commons Conservancy
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.github/workflows/clippy-fmt.yml:
--------------------------------------------------------------------------------
1 | # Copyright 2022-2022 Tauri Programme within The Commons Conservancy
2 | # SPDX-License-Identifier: Apache-2.0
3 | # SPDX-License-Identifier: MIT
4 |
5 | name: clippy & fmt
6 |
7 | on:
8 | push:
9 | branches:
10 | - dev
11 | pull_request:
12 |
13 | concurrency:
14 | group: ${{ github.workflow }}-${{ github.ref }}
15 | cancel-in-progress: true
16 |
17 | jobs:
18 | clippy:
19 | strategy:
20 | fail-fast: false
21 | matrix:
22 | platform: [ubuntu-latest, macos-latest, windows-latest]
23 |
24 | runs-on: ${{ matrix.platform }}
25 |
26 | steps:
27 | - uses: actions/checkout@v4
28 | - name: install system deps
29 | if: matrix.platform == 'ubuntu-latest'
30 | run: |
31 | sudo apt-get update
32 | sudo apt-get install -y libgtk-3-dev libxdo-dev
33 |
34 | - uses: dtolnay/rust-toolchain@stable
35 | with:
36 | components: clippy
37 |
38 | - run: cargo clippy --all-targets --all-features -- -D warnings
39 |
40 | fmt:
41 | runs-on: ubuntu-latest
42 | steps:
43 | - uses: actions/checkout@v4
44 | - uses: dtolnay/rust-toolchain@stable
45 | with:
46 | components: rustfmt
47 |
48 | - run: cargo fmt --all -- --check
49 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | global_hotkey lets you register Global HotKeys for Desktop Applications.
2 |
3 | ## Platforms-supported:
4 |
5 | - Windows
6 | - macOS
7 | - Linux (X11 Only)
8 |
9 | ## Platform-specific notes:
10 |
11 | - On Windows a win32 event loop must be running on the thread. It doesn't need to be the main thread but you have to create the global hotkey manager on the same thread as the event loop.
12 | - On macOS, an event loop must be running on the main thread so you also need to create the global hotkey manager on the main thread.
13 |
14 | ## Example
15 |
16 | ```rs
17 | use global_hotkey::{GlobalHotKeyManager, hotkey::{HotKey, Modifiers, Code}};
18 |
19 | // initialize the hotkeys manager
20 | let manager = GlobalHotKeyManager::new().unwrap();
21 |
22 | // construct the hotkey
23 | let hotkey = HotKey::new(Some(Modifiers::SHIFT), Code::KeyD);
24 |
25 | // register it
26 | manager.register(hotkey);
27 | ```
28 |
29 | ## Processing global hotkey events
30 |
31 | You can also listen for the menu events using `GlobalHotKeyEvent::receiver` to get events for the hotkey pressed events.
32 |
33 | ```rs
34 | use global_hotkey::GlobalHotKeyEvent;
35 |
36 | if let Ok(event) = GlobalHotKeyEvent::receiver().try_recv() {
37 | println!("{:?}", event);
38 | }
39 | ```
40 |
41 | ## License
42 |
43 | Apache-2.0/MIT
44 |
--------------------------------------------------------------------------------
/src/error.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2022 Tauri Programme within The Commons Conservancy
2 | // SPDX-License-Identifier: Apache-2.0
3 | // SPDX-License-Identifier: MIT
4 |
5 | use thiserror::Error;
6 |
7 | use crate::hotkey::HotKey;
8 |
9 | /// Errors returned by tray-icon.
10 | #[non_exhaustive]
11 | #[derive(Error, Debug)]
12 | pub enum Error {
13 | #[error(transparent)]
14 | OsError(#[from] std::io::Error),
15 | #[error("{0}")]
16 | HotKeyParseError(String),
17 | #[error("Couldn't recognize \"{0}\" as a valid HotKey Code, if you feel like it should be, please report this to https://github.com/tauri-apps/global-hotkey")]
18 | UnrecognizedHotKeyCode(String),
19 | #[error("Unexpected empty token while parsing hotkey: \"{0}\"")]
20 | EmptyHotKeyToken(String),
21 | #[error("Unexpected hotkey string format: \"{0}\", a hotkey should have the modifiers first and only contain one main key")]
22 | UnexpectedHotKeyFormat(String),
23 | #[error("Unable to register hotkey: {0}")]
24 | FailedToRegister(String),
25 | #[error("Failed to unregister hotkey: {0:?}")]
26 | FailedToUnRegister(HotKey),
27 | #[error("HotKey already registered: {0:?}")]
28 | AlreadyRegistered(HotKey),
29 | #[error("Failed to watch media key event")]
30 | FailedToWatchMediaKeyEvent,
31 | }
32 |
33 | /// Convenient type alias of Result type for tray-icon.
34 | pub type Result = std::result::Result;
35 |
--------------------------------------------------------------------------------
/.changes/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "gitSiteUrl": "https://www.github.com/tauri-apps/global-hotkey/",
3 | "timeout": 3600000,
4 | "pkgManagers": {
5 | "rust": {
6 | "version": true,
7 | "getPublishedVersion": "cargo search ${ pkg.pkg } --limit 1 | sed -nE 's/^[^\"]*\"//; s/\".*//1p' -",
8 | "publish": [
9 | {
10 | "command": "cargo package --no-verify",
11 | "dryRunCommand": true
12 | },
13 | {
14 | "command": "echo '\nCargo Publish
\n\n```'",
15 | "dryRunCommand": true,
16 | "pipe": true
17 | },
18 | {
19 | "command": "cargo publish",
20 | "dryRunCommand": "cargo publish --dry-run",
21 | "pipe": true
22 | },
23 | {
24 | "command": "echo '```\n\n \n'",
25 | "dryRunCommand": true,
26 | "pipe": true
27 | }
28 | ],
29 | "postpublish": [
30 | "git tag ${ pkg.pkg }-v${ pkgFile.versionMajor } -f",
31 | "git tag ${ pkg.pkg }-v${ pkgFile.versionMajor }.${ pkgFile.versionMinor } -f",
32 | "git push --tags -f"
33 | ]
34 | }
35 | },
36 | "packages": {
37 | "global-hotkey": {
38 | "path": ".",
39 | "manager": "rust",
40 | "assets": [
41 | {
42 | "path": "${ pkg.path }/target/package/global-hotkey-${ pkgFile.version }.crate",
43 | "name": "${ pkg.pkg }-${ pkgFile.version }.crate"
44 | }
45 | ]
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "global-hotkey"
3 | version = "0.7.0"
4 | description = "Global hotkeys for Desktop Applications"
5 | edition = "2021"
6 | keywords = ["windowing", "global", "global-hotkey", "hotkey"]
7 | license = "Apache-2.0 OR MIT"
8 | readme = "README.md"
9 | repository = "https://github.com/tauri-apps/global-hotkey"
10 | documentation = "https://docs.rs/global-hotkey"
11 | categories = ["gui"]
12 | rust-version = "1.77"
13 |
14 | [features]
15 | serde = ["dep:serde"]
16 | tracing = ["dep:tracing"]
17 |
18 | [dependencies]
19 | crossbeam-channel = "0.5"
20 | keyboard-types = "0.7"
21 | once_cell = "1"
22 | thiserror = "2"
23 | serde = { version = "1", optional = true, features = ["derive"] }
24 | tracing = { version = "0.1", optional = true }
25 |
26 | [target.'cfg(target_os = "macos")'.dependencies]
27 | objc2 = "0.6.0"
28 | objc2-app-kit = { version = "0.3.0", default-features = false, features = [
29 | "std",
30 | "NSEvent",
31 | ] }
32 |
33 | [target.'cfg(target_os = "windows")'.dependencies.windows-sys]
34 | version = "0.59"
35 | features = [
36 | "Win32_UI_WindowsAndMessaging",
37 | "Win32_Foundation",
38 | "Win32_System_SystemServices",
39 | "Win32_Graphics_Gdi",
40 | "Win32_UI_Shell",
41 | "Win32_UI_Input_KeyboardAndMouse",
42 | ]
43 |
44 | [target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies]
45 | x11rb = { version = "0.13.1", features = ["xkb"] }
46 | xkeysym = "0.2.1"
47 |
48 | [dev-dependencies]
49 | winit = "0.30"
50 | tao = "0.30"
51 | eframe = "0.27"
52 | iced = "0.13.1"
53 | async-std = "1.12.0"
54 |
--------------------------------------------------------------------------------
/.changes/readme.md:
--------------------------------------------------------------------------------
1 | # Changes
2 |
3 | ##### via https://github.com/jbolda/covector
4 |
5 | As you create PRs and make changes that require a version bump, please add a new markdown file in this folder. You do not note the version _number_, but rather the type of bump that you expect: major, minor, or patch. The filename is not important, as long as it is a `.md`, but we recommend it represents the overall change for our sanity.
6 |
7 | When you select the version bump required, you do _not_ need to consider dependencies. Only note the package with the actual change, and any packages that depend on that package will be bumped automatically in the process.
8 |
9 | Use the following format:
10 |
11 | ```md
12 | ---
13 | "global-hotkey": patch
14 | ---
15 |
16 | Change summary goes here
17 | ```
18 |
19 | Summaries do not have a specific character limit, but are text only. These summaries are used within the (future implementation of) changelogs. They will give context to the change and also point back to the original PR if more details and context are needed.
20 |
21 | Changes will be designated as a `major`, `minor` or `patch` as further described in [semver](https://semver.org/).
22 |
23 | Given a version number MAJOR.MINOR.PATCH, increment the:
24 |
25 | - MAJOR version when you make incompatible API changes,
26 | - MINOR version when you add functionality in a backwards compatible manner, and
27 | - PATCH version when you make backwards compatible bug fixes.
28 |
29 | Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format, but will be discussed prior to usage (as extra steps will be necessary in consideration of merging and publishing).
30 |
--------------------------------------------------------------------------------
/examples/tao.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2022 Tauri Programme within The Commons Conservancy
2 | // SPDX-License-Identifier: Apache-2.0
3 | // SPDX-License-Identifier: MIT
4 |
5 | use global_hotkey::{
6 | hotkey::{Code, HotKey, Modifiers},
7 | GlobalHotKeyEvent, GlobalHotKeyManager, HotKeyState,
8 | };
9 | use tao::event_loop::{ControlFlow, EventLoopBuilder};
10 |
11 | fn main() {
12 | let event_loop = EventLoopBuilder::new().build();
13 |
14 | let hotkeys_manager = GlobalHotKeyManager::new().unwrap();
15 |
16 | let hotkey = HotKey::new(Some(Modifiers::SHIFT), Code::KeyD);
17 | let hotkey2 = HotKey::new(Some(Modifiers::SHIFT | Modifiers::ALT), Code::KeyD);
18 | let hotkey3 = HotKey::new(None, Code::KeyF);
19 | let hotkey4 = {
20 | #[cfg(target_os = "macos")]
21 | {
22 | HotKey::new(
23 | Some(Modifiers::SHIFT | Modifiers::ALT),
24 | Code::MediaPlayPause,
25 | )
26 | }
27 | #[cfg(not(target_os = "macos"))]
28 | {
29 | HotKey::new(Some(Modifiers::SHIFT | Modifiers::ALT), Code::MediaPlay)
30 | }
31 | };
32 |
33 | hotkeys_manager.register(hotkey).unwrap();
34 | hotkeys_manager.register(hotkey2).unwrap();
35 | hotkeys_manager.register(hotkey3).unwrap();
36 | hotkeys_manager.register(hotkey4).unwrap();
37 |
38 | let global_hotkey_channel = GlobalHotKeyEvent::receiver();
39 |
40 | event_loop.run(move |_event, _, control_flow| {
41 | *control_flow = ControlFlow::Poll;
42 |
43 | if let Ok(event) = global_hotkey_channel.try_recv() {
44 | println!("{event:?}");
45 |
46 | if hotkey2.id() == event.id && event.state == HotKeyState::Released {
47 | hotkeys_manager.unregister(hotkey2).unwrap();
48 | }
49 | }
50 | })
51 | }
52 |
--------------------------------------------------------------------------------
/examples/egui.rs:
--------------------------------------------------------------------------------
1 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
2 |
3 | use std::time::Duration;
4 |
5 | use eframe::egui;
6 | use global_hotkey::{hotkey::HotKey, GlobalHotKeyEvent, GlobalHotKeyManager};
7 | use keyboard_types::{Code, Modifiers};
8 |
9 | fn main() -> Result<(), eframe::Error> {
10 | let manager = GlobalHotKeyManager::new().unwrap();
11 | let hotkey = HotKey::new(Some(Modifiers::SHIFT), Code::KeyD);
12 |
13 | manager.register(hotkey).unwrap();
14 | let receiver = GlobalHotKeyEvent::receiver();
15 | std::thread::spawn(|| loop {
16 | if let Ok(event) = receiver.try_recv() {
17 | println!("tray event: {event:?}");
18 | }
19 | std::thread::sleep(Duration::from_millis(100));
20 | });
21 |
22 | eframe::run_native(
23 | "My egui App",
24 | eframe::NativeOptions::default(),
25 | Box::new(|_cc| Box::::default()),
26 | )
27 | }
28 |
29 | struct MyApp {
30 | name: String,
31 | age: u32,
32 | }
33 |
34 | impl Default for MyApp {
35 | fn default() -> Self {
36 | Self {
37 | name: "Arthur".to_owned(),
38 | age: 42,
39 | }
40 | }
41 | }
42 |
43 | impl eframe::App for MyApp {
44 | fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
45 | egui::CentralPanel::default().show(ctx, |ui| {
46 | ui.heading("My egui Application");
47 | ui.horizontal(|ui| {
48 | let name_label = ui.label("Your name: ");
49 | ui.text_edit_singleline(&mut self.name)
50 | .labelled_by(name_label.id);
51 | });
52 | ui.add(egui::Slider::new(&mut self.age, 0..=120).text("age"));
53 | if ui.button("Click each year").clicked() {
54 | self.age += 1;
55 | }
56 | ui.label(format!("Hello '{}', age {}", self.name, self.age));
57 | });
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/.github/workflows/covector-version-or-publish.yml:
--------------------------------------------------------------------------------
1 | # Copyright 2022-2022 Tauri Programme within The Commons Conservancy
2 | # SPDX-License-Identifier: Apache-2.0
3 | # SPDX-License-Identifier: MIT
4 |
5 | name: covector version or publish
6 |
7 | on:
8 | push:
9 | branches:
10 | - dev
11 |
12 | jobs:
13 | version-or-publish:
14 | runs-on: ubuntu-latest
15 | timeout-minutes: 65
16 | outputs:
17 | change: ${{ steps.covector.outputs.change }}
18 | commandRan: ${{ steps.covector.outputs.commandRan }}
19 | successfulPublish: ${{ steps.covector.outputs.successfulPublish }}
20 |
21 | steps:
22 | - uses: actions/checkout@v4
23 | with:
24 | fetch-depth: 0
25 |
26 | - name: cargo login
27 | run: cargo login ${{ secrets.ORG_CRATES_IO_TOKEN }}
28 |
29 | - name: git config
30 | run: |
31 | git config --global user.name "${{ github.event.pusher.name }}"
32 | git config --global user.email "${{ github.event.pusher.email }}"
33 |
34 | - name: covector version or publish (publish when no change files present)
35 | uses: jbolda/covector/packages/action@covector-v0
36 | id: covector
37 | env:
38 | NODE_AUTH_TOKEN: ${{ secrets.ORG_NPM_TOKEN }}
39 | with:
40 | token: ${{ secrets.GITHUB_TOKEN }}
41 | command: 'version-or-publish'
42 | createRelease: true
43 | recognizeContributors: true
44 |
45 | - name: Sync Cargo.lock
46 | if: steps.covector.outputs.commandRan == 'version'
47 | run: cargo tree --depth 0
48 |
49 | - name: Create Pull Request With Versions Bumped
50 | if: steps.covector.outputs.commandRan == 'version'
51 | uses: tauri-apps/create-pull-request@v3
52 | with:
53 | token: ${{ secrets.GITHUB_TOKEN }}
54 | title: Apply Version Updates From Current Changes
55 | commit-message: 'apply version updates'
56 | labels: 'version updates'
57 | branch: 'release'
58 | body: ${{ steps.covector.outputs.change }}
59 |
--------------------------------------------------------------------------------
/examples/winit.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2022 Tauri Programme within The Commons Conservancy
2 | // SPDX-License-Identifier: Apache-2.0
3 | // SPDX-License-Identifier: MIT
4 |
5 | use global_hotkey::{
6 | hotkey::{Code, HotKey, Modifiers},
7 | GlobalHotKeyEvent, GlobalHotKeyManager, HotKeyState,
8 | };
9 | use winit::{
10 | application::ApplicationHandler,
11 | event::WindowEvent,
12 | event_loop::{ActiveEventLoop, EventLoop},
13 | window::WindowId,
14 | };
15 |
16 | fn main() {
17 | let hotkeys_manager = GlobalHotKeyManager::new().unwrap();
18 |
19 | let hotkey = HotKey::new(Some(Modifiers::SHIFT), Code::KeyD);
20 | let hotkey2 = HotKey::new(Some(Modifiers::SHIFT | Modifiers::ALT), Code::KeyD);
21 | let hotkey3 = HotKey::new(None, Code::KeyF);
22 |
23 | hotkeys_manager.register(hotkey).unwrap();
24 | hotkeys_manager.register(hotkey2).unwrap();
25 | hotkeys_manager.register(hotkey3).unwrap();
26 |
27 | let event_loop = EventLoop::::with_user_event().build().unwrap();
28 | let proxy = event_loop.create_proxy();
29 |
30 | GlobalHotKeyEvent::set_event_handler(Some(move |event| {
31 | let _ = proxy.send_event(AppEvent::HotKey(event));
32 | }));
33 |
34 | let mut app = App {
35 | hotkeys_manager,
36 | hotkey2,
37 | };
38 |
39 | event_loop.run_app(&mut app).unwrap()
40 | }
41 |
42 | #[derive(Debug)]
43 | enum AppEvent {
44 | HotKey(GlobalHotKeyEvent),
45 | }
46 |
47 | struct App {
48 | hotkeys_manager: GlobalHotKeyManager,
49 | hotkey2: HotKey,
50 | }
51 |
52 | impl ApplicationHandler for App {
53 | fn resumed(&mut self, _event_loop: &ActiveEventLoop) {}
54 |
55 | fn window_event(
56 | &mut self,
57 | _event_loop: &ActiveEventLoop,
58 | _window_id: WindowId,
59 | _event: WindowEvent,
60 | ) {
61 | }
62 |
63 | fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: AppEvent) {
64 | match event {
65 | AppEvent::HotKey(event) => {
66 | println!("{event:?}");
67 |
68 | if self.hotkey2.id() == event.id && event.state == HotKeyState::Released {
69 | self.hotkeys_manager.unregister(self.hotkey2).unwrap();
70 | }
71 | }
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/examples/iced.rs:
--------------------------------------------------------------------------------
1 | use global_hotkey::hotkey::{Code, HotKey, Modifiers};
2 | use global_hotkey::{GlobalHotKeyEvent, GlobalHotKeyManager};
3 |
4 | use iced::futures::{SinkExt, Stream};
5 | use iced::stream::channel;
6 | use iced::widget::{container, row, text};
7 | use iced::{application, Element, Subscription, Task, Theme};
8 |
9 | fn main() -> iced::Result {
10 | application("Iced Example!", update, view)
11 | .subscription(subscription)
12 | .theme(|_| Theme::Dark)
13 | .run_with(new)
14 | }
15 |
16 | struct Example {
17 | last_pressed: String,
18 | // store the global manager otherwise it will be dropped and events will not be emitted
19 | _manager: GlobalHotKeyManager,
20 | }
21 |
22 | #[derive(Debug, Clone)]
23 | enum ProgramCommands {
24 | // message received when the subscription calls back to the main gui thread
25 | Received(String),
26 | }
27 |
28 | fn new() -> (Example, Task) {
29 | let manager = GlobalHotKeyManager::new().unwrap();
30 | let hotkey_1 = HotKey::new(Some(Modifiers::CONTROL), Code::ArrowRight);
31 | let hotkey_2 = HotKey::new(None, Code::ArrowUp);
32 |
33 | manager.register(hotkey_1).unwrap();
34 | manager.register(hotkey_2).unwrap();
35 |
36 | (
37 | Example {
38 | last_pressed: "".to_string(),
39 | _manager: manager,
40 | },
41 | Task::none(),
42 | )
43 | }
44 |
45 | fn update(state: &mut Example, msg: ProgramCommands) -> Task {
46 | match msg {
47 | ProgramCommands::Received(code) => {
48 | // update the text widget
49 | state.last_pressed = code.to_string();
50 |
51 | Task::none()
52 | }
53 | }
54 | }
55 |
56 | fn view(state: &Example) -> Element<'_, ProgramCommands> {
57 | container(row![
58 | text("You pressed: "),
59 | text(state.last_pressed.clone())
60 | ])
61 | .into()
62 | }
63 |
64 | fn subscription(_state: &Example) -> Subscription {
65 | Subscription::run(hotkey_sub)
66 | }
67 |
68 | fn hotkey_sub() -> impl Stream- {
69 | channel(32, |mut sender| async move {
70 | let receiver = GlobalHotKeyEvent::receiver();
71 | // poll for global hotkey events every 50ms
72 | loop {
73 | if let Ok(event) = receiver.try_recv() {
74 | sender
75 | .send(ProgramCommands::Received(format!("{:?}", event)))
76 | .await
77 | .unwrap();
78 | }
79 | async_std::task::sleep(std::time::Duration::from_millis(50)).await;
80 | }
81 | })
82 | }
83 |
--------------------------------------------------------------------------------
/src/lib.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2022 Tauri Programme within The Commons Conservancy
2 | // SPDX-License-Identifier: Apache-2.0
3 | // SPDX-License-Identifier: MIT
4 |
5 | #![allow(clippy::uninlined_format_args)]
6 |
7 | //! global_hotkey lets you register Global HotKeys for Desktop Applications.
8 | //!
9 | //! ## Platforms-supported:
10 | //!
11 | //! - Windows
12 | //! - macOS
13 | //! - Linux (X11 Only)
14 | //!
15 | //! ## Platform-specific notes:
16 | //!
17 | //! - On Windows a win32 event loop must be running on the thread. It doesn't need to be the main thread but you have to create the global hotkey manager on the same thread as the event loop.
18 | //! - On macOS, an event loop must be running on the main thread so you also need to create the global hotkey manager on the main thread.
19 | //!
20 | //! # Example
21 | //!
22 | //! ```no_run
23 | //! use global_hotkey::{GlobalHotKeyManager, hotkey::{HotKey, Modifiers, Code}};
24 | //!
25 | //! // initialize the hotkeys manager
26 | //! let manager = GlobalHotKeyManager::new().unwrap();
27 | //!
28 | //! // construct the hotkey
29 | //! let hotkey = HotKey::new(Some(Modifiers::SHIFT), Code::KeyD);
30 | //!
31 | //! // register it
32 | //! manager.register(hotkey);
33 | //! ```
34 | //!
35 | //!
36 | //! # Processing global hotkey events
37 | //!
38 | //! You can also listen for the menu events using [`GlobalHotKeyEvent::receiver`] to get events for the hotkey pressed events.
39 | //! ```no_run
40 | //! use global_hotkey::GlobalHotKeyEvent;
41 | //!
42 | //! if let Ok(event) = GlobalHotKeyEvent::receiver().try_recv() {
43 | //! println!("{:?}", event);
44 | //! }
45 | //! ```
46 | //!
47 | //! # Platforms-supported:
48 | //!
49 | //! - Windows
50 | //! - macOS
51 | //! - Linux (X11 Only)
52 |
53 | use crossbeam_channel::{unbounded, Receiver, Sender};
54 | use once_cell::sync::{Lazy, OnceCell};
55 |
56 | mod error;
57 | pub mod hotkey;
58 | mod platform_impl;
59 |
60 | pub use self::error::*;
61 | use hotkey::HotKey;
62 |
63 | /// Describes the state of the [`HotKey`].
64 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
65 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
66 | pub enum HotKeyState {
67 | /// The [`HotKey`] is pressed (the key is down).
68 | Pressed,
69 | /// The [`HotKey`] is released (the key is up).
70 | Released,
71 | }
72 |
73 | /// Describes a global hotkey event emitted when a [`HotKey`] is pressed or released.
74 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
75 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
76 | pub struct GlobalHotKeyEvent {
77 | /// Id of the associated [`HotKey`].
78 | pub id: u32,
79 | /// State of the associated [`HotKey`].
80 | pub state: HotKeyState,
81 | }
82 |
83 | /// A reciever that could be used to listen to global hotkey events.
84 | pub type GlobalHotKeyEventReceiver = Receiver;
85 | type GlobalHotKeyEventHandler = Box;
86 |
87 | static GLOBAL_HOTKEY_CHANNEL: Lazy<(Sender, GlobalHotKeyEventReceiver)> =
88 | Lazy::new(unbounded);
89 | static GLOBAL_HOTKEY_EVENT_HANDLER: OnceCell