3 # This file is part of jack_mixer
5 # Copyright (C) 2006 Nedko Arnaudov <nedko@arnaudov.name>
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; version 2 of the License
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
32 print "Cannot load LASH python bindings, you want LASH unless you enjoy manual jack plumbing each time you use this app"
35 # no need for serialization if there is no LASH available
37 from serialization_xml
import xml_serialization
38 from serialization
import serialized_object
, serializator
40 class jack_mixer(serialized_object
):
41 def __init__(self
, name
, lash_client
):
42 self
.window
= gtk
.Window(gtk
.WINDOW_TOPLEVEL
)
43 self
.window
.set_title(name
)
45 self
.gui_factory
= gui
.factory(glade_xml
, self
.window
, meter_scales
, slider_scales
)
47 self
.vbox_top
= gtk
.VBox()
48 self
.window
.add(self
.vbox_top
)
50 self
.menubar
= gtk
.MenuBar()
51 self
.vbox_top
.pack_start(self
.menubar
, False)
53 self
.channels_menu_item
= gtk
.MenuItem("_Channel")
54 self
.menubar
.append(self
.channels_menu_item
)
56 self
.settings_menu_item
= gtk
.MenuItem("_Settings")
57 self
.menubar
.append(self
.settings_menu_item
)
59 self
.window
.set_size_request(120,300)
61 self
.channels_menu
= gtk
.Menu()
62 self
.channels_menu_item
.set_submenu(self
.channels_menu
)
64 self
.channel_add_menu_item
= gtk
.ImageMenuItem(gtk
.STOCK_ADD
)
65 self
.channels_menu
.append(self
.channel_add_menu_item
)
66 self
.channel_add_menu_item
.connect("activate", self
.on_add_channel
)
68 self
.channel_remove_menu_item
= gtk
.ImageMenuItem(gtk
.STOCK_REMOVE
)
69 self
.channels_menu
.append(self
.channel_remove_menu_item
)
71 self
.channel_remove_menu
= gtk
.Menu()
72 self
.channel_remove_menu_item
.set_submenu(self
.channel_remove_menu
)
74 self
.channel_remove_all_menu_item
= gtk
.ImageMenuItem(gtk
.STOCK_CLEAR
)
75 self
.channels_menu
.append(self
.channel_remove_all_menu_item
)
76 self
.channel_remove_all_menu_item
.connect("activate", self
.on_channels_clear
)
78 self
.settings_menu
= gtk
.Menu()
79 self
.settings_menu_item
.set_submenu(self
.settings_menu
)
81 self
.settings_choose_meter_scale_menu_item
= gtk
.MenuItem("_Meter scale")
82 self
.settings_choose_meter_scale_menu_item
.connect("activate", self
.on_choose_meter_scale
)
83 self
.settings_menu
.append(self
.settings_choose_meter_scale_menu_item
)
85 self
.settings_choose_slider_scale_menu_item
= gtk
.MenuItem("_Slider scale")
86 self
.settings_choose_slider_scale_menu_item
.connect("activate", self
.on_choose_slider_scale
)
87 self
.settings_menu
.append(self
.settings_choose_slider_scale_menu_item
)
89 self
.hbox_top
= gtk
.HBox()
90 self
.vbox_top
.pack_start(self
.hbox_top
, True)
92 self
.scrolled_window
= gtk
.ScrolledWindow()
93 self
.hbox_top
.pack_start(self
.scrolled_window
, True)
95 self
.hbox_inputs
= gtk
.HBox()
96 self
.hbox_inputs
.set_spacing(0)
97 self
.hbox_inputs
.set_border_width(0)
98 self
.hbox_top
.set_spacing(0)
99 self
.hbox_top
.set_border_width(0)
102 self
.scrolled_window
.set_policy(gtk
.POLICY_AUTOMATIC
, gtk
.POLICY_AUTOMATIC
)
103 self
.scrolled_window
.add_with_viewport(self
.hbox_inputs
)
105 self
.main_mix
= main_mix(self
.gui_factory
)
106 self
.main_mix
.realize()
108 frame
.add(self
.main_mix
)
109 self
.hbox_top
.pack_start(frame
, False)
111 self
.window
.connect("destroy", gtk
.main_quit
)
113 gobject
.timeout_add(80, self
.read_meters
)
114 self
.lash_client
= lash_client
117 gobject
.timeout_add(1000, self
.lash_check_events
)
120 print "Cleaning jack_mixer"
121 for channel
in self
.channels
:
124 def on_choose_meter_scale(self
, widget
):
125 self
.gui_factory
.run_dialog_choose_meter_scale()
127 def on_choose_slider_scale(self
, widget
):
128 self
.gui_factory
.run_dialog_choose_slider_scale()
130 def on_add_channel(self
, widget
):
131 result
= self
.gui_factory
.run_dialog_add_channel()
133 self
.add_channel(result
['name'], result
['stereo'])
134 self
.window
.show_all()
136 def on_remove_channel(self
, widget
, channel
, channel_remove_menu_item
):
137 print 'Removing channel "%s"' % channel
.channel_name
138 self
.channel_remove_menu
.remove(channel_remove_menu_item
)
139 for i
in range(len(self
.channels
)):
140 if self
.channels
[i
] is channel
:
143 self
.hbox_inputs
.remove(channel
.parent
)
145 if len(self
.channels
) == 0:
146 self
.channel_remove_menu_item
.set_sensitive(False)
148 def on_channels_clear(self
, widget
):
149 for channel
in self
.channels
:
151 self
.hbox_inputs
.remove(channel
.parent
)
153 self
.channel_remove_menu
= gtk
.Menu()
154 self
.channel_remove_menu_item
.set_submenu(self
.channel_remove_menu
)
155 self
.channel_remove_menu_item
.set_sensitive(False)
157 def add_channel(self
, name
, stereo
):
158 channel
= input_channel(self
.gui_factory
, name
, stereo
)
159 self
.add_channel_precreated(channel
)
161 def add_channel_precreated(self
, channel
):
162 self
.channels
.append(channel
)
165 self
.hbox_inputs
.pack_start(frame
, False)
167 channel_remove_menu_item
= gtk
.MenuItem(channel
.channel_name
)
168 self
.channel_remove_menu
.append(channel_remove_menu_item
)
169 channel_remove_menu_item
.connect("activate", self
.on_remove_channel
, channel
, channel_remove_menu_item
)
170 self
.channel_remove_menu_item
.set_sensitive(True)
172 def read_meters(self
):
173 for channel
in self
.channels
:
175 self
.main_mix
.read_meter()
178 def lash_check_events(self
):
179 while lash
.lash_get_pending_event_count(self
.lash_client
):
180 event
= lash
.lash_get_event(self
.lash_client
)
184 event_type
= lash
.lash_event_get_type(event
)
185 if event_type
== lash
.LASH_Quit
:
186 print "jack_mixer: LASH ordered quit."
189 elif event_type
== lash
.LASH_Save_File
:
190 directory
= lash
.lash_event_get_string(event
)
191 print "jack_mixer: LASH ordered to save data in directory %s" % directory
192 filename
= directory
+ os
.sep
+ "jack_mixer.xml"
193 f
= file(filename
, "w")
196 lash
.lash_send_event(self
.lash_client
, event
) # we crash with double free
197 elif event_type
== lash
.LASH_Restore_File
:
198 directory
= lash
.lash_event_get_string(event
)
199 print "jack_mixer: LASH ordered to restore data from directory %s" % directory
200 filename
= directory
+ os
.sep
+ "jack_mixer.xml"
201 f
= file(filename
, "r")
202 self
.load_from_xml(f
)
205 print "jack_mixer: Got unhandled LASH event, type " + str(event_type
)
208 #lash.lash_event_destroy(event)
212 def save_to_xml(self
, file):
213 #print "Saving to XML..."
214 b
= xml_serialization()
219 def load_from_xml(self
, file):
220 #print "Loading from XML..."
221 self
.on_channels_clear(None)
222 self
.unserialized_channels
= []
223 b
= xml_serialization()
226 s
.unserialize(self
, b
)
227 for channel
in self
.unserialized_channels
:
228 self
.add_channel_precreated(channel
)
229 del self
.unserialized_channels
230 self
.window
.show_all()
232 def serialize(self
, object_backend
):
235 def unserialize_property(self
, name
, value
):
238 def unserialize_child(self
, name
):
239 if name
== main_mix_serialization_name():
242 if name
== input_channel_serialization_name():
243 channel
= input_channel(self
.gui_factory
, "", True)
244 self
.unserialized_channels
.append(channel
)
247 def serialization_get_childs(self
):
248 '''Get child objects tha required and support serialization'''
249 childs
= self
.channels
[:]
250 childs
.append(self
.main_mix
)
253 def serialization_name(self
):
257 self
.window
.show_all()
261 #f = file("/dev/stdout", "w")
266 print "Usage: %s [mixer_name]" % sys
.argv
[0]
269 if lash
: # If LASH python bindings are available
270 # sys.argv is modified by this call
271 lash_client
= lash
.init(sys
.argv
, "jack_mixer", lash
.LASH_Config_File
)
278 for arg
in reversed(args
):
280 if len(arg
) != 0 and arg
[0] == '-':
285 print 'Unknown option "%s"' % args
[i
]
290 # print 'Non option argument "%s"' % args[i]
292 # Yeah , this sounds stupid, we connected earlier, but we dont want to show this if we got --help option
293 # This issue should be fixed in pylash, there is a reason for having two functions for initialization after all
295 print "Successfully connected to LASH server at " + lash
.lash_get_server_name(lash_client
)
303 name
= "jack_mixer-%u" % os
.getpid()
305 if not jack_mixer_c
.init(name
):
309 # Send our client name to server
310 lash_event
= lash
.lash_event_new_with_type(lash
.LASH_Client_Name
)
311 lash
.lash_event_set_string(lash_event
, name
)
312 lash
.lash_send_event(lash_client
, lash_event
)
314 lash
.lash_jack_client_name(lash_client
, name
)
316 mixer
= jack_mixer(name
, lash_client
)
322 jack_mixer_c
.uninit()
324 glade_dir
= os
.path
.dirname(sys
.argv
[0])
326 # since ppl tend to run "python jack_mixer.py", lets assume that it is in current directory
327 # "python ./jack_mixer.py" and "./jack_mixer.py" will work anyway.
331 glade_file
= glade_dir
+ os
.sep
+ "jack_mixer.glade"
333 if not os
.path
.isfile(glade_file
):
334 glade_file
= glade_dir
+ os
.sep
+ ".." + os
.sep
+ "share"+ os
.sep
+ "jack_mixer" + os
.sep
+ "jack_mixer.glade"
336 #print 'Loading glade from "%s"' % glade_file
338 glade_xml
= gtk
.glade
.XML(glade_file
)
340 # scales suitable as meter scales
341 meter_scales
= [scale
.iec_268(), scale
.linear_70dB()]
343 # scales suitable as volume slider scales
344 slider_scales
= [scale
.linear_30dB(), scale
.linear_70dB()]
346 if __name__
== "__main__":