627 lines
22 KiB
Python
Executable File
627 lines
22 KiB
Python
Executable File
#!/usr/bin/python
|
|
|
|
# LADITools - Linux Audio Desktop Integration Tools
|
|
# ladiconf - A configuration GUI for your Linux Audio Desktop
|
|
# Copyright (C) 2007-2010, Marc-Olivier Barre <marco@marcochapeau.org>
|
|
# Copyright (C) 2007-2009, Nedko Arnaudov <nedko@arnaudov.name>
|
|
# Copyright (C) 2008, Krzysztof Foltman <wdev@foltman.com>
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import os
|
|
import sys
|
|
import pygtk
|
|
pygtk.require ('2.0')
|
|
import gtk
|
|
import gobject
|
|
|
|
try:
|
|
import imp
|
|
imp.find_module('laditools')
|
|
except ImportError:
|
|
# Running from the build tree?
|
|
sys.path.insert(0, os.path.join(sys.path[0], os.pardir))
|
|
|
|
import laditools
|
|
|
|
jack = laditools.jack_configure()
|
|
|
|
def check_ladish():
|
|
try:
|
|
proxy = laditools.ladish_proxy()
|
|
except:
|
|
print("ladish proxy creation failed")
|
|
return
|
|
|
|
if not proxy.is_available():
|
|
print("ladish is not available")
|
|
return
|
|
|
|
if proxy.studio_is_loaded():
|
|
if not proxy.studio_is_started():
|
|
print("ladish studio is loaded and not started")
|
|
return
|
|
else:
|
|
msg = "JACK can only be configured with a stopped studio. Please stop your studio first."
|
|
title = "Studio is running"
|
|
else:
|
|
msg = "JACK can only be configured with a loaded and stopped studio. Please create a new studio or load and stop an existing one."
|
|
title = "No studio present"
|
|
|
|
# studio is not loaded or loaded and started
|
|
mdlg = gtk.MessageDialog(type = gtk.MESSAGE_ERROR, buttons = gtk.BUTTONS_CLOSE, message_format = msg)
|
|
mdlg.set_title(title)
|
|
mdlg.run()
|
|
mdlg.hide()
|
|
sys.exit(0)
|
|
|
|
class parameter:
|
|
def __init__(self, path):
|
|
self.path = path
|
|
self.name = path[-1:]
|
|
|
|
def get_name(self):
|
|
return self.name
|
|
|
|
def get_type(self):
|
|
return jack.get_param_type(self.path)
|
|
|
|
def get_value(self):
|
|
return jack.get_param_value(self.path)
|
|
|
|
def set_value(self, value):
|
|
jack.set_param_value(self.path, value)
|
|
|
|
def reset_value(self):
|
|
jack.reset_param_value(self.path)
|
|
|
|
def get_short_description(self):
|
|
return jack.get_param_short_description(self.path)
|
|
|
|
def get_long_description(self):
|
|
descr = jack.get_param_long_description(self.path)
|
|
if not descr:
|
|
descr = self.get_short_description()
|
|
return descr
|
|
|
|
def has_range(self):
|
|
return jack.param_has_range(self.path)
|
|
|
|
def get_range(self):
|
|
return jack.param_get_range(self.path)
|
|
|
|
def has_enum(self):
|
|
return jack.param_has_enum(self.path)
|
|
|
|
def is_strict_enum(self):
|
|
return jack.param_is_strict_enum(self.path)
|
|
|
|
def is_fake_values_enum(self):
|
|
return jack.param_is_fake_value(self.path)
|
|
|
|
def get_enum_values(self):
|
|
return jack.param_get_enum_values(self.path)
|
|
|
|
class configure_command:
|
|
def __init__(self):
|
|
pass
|
|
|
|
def get_description(self):
|
|
pass
|
|
|
|
def get_window_title(self):
|
|
return self.get_description();
|
|
|
|
def run(self, arg):
|
|
pass
|
|
|
|
class parameter_enum_value(gobject.GObject):
|
|
def __init__(self, is_fake_value, value, description):
|
|
gobject.GObject.__init__(self)
|
|
self.is_fake_value = is_fake_value
|
|
self.value = value
|
|
self.description = description
|
|
|
|
def get_description(self):
|
|
if self.is_fake_value:
|
|
return self.description
|
|
|
|
return str(self.value) + " - " + self.description
|
|
|
|
gobject.type_register(parameter_enum_value)
|
|
|
|
class parameter_store(gobject.GObject):
|
|
def __init__(self, param):
|
|
gobject.GObject.__init__(self)
|
|
self.param = param
|
|
self.name = self.param.get_name()
|
|
self.is_set, self.default_value, self.value = self.param.get_value()
|
|
self.modified = False
|
|
self.has_range = self.param.has_range()
|
|
self.is_strict = self.param.is_strict_enum()
|
|
self.is_fake_value = self.param.is_fake_values_enum()
|
|
|
|
self.enum_values = []
|
|
|
|
if self.has_range:
|
|
self.range_min, self.range_max = self.param.get_range()
|
|
else:
|
|
for enum_value in self.param.get_enum_values():
|
|
self.enum_values.append(parameter_enum_value(self.is_fake_value, enum_value[0], enum_value[1]))
|
|
|
|
def get_name(self):
|
|
return self.name
|
|
|
|
def get_type(self):
|
|
return self.param.get_type()
|
|
|
|
def get_value(self):
|
|
return self.value
|
|
|
|
def get_default_value(self):
|
|
if not self.is_fake_value:
|
|
return str(self.default_value)
|
|
|
|
for enum_value in self.get_enum_values():
|
|
if enum_value.value == self.default_value:
|
|
return enum_value.get_description()
|
|
|
|
return "???"
|
|
|
|
def set_value(self, value):
|
|
#print "%s -> %s" % (self.name, value)
|
|
self.value = value
|
|
self.modified = True
|
|
|
|
def reset_value(self):
|
|
self.value = self.default_value
|
|
|
|
def get_short_description(self):
|
|
return self.param.get_short_description()
|
|
|
|
def maybe_save_value(self):
|
|
if self.modified:
|
|
self.param.set_value(self.value)
|
|
self.modified = False
|
|
|
|
def get_range(self):
|
|
return self.range_min, self.range_max
|
|
|
|
def has_enum(self):
|
|
return len(self.enum_values) != 0
|
|
|
|
def is_strict_enum(self):
|
|
return self.is_strict
|
|
|
|
def get_enum_values(self):
|
|
return self.enum_values
|
|
|
|
gobject.type_register(parameter_store)
|
|
|
|
def combobox_get_active_text(combobox, model_index = 0):
|
|
model = combobox.get_model()
|
|
active = combobox.get_active()
|
|
if active < 0:
|
|
return None
|
|
return model[active][model_index]
|
|
|
|
class cell_renderer_param(gtk.GenericCellRenderer):
|
|
__gproperties__ = { "parameter": (gobject.TYPE_OBJECT, "Parameter", "Parameter", gobject.PARAM_READWRITE) }
|
|
|
|
def __init__(self):
|
|
self.__gobject_init__()
|
|
self.parameter = None
|
|
#self.set_property('mode', gtk.CELL_RENDERER_MODE_EDITABLE)
|
|
#self.set_property('mode', gtk.CELL_RENDERER_MODE_ACTIVATABLE)
|
|
self.renderer_text = gtk.CellRendererText()
|
|
self.renderer_toggle = gtk.CellRendererToggle()
|
|
self.renderer_combo = gtk.CellRendererCombo()
|
|
self.renderer_spinbutton = gtk.CellRendererSpin()
|
|
for r in (self.renderer_text, self.renderer_combo, self.renderer_spinbutton):
|
|
r.connect("edited", self.on_edited)
|
|
self.renderer = None
|
|
self.edit_widget = None
|
|
|
|
def do_set_property(self, pspec, value):
|
|
if pspec.name == 'parameter':
|
|
if value.get_type() == 'b':
|
|
self.set_property('mode', gtk.CELL_RENDERER_MODE_ACTIVATABLE)
|
|
else:
|
|
self.set_property('mode', gtk.CELL_RENDERER_MODE_EDITABLE)
|
|
else:
|
|
print(pspec.name)
|
|
setattr(self, pspec.name, value)
|
|
|
|
def do_get_property(self, pspec):
|
|
return getattr(self, pspec.name)
|
|
|
|
def choose_renderer(self):
|
|
typechar = self.parameter.get_type()
|
|
value = self.parameter.get_value()
|
|
|
|
if typechar == "b":
|
|
self.renderer = self.renderer_toggle
|
|
self.renderer.set_property('activatable', True)
|
|
self.renderer.set_property('active', value)
|
|
self.renderer.set_property("xalign", 0.0)
|
|
return
|
|
|
|
if self.parameter.has_enum():
|
|
self.renderer = self.renderer_combo
|
|
|
|
m = gtk.ListStore(str, parameter_enum_value)
|
|
|
|
for value in self.parameter.get_enum_values():
|
|
m.append([value.get_description(), value])
|
|
|
|
self.renderer.set_property("model",m)
|
|
self.renderer.set_property('text-column', 0)
|
|
self.renderer.set_property('editable', True)
|
|
self.renderer.set_property('has_entry', not self.parameter.is_strict_enum())
|
|
|
|
value = self.parameter.get_value()
|
|
if self.parameter.is_fake_value:
|
|
text = "???"
|
|
for enum_value in self.parameter.get_enum_values():
|
|
if enum_value.value == value:
|
|
text = enum_value.get_description()
|
|
break
|
|
else:
|
|
text = str(value)
|
|
|
|
self.renderer.set_property('text', text)
|
|
|
|
return
|
|
|
|
if typechar == 'u' or typechar == 'i':
|
|
self.renderer = self.renderer_spinbutton
|
|
self.renderer.set_property('text', str(value))
|
|
self.renderer.set_property('editable', True)
|
|
if self.parameter.has_range:
|
|
range_min, range_max = self.parameter.get_range()
|
|
self.renderer.set_property('adjustment', gtk.Adjustment(value, range_min, range_max, 1, abs(int((range_max - range_min) / 10))))
|
|
else:
|
|
self.renderer.set_property('adjustment', gtk.Adjustment(value, 0, 100000, 1, 1000))
|
|
return
|
|
|
|
self.renderer = self.renderer_text
|
|
self.renderer.set_property('editable', True)
|
|
self.renderer.set_property('text', self.parameter.get_value())
|
|
|
|
def on_render(self, window, widget, background_area, cell_area, expose_area, flags):
|
|
self.choose_renderer()
|
|
return self.renderer.render(window, widget, background_area, cell_area, expose_area, flags)
|
|
|
|
def on_get_size(self, widget, cell_area=None):
|
|
self.choose_renderer()
|
|
return self.renderer.get_size(widget, cell_area)
|
|
|
|
def on_activate(self, event, widget, path, background_area, cell_area, flags):
|
|
self.choose_renderer()
|
|
if self.parameter.get_type() == 'b':
|
|
self.parameter.set_value(not self.parameter.get_value())
|
|
widget.get_model()[path][4] = "modified"
|
|
return True
|
|
|
|
def on_edited(self, renderer, path, value_str):
|
|
parameter = self.edit_parameter
|
|
widget = self.edit_widget
|
|
model = self.edit_tree.get_model()
|
|
self.edit_widget = None
|
|
typechar = parameter.get_type()
|
|
if type(widget) == gtk.ComboBox:
|
|
value = combobox_get_active_text(widget, 1)
|
|
if value == None:
|
|
return
|
|
value_str = value.value
|
|
elif type(widget) == gtk.ComboBoxEntry:
|
|
enum_value = combobox_get_active_text(widget, 1)
|
|
if enum_value:
|
|
value_str = enum_value.value
|
|
else:
|
|
value_str = widget.get_active_text()
|
|
|
|
if typechar == 'u' or typechar == 'i':
|
|
try:
|
|
value = int(value_str)
|
|
except ValueError as e:
|
|
# Hide the widget (because it may display something else than what user typed in)
|
|
widget.hide()
|
|
# Display the error
|
|
mdlg = gtk.MessageDialog(buttons = gtk.BUTTONS_OK, message_format = "Invalid value. Please enter an integer number.")
|
|
mdlg.run()
|
|
mdlg.hide()
|
|
# Return the focus back to the tree to prevent buttons from stealing it
|
|
self.edit_tree.grab_focus()
|
|
return
|
|
else:
|
|
value = value_str
|
|
parameter.set_value(value)
|
|
model[path][4] = "modified"
|
|
self.edit_tree.grab_focus()
|
|
|
|
def on_start_editing(self, event, widget, path, background_area, cell_area, flags):
|
|
# this happens when edit requested using keyboard
|
|
if not event:
|
|
event = gtk.gdk.Event(gtk.gdk.NOTHING)
|
|
|
|
self.choose_renderer()
|
|
ret = self.renderer.start_editing(event, widget, path, background_area, cell_area, flags)
|
|
self.edit_widget = ret
|
|
self.edit_tree = widget
|
|
self.edit_parameter = self.parameter
|
|
return ret
|
|
|
|
gobject.type_register(cell_renderer_param)
|
|
|
|
class ladiconf_tooltips(laditools.TreeViewTooltips):
|
|
def __init__(self, name_column, is_set_column):
|
|
self.name_column = name_column
|
|
self.is_set_column = is_set_column
|
|
laditools.TreeViewTooltips.__init__(self)
|
|
|
|
def location(self, x, y, w, h):
|
|
return x + 10, y + 10
|
|
|
|
def get_tooltip(self, view, column, path):
|
|
if column is self.name_column:
|
|
model = view.get_model()
|
|
tooltip = model[path][3]
|
|
return tooltip
|
|
|
|
if column is self.is_set_column:
|
|
model = view.get_model()
|
|
param = model[path][1]
|
|
is_set = model[path][4]
|
|
|
|
if is_set == "default":
|
|
return None
|
|
|
|
if is_set == "reset":
|
|
return "Value will be reset to %s" % param.get_default_value()
|
|
|
|
return "Double-click to schedule reset of value to %s" % param.get_default_value()
|
|
|
|
return None
|
|
|
|
class jack_params_configure_command(configure_command):
|
|
def __init__(self, path):
|
|
self.path = path
|
|
self.is_setting = False
|
|
|
|
def reset_value(self, row_path):
|
|
row = self.liststore[row_path]
|
|
param = row[1]
|
|
param.reset_value()
|
|
row[4] = "reset"
|
|
|
|
def on_row_activated(self, treeview, path, view_column):
|
|
if view_column == self.tvcolumn_is_set:
|
|
self.reset_value(path)
|
|
|
|
def on_button_press_event(self, tree, event):
|
|
if event.type != gtk.gdk._2BUTTON_PRESS:
|
|
return False
|
|
# this is needed for proper double-click handling in the list; don't ask me why, I don't know
|
|
# it's probably because _2BUTTON_PRESS event is still delivered to tree view, automatically deactivating
|
|
# the newly created edit widget (which gets created on second BUTTON_PRESS but before _2BUTTON_PRESS)
|
|
# deactivating the widget causes it to be deleted
|
|
return True
|
|
|
|
def on_key_press_event(self, tree, event):
|
|
cur = self.treeview.get_cursor()
|
|
row_path = cur[0][0]
|
|
|
|
# if Delete was pressed, reset the value
|
|
if event.state == 0 and event.keyval == gtk.keysyms.Delete:
|
|
self.reset_value(row_path)
|
|
tree.queue_draw()
|
|
return True
|
|
|
|
# prevent ESC from activating the editor
|
|
if event.string < " ":
|
|
return False
|
|
|
|
# single-key data entry: if the control is a text entry, spin button or combo box/combo box entry,
|
|
# then edit the current and set the text value to what user has already typed in
|
|
cur = self.treeview.get_cursor()
|
|
param = self.liststore[row_path][1]
|
|
ptype = param.get_type()
|
|
|
|
# we don't care about booleans
|
|
if ptype == 'b':
|
|
return False
|
|
|
|
# accept only digits for integer input (or a minus, but only if it's a signed field)
|
|
if ptype in ('i', 'u'):
|
|
if not (event.string.isdigit() or (event.string == "-" and ptype == 'i')):
|
|
return False
|
|
|
|
# Start cell editing
|
|
# MAYBE: call a specially crafted cell_renderer_param method for this
|
|
self.treeview.set_cursor_on_cell(cur[0], self.tvcolumn_value, self.renderer_value.renderer, True)
|
|
|
|
# cell_renderer_param::on_start_editing() should set edit_widget.
|
|
# if edit operation has failed (didn't create a widget), pass the key on
|
|
# MAYBE: call a specially crafted cell_renderer_param method to do this check
|
|
if self.renderer_value.edit_widget == None:
|
|
return
|
|
|
|
widget = self.renderer_value.edit_widget
|
|
if type(widget) in (gtk.Entry, gtk.ComboBoxEntry):
|
|
# (combo or plain) text entry - set the content and move the cursor to the end
|
|
sl = len(event.string)
|
|
widget.set_text(event.string)
|
|
widget.select_region(sl, sl)
|
|
return True
|
|
|
|
if type(self.renderer_value.edit_widget) == gtk.SpinButton:
|
|
# spin button - set the value and move the cursor to the end
|
|
if event.string == "-":
|
|
# special case for minus sign (which can't be converted to float)
|
|
widget.set_text(event.string)
|
|
else:
|
|
widget.set_value(float(event.string))
|
|
sl = len(widget.get_text())
|
|
widget.select_region(sl, sl)
|
|
return True
|
|
|
|
if type(self.renderer_value.edit_widget) == gtk.ComboBox:
|
|
# combo box - select the first item that starts with typed character
|
|
model = widget.get_model()
|
|
item = -1
|
|
iter = model.get_iter_root()
|
|
while iter != None:
|
|
if model.get_value(iter, 0).startswith(event.string):
|
|
item = model.get_path(iter)[0]
|
|
break
|
|
iter = model.iter_next(iter)
|
|
widget.set_active(item)
|
|
return True
|
|
|
|
return False
|
|
|
|
def on_cursor_changed(self, tree):
|
|
cur = self.treeview.get_cursor()
|
|
if not self.is_setting and cur[1] != None and cur[1].get_title() != self.tvcolumn_value.get_title():
|
|
self.is_setting = True
|
|
try:
|
|
self.treeview.set_cursor_on_cell(cur[0], self.tvcolumn_value)
|
|
finally:
|
|
self.is_setting = False
|
|
|
|
def ok_clicked(self, dlg):
|
|
if self.renderer_value.edit_widget:
|
|
self.renderer_value.edit_widget.editing_done()
|
|
|
|
def run(self, arg):
|
|
dlg = gtk.Dialog()
|
|
dlg.set_title(self.get_window_title())
|
|
dlg.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
|
|
dlg.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK).connect("clicked", self.ok_clicked)
|
|
|
|
#dlg.set_transient_for(window)
|
|
|
|
self.liststore = gtk.ListStore(str, parameter_store, str, str, str)
|
|
self.treeview = gtk.TreeView(self.liststore)
|
|
self.treeview.set_rules_hint(True)
|
|
self.treeview.set_enable_search(False)
|
|
|
|
renderer_text = gtk.CellRendererText()
|
|
renderer_toggle = gtk.CellRendererToggle()
|
|
renderer_value = cell_renderer_param()
|
|
self.renderer_value = renderer_value # save for use in event handler methods
|
|
|
|
self.tvcolumn_parameter = gtk.TreeViewColumn('Parameter', renderer_text, text=0)
|
|
self.tvcolumn_is_set = gtk.TreeViewColumn('Status', renderer_text, text=4)
|
|
self.tvcolumn_value = gtk.TreeViewColumn('Value', renderer_value, parameter=1)
|
|
self.tvcolumn_description = gtk.TreeViewColumn('Description', renderer_text, text=2)
|
|
|
|
self.tvcolumn_value.set_resizable(True)
|
|
self.tvcolumn_value.set_min_width(100)
|
|
|
|
self.treeview.append_column(self.tvcolumn_parameter)
|
|
self.treeview.append_column(self.tvcolumn_is_set)
|
|
self.treeview.append_column(self.tvcolumn_value)
|
|
self.treeview.append_column(self.tvcolumn_description)
|
|
|
|
dlg.vbox.pack_start(self.treeview, True)
|
|
|
|
param_names = jack.get_param_names(self.path)
|
|
for name in param_names:
|
|
param = parameter(self.path + [name])
|
|
store = parameter_store(param)
|
|
if store.is_set:
|
|
is_set = "set"
|
|
else:
|
|
is_set = "default"
|
|
self.liststore.append([name, store, param.get_short_description(), param.get_long_description(), is_set])
|
|
|
|
self.treeview.connect("row-activated", self.on_row_activated)
|
|
self.treeview.connect("cursor-changed", self.on_cursor_changed)
|
|
self.treeview.connect("key-press-event", self.on_key_press_event)
|
|
self.treeview.connect("button-press-event", self.on_button_press_event)
|
|
|
|
self.tooltips = ladiconf_tooltips(self.tvcolumn_parameter, self.tvcolumn_is_set)
|
|
self.tooltips.add_view(self.treeview)
|
|
if len(param_names):
|
|
# move cursor to first row and 'value' column
|
|
self.treeview.set_cursor((0,), self.tvcolumn_value)
|
|
|
|
dlg.show_all()
|
|
ret = dlg.run()
|
|
if ret == gtk.RESPONSE_OK:
|
|
for row in self.liststore:
|
|
param = row[1]
|
|
reset = row[4] == "reset"
|
|
if reset:
|
|
#print "%s -> reset" % param.get_name()
|
|
param.param.reset_value()
|
|
else:
|
|
if param.modified:
|
|
#print "%s -> %s" % (param.get_name(), param.get_value())
|
|
param.maybe_save_value()
|
|
dlg.hide()
|
|
|
|
class jack_engine_params_configure_command(jack_params_configure_command):
|
|
def __init__(self):
|
|
jack_params_configure_command.__init__(self, ['engine'])
|
|
|
|
def get_description(self):
|
|
return 'JACK engine parameters'
|
|
|
|
class jack_driver_params_configure_command(jack_params_configure_command):
|
|
def __init__(self):
|
|
jack_params_configure_command.__init__(self, ['driver'])
|
|
|
|
def get_description(self):
|
|
return 'JACK driver parameters'
|
|
|
|
def get_window_title(self):
|
|
return 'JACK "%s" driver parameters' % jack.get_selected_driver()
|
|
|
|
class jack_internal_params_configure_command(jack_params_configure_command):
|
|
def __init__(self, name):
|
|
self.name = name
|
|
jack_params_configure_command.__init__(self, ['internals', name])
|
|
|
|
def get_description(self):
|
|
return 'JACK "%s" parameters' % self.name
|
|
|
|
check_ladish()
|
|
|
|
commands = [
|
|
jack_engine_params_configure_command(),
|
|
jack_driver_params_configure_command(),
|
|
]
|
|
|
|
for internal in jack.read_container(['internals']):
|
|
commands.append(jack_internal_params_configure_command(internal))
|
|
|
|
window = gtk.Window()
|
|
|
|
buttons_widget = gtk.VBox()
|
|
|
|
for command in commands:
|
|
button = gtk.Button(command.get_description())
|
|
button.connect('clicked', command.run)
|
|
buttons_widget.pack_start(button)
|
|
|
|
window.add(buttons_widget)
|
|
|
|
window.show_all()
|
|
window.connect('destroy', gtk.main_quit)
|
|
|
|
gtk.main()
|