Last active
January 8, 2022 13:59
-
-
Save cosven/75523f1e970edcc4da8cf1908a9c1463 to your computer and use it in GitHub Desktop.
OSX global hotkeys via python.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
""" | |
Dependency:: | |
pip3 install pyobjc-framework-Cocoa | |
pip3 install pyobjc-framework-Quartz | |
Usage:: | |
python3 xx.py | |
""" | |
import logging | |
import os | |
from AppKit import NSKeyUp, NSEvent, NSBundle | |
import Quartz | |
from AppKit import NSSystemDefined | |
logger = logging.getLogger(__name__) | |
def keyboard_tap_callback(proxy, type_, event, refcon): | |
NSBundle.mainBundle().infoDictionary()['NSAppTransportSecurity'] =\ | |
dict(NSAllowsArbitraryLoads=True) | |
if type_ < 0 or type_ > 0x7fffffff: | |
logger.error('Unkown mac event') | |
run_event_loop() | |
logger.error('restart mac key board event loop') | |
return event | |
try: | |
key_event = NSEvent.eventWithCGEvent_(event) | |
except: # noqa | |
logger.info("mac event cast error") | |
return event | |
if key_event.subtype() == 8: | |
key_code = (key_event.data1() & 0xFFFF0000) >> 16 | |
key_state = (key_event.data1() & 0xFF00) >> 8 | |
if key_code in (16, 19, 20): | |
# 16 for play-pause, 19 for next, 20 for previous | |
if key_state == NSKeyUp: | |
if key_code is 19: | |
logger.info('mac hotkey: play next') | |
os.system('fuocli next') | |
elif key_code is 20: | |
logger.info('mac hotkey: play last') | |
os.system('fuocli previous') | |
elif key_code is 16: | |
logger.info('mac hotkey: play or pause') | |
os.system('fuocli toggle') | |
return None | |
return event | |
def run_event_loop(): | |
logger.info("try to load mac hotkey event loop") | |
# Set up a tap, with type of tap, location, options and event mask | |
tap = Quartz.CGEventTapCreate( | |
Quartz.kCGSessionEventTap, # Session level is enough for our needs | |
Quartz.kCGHeadInsertEventTap, # Insert wherever, we do not filter | |
Quartz.kCGEventTapOptionDefault, | |
# NSSystemDefined for media keys | |
Quartz.CGEventMaskBit(NSSystemDefined), | |
keyboard_tap_callback, | |
None | |
) | |
run_loop_source = Quartz.CFMachPortCreateRunLoopSource( | |
None, tap, 0) | |
Quartz.CFRunLoopAddSource( | |
Quartz.CFRunLoopGetCurrent(), | |
run_loop_source, | |
Quartz.kCFRunLoopDefaultMode | |
) | |
# Enable the tap | |
Quartz.CGEventTapEnable(tap, True) | |
# and run! This won't return until we exit or are terminated. | |
Quartz.CFRunLoopRun() | |
logger.error('Mac hotkey event loop exit') | |
if __name__ == '__main__': | |
run_event_loop() |
this results in segmentation fault on osx 11.0.1
On latest macOS(maybe 10.13+), we should grant accessbility priviledges first:
https://github.com/feeluown/FeelUOwn/blob/602fe097305474441dadd8c0fb223538b1f72cc2/feeluown/global_hotkey_mac.py#L73-L93
Note: I does not test the script on macOS 11, and I'm not sure if it works.
https://github.com/feeluown/FeelUOwn/blob/master/feeluown/global_hotkey_mac.py#L73-L93
This should works on latest macOS(Big Sur).
Here is the approach using pynput
:
from pynput import keyboard
def function_1():
print('Function 1 activated')
def function_2():
print('Function 2 activated')
with keyboard.GlobalHotKeys({
'<alt>+<ctrl>+r': function_1,
'<alt>+<ctrl>+t': function_1,
'<alt>+<ctrl>+y': function_2}) as h:
h.join()
more details: https://nitratine.net/blog/post/how-to-make-hotkeys-in-python/
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
this results in segmentation fault on osx 11.0.1