source: trunk/StreamVision/StreamVision.py@ 339

Last change on this file since 339 was 339, checked in by Nicholas Riley, 15 years ago

StreamVision.py: fix Finder zooming issue by using PID; document
problem with a-umlaut; handle HIDRemote missing on exit.

File size: 9.4 KB
Line 
1#!/usr/bin/pythonw
2# -*- coding: utf-8 -*-
3
4from appscript import app, k, its, CommandError
5from AppKit import NSApplication, NSApplicationDefined, NSBeep, NSSystemDefined, NSURL, NSWorkspace
6from Foundation import NSDistributedNotificationCenter
7from PyObjCTools import AppHelper
8from Carbon.CarbonEvt import RegisterEventHotKey, GetApplicationEventTarget
9from Carbon.Events import cmdKey, shiftKey, controlKey
10import struct
11import scrape
12import HotKey
13
14GROWL_APP_NAME = 'StreamVision'
15NOTIFICATION_TRACK_INFO = 'iTunes Track Info'
16NOTIFICATIONS_ALL = [NOTIFICATION_TRACK_INFO]
17
18kEventHotKeyPressedSubtype = 6
19kEventHotKeyReleasedSubtype = 9
20
21kHIDUsage_Csmr_ScanNextTrack = 0xB5
22kHIDUsage_Csmr_ScanPreviousTrack = 0xB6
23kHIDUsage_Csmr_PlayOrPause = 0xCD
24
25growl = app('GrowlHelperApp')
26
27growl.register(
28 as_application=GROWL_APP_NAME,
29 all_notifications=NOTIFICATIONS_ALL,
30 default_notifications=NOTIFICATIONS_ALL,
31 icon_of_application='iTunes.app')
32 # if we leave off the .app, we can get Classic iTunes's icon
33
34def growlNotify(title, description, **kw):
35 growl.notify(
36 with_name=NOTIFICATION_TRACK_INFO,
37 title=title,
38 description=description,
39 application_name=GROWL_APP_NAME,
40 **kw)
41
42def radioParadiseURL():
43 # XXX better to use http://www2.radioparadise.com/playlist.xml ?
44 session = scrape.Session()
45 session.go('http://www2.radioparadise.com/nowplay_b.php')
46 return session.region.firsttag('a')['href']
47
48def cleanStreamTitle(title):
49 if title == k.missing_value:
50 return ''
51 title = title.split(' [')[0] # XXX move to description
52 # XXX fails with Arvo Pärt
53 title = title.encode('iso-8859-1').decode('utf-8') # XXX iTunes 7.1 or RP?
54 title = title.replace('`', u'’')
55 return title
56
57def cleanStreamTrackName(name):
58 name = name.split('. ')[0]
59 name = name.split(': ')[0]
60 name = name.split(' - ')
61 if len(name) > 1:
62 name = ' - '.join(name[:-1])
63 else:
64 name = name[0]
65 return name
66
67def iTunesApp(): return app(id='com.apple.iTunes')
68def XTensionApp(): return app(creator='SHEx')
69
70HAVE_XTENSION = False
71try:
72 XTensionApp()
73 HAVE_XTENSION = True
74except:
75 pass
76
77needsStereoPowerOn = HAVE_XTENSION
78
79class StreamVision(NSApplication):
80
81 hotKeyActions = {}
82 hotKeys = []
83
84 def displayTrackInfo(self):
85 global needsStereoPowerOn
86
87 iTunes = iTunesApp()
88
89 trackClass = iTunes.current_track.class_()
90 trackName = ''
91 if trackClass != k.property:
92 trackName = iTunes.current_track.name()
93
94 if iTunes.player_state() != k.playing:
95 growlNotify('iTunes is not playing.', trackName)
96 if HAVE_XTENSION:
97 if not needsStereoPowerOn and XTensionApp().status('Stereo'):
98 XTensionApp().turnoff('Stereo')
99 needsStereoPowerOn = True
100 return
101 if needsStereoPowerOn:
102 if not XTensionApp().status('Stereo'):
103 XTensionApp().turnon('Stereo')
104 needsStereoPowerOn = False
105 if trackClass == k.URL_track:
106 growlNotify(cleanStreamTitle(iTunes.current_stream_title()),
107 cleanStreamTrackName(trackName))
108 return
109 if trackClass == k.property:
110 growlNotify('iTunes is playing.', '')
111 return
112 kw = {}
113 # XXX iTunes doesn't let you get artwork for shared tracks
114 if trackClass != k.shared_track:
115 artwork = iTunes.current_track.artworks()
116 if artwork:
117 kw['pictImage'] = artwork[0].data()
118 growlNotify(trackName + ' ' +
119 u'★' * (iTunes.current_track.rating() / 20),
120 iTunes.current_track.album() + '\n' +
121 iTunes.current_track.artist(),
122 **kw)
123
124 def goToSite(self):
125 iTunes = iTunesApp()
126 if iTunes.player_state() == k.playing:
127 url = iTunes.current_stream_URL()
128 if url != k.missing_value:
129 if 'radioparadise.com' in url and 'review' not in url:
130 url = radioParadiseURL()
131 NSWorkspace.sharedWorkspace().openURL_(NSURL.URLWithString_(url))
132 return
133 NSBeep()
134
135 def registerHotKey(self, func, keyCode, mods=0):
136 hotKeyRef = RegisterEventHotKey(keyCode, mods, (0, 0),
137 GetApplicationEventTarget(), 0)
138 self.hotKeys.append(hotKeyRef)
139 self.hotKeyActions[HotKey.HotKeyAddress(hotKeyRef)] = func
140 return hotKeyRef
141
142 def unregisterHotKey(self, hotKeyRef):
143 self.hotKeys.remove(hotKeyRef)
144 del self.hotKeyActions[HotKey.HotKeyAddress(hotKeyRef)]
145 hotKeyRef.UnregisterEventHotKey()
146
147 def incrementRatingBy(self, increment):
148 iTunes = iTunesApp()
149 rating = iTunes.current_track.rating()
150 rating += increment
151 if rating < 0:
152 rating = 0
153 NSBeep()
154 elif rating > 100:
155 rating = 100
156 NSBeep()
157 iTunes.current_track.rating.set(rating)
158
159 def playPause(self, useStereo=True):
160 global needsStereoPowerOn
161
162 iTunes = iTunesApp()
163 was_playing = (iTunes.player_state() == k.playing)
164 if not useStereo:
165 needsStereoPowerOn = False
166 iTunes.playpause()
167 if not was_playing and iTunes.player_state() == k.stopped:
168 # most likely, we're focused on the iPod, so playing does nothing
169 iTunes.browser_windows[1].view.set(iTunes.user_playlists[its.name=='Stations'][1]())
170 iTunes.play()
171 if HAVE_XTENSION and useStereo:
172 if iTunes.player_state() == k.playing:
173 XTensionApp().turnon('Stereo')
174 needsStereoPowerOn = False
175 else:
176 XTensionApp().turnoff('Stereo')
177 needsStereoPowerOn = True
178
179 def playPauseFront(self):
180 systemEvents = app(id='com.apple.systemEvents')
181 frontName = systemEvents.processes[its.frontmost][1].name()
182 if frontName == 'RealPlayer':
183 realPlayer = app(id='com.RealNetworks.RealPlayer')
184 if len(realPlayer.players()) > 0:
185 if realPlayer.players[1].state() == k.playing:
186 realPlayer.pause()
187 else:
188 realPlayer.play()
189 return
190 elif frontName == 'VLC':
191 app(id='org.videolan.vlc').play() # equivalent to playpause
192 else:
193 self.playPause(useStereo=False)
194
195 def registerZoomWindowHotKey(self):
196 self.zoomWindowHotKey = self.registerHotKey(self.zoomWindow, 42, cmdKey | controlKey) # cmd-ctrl-\
197
198 def unregisterZoomWindowHotKey(self):
199 self.unregisterHotKey(self.zoomWindowHotKey)
200 self.zoomWindowHotKey = None
201
202 def zoomWindow(self):
203 # XXX detect if "enable access for assistive devices" needs to be enabled
204 systemEvents = app(id='com.apple.systemEvents')
205 frontName = systemEvents.processes[its.frontmost][1].name()
206 if frontName == 'iTunes':
207 systemEvents.processes['iTunes'].menu_bars[1]. \
208 menu_bar_items['Window'].menus.menu_items['Zoom'].click()
209 return
210 elif frontName in ('X11', 'Emacs'): # preserve C-M-\
211 self.unregisterZoomWindowHotKey()
212 systemEvents.key_code(42, using=[k.command_down, k.control_down])
213 self.registerZoomWindowHotKey()
214 return
215 frontPID = systemEvents.processes[its.frontmost][1].unix_id()
216 try:
217 zoomed = app(pid=frontPID).windows[1].zoomed
218 zoomed.set(not zoomed())
219 except (CommandError, RuntimeError):
220 systemEvents.processes[frontName].windows \
221 [its.subrole == 'AXStandardWindow'].windows[1]. \
222 buttons[its.subrole == 'AXZoomButton'].buttons[1].click()
223
224 def finishLaunching(self):
225 super(StreamVision, self).finishLaunching()
226 self.registerHotKey(self.displayTrackInfo, 100) # F8
227 self.registerHotKey(self.goToSite, 100, cmdKey) # cmd-F8
228 self.registerHotKey(self.playPause, 101) # F9
229 self.registerHotKey(lambda: iTunesApp().previous_track(), 109) # F10
230 self.registerHotKey(lambda: iTunesApp().next_track(), 103) # F11
231 self.registerHotKey(lambda: self.incrementRatingBy(-20), 109, shiftKey) # shift-F10
232 self.registerHotKey(lambda: self.incrementRatingBy(20), 103, shiftKey) # shift-F11
233 self.registerZoomWindowHotKey()
234 NSDistributedNotificationCenter.defaultCenter().addObserver_selector_name_object_(self, self.displayTrackInfo, 'com.apple.iTunes.playerInfo', None)
235 try:
236 import HIDRemote
237 HIDRemote.connect()
238 except ImportError:
239 print "failed to import HIDRemote (XXX fix - on Intel)"
240 except OSError, e:
241 print "failed to connect to remote: ", e
242
243 def sendEvent_(self, theEvent):
244 eventType = theEvent.type()
245 if eventType == NSSystemDefined and \
246 theEvent.subtype() == kEventHotKeyPressedSubtype:
247 self.hotKeyActions[theEvent.data1()]()
248 elif eventType == NSApplicationDefined:
249 key = theEvent.data1()
250 if key == kHIDUsage_Csmr_ScanNextTrack:
251 iTunesApp().next_track()
252 elif key == kHIDUsage_Csmr_ScanPreviousTrack:
253 iTunesApp().previous_track()
254 elif key == kHIDUsage_Csmr_PlayOrPause:
255 self.playPauseFront()
256 super(StreamVision, self).sendEvent_(theEvent)
257
258if __name__ == "__main__":
259 AppHelper.runEventLoop()
260 try:
261 HIDRemote.disconnect()
262 except:
263 pass
Note: See TracBrowser for help on using the repository browser.