Skip to content

Instantly share code, notes, and snippets.

@cosven
Last active January 8, 2022 13:59
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cosven/75523f1e970edcc4da8cf1908a9c1463 to your computer and use it in GitHub Desktop.
Save cosven/75523f1e970edcc4da8cf1908a9c1463 to your computer and use it in GitHub Desktop.
OSX global hotkeys via python.
# -*- 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()
@vladiuz1
Copy link

this results in segmentation fault on osx 11.0.1

@cosven
Copy link
Author

cosven commented Dec 1, 2020

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.

@cosven
Copy link
Author

cosven commented Jul 12, 2021

@nicoster
Copy link

nicoster commented Jan 8, 2022

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