forked from albertz/music-player
-
Notifications
You must be signed in to change notification settings - Fork 0
/
mediakeys.py
158 lines (136 loc) · 4.77 KB
/
mediakeys.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# MusicPlayer, https://github.com/albertz/music-player
# Copyright (c) 2012, Albert Zeyer, www.az2000.de
# All rights reserved.
# This code is under the 2-clause BSD license, see License.txt in the root directory of this project.
import sys
class MacMediaKeyEventsTap:
# http://weblog.rogueamoeba.com/2007/09/29/apple-keyboard-media-key-event-handling/
def __init__(self):
# IOKit/hidsystem/ev_keymap.h
self._keyControls = {
16: 'play-pause',
17: 'next',
18: 'previous',
19: 'next', # actually 'fast', but we handle it like next
20: 'previous', # actually 'rewind', but we handle it like previous
}
def eventTap(self, proxy, type_, event, refcon):
import utils
if type_ < 0 or type_ > 0x7fffffff:
print "eventTrap: got", hex(type_)
# This might be kCGEventTapDisabledByTimeout.
# If we would just pass it, we would get an exception in runEventsCapture like:
# error: NSInternalInconsistencyException - Invalid parameter not satisfying: cgsEvent.type > 0 && cgsEvent.type <= kCGSLastEventType
# See <http://stackoverflow.com/q/16190680/133374>.
# However, just passing None is also not a solution because this event tap
# is disabled from now on. Thus we must restart it.
def doRestart():
self.stop()
self.start()
utils.daemonThreadCall(doRestart)
return None
from AppKit import NSKeyUp, NSEvent
# Convert the Quartz CGEvent into something more useful
keyEvent = NSEvent.eventWithCGEvent_(event)
if keyEvent.subtype() is 8: # subtype 8 is media keys
data = keyEvent.data1()
keyCode = (data & 0xFFFF0000) >> 16
keyState = (data & 0xFF00) >> 8
if keyCode in self._keyControls:
if keyState == NSKeyUp:
# debug timeout
#import time
#time.sleep(1)
# We want to avoid timeouts, so do this in another thread.
utils.daemonThreadCall(self.onMediaKeyUp, args=(self._keyControls[keyCode],), name="onMediaKeyUp")
return None # consume event
return event # pass through
def onMediaKeyUp(self, control):
#print "handleKeyUp:", control
pass
def runEventsCapture(self):
import AppKit
pool = AppKit.NSAutoreleasePool.alloc().init()
from AppKit import NSSystemDefined
import Quartz
self.runLoopRef = Quartz.CFRunLoopGetCurrent()
while True:
# https://developer.apple.com/library/mac/#documentation/Carbon/Reference/QuartzEventServicesRef/Reference/reference.html
try:
tap = Quartz.CGEventTapCreate(
Quartz.kCGSessionEventTap, # Quartz.kCGSessionEventTap or kCGHIDEventTap
Quartz.kCGHeadInsertEventTap, # Insert wherever, we do not filter
Quartz.kCGEventTapOptionDefault, # we can't listen-only because we want to consume it
Quartz.CGEventMaskBit(NSSystemDefined), # NSSystemDefined for media keys
self.eventTap,
None
)
except TypeError:
# wrong number of args? I got this on MacOSX 10.6,
# where it expected 5 args (instead of 6).
# however, it crashes when I call it without the last arg!
assert False, "CGEventTapCreate has an invalid signature on your system. This is fixed in later MacOSX versions. It should work in at least >=MacOSX 10.8. It is known to be broken in MacOSX 10.6."
assert tap
# Create a runloop source and add it to the current loop
runLoopSource = Quartz.CFMachPortCreateRunLoopSource(None, tap, 0)
Quartz.CFRunLoopAddSource(
self.runLoopRef,
runLoopSource,
Quartz.kCFRunLoopDefaultMode
)
# Enable the tap
Quartz.CGEventTapEnable(tap, True)
try:
# and run! This won't return until we exit or are terminated.
Quartz.CFRunLoopRun()
except Exception:
sys.excepthook(*sys.exc_info())
continue # rerun
# this is a regular quit
break
del pool
def start(self):
import utils
utils.daemonThreadCall(self.runEventsCapture, name = "mediakeys runEventsCapture")
def stop(self):
import AppKit
pool = AppKit.NSAutoreleasePool.alloc().init()
import Quartz
Quartz.CFRunLoopStop(self.runLoopRef)
del pool
if sys.platform == "darwin":
EventListener = MacMediaKeyEventsTap
else:
print "No media key event listener implementation"
# Dummy implementation
class EventListener:
def start(self): pass
def stop(self): pass
def onMediaKeyUp(control):
from State import state
try:
if control == "play-pause":
state.playPause()
elif control == "next":
state.nextSong()
except Exception:
sys.excepthook(*sys.exc_info())
def mediakeysMain():
from State import state
eventTap = EventListener()
eventTap.onMediaKeyUp = onMediaKeyUp
eventTap.start()
for ev in state.updates.read(): pass # wait for exit
eventTap.stop()
if __name__ == '__main__':
tap = EventListener()
def onMediaKeyUp(control):
print "onMediaKeyUp:", control
tap.onMediaKeyUp = onMediaKeyUp
tap.start()
import time, sys
try:
while True: time.sleep(10)
except Exception: pass
tap.stop()
print