Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support registering media play/pause/stop/next/prev keys #71

Merged
merged 4 commits into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading