source: trunk/StreamVision/StreamVision.py@ 339

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