Merge lp:~mbudde/earcandy/reorganization into lp:earcandy
- reorganization
- Merge into trunk
Status: | Merged |
---|---|
Merge reported by: | KillerKiwi |
Merged at revision: | not available |
Proposed branch: | lp:~mbudde/earcandy/reorganization |
Merge into: | lp:earcandy |
Diff against target: | None lines |
To merge this branch: | bzr merge lp:~mbudde/earcandy/reorganization |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
KillerKiwi | Approve | ||
Review via email: mp+6748@code.launchpad.net |
Commit message
Description of the change
Michael Budde (mbudde) wrote : | # |
David D Lowe (flimm) wrote : | # |
Hello Michael Budde! It seems like we were working on the same bug #379266 in parallel, so a lot of our work is similar, if not identical (earcandy.desktop has the exact same comment!).
So I'm going to comment on the changes you've made versus what's now in the trunk (lp:earcandy revision 50).
I find your approach to the runnable script earcandy interesting. You basically moved the code under if __name__ == "__main__" from the EarCandy.py to the script. Theoretically, this is the best method, practically, I've found from my personal experience with Epidermis that it makes namespaces rather confusing. The other approach which I took calls the script by running subprocess.
Since you're using distutils, the data files will be separated from the .py files, so you created a function called find_data_file, similar to my find_program_file. You check to see in which directory of /usr/ or /usr/local/ the files are installed. This actually superfluous, they're always going to be under sys.prefix, according to the distutils documentation.
Having a seperate EarCandyInfo.py file for the application's metadata is certainly interesting. Having one place to modify the program's version number is nice, but do you really need it for things like APPNAME?
I'm going to go ahead and comment on your Ubuntu packaging branch too, lp:~mbudde/earcandy/ubuntu revision 59 if you don't mind. You included a dependency which I somehow forgot: pulseaudio, well done! Having the Ubuntu packaging in a separate branch from the trunk is an interesting idea, and one which a lot of projects go by. I personally prefer having the debian/ folder in the original source, keeps the developers responsible for the packaging and things like dependencies.
Obviously, I'm not the developer of EarCandy, it'll be up to KillerKiwi to decide what to take and what to keep. Thanks for contributing!
Michael Budde (mbudde) wrote : | # |
> Hello Michael Budde! It seems like we were working on the same bug #379266 in
> parallel, so a lot of our work is similar, if not identical (earcandy.desktop
> has the exact same comment!).
> So I'm going to comment on the changes you've made versus what's now in the
> trunk (lp:earcandy revision 50).
>
Yeah, I noticed your branch the day after I started my branch. I've certainly
got some inspiration from your branch, hope you don't mind that ;)
> I find your approach to the runnable script earcandy interesting. You
> basically moved the code under if __name__ == "__main__" from the EarCandy.py
> to the script. Theoretically, this is the best method, practically, I've found
> from my personal experience with Epidermis that it makes namespaces rather
> confusing. The other approach which I took calls the script by running
> subprocess.
> depending on the other, which is a bit of a waste but makes the result in
> System Monitor prettier. The final approach (that I can think of) is to launch
> it by running subprocess.
> but less pretty in System Monitor.
I'm not sure what you mean when you say that it "makes namespaces rather confusing".
Do you have any examples where your method would be prefered to mine?
> Since you're using distutils, the data files will be separated from the .py
> files, so you created a function called find_data_file, similar to my
> find_program_file. You check to see in which directory of /usr/ or /usr/local/
> the files are installed. This actually superfluous, they're always going to be
> under sys.prefix, according to the distutils documentation.
Running setup.py in the source tree gives me this:
$ ./setup.py -n install
running install
[..snip..]
running install_data
creating /usr/local/
copying data/earsLabel.png -> /usr/local/
copying data/pulseoptio
copying data/settings.xml -> /usr/local/
copying data/earsLabelO
copying earcandy.desktop -> /usr/local/
creating /usr/local/
copying data/earsLabel.png -> /usr/local/
running install_egg_info
Writing /usr/local/
Also I don't quite like how you've copied find_program_file() to every source file
that needs it. Right now it's a pain to edit that function as you have to do it in three
places. Why not put it in a separate file and import it?
> Having a seperate EarCandyInfo.py file for the application's metadata is
> certainly interesting. Having one place to modify the program's version number
> is nice, but do you really need it for things like APPNAME?
You're right. That probably is overkill.
> I'm going to go ahead and comment on your Ubuntu packaging branch too,
> lp:~mbudde/earcandy/ubuntu revision 59 if you don't mind. You included a
> dependency which I somehow forgot: pulseaudio, well done! Having the Ubuntu
> packaging in a separate branch from the trunk is an interesting idea, and one
> which a lot of projects go by. I personally ...
KillerKiwi (killerkiwi2005) : | # |
Preview Diff
1 | === added file 'MANIFEST.in' |
2 | --- MANIFEST.in 1970-01-01 00:00:00 +0000 |
3 | +++ MANIFEST.in 2009-05-23 12:12:46 +0000 |
4 | @@ -0,0 +1,2 @@ |
5 | +include earcandy earcandy.desktop MANIFEST.in |
6 | +recursive-include data * |
7 | |
8 | === added directory 'data' |
9 | === renamed file 'earsLabel.png' => 'data/earsLabel.png' |
10 | === renamed file 'earsLabelOff.png' => 'data/earsLabelOff.png' |
11 | === renamed file 'pulseoptions.glade' => 'data/pulseoptions.glade' |
12 | === renamed file 'settings.xml' => 'data/settings.xml' |
13 | === added file 'earcandy' |
14 | --- earcandy 1970-01-01 00:00:00 +0000 |
15 | +++ earcandy 2009-05-22 14:09:54 +0000 |
16 | @@ -0,0 +1,8 @@ |
17 | +#!/usr/bin/env python |
18 | + |
19 | +from earcandylib.EarCandy import EarCandy |
20 | + |
21 | +if __name__ == '__main__': |
22 | + ec = EarCandy() |
23 | + ec.run() |
24 | + ec.save() |
25 | |
26 | === added file 'earcandy.desktop' |
27 | --- earcandy.desktop 1970-01-01 00:00:00 +0000 |
28 | +++ earcandy.desktop 2009-05-23 12:12:46 +0000 |
29 | @@ -0,0 +1,12 @@ |
30 | +[Desktop Entry] |
31 | +Version=1.0 |
32 | +Encoding=UTF-8 |
33 | +Name=Ear Candy |
34 | +Comment=Gracefully fade applications' volume in and out |
35 | +Generic Name=Sound level manager |
36 | +Terminal=false |
37 | +Icon=earsLabel |
38 | +Type=Application |
39 | +Exec=earcandy |
40 | +Categories=Utility; |
41 | +StartupNotify=True |
42 | |
43 | === added directory 'earcandylib' |
44 | === renamed file 'Client.py' => 'earcandylib/Client.py' |
45 | --- Client.py 2009-05-21 19:43:15 +0000 |
46 | +++ earcandylib/Client.py 2009-05-22 15:58:21 +0000 |
47 | @@ -17,7 +17,7 @@ |
48 | self.category = "" # music | video | phone |
49 | self.window_position_fade = False |
50 | |
51 | - self.volume_step = 2 |
52 | + self.volume_step = 2 |
53 | self.has_focus = False |
54 | self.fullscreen = False |
55 | self.icon = None |
56 | @@ -60,7 +60,7 @@ |
57 | # check meter levels on others |
58 | for sink in self.sinks.values(): |
59 | if sink.volume_meter > 0: return True |
60 | - |
61 | + |
62 | # HACK: Check how long its been inactive... and if more than a second count as expired |
63 | if timestamp - sink.volume_meter_last_non_zero < 1: |
64 | return True |
65 | @@ -107,7 +107,7 @@ |
66 | |
67 | def __fade_mute(self): |
68 | self.volume_target = self.volume_step # if we goto 0 than our volume meter will never register a value |
69 | - |
70 | + |
71 | def set_primary(self, value): |
72 | if value or self.category == "default": |
73 | self.__fade_in() |
74 | @@ -127,7 +127,7 @@ |
75 | el.setAttribute("category",self.category) |
76 | el.setAttribute("window_position_fade", str(self.window_position_fade)) |
77 | el.setAttribute("icon_name", str(self.icon_name)) |
78 | - |
79 | + |
80 | def from_xml(self, el): |
81 | |
82 | self.name = el.getAttribute("name") |
83 | |
84 | === renamed file 'DesktopFiles.py' => 'earcandylib/DesktopFiles.py' |
85 | === renamed file 'ear_candy' => 'earcandylib/EarCandy.py' (properties changed: +x to -x) |
86 | --- ear_candy 2009-05-21 19:43:15 +0000 |
87 | +++ earcandylib/EarCandy.py 2009-05-22 15:58:21 +0000 |
88 | @@ -16,19 +16,20 @@ |
89 | from xml.dom.minidom import * |
90 | |
91 | from window.WindowWatcher import WindowWatcher |
92 | -from pulseaudio.PulseAudio import PulseAudio |
93 | +from pulseaudio.PulseAudio import PulseAudio |
94 | from Client import Client |
95 | from Sink import Sink |
96 | from Threads import threaded |
97 | from TrayIcon import EarCandyStatusIcon |
98 | -from EarCandyPrefs import EarCandayPref |
99 | -from VolumeSlider import EarCandyVolumeSlider |
100 | +from EarCandyPrefs import EarCandayPref |
101 | +from VolumeSlider import EarCandyVolumeSlider |
102 | from EarCandyDBus import EarCandyDBusClient |
103 | +from Paths import get_data_path |
104 | |
105 | # Turn on gtk threading |
106 | gtk.gdk.threads_init() |
107 | |
108 | -gtk.window_set_default_icon_from_file(os.path.join(os.path.dirname(__file__), "earsLabel.png")) |
109 | +gtk.window_set_default_icon_from_file(get_data_path("earsLabel.png")) |
110 | |
111 | # hack to get around 0.2 not have alsaaudio.cards() |
112 | def reset_alsa_volumes(value=100): |
113 | @@ -50,7 +51,7 @@ |
114 | |
115 | |
116 | for track in mixer.list_tracks(): |
117 | - |
118 | + |
119 | if track.flags & gst.interfaces.MIXER_TRACK_OUTPUT and track.num_channels > 0: |
120 | print "|", track.label |
121 | |
122 | @@ -85,7 +86,7 @@ |
123 | rules.appendChild(el) |
124 | client.to_xml(el) |
125 | skip.append(client.name) |
126 | - |
127 | + |
128 | |
129 | doc.documentElement.setAttribute("fade_timer_speed", str(self.fade_timer_speed)) |
130 | doc.documentElement.setAttribute("mute_level", str(self.mute_level)) |
131 | @@ -146,8 +147,8 @@ |
132 | self.active = True |
133 | self.display = {"": "[ unknown ]", "phone" : "Phone (VoIP)", "video" : "Video Player", "music" : "Music Player", "default" : "Notification" } |
134 | self.ignore = ["EsounD client (UNIX socket client)", "ear-candy", "Native client (UNIX socket client)"] |
135 | - self.config_file = os.path.expanduser("~/.config/Ear Candy/settings.xml") |
136 | - self.default_config_file = os.path.join(os.path.dirname(__file__),"settings.xml") |
137 | + self.config_file = os.path.expanduser("~/.config/Ear Candy/settings.xml") |
138 | + self.default_config_file = get_data_path("settings.xml") |
139 | |
140 | self.pa_clients = {} # clients by name |
141 | self.pa_clients_by_id = {} # clients by id |
142 | @@ -161,13 +162,11 @@ |
143 | |
144 | self.pref = None |
145 | |
146 | - self.fade_timer_speed = 0.1 |
147 | + self.fade_timer_speed = 0.1 |
148 | self.mute_level = 20 |
149 | self.follow_new_outputs = True |
150 | |
151 | - self.priority = { "" : [], "phone" : [], "video" : [], "music" : [] } |
152 | - |
153 | - pass |
154 | + self.priority = { "" : [], "phone" : [], "video" : [], "music" : [] } |
155 | |
156 | def run(self): |
157 | self.ecb = EarCandyDBusClient(self) |
158 | @@ -179,7 +178,7 @@ |
159 | else: |
160 | |
161 | self.tray = EarCandyStatusIcon(self) |
162 | - |
163 | + |
164 | self.slider = EarCandyVolumeSlider(self) |
165 | |
166 | self.ecb.start_service() |
167 | @@ -187,7 +186,7 @@ |
168 | self.pa = PulseAudio( self.on_new_pa_client, self.on_remove_pa_client, self.on_new_pa_sink, self.on_remove_pa_sink, self.on_new_pa_output, self.on_remove_pa_output, self.on_volume_change, self.pa_volume_meter) |
169 | self.ww = WindowWatcher(self.on_active_window_change) |
170 | self.fade_timer() |
171 | - |
172 | + |
173 | gtk.main() |
174 | |
175 | |
176 | @@ -207,7 +206,7 @@ |
177 | def fade_timer(self): |
178 | while True: |
179 | time.sleep(self.fade_timer_speed) |
180 | - gobject.idle_add(self.adjust_volumes) |
181 | + gobject.idle_add(self.adjust_volumes) |
182 | |
183 | def adjust_volumes(self): |
184 | |
185 | @@ -217,11 +216,11 @@ |
186 | for sink in self.pa_sinks.values(): |
187 | if sink.set_volume(): |
188 | # set pa volume |
189 | - self.pa.set_sink_volume(sink.index, sink.volume, sink.channels) |
190 | + self.pa.set_sink_volume(sink.index, sink.volume, sink.channels) |
191 | |
192 | def __set_primary_client(self): |
193 | # Apply fade rules |
194 | - |
195 | + |
196 | if self.active: |
197 | for client in self.pa_clients.values(): |
198 | # self.pref.update_client( client ) |
199 | @@ -256,7 +255,7 @@ |
200 | flagFound = False |
201 | for key in self.priority.keys(): |
202 | if key: |
203 | - for client in self.priority[key]: |
204 | + for client in self.priority[key]: |
205 | |
206 | if not flagFound and self.active and client.is_active(): |
207 | # Toggle old client to inactive |
208 | @@ -271,7 +270,7 @@ |
209 | # all clients with no category should be treated as active for volume |
210 | for client in self.priority[key]: |
211 | client.set_primary(True) |
212 | - |
213 | + |
214 | |
215 | def on_volume_change(self, level): |
216 | self.slider.set_volume( level ) |
217 | @@ -281,7 +280,7 @@ |
218 | # /desktop/gnome/sound/default_mixer_device |
219 | |
220 | del( self.pa_outputs[index] ) |
221 | - |
222 | + |
223 | # fall back to previous value... |
224 | for value in self.pa_outputs.values(): |
225 | self.set_last_output(value) |
226 | @@ -289,7 +288,7 @@ |
227 | |
228 | def set_last_output(self, name): |
229 | self.last_output_name = name |
230 | - |
231 | + |
232 | # Update gconf key that governs multimedia key controls |
233 | gconf_key = "/desktop/gnome/sound/default_mixer_device" |
234 | prefix = "pulsemixer:" |
235 | @@ -299,12 +298,12 @@ |
236 | |
237 | print "== Change gnome default sound device ==" |
238 | client.set_string(gconf_key, prefix + name) |
239 | - |
240 | + |
241 | |
242 | def on_new_pa_output(self, index, output_name, startup): |
243 | self.pa_outputs[index] = output_name |
244 | self.save() |
245 | - |
246 | + |
247 | if self.follow_new_outputs and (self.last_output_name == "" or output_name.lower().count("usb") > 0): |
248 | self.set_last_output(output_name) |
249 | # Move all streams to the new output ;) |
250 | @@ -318,7 +317,7 @@ |
251 | def on_new_pa_sink(self, index, name, client_index, volume, sink, channels): |
252 | |
253 | if not self.pa_sinks.has_key(index): |
254 | |
255 | |
256 | print "== pa sink input ==" |
257 | print "sink:", index, name |
258 | if self.pa_clients_by_id.has_key(client_index): |
259 | @@ -329,13 +328,13 @@ |
260 | client.sinks[index] = sink |
261 | self.pa_sinks[index] = sink |
262 | # insure the sink input is on the correct output.. |
263 | - if self.follow_new_outputs and self.last_output_name: |
264 | + if self.follow_new_outputs and self.last_output_name: |
265 | self.pa.move_sink(sink.index, self.last_output_name) |
266 | else: |
267 | return |
268 | else: |
269 | self.pa_sinks[index].volume = volume |
270 | - |
271 | + |
272 | if self.pref: |
273 | self.pref.update_client( self.pa_clients_by_id[client_index] ) |
274 | self.on_active_window_change( self.last_application, "pa") |
275 | @@ -344,10 +343,11 @@ |
276 | if self.pa_sinks.has_key(index): |
277 | sink = self.pa_sinks[index] |
278 | sink.volume_meter = level |
279 | - if(level > sink.client.volume_step): sink.volume_meter_last_non_zero = time.mktime(datetime.datetime.now().timetuple()) |
280 | + if (level > sink.client.volume_step): |
281 | + sink.volume_meter_last_non_zero = time.mktime(datetime.datetime.now().timetuple()) |
282 | |
283 | def on_remove_pa_sink(self, index): |
284 | |
285 | |
286 | print "== pa remove sink input ==" |
287 | print index |
288 | if self.pa_sinks.has_key(index): |
289 | @@ -357,7 +357,7 @@ |
290 | if self.pref: |
291 | self.pref.update_client( client ) |
292 | self.on_active_window_change( self.last_application, "pa") |
293 | - |
294 | + |
295 | |
296 | def get_unregistered_clients(self): |
297 | clients = [] |
298 | @@ -371,11 +371,11 @@ |
299 | return clients |
300 | |
301 | def on_new_pa_client(self, index, name): |
302 | |
303 | |
304 | print "== pa sink client ==" |
305 | print name |
306 | # Link all clients with same name into same object |
307 | - if not self.pa_clients.has_key(index) or not self.pa_clients[index].name == name: |
308 | + if not self.pa_clients.has_key(index) or not self.pa_clients[index].name == name: |
309 | if not self.pa_clients.has_key(name): |
310 | client = Client(self, name) |
311 | self.pa_clients[name] = client |
312 | @@ -419,10 +419,10 @@ |
313 | self.last_application = application |
314 | |
315 | def match_client_to_application(self, client, application, index=0): |
316 | - if client.test_focus_window(application.window_name, application.command, application.name): |
317 | + if client.test_focus_window(application.window_name, application.command, application.name): |
318 | #print "MATCH pa client:", client.name, index |
319 | |
320 | - client.fullscreen = application.fullscreen |
321 | + client.fullscreen = application.fullscreen |
322 | client.icon = application.icon |
323 | client.icon_name = application.icon_name |
324 | client.description = application.description |
325 | @@ -447,13 +447,3 @@ |
326 | if name.startswith(alsa_plugin): |
327 | name = name[len(alsa_plugin): -1] |
328 | return name |
329 | - |
330 | - |
331 | -if __name__ == '__main__': |
332 | - |
333 | - os.chdir(os.path.dirname(sys.argv[0])) |
334 | - |
335 | - ec = EarCandy() |
336 | - ec.run() |
337 | - ec.save() |
338 | - |
339 | |
340 | === renamed file 'EarCandyAppSelect.py' => 'earcandylib/EarCandyAppSelect.py' |
341 | --- EarCandyAppSelect.py 2009-02-20 02:08:20 +0000 |
342 | +++ earcandylib/EarCandyAppSelect.py 2009-05-22 15:58:21 +0000 |
343 | @@ -16,14 +16,15 @@ |
344 | from glade_window import GladeWindow |
345 | from window.WindowWatcher import WindowWatcher |
346 | from Client import Client |
347 | +from Paths import get_data_path |
348 | |
349 | class EarCandyAppSelect(GladeWindow): |
350 | def __init__(self, core): |
351 | - |
352 | + |
353 | self.core = core |
354 | - |
355 | - GladeWindow.__init__(self, "pulseoptions.glade", "dialog_select") |
356 | - |
357 | + |
358 | + GladeWindow.__init__(self, get_data_path("pulseoptions.glade"), "dialog_select") |
359 | + |
360 | self.combobox_clients = self.wtree.get_widget("combobox_clients") |
361 | self.treeview_applications = self.wtree.get_widget("treeview_applications") |
362 | self.combobox_category = self.wtree.get_widget("combobox_category") |
363 | @@ -33,7 +34,7 @@ |
364 | "on_button_cancel_clicked" : self.on_button_cancel_clicked |
365 | } |
366 | self.wtree.signal_autoconnect(signals) |
367 | - |
368 | + |
369 | # Setup tree |
370 | column = gtk.TreeViewColumn(('')) |
371 | column.set_spacing(4) |
372 | @@ -48,8 +49,8 @@ |
373 | column.set_attributes(cell, markup=1) |
374 | self.treeview_applications.append_column(column) |
375 | self.store = gtk.ListStore(gtk.gdk.Pixbuf, str, object) |
376 | - self.treeview_applications.set_model(self.store) |
377 | - self.store.set_sort_column_id(1, gtk.SORT_ASCENDING) |
378 | + self.treeview_applications.set_model(self.store) |
379 | + self.store.set_sort_column_id(1, gtk.SORT_ASCENDING) |
380 | |
381 | self.update() |
382 | |
383 | @@ -65,8 +66,8 @@ |
384 | if self.store.get_value(iter, 1) == app_name: return |
385 | iter = self.store.iter_next(iter) |
386 | |
387 | - self.store.append(([icon, app_name, client])) |
388 | - |
389 | + self.store.append(([icon, app_name, client])) |
390 | + |
391 | def update(self): |
392 | |
393 | icon_theme = gtk.icon_theme_get_default() |
394 | @@ -76,7 +77,7 @@ |
395 | self.combobox_clients.append_text(client.name) |
396 | |
397 | for app in self.core.ww.applications.values(): |
398 | - self.store.append(([app.icon, app.name, app])) |
399 | + self.store.append(([app.icon, app.name, app])) |
400 | |
401 | self.combobox_clients.set_active(0) |
402 | |
403 | @@ -88,7 +89,7 @@ |
404 | def on_button_add_clicked(self, widget): |
405 | |
406 | client = self.core.pa_clients[self.combobox_clients.get_active_text()] |
407 | - |
408 | + |
409 | |
410 | # override with selected app |
411 | model, iter = self.treeview_applications.get_selection().get_selected() |
412 | @@ -107,6 +108,6 @@ |
413 | |
414 | self.core.pref.update_client( client ) |
415 | self.stop() |
416 | - |
417 | + |
418 | def on_button_cancel_clicked(self, widget): |
419 | self.stop() |
420 | |
421 | === renamed file 'EarCandyDBus.py' => 'earcandylib/EarCandyDBus.py' |
422 | --- EarCandyDBus.py 2009-05-19 04:28:48 +0000 |
423 | +++ earcandylib/EarCandyDBus.py 2009-05-22 15:58:21 +0000 |
424 | @@ -6,10 +6,9 @@ |
425 | DBUS_KEY = "org.earCandy.EarCandyDBus2" |
426 | |
427 | class EarCandyDBusClient(dbus.service.Object): |
428 | - |
429 | + |
430 | def __init__(self, core): |
431 | self.core = core |
432 | - |
433 | |
434 | def is_running(self): |
435 | dbus.mainloop.glib.DBusGMainLoop (set_as_default=True) |
436 | @@ -34,7 +33,7 @@ |
437 | dbus.mainloop.glib.DBusGMainLoop (set_as_default=True) |
438 | bus = dbus.SessionBus () |
439 | dbus.service.Object.__init__(self, bus, "/", DBUS_KEY) |
440 | |
441 | |
442 | print "registered as dbus service..." |
443 | |
444 | |
445 | |
446 | === added file 'earcandylib/EarCandyInfo.py' |
447 | --- earcandylib/EarCandyInfo.py 1970-01-01 00:00:00 +0000 |
448 | +++ earcandylib/EarCandyInfo.py 2009-05-23 11:53:46 +0000 |
449 | @@ -0,0 +1,8 @@ |
450 | +NAME = "Ear Candy" |
451 | +APPNAME = "earcandy" |
452 | +VERSION = "0.5" |
453 | +DESC = "A PulseAudio volume manager" |
454 | +WEBSITE = "http://edge.launchpad.net/earcandy" |
455 | +AUTHOR = "Jason Taylor" |
456 | +AUTHOR_EMAIL = "killerkiwi2005@gmail.com" |
457 | +LICENSE = "GNU GPL v2" |
458 | |
459 | === renamed file 'EarCandyPrefs.py' => 'earcandylib/EarCandyPrefs.py' |
460 | --- EarCandyPrefs.py 2009-05-19 04:28:48 +0000 |
461 | +++ earcandylib/EarCandyPrefs.py 2009-05-22 15:58:21 +0000 |
462 | @@ -17,15 +17,16 @@ |
463 | from Threads import threaded |
464 | from glade_window import GladeWindow |
465 | from EarCandyAppSelect import EarCandyAppSelect |
466 | +from Paths import get_data_path |
467 | |
468 | class EarCandayPref(GladeWindow): |
469 | def __init__(self, core): |
470 | - |
471 | + |
472 | self.core = core |
473 | |
474 | self.vals = {"" : 0, "music" : 1, "video" : 2, "phone": 3} |
475 | |
476 | - GladeWindow.__init__(self, "pulseoptions.glade", "dialog_options") |
477 | + GladeWindow.__init__(self, get_data_path("pulseoptions.glade"), "dialog_options") |
478 | |
479 | self.treeview_pulse_clients = self.wtree.get_widget("treeview_pulse_clients") |
480 | self.label_pulse_client = self.wtree.get_widget("label_pulse_client") |
481 | @@ -63,9 +64,9 @@ |
482 | "on_comboboxentry_window_match_changed" : self.on_comboboxentry_window_match_changed, |
483 | "on_entry_pulse_client_description_changed" : self.on_entry_pulse_client_description_changed, |
484 | "on_toolbutton_delete_clicked" : self.on_toolbutton_delete_clicked, |
485 | - |
486 | - "on_hscale_mute_level_value_changed" : self.on_hscale_mute_level_value_changed, |
487 | - "on_hscale_fade_value_changed" : self.on_hscale_fade_value_changed, |
488 | + |
489 | + "on_hscale_mute_level_value_changed" : self.on_hscale_mute_level_value_changed, |
490 | + "on_hscale_fade_value_changed" : self.on_hscale_fade_value_changed, |
491 | |
492 | "on_close_button_clicked" : self.on_close_button_clicked, |
493 | "on_button_add_new_clicked" : self.on_button_add_new_clicked, |
494 | @@ -74,7 +75,7 @@ |
495 | "on_checkbutton_output_toggled" : self.on_checkbutton_output_toggled |
496 | } |
497 | self.wtree.signal_autoconnect(signals) |
498 | - |
499 | + |
500 | # Setup tree |
501 | #column = gtk.TreeViewColumn('Target', gtk.CellRendererProgress(), value=4) |
502 | #self.treeview_pulse_clients.append_column(column) |
503 | @@ -145,7 +146,7 @@ |
504 | |
505 | self.selected_client = model.get_value(iter, 5) |
506 | self.selected_client.window_position_fade = model[path][3] |
507 | - |
508 | + |
509 | def on_hscale_mute_level_value_changed(self, widget): |
510 | self.core.mute_level = self.hscale_mute_level.get_value() |
511 | print "Mute Level ", self.core.mute_level |
512 | @@ -170,7 +171,8 @@ |
513 | model, iter = self.treeview_pulse_clients.get_selection().get_selected() |
514 | if not iter: return |
515 | self.selected_client = model.get_value(iter, 2) |
516 | - md = gtk.MessageDialog(None, flags=0, type=gtk.MESSAGE_QUESTION, buttons=gtk.BUTTONS_YES_NO, message_format="Are you sure you want to delete this rule?") |
517 | + md = gtk.MessageDialog(None, flags=0, type=gtk.MESSAGE_QUESTION, buttons=gtk.BUTTONS_YES_NO, |
518 | + message_format="Are you sure you want to delete this rule?") |
519 | result = md.run() |
520 | md.destroy() |
521 | if result == gtk.RESPONSE_YES: |
522 | @@ -179,17 +181,20 @@ |
523 | del self.core.pa_clients[ key ] |
524 | self.store.remove(iter) |
525 | self.core.save() |
526 | - |
527 | + |
528 | def on_button_add_new_clicked(self, widget): |
529 | - |
530 | + |
531 | if len(self.core.get_unregistered_clients()) > 0: |
532 | - |
533 | + |
534 | ecas = EarCandyAppSelect(self.core) |
535 | client = ecas.run() |
536 | if client: |
537 | self.update_client( client ) |
538 | else: |
539 | - md = gtk.MessageDialog(self.window, flags=0, type=gtk.MESSAGE_QUESTION, buttons=gtk.BUTTONS_OK, message_format="There are currently no pulseaudio streams unassigned,\n\nYou will need to start your sound application playing first") |
540 | + md = gtk.MessageDialog(self.window, flags=0, |
541 | + type=gtk.MESSAGE_QUESTION, buttons=gtk.BUTTONS_OK, |
542 | + message_format="There are currently no PulseAudio streams assigned.\n\n" |
543 | + "You will need to start your sound application playing first") |
544 | result = md.run() |
545 | md.destroy() |
546 | |
547 | @@ -201,9 +206,9 @@ |
548 | |
549 | def on_entry_command_changed(self, widget): |
550 | self.current_client.rule_re_command = re.compile(widget.get_text(), re.IGNORECASE) |
551 | - |
552 | + |
553 | def on_entry_window_title_changed(self, widget): |
554 | - self.current_client.rule_re_window_title = re.compile(widget.get_text(), re.IGNORECASE) |
555 | + self.current_client.rule_re_window_title = re.compile(widget.get_text(), re.IGNORECASE) |
556 | |
557 | def on_combobox_profile_changed(self, widget): |
558 | for key in self.vals.keys(): |
559 | @@ -213,7 +218,7 @@ |
560 | |
561 | def on_vscale_volume_change_value(self, widget, a, b): |
562 | self.current_client.volume_default = self.vscale_volume.get_value() |
563 | - |
564 | + |
565 | def on_vscale_mute_change_value(self, widget, a, b): |
566 | self.current_client.volume_mute = self.vscale_mute.get_value() |
567 | |
568 | @@ -222,24 +227,24 @@ |
569 | if cvh.description: |
570 | text = cvh.description |
571 | model.set_value(iter,1, text) |
572 | - |
573 | - def on_entry_pulse_client_description_changed(self, widget): |
574 | + |
575 | + def on_entry_pulse_client_description_changed(self, widget): |
576 | model, iter = self.treeview_pulse_clients.get_selection().get_selected() |
577 | - cvh = model.get_value(iter, 5) |
578 | + cvh = model.get_value(iter, 5) |
579 | #cvh.description = self.entry_pulse_client_description.get_text() |
580 | self.update_label(cvh, model, iter) |
581 | - |
582 | + |
583 | def on_comboboxentry_window_match_changed(self, widget): |
584 | model, iter = self.treeview_pulse_clients.get_selection().get_selected() |
585 | - cvh = model.get_value(iter, 5) |
586 | + cvh = model.get_value(iter, 5) |
587 | cvh.window_name_rule = self.comboboxentry_window_match.get_text() |
588 | - |
589 | + |
590 | def on_treeview_pulse_clients_cursor_changed(self, widget): |
591 | model, iter = self.treeview_pulse_clients.get_selection().get_selected() |
592 | if not iter: return |
593 | self.selected_client = model.get_value(iter, 5) |
594 | self.__update_detail( self.selected_client) |
595 | - |
596 | + |
597 | def on_checkbutton_channel_window_toggled(self, widget): |
598 | self.current_client.window_position_fade = self.checkbutton_channel_window.get_active() |
599 | |
600 | @@ -249,7 +254,6 @@ |
601 | |
602 | def on_close_button_clicked(self,widget=None): |
603 | self.stop() |
604 | - |
605 | |
606 | def __get_title(self, client): |
607 | text = client.name |
608 | @@ -262,15 +266,17 @@ |
609 | client.iter = None |
610 | |
611 | self.store = gtk.ListStore(gtk.gdk.Pixbuf, str, str, bool, int, object) |
612 | - self.treeview_pulse_clients.set_model(self.store) |
613 | + self.treeview_pulse_clients.set_model(self.store) |
614 | |
615 | icon_theme = gtk.icon_theme_get_default() |
616 | icon = icon_theme.lookup_icon("audio-volume-medium", 32, 0).load_icon() |
617 | for client in self.core.pa_clients.values() : |
618 | if client.category and client.is_active() and not (client.name in self.core.ignore): |
619 | - client.iter = self.store.append(([client.icon or icon, self.__get_title(client), self.core.display[client.category], client.window_position_fade, 50, client])) |
620 | + client.iter = self.store.append(([client.icon or icon, self.__get_title(client), |
621 | + self.core.display[client.category], |
622 | + client.window_position_fade, 50, client])) |
623 | |
624 | - self.store.set_sort_column_id(1, gtk.SORT_ASCENDING) |
625 | + self.store.set_sort_column_id(1, gtk.SORT_ASCENDING) |
626 | |
627 | def update_client(self, client): |
628 | |
629 | @@ -303,12 +309,15 @@ |
630 | if client.has_rule() and not client.name in self.core.ignore: |
631 | icon_theme = gtk.icon_theme_get_default() |
632 | icon = icon_theme.lookup_icon("audio-volume-medium", 32, 0).load_icon() |
633 | - client.iter = self.store.append(([client.icon or icon, self.__get_title(client), self.core.display[client.category], client.window_position_fade, client.get_volume(), client])) |
634 | + client.iter = self.store.append(([client.icon or icon, self.__get_title(client), |
635 | + self.core.display[client.category], |
636 | + client.window_position_fade, client.get_volume(), |
637 | + client])) |
638 | |
639 | self.store.set_sort_column_id(1,gtk.SORT_ASCENDING) |
640 | |
641 | def run(self): |
642 | - self.window.show() |
643 | + self.window.show() |
644 | self.hscale_mute_level.set_value(self.core.mute_level) |
645 | self.hscale_fade.set_value(self.core.fade_timer_speed) |
646 | self.checkbutton_tray.set_active( self.core.tray.get_visible() ) |
647 | @@ -320,19 +329,14 @@ |
648 | self.core.save() |
649 | self.core.close_preferances() |
650 | self.window.destroy() |
651 | - |
652 | + |
653 | @threaded |
654 | def remove_client_on_timer(self, client): |
655 | #time.sleep(60) |
656 | #gobject.idle_add(self.remove_client, client) |
657 | return |
658 | - |
659 | + |
660 | def remove_client(self, client): |
661 | if not client.is_active() and client.iter : |
662 | self.store.remove(client.iter) |
663 | client.iter = None |
664 | - |
665 | - |
666 | - |
667 | - |
668 | - |
669 | |
670 | === renamed file 'Freedesktop.py' => 'earcandylib/Freedesktop.py' |
671 | --- Freedesktop.py 2009-02-06 10:31:04 +0000 |
672 | +++ earcandylib/Freedesktop.py 2009-05-22 15:58:21 +0000 |
673 | @@ -81,7 +81,7 @@ |
674 | items.sort() |
675 | for item in items: |
676 | file_object.write(item[0] + '=' + item[1] + '\n') |
677 | - |
678 | + |
679 | # turn command into an array |
680 | def get_exec_array(self): |
681 | command = [] |
682 | @@ -89,4 +89,3 @@ |
683 | for r in re.finditer(p, self.get("Exec")): |
684 | command.append( str(r.group(0)) ) |
685 | return command |
686 | - |
687 | |
688 | === added file 'earcandylib/Paths.py' |
689 | --- earcandylib/Paths.py 1970-01-01 00:00:00 +0000 |
690 | +++ earcandylib/Paths.py 2009-05-23 12:53:07 +0000 |
691 | @@ -0,0 +1,27 @@ |
692 | + |
693 | +from os.path import isdir, isfile, join, dirname, abspath |
694 | + |
695 | + |
696 | +DATA_DIR = None |
697 | + |
698 | + |
699 | +_path = abspath(join(dirname(__file__), '..', 'data')) |
700 | +if isdir(_path): |
701 | + DATA_DIR = _path |
702 | +else: |
703 | + # We are installed on the system |
704 | + for prefix in ["/usr", "/usr/local"]: |
705 | + _path = join(prefix, "share", "earcandy") |
706 | + if isdir(_path): |
707 | + DATA_DIR = _path |
708 | + |
709 | +if DATA_DIR == None: |
710 | + raise IOError, "cannot find data directory" |
711 | + |
712 | + |
713 | +def get_data_path(filename): |
714 | + path = join(DATA_DIR, filename) |
715 | + if isfile(path): |
716 | + return path |
717 | + else: |
718 | + raise IOError, "file not found" |
719 | |
720 | === renamed file 'Sink.py' => 'earcandylib/Sink.py' |
721 | --- Sink.py 2009-05-19 04:28:48 +0000 |
722 | +++ earcandylib/Sink.py 2009-05-22 15:58:21 +0000 |
723 | @@ -1,6 +1,6 @@ |
724 | -import math |
725 | +import math |
726 | import time |
727 | - |
728 | + |
729 | class Sink(): |
730 | def __init__(self, index, name, volume, client, channels=1): |
731 | |
732 | @@ -15,7 +15,7 @@ |
733 | |
734 | self.__previous_left = 0 |
735 | self.__previous_right = 0 |
736 | - |
737 | + |
738 | def set_volume(self): |
739 | |
740 | if len(self.volume) > 2: |
741 | @@ -26,9 +26,9 @@ |
742 | # Mono channel |
743 | target_volume = round(self.volume[1]) |
744 | |
745 | - if target_volume < self.client.volume_target: |
746 | + if target_volume < self.client.volume_target: |
747 | target_volume = round(target_volume + self.client.volume_step) |
748 | - elif target_volume > self.client.volume_target: |
749 | + elif target_volume > self.client.volume_target: |
750 | target_volume = round(target_volume - self.client.volume_step) |
751 | |
752 | if len(self.volume) > 2: |
753 | @@ -38,7 +38,7 @@ |
754 | if len(self.volume) > 2: |
755 | if self.client.balance < 0: left = math.fabs(target_volume - (target_volume / 100 * self.client.balance)) |
756 | if self.client.balance > 0: right = math.fabs(target_volume - (target_volume / 100 * self.client.balance)) |
757 | - |
758 | + |
759 | if self.volume[1] < left: |
760 | self.volume[1] = round(self.volume[1]) + self.client.volume_step |
761 | if self.volume[1] > left: |
762 | @@ -57,20 +57,20 @@ |
763 | else: |
764 | self.volume[1] = round(target_volume) |
765 | self.volume.append(target_volume) #FIXME: remove second channel volume |
766 | - |
767 | + |
768 | # we dont want to get stuck in a loop because volumes arn't exactly the same |
769 | result = math.fabs(self.volume[1] - self.__previous_left) > self.client.volume_step / 2 |
770 | result = result and (math.fabs(self.volume[2] - self.__previous_right) > self.client.volume_step /2) |
771 | - |
772 | + |
773 | # We never want a zero volume as we cant read meter levels from zero volume |
774 | result = result and not self.volume[1] == self.client.volume_step |
775 | - result = result and not self.volume[2] == self.client.volume_step |
776 | + result = result and not self.volume[2] == self.client.volume_step |
777 | if result: |
778 | |
779 | print "Adjust Volume", self.client.name, self.volume[1] , self.__previous_left |
780 | |
781 | self.__previous_left = self.volume[1] |
782 | self.__previous_right = self.volume[2] |
783 | - |
784 | + |
785 | return result |
786 | |
787 | |
788 | === renamed file 'Threads.py' => 'earcandylib/Threads.py' |
789 | === renamed file 'TrayIcon.py' => 'earcandylib/TrayIcon.py' |
790 | --- TrayIcon.py 2009-05-19 04:28:48 +0000 |
791 | +++ earcandylib/TrayIcon.py 2009-05-22 15:58:21 +0000 |
792 | @@ -1,15 +1,17 @@ |
793 | #!/usr/bin/env python |
794 | |
795 | -import os |
796 | import sys |
797 | import gtk |
798 | |
799 | +import EarCandyInfo |
800 | +from Paths import get_data_path |
801 | + |
802 | |
803 | class EarCandyStatusIcon(gtk.StatusIcon): |
804 | def __init__(self, core): |
805 | gtk.StatusIcon.__init__(self) |
806 | self.core = core |
807 | - |
808 | + |
809 | menu = ''' |
810 | <ui> |
811 | <menubar name="Menubar"> |
812 | @@ -44,7 +46,7 @@ |
813 | # |
814 | #self.core.slider.window.present() |
815 | self.core.slider.window.set_position(gtk.WIN_POS_MOUSE) |
816 | - screen, rect, orin = self.get_geometry() |
817 | + screen, rect, orin = self.get_geometry() |
818 | x, y = self.core.slider.window.get_position() |
819 | if rect.y - rect.height/2 > y - rect.height: |
820 | self.core.slider.window.move(x, y + rect.height) |
821 | @@ -58,24 +60,24 @@ |
822 | |
823 | def on_about(self, data): |
824 | dialog = gtk.AboutDialog() |
825 | - dialog.set_name('Ear Candy') |
826 | - dialog.set_version('0.4') |
827 | - dialog.set_comments('A pulse audio ear candy manager') |
828 | - dialog.set_website('http://edge.launchpad.net/eyecandy') |
829 | + dialog.set_name(EarCandyInfo.NAME) |
830 | + dialog.set_version(EarCandyInfo.VERSION) |
831 | + dialog.set_comments(EarCandyInfo.DESC) |
832 | + dialog.set_website(EarCandyInfo.WEBSITE) |
833 | dialog.run() |
834 | dialog.destroy() |
835 | - |
836 | + |
837 | |
838 | def set_icon(self): |
839 | if self.core.active: |
840 | - icon_path = os.path.join(os.path.dirname(__file__),"earsLabel.png") |
841 | + icon_path = get_data_path("earsLabel.png") |
842 | else: |
843 | - icon_path = os.path.join(os.path.dirname(__file__),"earsLabelOff.png") |
844 | + icon_path = get_data_path("earsLabelOff.png") |
845 | self.set_from_file( icon_path ) |
846 | |
847 | def stop(self, data=None): |
848 | gtk.main_quit() |
849 | - |
850 | + |
851 | if __name__ == '__main__': |
852 | ts = TrackerStatusIcon() |
853 | ts.run() |
854 | |
855 | === renamed file 'VolumeSlider.py' => 'earcandylib/VolumeSlider.py' |
856 | --- VolumeSlider.py 2009-05-19 04:28:48 +0000 |
857 | +++ earcandylib/VolumeSlider.py 2009-05-22 15:58:21 +0000 |
858 | @@ -16,13 +16,14 @@ |
859 | from glade_window import GladeWindow |
860 | from window.WindowWatcher import WindowWatcher |
861 | from Client import Client |
862 | +from Paths import get_data_path |
863 | |
864 | class EarCandyVolumeSlider(GladeWindow): |
865 | def __init__(self, core): |
866 | self.core = core |
867 | - |
868 | - GladeWindow.__init__(self, "pulseoptions.glade", "popup_volume_control") |
869 | - |
870 | + |
871 | + GladeWindow.__init__(self, get_data_path("pulseoptions.glade"), "popup_volume_control") |
872 | + |
873 | self.vscale_volume = self.wtree.get_widget("vscale_volume") |
874 | self.image_status = self.wtree.get_widget("image_status") |
875 | |
876 | @@ -48,7 +49,7 @@ |
877 | self.image_status.set_from_stock("gtk-media-pause", 4) |
878 | else: |
879 | self.image_status.set_from_stock("gtk-media-play", 4) |
880 | - |
881 | + |
882 | self.core.tray.set_icon() |
883 | self.stop() |
884 | |
885 | |
886 | === added file 'earcandylib/__init__.py' |
887 | === renamed file 'glade_window.py' => 'earcandylib/glade_window.py' |
888 | --- glade_window.py 2008-10-09 20:37:12 +0000 |
889 | +++ earcandylib/glade_window.py 2009-05-22 15:58:21 +0000 |
890 | @@ -18,9 +18,9 @@ |
891 | self.wtree = gtk.glade.XML(glade_file) |
892 | self.window = self.wtree.get_widget(window_name) |
893 | self.window.connect("destroy", self.on_destroy) |
894 | - |
895 | + |
896 | def run(self): |
897 | - self.window.show() |
898 | + self.window.show() |
899 | gtk.main() |
900 | return self.return_value |
901 | |
902 | |
903 | === renamed directory 'pulseaudio' => 'earcandylib/pulseaudio' |
904 | === renamed directory 'window' => 'earcandylib/window' |
905 | === modified file 'earcandylib/window/WindowWatcher.py' |
906 | --- window/WindowWatcher.py 2009-05-11 22:27:41 +0000 |
907 | +++ earcandylib/window/WindowWatcher.py 2009-05-22 14:14:10 +0000 |
908 | @@ -6,7 +6,7 @@ |
909 | import wnck |
910 | import gobject |
911 | import os |
912 | -from DesktopFiles import DesktopFiles |
913 | +from earcandylib.DesktopFiles import DesktopFiles |
914 | |
915 | class Application(): |
916 | def __init__(self, command, name, desktop_files, icon): |
917 | |
918 | === added file 'setup.py' |
919 | --- setup.py 1970-01-01 00:00:00 +0000 |
920 | +++ setup.py 2009-05-23 12:26:12 +0000 |
921 | @@ -0,0 +1,21 @@ |
922 | +#!/usr/bin/env python |
923 | + |
924 | +from distutils.core import setup |
925 | +from glob import glob |
926 | + |
927 | +from earcandylib.EarCandyInfo import * |
928 | + |
929 | +DATA_FILES = [('share/earcandy', glob('data/*'))] |
930 | +DATA_FILES += [('share/applications', ['earcandy.desktop'])] |
931 | +DATA_FILES += [('share/pixmaps', ['data/earsLabel.png'])] |
932 | + |
933 | +setup(name = APPNAME, |
934 | + version = VERSION, |
935 | + description = DESC, |
936 | + author = AUTHOR, |
937 | + author_email = AUTHOR_EMAIL, |
938 | + url = WEBSITE, |
939 | + license = LICENSE, |
940 | + packages = ['earcandylib', 'earcandylib.pulseaudio', 'earcandylib.window'], |
941 | + data_files = DATA_FILES, |
942 | + scripts = ['earcandy']) |
In this branch the tree has been reorganized to make the application a package (earcandylib). ear_candy.py has been moved to earcandylib/ EarCandy. py and a earcandy script for running the application has been created. Data files have been moved to data/.
A distutils script (setup.py) has also been added. A tar ball can now be created with ./setup.py sdist.
A .desktop file has been added for easily running the application when it has been installed.