Skip to content

Commit

Permalink
feat: support registering media play/pause/stop/next/prev keys (#71)
Browse files Browse the repository at this point in the history
* feat: support registering media play/pause/stop/next/prev keys

closes #70

* feat(macos): register media keys (#72)

* feat(macos): register media keys

* chore: update documents

* organize codes

* fix: using RefCell for event_tap

* docs: remove mut from GlobalHotKeyManager::new()

* use mutex and stop watching media keys on drop

---------

Co-authored-by: Jason Tsai <[email protected]>
  • Loading branch information
amrbashir and pewsheen authored Apr 23, 2024
1 parent 24f41b0 commit c530be0
Show file tree
Hide file tree
Showing 12 changed files with 427 additions and 10 deletions.
5 changes: 5 additions & 0 deletions .changes/media-keys.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"global-hotkey": "patch"
---

Support registering media play/pause/stop/next/prev keys.
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ keyboard-types = "0.7"
once_cell = "1"
thiserror = "1"

[target."cfg(target_os = \"macos\")".dependencies]
bitflags = "2"
cocoa = "0.25"
objc = "0.2"

[target."cfg(target_os = \"windows\")".dependencies.windows-sys]
version = "0.52"
features = [
Expand Down
1 change: 1 addition & 0 deletions examples/egui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use keyboard_types::{Code, Modifiers};
fn main() -> Result<(), eframe::Error> {
let manager = GlobalHotKeyManager::new().unwrap();
let hotkey = HotKey::new(Some(Modifiers::SHIFT), Code::KeyD);

manager.register(hotkey).unwrap();
let receiver = GlobalHotKeyEvent::receiver();
std::thread::spawn(|| loop {
Expand Down
3 changes: 2 additions & 1 deletion examples/iced.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ impl Application for Example {
let manager = GlobalHotKeyManager::new().unwrap();
let hotkey_1 = HotKey::new(Some(Modifiers::CONTROL), Code::ArrowRight);
let hotkey_2 = HotKey::new(None, Code::ArrowUp);
manager.register(hotkey_2).unwrap();

manager.register(hotkey_1).unwrap();
manager.register(hotkey_2).unwrap();
(
Example {
last_pressed: "".to_string(),
Expand Down
14 changes: 14 additions & 0 deletions examples/tao.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,24 @@ fn main() {
let hotkey = HotKey::new(Some(Modifiers::SHIFT), Code::KeyD);
let hotkey2 = HotKey::new(Some(Modifiers::SHIFT | Modifiers::ALT), Code::KeyD);
let hotkey3 = HotKey::new(None, Code::KeyF);
let hotkey4 = {
#[cfg(target_os = "macos")]
{
HotKey::new(
Some(Modifiers::SHIFT | Modifiers::ALT),
Code::MediaPlayPause,
)
}
#[cfg(not(target_os = "macos"))]
{
HotKey::new(Some(Modifiers::SHIFT | Modifiers::ALT), Code::MediaPlay)
}
};

hotkeys_manager.register(hotkey).unwrap();
hotkeys_manager.register(hotkey2).unwrap();
hotkeys_manager.register(hotkey3).unwrap();
hotkeys_manager.register(hotkey4).unwrap();

let global_hotkey_channel = GlobalHotKeyEvent::receiver();

Expand Down
2 changes: 2 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ pub enum Error {
FailedToUnRegister(HotKey),
#[error("HotKey already registerd: {0:?}")]
AlreadyRegistered(HotKey),
#[error("Failed to watch media key event")]
FailedToWatchMediaKeyEvent,
}

/// Convenient type alias of Result type for tray-icon.
Expand Down
6 changes: 6 additions & 0 deletions src/hotkey.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,12 @@ fn parse_key(key: &str) -> Result<Code, HotKeyParseError> {
"AUDIOVOLUMEDOWN" | "VOLUMEDOWN" => Ok(AudioVolumeDown),
"AUDIOVOLUMEUP" | "VOLUMEUP" => Ok(AudioVolumeUp),
"AUDIOVOLUMEMUTE" | "VOLUMEMUTE" => Ok(AudioVolumeMute),
"MEDIAPLAY" => Ok(MediaPlay),
"MEDIAPAUSE" => Ok(MediaPause),
"MEDIAPLAYPAUSE" => Ok(MediaPlayPause),
"MEDIASTOP" => Ok(MediaStop),
"MEDIATRACKNEXT" => Ok(MediaTrackNext),
"MEDIATRACTPREV" | "MEDIATRACTPREVIOUS" => Ok(MediaTrackPrevious),
"F13" => Ok(F13),
"F14" => Ok(F14),
"F15" => Ok(F15),
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ impl GlobalHotKeyManager {
Ok(())
}
}

#[cfg(test)]
mod tests {
fn assert_send<T: Send>() {}
Expand Down
143 changes: 143 additions & 0 deletions src/platform_impl/macos/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

/* taken from https://github.com/wusyong/carbon-bindgen/blob/467fca5d71047050b632fbdfb41b1f14575a8499/bindings.rs */

use std::ffi::{c_long, c_void};

pub type UInt32 = ::std::os::raw::c_uint;
pub type SInt32 = ::std::os::raw::c_int;
pub type OSStatus = SInt32;
Expand Down Expand Up @@ -115,3 +117,144 @@ extern "C" {
) -> OSStatus;
pub fn UnregisterEventHotKey(inHotKey: EventHotKeyRef) -> OSStatus;
}

/* Core Graphics */

/// Possible tapping points for events.
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub enum CGEventTapLocation {
Hid,
Session,
AnnotatedSession,
}

// The next three enums are taken from:
// [Ref](https://github.com/phracker/MacOSX-SDKs/blob/ef9fe35d5691b6dd383c8c46d867a499817a01b6/MacOSX10.15.sdk/System/Library/Frameworks/CoreGraphics.framework/Versions/A/Headers/CGEventTypes.h)
/* Constants that specify where a new event tap is inserted into the list of active event taps. */
#[repr(u32)]
#[derive(Clone, Copy, Debug)]
pub enum CGEventTapPlacement {
HeadInsertEventTap = 0,
TailAppendEventTap,
}

/* Constants that specify whether a new event tap is an active filter or a passive listener. */
#[repr(u32)]
#[derive(Clone, Copy, Debug)]
pub enum CGEventTapOptions {
Default = 0x00000000,
ListenOnly = 0x00000001,
}

/// Constants that specify the different types of input events.
///
/// [Ref](http://opensource.apple.com/source/IOHIDFamily/IOHIDFamily-700/IOHIDSystem/IOKit/hidsystem/IOLLEvent.h)
#[repr(u32)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CGEventType {
Null = 0,

// Mouse events.
LeftMouseDown = 1,
LeftMouseUp = 2,
RightMouseDown = 3,
RightMouseUp = 4,
MouseMoved = 5,
LeftMouseDragged = 6,
RightMouseDragged = 7,

// Keyboard events.
KeyDown = 10,
KeyUp = 11,
FlagsChanged = 12,

// Composite events.
AppKitDefined = 13,
SystemDefined = 14,
ApplicationDefined = 15,

// Specialized control devices.
ScrollWheel = 22,
TabletPointer = 23,
TabletProximity = 24,
OtherMouseDown = 25,
OtherMouseUp = 26,
OtherMouseDragged = 27,

// Out of band event types. These are delivered to the event tap callback
// to notify it of unusual conditions that disable the event tap.
TapDisabledByTimeout = 0xFFFFFFFE,
TapDisabledByUserInput = 0xFFFFFFFF,
}

pub type CGEventMask = u64;
#[macro_export]
macro_rules! CGEventMaskBit {
($eventType:expr) => {
1 << $eventType as CGEventMask
};
}

pub enum CGEvent {}
pub type CGEventRef = *const CGEvent;

pub type CGEventTapProxy = *const c_void;
type CGEventTapCallBack = unsafe extern "C" fn(
proxy: CGEventTapProxy,
etype: CGEventType,
event: CGEventRef,
user_info: *const c_void,
) -> CGEventRef;

#[link(name = "CoreGraphics", kind = "framework")]
extern "C" {
pub fn CGEventTapCreate(
tap: CGEventTapLocation,
place: CGEventTapPlacement,
options: CGEventTapOptions,
events_of_interest: CGEventMask,
callback: CGEventTapCallBack,
user_info: *const c_void,
) -> CFMachPortRef;
pub fn CGEventTapEnable(tap: CFMachPortRef, enable: bool);
}

/* Core Foundation */

pub enum CFAllocator {}
pub type CFAllocatorRef = *mut CFAllocator;
pub enum CFRunLoop {}
pub type CFRunLoopRef = *mut CFRunLoop;
pub type CFRunLoopMode = CFStringRef;
pub enum CFRunLoopObserver {}
pub type CFRunLoopObserverRef = *mut CFRunLoopObserver;
pub enum CFRunLoopTimer {}
pub type CFRunLoopTimerRef = *mut CFRunLoopTimer;
pub enum CFRunLoopSource {}
pub type CFRunLoopSourceRef = *mut CFRunLoopSource;
pub enum CFString {}
pub type CFStringRef = *const CFString;

pub enum CFMachPort {}
pub type CFMachPortRef = *mut CFMachPort;

pub type CFIndex = c_long;

#[link(name = "CoreFoundation", kind = "framework")]
extern "C" {
pub static kCFRunLoopCommonModes: CFRunLoopMode;
pub static kCFAllocatorDefault: CFAllocatorRef;

pub fn CFRunLoopGetMain() -> CFRunLoopRef;

pub fn CFMachPortCreateRunLoopSource(
allocator: CFAllocatorRef,
port: CFMachPortRef,
order: CFIndex,
) -> CFRunLoopSourceRef;
pub fn CFMachPortInvalidate(port: CFMachPortRef);
pub fn CFRunLoopAddSource(rl: CFRunLoopRef, source: CFRunLoopSourceRef, mode: CFRunLoopMode);
pub fn CFRunLoopRemoveSource(rl: CFRunLoopRef, source: CFRunLoopSourceRef, mode: CFRunLoopMode);
pub fn CFRelease(cftype: *const c_void);
}
Loading

0 comments on commit c530be0

Please sign in to comment.