]> git.0d.be Git - jack_mixer.git/blob - jack_mixer.py
Put output channels in scrolled window and seperate in/out channels with a draggable...
[jack_mixer.git] / jack_mixer.py
1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*-
3 #
4 # This file is part of jack_mixer
5 #
6 # Copyright (C) 2006-2009 Nedko Arnaudov <nedko@arnaudov.name>
7 # Copyright (C) 2009 Frederic Peters <fpeters@0d.be>
8 #
9 # This program is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; version 2 of the License
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with this program; if not, write to the Free Software
20 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
21
22 import os
23 import signal
24 import sys
25 from argparse import ArgumentParser
26
27 import gi
28 gi.require_version('Gtk', '3.0')
29 from gi.repository import Gtk
30 from gi.repository import GObject
31 from gi.repository import GLib
32
33 # temporary change Python modules lookup path to look into installation
34 # directory ($prefix/share/jack_mixer/)
35 old_path = sys.path
36 sys.path.insert(0, os.path.join(os.path.dirname(sys.argv[0]), '..', 'share', 'jack_mixer'))
37
38 import jack_mixer_c
39
40 import scale
41 from channel import *
42
43 import gui
44 from preferences import PreferencesDialog
45
46 from serialization_xml import XmlSerialization
47 from serialization import SerializedObject, Serializator
48
49 from nsmclient import NSMClient
50
51 # restore Python modules lookup path
52 sys.path = old_path
53
54 class JackMixer(SerializedObject):
55
56     # scales suitable as meter scales
57     meter_scales = [scale.IEC268(), scale.Linear70dB(), scale.IEC268Minimalistic()]
58
59     # scales suitable as volume slider scales
60     slider_scales = [scale.Linear30dB(), scale.Linear70dB()]
61
62     # name of settngs file that is currently open
63     current_filename = None
64
65     _init_solo_channels = None
66
67     def __init__(self, client_name='jack_mixer'):
68         self.visible = False
69         self.nsm_client = None
70
71         if os.environ.get('NSM_URL'):
72             self.nsm_client = NSMClient(prettyName = "jack_mixer",
73                                         saveCallback = self.nsm_save_cb,
74                                         openOrNewCallback = self.nsm_open_cb,
75                                         supportsSaveStatus = False,
76                                         hideGUICallback = self.nsm_hide_cb,
77                                         showGUICallback = self.nsm_show_cb,
78                                         exitProgramCallback = self.nsm_exit_cb,
79                                         loggingLevel = "error",
80                                        )
81             self.nsm_client.announceGuiVisibility(self.visible)
82         else:
83
84             self.visible = True
85             self.create_mixer(client_name, with_nsm = False)
86
87     def create_mixer(self, client_name, with_nsm = True):
88         self.create_ui(with_nsm)
89         self.mixer = jack_mixer_c.Mixer(client_name)
90         if not self.mixer:
91             sys.exit(1)
92
93         #self.mixer.midi_behavior_mode = self.gui_factory.midi_behavior_modes[self.gui_factory.get_midi_behavior_mode()]
94
95         self.window.set_title(client_name)
96
97         self.monitor_channel = self.mixer.add_output_channel("Monitor", True, True)
98         self.save = False
99
100         GLib.timeout_add(80, self.read_meters)
101         if with_nsm:
102             GLib.timeout_add(200, self.nsm_react)
103         GLib.timeout_add(50, self.midi_events_check)
104
105     def new_menu_item(self, title, callback=None, accel=None, enabled=True):
106         menuitem = Gtk.MenuItem.new_with_mnemonic(title)
107         menuitem.set_sensitive(enabled)
108         if callback:
109             menuitem.connect("activate", callback)
110         if accel:
111             key, mod = Gtk.accelerator_parse(accel)
112             menuitem.add_accelerator("activate", self.menu_accelgroup, key, mod,
113                                      Gtk.AccelFlags.VISIBLE)
114         return menuitem
115
116     def create_ui(self, with_nsm):
117         self.channels = []
118         self.output_channels = []
119         self.window = Gtk.Window(type=Gtk.WindowType.TOPLEVEL)
120         self.window.set_icon_name('jack_mixer')
121         self.gui_factory = gui.Factory(self.window, self.meter_scales, self.slider_scales)
122         self.gui_factory.connect('midi-behavior-mode-changed', self.on_midi_behavior_mode_changed)
123         self.vbox_top = Gtk.VBox()
124         self.window.add(self.vbox_top)
125
126         self.menu_accelgroup = Gtk.AccelGroup()
127         self.window.add_accel_group(self.menu_accelgroup)
128
129         self.menubar = Gtk.MenuBar()
130         self.vbox_top.pack_start(self.menubar, False, True, 0)
131
132         mixer_menu_item = Gtk.MenuItem.new_with_mnemonic("_Mixer")
133         self.menubar.append(mixer_menu_item)
134         edit_menu_item = Gtk.MenuItem.new_with_mnemonic('_Edit')
135         self.menubar.append(edit_menu_item)
136         help_menu_item = Gtk.MenuItem.new_with_mnemonic('_Help')
137         self.menubar.append(help_menu_item)
138
139         self.window.set_default_size(120, 300)
140
141         self.mixer_menu = Gtk.Menu()
142         mixer_menu_item.set_submenu(self.mixer_menu)
143
144         self.mixer_menu.append(self.new_menu_item('New _Input Channel',
145                                                   self.on_add_input_channel, "<Control>N"))
146         self.mixer_menu.append(self.new_menu_item('New Output _Channel',
147                                                   self.on_add_output_channel, "<Shift><Control>N"))
148
149         self.mixer_menu.append(Gtk.SeparatorMenuItem())
150         if not with_nsm:
151             self.mixer_menu.append(self.new_menu_item('_Open...', self.on_open_cb, "<Control>O"))
152
153         self.mixer_menu.append(self.new_menu_item('_Save', self.on_save_cb, "<Control>S"))
154
155         if not with_nsm:
156             self.mixer_menu.append(self.new_menu_item('Save _As...', self.on_save_as_cb,
157                                                       "<Shift><Control>S"))
158
159         self.mixer_menu.append(Gtk.SeparatorMenuItem())
160         self.mixer_menu.append(self.new_menu_item('_Quit', self.on_quit_cb, "<Control>Q"))
161
162         edit_menu = Gtk.Menu()
163         edit_menu_item.set_submenu(edit_menu)
164
165         self.channel_edit_input_menu_item = self.new_menu_item('_Edit Input Channel',
166                                                                enabled=False)
167         edit_menu.append(self.channel_edit_input_menu_item)
168         self.channel_edit_input_menu = Gtk.Menu()
169         self.channel_edit_input_menu_item.set_submenu(self.channel_edit_input_menu)
170
171         self.channel_edit_output_menu_item = self.new_menu_item('E_dit Output Channel',
172                                                                 enabled=False)
173         edit_menu.append(self.channel_edit_output_menu_item)
174         self.channel_edit_output_menu = Gtk.Menu()
175         self.channel_edit_output_menu_item.set_submenu(self.channel_edit_output_menu)
176
177         self.channel_remove_input_menu_item = self.new_menu_item('_Remove Input Channel',
178                                                                  enabled=False)
179         edit_menu.append(self.channel_remove_input_menu_item)
180         self.channel_remove_input_menu = Gtk.Menu()
181         self.channel_remove_input_menu_item.set_submenu(self.channel_remove_input_menu)
182
183         self.channel_remove_output_menu_item = self.new_menu_item('Re_move Output Channel',
184                                                                   enabled=False)
185         edit_menu.append(self.channel_remove_output_menu_item)
186         self.channel_remove_output_menu = Gtk.Menu()
187         self.channel_remove_output_menu_item.set_submenu(self.channel_remove_output_menu)
188
189         edit_menu.append(self.new_menu_item('_Clear', self.on_channels_clear, "<Control>X"))
190         edit_menu.append(Gtk.SeparatorMenuItem())
191         edit_menu.append(self.new_menu_item('_Preferences', self.on_preferences_cb, "<Control>P"))
192
193         help_menu = Gtk.Menu()
194         help_menu_item.set_submenu(help_menu)
195
196         help_menu.append(self.new_menu_item('_About', self.on_about, "F1"))
197
198         self.hbox_top = Gtk.HBox()
199         self.vbox_top.pack_start(self.hbox_top, True, True, 0)
200
201         self.scrolled_window = Gtk.ScrolledWindow()
202         self.scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
203
204         self.hbox_inputs = Gtk.Box()
205         self.hbox_inputs.set_spacing(0)
206         self.hbox_inputs.set_border_width(0)
207         self.hbox_top.set_spacing(0)
208         self.hbox_top.set_border_width(0)
209         self.scrolled_window.add(self.hbox_inputs)
210         self.hbox_outputs = Gtk.Box()
211         self.hbox_outputs.set_spacing(0)
212         self.hbox_outputs.set_border_width(0)
213         self.scrolled_output = Gtk.ScrolledWindow()
214         self.scrolled_output.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
215         self.scrolled_output.add(self.hbox_outputs)
216         paned = Gtk.HPaned()
217         self.hbox_top.pack_start(paned, True, True, 0)
218         paned.pack1(self.scrolled_window, True, False)
219         paned.pack2(self.scrolled_output, True, False)
220
221         self.window.connect("destroy", Gtk.main_quit)
222
223         self.window.connect('delete-event', self.on_delete_event)
224
225     def nsm_react(self):
226         self.nsm_client.reactToMessage()
227         return True
228
229     def nsm_hide_cb(self):
230         self.window.hide()
231         self.visible = False
232         self.nsm_client.announceGuiVisibility(False)
233
234     def nsm_show_cb(self):
235         self.window.show_all()
236         self.visible = True
237         self.nsm_client.announceGuiVisibility(True)
238
239     def nsm_open_cb(self, path, session_name, client_name):
240         self.create_mixer(client_name, with_nsm = True)
241         self.current_filename = path + '.xml'
242         if os.path.isfile(self.current_filename):
243             f = open(self.current_filename, 'r')
244             self.load_from_xml(f)
245             f.close()
246         else:
247             f = open(self.current_filename, 'w')
248             f.close()
249
250     def nsm_save_cb(self, path, session_name, client_name):
251         self.current_filename = path + '.xml'
252         f = open(self.current_filename, 'w')
253         self.save_to_xml(f)
254         f.close()
255
256     def nsm_exit_cb(self, path, session_name, client_name):
257         Gtk.main_quit()
258
259     def on_midi_behavior_mode_changed(self, gui_factory, value):
260         self.mixer.midi_behavior_mode = value
261
262     def on_delete_event(self, widget, event):
263         return False
264
265     def sighandler(self, signum, frame):
266         #print "Signal %d received" % signum
267         if signum == signal.SIGUSR1:
268             self.save = True
269         elif signum == signal.SIGTERM:
270             Gtk.main_quit()
271         elif signum == signal.SIGINT:
272             Gtk.main_quit()
273         else:
274             print("Unknown signal %d received" % signum)
275
276     def cleanup(self):
277         print("Cleaning jack_mixer")
278         if not self.mixer:
279             return
280
281         for channel in self.channels:
282             channel.unrealize()
283
284         self.mixer.destroy()
285
286     def on_open_cb(self, *args):
287         dlg = Gtk.FileChooserDialog(title='Open', parent=self.window,
288                         action=Gtk.FileChooserAction.OPEN)
289         dlg.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
290                         Gtk.STOCK_OPEN, Gtk.ResponseType.OK)
291         dlg.set_default_response(Gtk.ResponseType.OK)
292         if dlg.run() == Gtk.ResponseType.OK:
293             filename = dlg.get_filename()
294             try:
295                 f = open(filename, 'r')
296                 self.load_from_xml(f)
297             except Exception as e:
298                 err = Gtk.MessageDialog(parent = self.window,
299                             modal = True,
300                             message_type = Gtk.MessageType.ERROR,
301                             buttons = Gtk.ButtonsType.OK,
302                             text = "Failed loading settings (%s)." % str(e))
303                 err.run()
304                 err.destroy()
305             else:
306                 self.current_filename = filename
307             finally:
308                 f.close()
309         dlg.destroy()
310
311     def on_save_cb(self, *args):
312         if not self.current_filename:
313             return self.on_save_as_cb()
314         f = open(self.current_filename, 'w')
315         self.save_to_xml(f)
316         f.close()
317
318     def on_save_as_cb(self, *args):
319         dlg = Gtk.FileChooserDialog(title='Save', parent=self.window,
320                         action=Gtk.FileChooserAction.SAVE)
321         dlg.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
322                         Gtk.STOCK_SAVE, Gtk.ResponseType.OK)
323         dlg.set_default_response(Gtk.ResponseType.OK)
324         if dlg.run() == Gtk.ResponseType.OK:
325             self.current_filename = dlg.get_filename()
326             self.on_save_cb()
327         dlg.destroy()
328
329     def on_quit_cb(self, *args):
330         Gtk.main_quit()
331
332     preferences_dialog = None
333     def on_preferences_cb(self, widget):
334         if not self.preferences_dialog:
335             self.preferences_dialog = PreferencesDialog(self)
336         self.preferences_dialog.show()
337         self.preferences_dialog.present()
338
339     def on_add_input_channel(self, widget):
340         dialog = NewChannelDialog(app=self)
341         dialog.set_transient_for(self.window)
342         dialog.show()
343         ret = dialog.run()
344         dialog.hide()
345
346         if ret == Gtk.ResponseType.OK:
347             result = dialog.get_result()
348             channel = self.add_channel(**result)
349             if self.visible:
350                 self.window.show_all()
351
352     def on_add_output_channel(self, widget):
353         dialog = NewOutputChannelDialog(app=self)
354         dialog.set_transient_for(self.window)
355         dialog.show()
356         ret = dialog.run()
357         dialog.hide()
358
359         if ret == Gtk.ResponseType.OK:
360             result = dialog.get_result()
361             channel = self.add_output_channel(**result)
362             if self.visible:
363                 self.window.show_all()
364
365     def on_edit_input_channel(self, widget, channel):
366         print('Editing channel "%s"' % channel.channel_name)
367         channel.on_channel_properties()
368
369     def remove_channel_edit_input_menuitem_by_label(self, widget, label):
370         if (widget.get_label() == label):
371             self.channel_edit_input_menu.remove(widget)
372
373     def on_remove_input_channel(self, widget, channel):
374         print('Removing channel "%s"' % channel.channel_name)
375         self.channel_remove_input_menu.remove(widget)
376         self.channel_edit_input_menu.foreach(
377             self.remove_channel_edit_input_menuitem_by_label,
378             channel.channel_name);
379         if self.monitored_channel is channel:
380             channel.monitor_button.set_active(False)
381         for i in range(len(self.channels)):
382             if self.channels[i] is channel:
383                 channel.unrealize()
384                 del self.channels[i]
385                 self.hbox_inputs.remove(channel.get_parent())
386                 break
387         if not self.channels:
388             self.channel_edit_input_menu_item.set_sensitive(False)
389             self.channel_remove_input_menu_item.set_sensitive(False)
390
391     def on_edit_output_channel(self, widget, channel):
392         print('Editing channel "%s"' % channel.channel_name)
393         channel.on_channel_properties()
394
395     def remove_channel_edit_output_menuitem_by_label(self, widget, label):
396         if (widget.get_label() == label):
397             self.channel_edit_output_menu.remove(widget)
398
399     def on_remove_output_channel(self, widget, channel):
400         print('Removing channel "%s"' % channel.channel_name)
401         self.channel_remove_output_menu.remove(widget)
402         self.channel_edit_output_menu.foreach(
403             self.remove_channel_edit_output_menuitem_by_label,
404             channel.channel_name);
405         if self.monitored_channel is channel:
406             channel.monitor_button.set_active(False)
407         for i in range(len(self.channels)):
408             if self.output_channels[i] is channel:
409                 channel.unrealize()
410                 del self.output_channels[i]
411                 self.hbox_outputs.remove(channel.get_parent())
412                 break
413         if not self.output_channels:
414             self.channel_edit_output_menu_item.set_sensitive(False)
415             self.channel_remove_output_menu_item.set_sensitive(False)
416
417     def rename_channels(self, container, parameters):
418         if (container.get_label() == parameters['oldname']):
419             container.set_label(parameters['newname'])
420
421     def on_channel_rename(self, oldname, newname):
422         rename_parameters = { 'oldname' : oldname, 'newname' : newname }
423         self.channel_edit_input_menu.foreach(self.rename_channels,
424             rename_parameters)
425         self.channel_edit_output_menu.foreach(self.rename_channels,
426             rename_parameters)
427         self.channel_remove_input_menu.foreach(self.rename_channels,
428             rename_parameters)
429         self.channel_remove_output_menu.foreach(self.rename_channels,
430             rename_parameters)
431         #print("Renaming channel from %s to %s\n" % (oldname, newname))
432
433
434     def on_channels_clear(self, widget):
435         dlg = Gtk.MessageDialog(parent = self.window,
436                 modal = True,
437                 message_type = Gtk.MessageType.WARNING,
438                 text = "Are you sure you want to clear all channels?",
439                 buttons = Gtk.ButtonsType.OK_CANCEL)
440         if not widget or dlg.run() == Gtk.ResponseType.OK:
441             for channel in self.output_channels:
442                 channel.unrealize()
443                 self.hbox_outputs.remove(channel.get_parent())
444             for channel in self.channels:
445                 channel.unrealize()
446                 self.hbox_inputs.remove(channel.get_parent())
447             self.channels = []
448             self.output_channels = []
449             self.channel_edit_input_menu = Gtk.Menu()
450             self.channel_edit_input_menu_item.set_submenu(self.channel_edit_input_menu)
451             self.channel_edit_input_menu_item.set_sensitive(False)
452             self.channel_remove_input_menu = Gtk.Menu()
453             self.channel_remove_input_menu_item.set_submenu(self.channel_remove_input_menu)
454             self.channel_remove_input_menu_item.set_sensitive(False)
455             self.channel_edit_output_menu = Gtk.Menu()
456             self.channel_edit_output_menu_item.set_submenu(self.channel_edit_output_menu)
457             self.channel_edit_output_menu_item.set_sensitive(False)
458             self.channel_remove_output_menu = Gtk.Menu()
459             self.channel_remove_output_menu_item.set_submenu(self.channel_remove_output_menu)
460             self.channel_remove_output_menu_item.set_sensitive(False)
461         dlg.destroy()
462
463     def add_channel(self, name, stereo, volume_cc, balance_cc, mute_cc, solo_cc):
464         try:
465             channel = InputChannel(self, name, stereo)
466             self.add_channel_precreated(channel)
467         except Exception:
468             e = sys.exc_info()[0]
469             err = Gtk.MessageDialog(self.window,
470                             Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
471                             Gtk.MessageType.ERROR,
472                             Gtk.ButtonsType.OK,
473                             "Channel creation failed")
474             err.run()
475             err.destroy()
476             return
477         if volume_cc != -1:
478             channel.channel.volume_midi_cc = volume_cc
479         else:
480             channel.channel.autoset_volume_midi_cc()
481         if balance_cc != -1:
482             channel.channel.balance_midi_cc = balance_cc
483         else:
484             channel.channel.autoset_balance_midi_cc()
485         if mute_cc != -1:
486             channel.channel.mute_midi_cc = mute_cc
487         else:
488             channel.channel.autoset_mute_midi_cc()
489         if solo_cc != -1:
490             channel.channel.solo_midi_cc = solo_cc
491         else:
492             channel.channel.autoset_solo_midi_cc()
493         return channel
494
495     def add_channel_precreated(self, channel):
496         frame = Gtk.Frame()
497         frame.add(channel)
498         self.hbox_inputs.pack_start(frame, False, True, 0)
499         channel.realize()
500
501         channel_edit_menu_item = Gtk.MenuItem(label=channel.channel_name)
502         self.channel_edit_input_menu.append(channel_edit_menu_item)
503         channel_edit_menu_item.connect("activate", self.on_edit_input_channel, channel)
504         self.channel_edit_input_menu_item.set_sensitive(True)
505
506         channel_remove_menu_item = Gtk.MenuItem(label=channel.channel_name)
507         self.channel_remove_input_menu.append(channel_remove_menu_item)
508         channel_remove_menu_item.connect("activate", self.on_remove_input_channel, channel)
509         self.channel_remove_input_menu_item.set_sensitive(True)
510
511         self.channels.append(channel)
512
513         for outputchannel in self.output_channels:
514             channel.add_control_group(outputchannel)
515
516         # create post fader output channel matching the input channel
517         channel.post_fader_output_channel = self.mixer.add_output_channel(
518                         channel.channel.name + ' Out', channel.channel.is_stereo, True)
519         channel.post_fader_output_channel.volume = 0
520         channel.post_fader_output_channel.set_solo(channel.channel, True)
521
522     def read_meters(self):
523         for channel in self.channels:
524             channel.read_meter()
525         for channel in self.output_channels:
526             channel.read_meter()
527         return True
528
529     def midi_events_check(self):
530         for channel in self.channels + self.output_channels:
531             channel.midi_events_check()
532         return True
533
534     def add_output_channel(self, name, stereo, volume_cc, balance_cc, mute_cc,
535             display_solo_buttons, color):
536         try:
537             channel = OutputChannel(self, name, stereo)
538             channel.display_solo_buttons = display_solo_buttons
539             channel.color = color
540             self.add_output_channel_precreated(channel)
541         except Exception:
542             err = Gtk.MessageDialog(self.window,
543                             Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
544                             Gtk.MessageType.ERROR,
545                             Gtk.ButtonsType.OK,
546                             "Channel creation failed")
547             err.run()
548             err.destroy()
549             return
550
551         if volume_cc != -1:
552             channel.channel.volume_midi_cc = volume_cc
553         else:
554             channel.channel.autoset_volume_midi_cc()
555         if balance_cc != -1:
556             channel.channel.balance_midi_cc = balance_cc
557         else:
558             channel.channel.autoset_balance_midi_cc()
559         if mute_cc != -1:
560             channel.channel.mute_midi_cc = mute_cc
561         else:
562             channel.channel.autoset_mute_midi_cc()
563
564         return channel
565
566     def add_output_channel_precreated(self, channel):
567         frame = Gtk.Frame()
568         frame.add(channel)
569         self.hbox_outputs.pack_end(frame, False, True, 0)
570         self.hbox_outputs.reorder_child(frame, 0)
571         channel.realize()
572
573         channel_edit_menu_item = Gtk.MenuItem(label=channel.channel_name)
574         self.channel_edit_output_menu.append(channel_edit_menu_item)
575         channel_edit_menu_item.connect("activate", self.on_edit_output_channel, channel)
576         self.channel_edit_output_menu_item.set_sensitive(True)
577
578         channel_remove_menu_item = Gtk.MenuItem(label=channel.channel_name)
579         self.channel_remove_output_menu.append(channel_remove_menu_item)
580         channel_remove_menu_item.connect("activate", self.on_remove_output_channel, channel)
581         self.channel_remove_output_menu_item.set_sensitive(True)
582
583         self.output_channels.append(channel)
584
585     _monitored_channel = None
586     def get_monitored_channel(self):
587         return self._monitored_channel
588
589     def set_monitored_channel(self, channel):
590         if self._monitored_channel:
591             if channel.channel.name == self._monitored_channel.channel.name:
592                 return
593         self._monitored_channel = channel
594         if type(channel) is InputChannel:
595             # reset all solo/mute settings
596             for in_channel in self.channels:
597                 self.monitor_channel.set_solo(in_channel.channel, False)
598                 self.monitor_channel.set_muted(in_channel.channel, False)
599             self.monitor_channel.set_solo(channel.channel, True)
600             self.monitor_channel.prefader = True
601         else:
602             self.monitor_channel.prefader = False
603         self.update_monitor(channel)
604     monitored_channel = property(get_monitored_channel, set_monitored_channel)
605
606     def update_monitor(self, channel):
607         if self.monitored_channel is not channel:
608             return
609         self.monitor_channel.volume = channel.channel.volume
610         self.monitor_channel.balance = channel.channel.balance
611         self.monitor_channel.out_mute = channel.channel.out_mute
612         if type(self.monitored_channel) is OutputChannel:
613             # sync solo/muted channels
614             for input_channel in self.channels:
615                 self.monitor_channel.set_solo(input_channel.channel,
616                                 channel.channel.is_solo(input_channel.channel))
617                 self.monitor_channel.set_muted(input_channel.channel,
618                                 channel.channel.is_muted(input_channel.channel))
619
620     def get_input_channel_by_name(self, name):
621         for input_channel in self.channels:
622             if input_channel.channel.name == name:
623                 return input_channel
624         return None
625
626     def on_about(self, *args):
627         about = Gtk.AboutDialog()
628         about.set_name('jack_mixer')
629         about.set_copyright('Copyright © 2006-2020\nNedko Arnaudov, Frédéric Péters, Arnout Engelen, Daniel Sheeler')
630         about.set_license('''\
631 jack_mixer is free software; you can redistribute it and/or modify it
632 under the terms of the GNU General Public License as published by the
633 Free Software Foundation; either version 2 of the License, or (at your
634 option) any later version.
635
636 jack_mixer is distributed in the hope that it will be useful, but
637 WITHOUT ANY WARRANTY; without even the implied warranty of
638 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
639 General Public License for more details.
640
641 You should have received a copy of the GNU General Public License along
642 with jack_mixer; if not, write to the Free Software Foundation, Inc., 51
643 Franklin Street, Fifth Floor, Boston, MA 02110-130159 USA''')
644         about.set_authors([
645             'Nedko Arnaudov <nedko@arnaudov.name>',
646             'Christopher Arndt <chris@chrisarndt.de>',
647             'Arnout Engelen <arnouten@bzzt.net>',
648             'John Hedges <john@drystone.co.uk>',
649             'Olivier Humbert <trebmuh@tuxfamily.org>',
650             'Sarah Mischke <sarah@spooky-online.de>',
651             'Frédéric Péters <fpeters@0d.be>',
652             'Daniel Sheeler <dsheeler@pobox.com>',
653             'Athanasios Silis <athanasios.silis@gmail.com>',
654         ])
655         about.set_logo_icon_name('jack_mixer')
656         about.set_website('https://rdio.space/jackmixer/')
657
658         about.run()
659         about.destroy()
660
661     def save_to_xml(self, file):
662         #print "Saving to XML..."
663         b = XmlSerialization()
664         s = Serializator()
665         s.serialize(self, b)
666         b.save(file)
667
668     def load_from_xml(self, file, silence_errors=False):
669         #print "Loading from XML..."
670         self.unserialized_channels = []
671         b = XmlSerialization()
672         try:
673             b.load(file)
674         except:
675             if silence_errors:
676                 return
677             raise
678         self.on_channels_clear(None)
679         s = Serializator()
680         s.unserialize(self, b)
681         for channel in self.unserialized_channels:
682             if isinstance(channel, InputChannel):
683                 if self._init_solo_channels and channel.channel_name in self._init_solo_channels:
684                     channel.solo = True
685                 self.add_channel_precreated(channel)
686         self._init_solo_channels = None
687         for channel in self.unserialized_channels:
688             if isinstance(channel, OutputChannel):
689                 self.add_output_channel_precreated(channel)
690         del self.unserialized_channels
691         if self.visible:
692             self.window.show_all()
693
694     def serialize(self, object_backend):
695         width, height = self.window.get_size()
696         object_backend.add_property('geometry',
697                         '%sx%s' % (width, height))
698         solo_channels = []
699         for input_channel in self.channels:
700             if input_channel.channel.solo:
701                 solo_channels.append(input_channel)
702         if solo_channels:
703             object_backend.add_property('solo_channels', '|'.join([x.channel.name for x in solo_channels]))
704         object_backend.add_property('visible', '%s' % str(self.visible))
705
706     def unserialize_property(self, name, value):
707         if name == 'geometry':
708             width, height = value.split('x')
709             self.window.resize(int(width), int(height))
710             return True
711         if name == 'solo_channels':
712             self._init_solo_channels = value.split('|')
713             return True
714         if name == 'visible':
715             self.visible = value == 'True'
716             return True
717
718     def unserialize_child(self, name):
719         if name == InputChannel.serialization_name():
720             channel = InputChannel(self, "", True)
721             self.unserialized_channels.append(channel)
722             return channel
723
724         if name == OutputChannel.serialization_name():
725             channel = OutputChannel(self, "", True)
726             self.unserialized_channels.append(channel)
727             return channel
728
729         if name == gui.Factory.serialization_name():
730             return self.gui_factory
731
732     def serialization_get_childs(self):
733         '''Get child objects that required and support serialization'''
734         childs = self.channels[:] + self.output_channels[:] + [self.gui_factory]
735         return childs
736
737     def serialization_name(self):
738         return "jack_mixer"
739
740     def main(self):
741         if not self.mixer:
742             return
743
744         if self.visible:
745             self.window.show_all()
746
747         signal.signal(signal.SIGUSR1, self.sighandler)
748         signal.signal(signal.SIGTERM, self.sighandler)
749         signal.signal(signal.SIGINT, self.sighandler)
750         signal.signal(signal.SIGHUP, signal.SIG_IGN)
751
752         Gtk.main()
753
754         #f = file("/dev/stdout", "w")
755         #self.save_to_xml(f)
756         #f.close
757
758 def main():
759     parser = ArgumentParser()
760     parser.add_argument('-c', '--config', help='use a non default configuration file')
761     parser.add_argument('client_name', metavar='NAME', nargs='?', default='jack_mixer',
762                         help='set JACK client name')
763     args = parser.parse_args()
764
765     try:
766         mixer = JackMixer(args.client_name)
767     except Exception as e:
768         err = Gtk.MessageDialog(parent = None,
769                                 modal = True,
770                                 message_type = Gtk.MessageType.ERROR,
771                                 buttons = Gtk.ButtonsType.OK,
772                                 text = "Mixer creation failed (%s)" % str(e))
773         err.run()
774         err.destroy()
775         sys.exit(1)
776
777     if not mixer.nsm_client and args.config:
778         f = open(args.config)
779         mixer.current_filename = args.config
780
781         try:
782             mixer.load_from_xml(f)
783         except Exception as e:
784             err = Gtk.MessageDialog(parent = mixer.window,
785                             modal =  True,
786                             message_type = Gtk.MessageType.ERROR,
787                             buttons = Gtk.ButtonsType.OK,
788                             text = "Failed loading settings (%s)." % str(e))
789             err.run()
790             err.destroy()
791         mixer.window.set_default_size(60*(1+len(mixer.channels)+len(mixer.output_channels)), 300)
792         f.close()
793
794     mixer.main()
795
796     mixer.cleanup()
797
798 if __name__ == "__main__":
799     main()