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.
30 print "Cannot load LASH python bindings, you want LASH unless you enjoy manual jack plumbing each time you use this app"
34 sys
.path
.insert(0, os
.path
.dirname(sys
.argv
[0]) + os
.sep
+ ".." + os
.sep
+ "share"+ os
.sep
+ "jack_mixer")
40 # no need for serialization if there is no LASH available
42 from serialization_xml
import xml_serialization
43 from serialization
import serialized_object
, serializator
45 class jack_mixer(serialized_object
):
46 def __init__(self
, name
, lash_client
):
47 self
.mixer
= jack_mixer_c
.create(name
)
52 # Send our client name to server
53 lash_event
= lash
.lash_event_new_with_type(lash
.LASH_Client_Name
)
54 lash
.lash_event_set_string(lash_event
, name
)
55 lash
.lash_send_event(lash_client
, lash_event
)
57 lash
.lash_jack_client_name(lash_client
, name
)
59 self
.window
= gtk
.Window(gtk
.WINDOW_TOPLEVEL
)
60 self
.window
.set_title(name
)
62 self
.gui_factory
= gui
.factory(glade_xml
, self
.window
, meter_scales
, slider_scales
)
64 self
.vbox_top
= gtk
.VBox()
65 self
.window
.add(self
.vbox_top
)
67 self
.menubar
= gtk
.MenuBar()
68 self
.vbox_top
.pack_start(self
.menubar
, False)
70 self
.channels_menu_item
= gtk
.MenuItem("_Channel")
71 self
.menubar
.append(self
.channels_menu_item
)
73 self
.settings_menu_item
= gtk
.MenuItem("_Settings")
74 self
.menubar
.append(self
.settings_menu_item
)
76 self
.window
.set_size_request(120,300)
78 self
.channels_menu
= gtk
.Menu()
79 self
.channels_menu_item
.set_submenu(self
.channels_menu
)
81 self
.channel_add_menu_item
= gtk
.ImageMenuItem(gtk
.STOCK_ADD
)
82 self
.channels_menu
.append(self
.channel_add_menu_item
)
83 self
.channel_add_menu_item
.connect("activate", self
.on_add_channel
)
85 self
.channel_remove_menu_item
= gtk
.ImageMenuItem(gtk
.STOCK_REMOVE
)
86 self
.channels_menu
.append(self
.channel_remove_menu_item
)
88 self
.channel_remove_menu
= gtk
.Menu()
89 self
.channel_remove_menu_item
.set_submenu(self
.channel_remove_menu
)
91 self
.channel_remove_all_menu_item
= gtk
.ImageMenuItem(gtk
.STOCK_CLEAR
)
92 self
.channels_menu
.append(self
.channel_remove_all_menu_item
)
93 self
.channel_remove_all_menu_item
.connect("activate", self
.on_channels_clear
)
95 self
.settings_menu
= gtk
.Menu()
96 self
.settings_menu_item
.set_submenu(self
.settings_menu
)
98 self
.settings_choose_meter_scale_menu_item
= gtk
.MenuItem("_Meter scale")
99 self
.settings_choose_meter_scale_menu_item
.connect("activate", self
.on_choose_meter_scale
)
100 self
.settings_menu
.append(self
.settings_choose_meter_scale_menu_item
)
102 self
.settings_choose_slider_scale_menu_item
= gtk
.MenuItem("_Slider scale")
103 self
.settings_choose_slider_scale_menu_item
.connect("activate", self
.on_choose_slider_scale
)
104 self
.settings_menu
.append(self
.settings_choose_slider_scale_menu_item
)
106 self
.hbox_top
= gtk
.HBox()
107 self
.vbox_top
.pack_start(self
.hbox_top
, True)
109 self
.scrolled_window
= gtk
.ScrolledWindow()
110 self
.hbox_top
.pack_start(self
.scrolled_window
, True)
112 self
.hbox_inputs
= gtk
.HBox()
113 self
.hbox_inputs
.set_spacing(0)
114 self
.hbox_inputs
.set_border_width(0)
115 self
.hbox_top
.set_spacing(0)
116 self
.hbox_top
.set_border_width(0)
119 self
.scrolled_window
.set_policy(gtk
.POLICY_AUTOMATIC
, gtk
.POLICY_AUTOMATIC
)
120 self
.scrolled_window
.add_with_viewport(self
.hbox_inputs
)
122 self
.main_mix
= main_mix(self
.mixer
, self
.gui_factory
)
123 self
.main_mix
.realize()
125 frame
.add(self
.main_mix
)
126 self
.hbox_top
.pack_start(frame
, False)
128 self
.window
.connect("destroy", gtk
.main_quit
)
130 gobject
.timeout_add(80, self
.read_meters
)
131 self
.lash_client
= lash_client
134 gobject
.timeout_add(1000, self
.lash_check_events
)
137 print "Cleaning jack_mixer"
141 for channel
in self
.channels
:
144 jack_mixer_c
.destroy(self
.mixer
)
146 def on_choose_meter_scale(self
, widget
):
147 self
.gui_factory
.run_dialog_choose_meter_scale()
149 def on_choose_slider_scale(self
, widget
):
150 self
.gui_factory
.run_dialog_choose_slider_scale()
152 def on_add_channel(self
, widget
):
153 result
= self
.gui_factory
.run_dialog_add_channel()
155 self
.add_channel(result
['name'], result
['stereo'])
156 self
.window
.show_all()
158 def on_remove_channel(self
, widget
, channel
, channel_remove_menu_item
):
159 print 'Removing channel "%s"' % channel
.channel_name
160 self
.channel_remove_menu
.remove(channel_remove_menu_item
)
161 for i
in range(len(self
.channels
)):
162 if self
.channels
[i
] is channel
:
165 self
.hbox_inputs
.remove(channel
.parent
)
167 if len(self
.channels
) == 0:
168 self
.channel_remove_menu_item
.set_sensitive(False)
170 def on_channels_clear(self
, widget
):
171 for channel
in self
.channels
:
173 self
.hbox_inputs
.remove(channel
.parent
)
175 self
.channel_remove_menu
= gtk
.Menu()
176 self
.channel_remove_menu_item
.set_submenu(self
.channel_remove_menu
)
177 self
.channel_remove_menu_item
.set_sensitive(False)
179 def add_channel(self
, name
, stereo
):
180 channel
= input_channel(self
.mixer
, self
.gui_factory
, name
, stereo
)
181 self
.add_channel_precreated(channel
)
183 def add_channel_precreated(self
, channel
):
184 self
.channels
.append(channel
)
187 self
.hbox_inputs
.pack_start(frame
, False)
189 channel_remove_menu_item
= gtk
.MenuItem(channel
.channel_name
)
190 self
.channel_remove_menu
.append(channel_remove_menu_item
)
191 channel_remove_menu_item
.connect("activate", self
.on_remove_channel
, channel
, channel_remove_menu_item
)
192 self
.channel_remove_menu_item
.set_sensitive(True)
194 def read_meters(self
):
195 for channel
in self
.channels
:
197 self
.main_mix
.read_meter()
200 def lash_check_events(self
):
201 while lash
.lash_get_pending_event_count(self
.lash_client
):
202 event
= lash
.lash_get_event(self
.lash_client
)
206 event_type
= lash
.lash_event_get_type(event
)
207 if event_type
== lash
.LASH_Quit
:
208 print "jack_mixer: LASH ordered quit."
211 elif event_type
== lash
.LASH_Save_File
:
212 directory
= lash
.lash_event_get_string(event
)
213 print "jack_mixer: LASH ordered to save data in directory %s" % directory
214 filename
= directory
+ os
.sep
+ "jack_mixer.xml"
215 f
= file(filename
, "w")
218 lash
.lash_send_event(self
.lash_client
, event
) # we crash with double free
219 elif event_type
== lash
.LASH_Restore_File
:
220 directory
= lash
.lash_event_get_string(event
)
221 print "jack_mixer: LASH ordered to restore data from directory %s" % directory
222 filename
= directory
+ os
.sep
+ "jack_mixer.xml"
223 f
= file(filename
, "r")
224 self
.load_from_xml(f
)
227 print "jack_mixer: Got unhandled LASH event, type " + str(event_type
)
230 #lash.lash_event_destroy(event)
234 def save_to_xml(self
, file):
235 #print "Saving to XML..."
236 b
= xml_serialization()
241 def load_from_xml(self
, file):
242 #print "Loading from XML..."
243 self
.on_channels_clear(None)
244 self
.unserialized_channels
= []
245 b
= xml_serialization()
248 s
.unserialize(self
, b
)
249 for channel
in self
.unserialized_channels
:
250 self
.add_channel_precreated(channel
)
251 del self
.unserialized_channels
252 self
.window
.show_all()
254 def serialize(self
, object_backend
):
257 def unserialize_property(self
, name
, value
):
260 def unserialize_child(self
, name
):
261 if name
== main_mix_serialization_name():
264 if name
== input_channel_serialization_name():
265 channel
= input_channel(self
.gui_factory
, "", True)
266 self
.unserialized_channels
.append(channel
)
269 def serialization_get_childs(self
):
270 '''Get child objects tha required and support serialization'''
271 childs
= self
.channels
[:]
272 childs
.append(self
.main_mix
)
275 def serialization_name(self
):
282 self
.window
.show_all()
286 #f = file("/dev/stdout", "w")
291 print "Usage: %s [mixer_name]" % sys
.argv
[0]
294 if lash
: # If LASH python bindings are available
295 # sys.argv is modified by this call
296 lash_client
= lash
.init(sys
.argv
, "jack_mixer", lash
.LASH_Config_File
)
303 for arg
in reversed(args
):
305 if len(arg
) != 0 and arg
[0] == '-':
310 print 'Unknown option "%s"' % args
[i
]
315 # print 'Non option argument "%s"' % args[i]
317 # Yeah , this sounds stupid, we connected earlier, but we dont want to show this if we got --help option
318 # This issue should be fixed in pylash, there is a reason for having two functions for initialization after all
320 print "Successfully connected to LASH server at " + lash
.lash_get_server_name(lash_client
)
328 name
= "jack_mixer-%u" % os
.getpid()
330 mixer
= jack_mixer(name
, lash_client
)
336 glade_dir
= os
.path
.dirname(sys
.argv
[0])
338 # since ppl tend to run "python jack_mixer.py", lets assume that it is in current directory
339 # "python ./jack_mixer.py" and "./jack_mixer.py" will work anyway.
343 glade_file
= glade_dir
+ os
.sep
+ "jack_mixer.glade"
345 if not os
.path
.isfile(glade_file
):
346 glade_file
= glade_dir
+ os
.sep
+ ".." + os
.sep
+ "share"+ os
.sep
+ "jack_mixer" + os
.sep
+ "jack_mixer.glade"
348 #print 'Loading glade from "%s"' % glade_file
350 glade_xml
= gtk
.glade
.XML(glade_file
)
352 # scales suitable as meter scales
353 meter_scales
= [scale
.iec_268(), scale
.linear_70dB(), scale
.iec_268_minimalistic()]
355 # scales suitable as volume slider scales
356 slider_scales
= [scale
.linear_30dB(), scale
.linear_70dB()]
358 if __name__
== "__main__":