Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

564 lines (453 sloc) 23.366 kB
#!/usr/bin/python
# -*- coding: utf-8 -*-
# vim: ts=4
###
#
# Copyright (c) 2010 J. Félix Ontañón
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3 as
# published by the Free Software Foundation
#
# 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/>.
#
# Authors : J. Félix Ontañón <fontanon@emergya.es>
#
###
import sys
from gi.repository import Gtk, Gdk
from gi.repository import GObject
import logging
from udevdiscover import DeviceFinder, get_subsystems
from udevdiscover.utils import GConfStore, TextBufferHandler
import udevdiscover.device
# FIXME: This path needs to be assigned at installing time
UDEVDISCOVER_UI = '@PREFIX@/share/udev-discover/udev-discover.ui'
GCONF_KEY = '/apps/udevdiscover'
DEFAULT_SUBSYSTEMS = ['pci', 'usb', 'net', 'power_supply', 'block', 'sound',
'input', 'serio', 'platform', 'drm', 'video4linux', 'rfkill', 'bluetooth',
'leds', 'dvb']
TEXTBUFFER_LOGGER = 'udevdiscover'
LOG_LEVEL = logging.DEBUG
LOG_FORMAT = '%(asctime)s %(levelname)s - %(message)s'
LOG_DATE_FORMAT = '%H:%M:%S'
PATH_COL, ICON_COL, NAME_COL, SUBSYSTEM_COL, VISIBLE_COL = range(5)
DEFAULT_SUBSYS_PRESET, ALL_SUBSYS_PRESET, CUSTOM_SUBSYS_PRESET = range(3)
ICON_SUBSYS_COL, NAME_SUBSYS_COL = range(2)
# Load icons
theme = Gtk.IconTheme.get_default()
device_icon = theme.load_icon('dialog-question', 24, 0)
class SubsystemChoserDialog(GConfStore):
defaults = {
'dialog_width': 600,
'dialog_height': 400,
}
def __init__(self, preset=DEFAULT_SUBSYS_PRESET, chosen_subsystems=[]):
GConfStore.__init__(self, GCONF_KEY)
self.preset = preset
self.chosen_subsystems = chosen_subsystems
self.subsystems = get_subsystems()
self.builder = Gtk.Builder()
if not self.builder.add_objects_from_file(UDEVDISCOVER_UI,
['subsys_dialog', 'allsubsys_liststore', 'chosensubsys_liststore',
'preset_radioaction', 'all_radioaction', 'custom_radioaction']):
raise 'Cant load %s' % UDEVDISCOVER_UI
self.builder.connect_signals(self)
# Widgets
self.subsys_dialog = self.builder.get_object('subsys_dialog')
self.default_radiobutton = self.builder.get_object('default_radiobutton')
self.all_radiobutton = self.builder.get_object('all_radiobutton')
self.custom_radiobutton = self.builder.get_object('custom_radiobutton')
self.addsubsys_button = self.builder.get_object('addsubsys_button')
self.removesubsys_button = self.builder.get_object('removesubsys_button')
self.allsubsys_liststore = self.builder.get_object('allsubsys_liststore')
self.chosensubsys_liststore = \
self.builder.get_object('chosensubsys_liststore')
self.allsubsys_treeview = self.builder.get_object('allsubsys_treeview')
self.chosensubsys_treeview = \
self.builder.get_object('chosensubsys_treeview')
self.allsubsys_treeview.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE)
self.chosensubsys_treeview.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE)
# Signals
self.subsys_dialog.connect('response', lambda d, r: d.hide())
for widget, def_preset in (self.default_radiobutton, DEFAULT_SUBSYS_PRESET), \
(self.all_radiobutton, ALL_SUBSYS_PRESET), \
(self.custom_radiobutton, CUSTOM_SUBSYS_PRESET):
widget.connect('toggled', self.preset_toggled_cb, def_preset)
# Load gconf preferences
self.loadconf()
# Track main window width/height
def catch_window_size(widget, allocate, options):
window_alloc = widget.get_allocation()
options['dialog_width'] = window_alloc.width
options['dialog_height'] = window_alloc.height
self.subsys_dialog.connect('size_allocate', catch_window_size,
self.options)
# Restore main window width/heigh
self.subsys_dialog.resize(self.options['dialog_width'],
self.options['dialog_height'])
if self.preset == DEFAULT_SUBSYS_PRESET:
self.default_radiobutton.set_active(True)
self.populate_preset()
elif self.preset == ALL_SUBSYS_PRESET:
self.all_radiobutton.set_active(True)
else:
self.custom_radiobutton.set_active(True)
def populate_preset(self, preset=DEFAULT_SUBSYS_PRESET):
def populate(all_set, chosen_set):
self.allsubsys_liststore.clear()
self.chosensubsys_liststore.clear()
for subsys in all_set:
self.allsubsys_liststore.append([None, subsys])
for subsys in chosen_set:
self.chosensubsys_liststore.append([None, subsys])
def subsystem_allow_edit(allow=True):
for widget in self.addsubsys_button, self.removesubsys_button, \
self.allsubsys_treeview, self.chosensubsys_treeview:
widget.set_sensitive(allow)
if preset == DEFAULT_SUBSYS_PRESET:
subsystem_allow_edit(False)
default = set(DEFAULT_SUBSYSTEMS)
populate(set(self.subsystems) - default, default)
elif preset == ALL_SUBSYS_PRESET:
subsystem_allow_edit(False)
populate([], self.subsystems)
elif preset == CUSTOM_SUBSYS_PRESET:
subsystem_allow_edit()
chosen = set(self.chosen_subsystems)
populate(set(self.subsystems) - chosen, chosen)
self.preset = preset
def preset_toggled_cb(self, widget, preset):
if not widget.get_active(): return
self.populate_preset(preset)
def subsyslist_item_activated(self, orig_treeview, dest_treeview):
selection = orig_treeview.get_selection()
model, selected_rows = selection.get_selected_rows()
items = [(Gtk.TreeRowReference.new(model, selected), model[selected]) \
for selected in selected_rows]
if items: next = items[-1][1].get_next()
for row_ref, (icon, name) in items:
dest_treeview.get_model().append([icon, name])
model.remove(model.get_iter(row_ref.get_path()))
if next: orig_treeview.set_cursor(next.path, None, False)
def allsubsys_treeview_row_activated_cb(self, widget, path, view_column):
self.subsyslist_item_activated(widget, self.chosensubsys_treeview)
def chosensubsys_treeview_key_release_event_cb(self, widget, event):
if event.keyval == Gdk.keyval_from_name("Delete"):
self.subsyslist_item_activated(widget, self.allsubsys_treeview)
def addsubsys_button_clicked_cb(self, widget):
self.subsyslist_item_activated(self.allsubsys_treeview,
self.chosensubsys_treeview)
def removesubsys_button_clicked_cb(self, widget):
self.subsyslist_item_activated(self.chosensubsys_treeview,
self.allsubsys_treeview)
def get_current_preset(self):
return self.preset
def get_chosen_subsystems(self):
return [subsys[1] for subsys in self.chosensubsys_liststore]
def run(self):
return self.subsys_dialog.run()
def destroy(self):
self.saveconf()
self.subsys_dialog.hide()
class UDevDiscoverGUI(GConfStore):
defaults = {
'window_width': 600,
'window_height': 400,
'devices_hpaned': 300,
'parent_tree': True,
'expanded': True,
'eventslog_vpaned': 300,
'eventslog_expanded': True,
'follownew': True,
'followchanged': True,
'subsystem_preset': DEFAULT_SUBSYS_PRESET,
'custom_subsystem_preset': ' '.join(DEFAULT_SUBSYSTEMS)
}
def __init__(self):
GConfStore.__init__(self, GCONF_KEY)
self.rows = {}
self.is_filtered = False
self.builder = Gtk.Builder()
if not self.builder.add_objects_from_file(UDEVDISCOVER_UI,
['about_action', 'expand_toggleaction',
'followchanged_toggleaction', 'follownew_toggleaction',
'help_action', 'preferences_action', 'quit_action',
'reload_action', 'showparents_toggleaction', 'about_dialog',
'deviceprop_store', 'devices_treestore', 'eventslog_textbuffer',
'main_window']):
raise 'Cant load %s' % UDEVDISCOVER_UI
self.builder.connect_signals(self)
# Widgets
self.main_window = self.builder.get_object('main_window')
self.devices_hpaned = self.builder.get_object('devices_hpaned')
self.eventslog_vpaned = self.builder.get_object('eventslog_vpaned')
self.devices_tv = self.builder.get_object('devices_tv')
self.devices_treestore = self.builder.get_object('devices_treestore')
self.deviceprop_tv = self.builder.get_object('deviceprop_tv')
self.deviceprop_store = self.builder.get_object('deviceprop_store')
self.devicename_label = self.builder.get_object('devicename_label')
self.devicedesc_label = self.builder.get_object('devicedesc_label')
self.device_image = self.builder.get_object('device_image')
self.parents_toolbtn = self.builder.get_object('parents_toolbtn')
self.expand_toggleaction = self.builder.get_object('expand_toggleaction')
self.showparents_toggleaction = self.builder.get_object('showparents_toggleaction')
self.eventslog_expander = self.builder.get_object('eventslog_expander')
self.eventslog_textbuffer = self.builder.get_object('eventslog_textbuffer')
self.follownew_toggleaction = self.builder.get_object('follownew_toggleaction')
self.followchanged_toggleaction = \
self.builder.get_object('followchanged_toggleaction')
self.summary_scrollwindow = self.builder.get_object('summary_scrollwindow')
self.devicepropsum_label = self.builder.get_object('devicepropsum_label')
self.search_entry = self.builder.get_object('search_entry')
self.default_radiobutton = self.builder.get_object('default_radiobutton')
# Load gconf preferences
self.loadconf()
# Track main window width/height
def catch_window_size(widget, allocate, options):
window_alloc = widget.get_allocation()
options['window_width'] = window_alloc.width
options['window_height'] = window_alloc.height
self.main_window.connect('size_allocate', catch_window_size,
self.options)
# Restore main window width/heigh
self.main_window.resize(self.options['window_width'],
self.options['window_height'])
# Subsystem chooser dialog
self.subsys_dialog = SubsystemChoserDialog(
self.options['subsystem_preset'],
self.options['custom_subsystem_preset'].split(' '))
# About dialog
self.about_dialog = self.builder.get_object('about_dialog')
self.about_dialog.connect('response', lambda d, r: d.hide())
# Sets up the logger
self.logger = logging.getLogger(TEXTBUFFER_LOGGER)
self.logger.setLevel(LOG_LEVEL)
handler = TextBufferHandler(self.eventslog_textbuffer)
handler.setFormatter(logging.Formatter(LOG_FORMAT,
datefmt=LOG_DATE_FORMAT))
self.logger.addHandler(handler)
# Populate treeview
self.device_finder = DeviceFinder()
self.device_finder.scan_subsystems(
self.subsys_dialog.get_chosen_subsystems(),
self.options['parent_tree'])
self.device_finder.connect('added', self.new_device)
self.device_finder.connect('removed', self.removed_device)
self.device_finder.connect('changed', self.changed_device)
self.populate(self.device_finder.get_devices())
self.parents_toolbtn.set_active(self.options['parent_tree'])
self.expand_toggleaction.set_active(self.options['expanded'])
self.expand_toggleaction_toggled_cb(self.expand_toggleaction)
self.follownew_toggleaction.set_active(self.options['follownew'])
self.followchanged_toggleaction.set_active(self.options['followchanged'])
self.search_entry.set_property('primary-icon-sensitive',False)
self.search_entry.set_property('secondary-icon-sensitive',False)
# Restore paned window positions
self.devices_hpaned.set_position(self.options['devices_hpaned'])
self.eventslog_vpaned.set_position(self.options['eventslog_vpaned'])
self.eventslog_expander.set_expanded(self.options['eventslog_expanded'])
def new_device(self, device_finder, device):
row_ref = self.add_new_device(device)
if self.options['expanded']:
iter_path = row_ref.get_path()
if self.is_filtered:
iter_path = self.modelfilter.convert_child_path_to_path(row_ref.get_path())
self.devices_tv.expand_to_path(iter_path)
if self.options['follownew']:
self.devices_tv.set_cursor(row_ref.get_path(), None, False)
self.logger.info(_('Device added: %s') % device.nice_label)
def removed_device(self, device_finder, device):
if self.rows.has_key(device.path):
ref_row = self.rows[device.path]
treeiter = self.devices_treestore.get_iter(ref_row.get_path())
self.devices_treestore.remove(treeiter)
del(self.rows[device.path])
self.logger.info(_('Device removed: %s') % device.nice_label)
def changed_device(self, device_finder, device):
old_device = self.device_finder.get_devices_tree()[device.path]
if self.rows.has_key(device.path):
# Remove from tree first
ref_row = self.rows[device.path]
treeiter = self.devices_treestore.get_iter(ref_row.get_path())
self.devices_treestore.remove(treeiter)
del(self.rows[device.path])
# Add again
row_ref = self.add_new_device(device)
if self.options['expanded']:
iter_path = row_ref.get_path()
if self.is_filtered:
iter_path = self.modelfilter.convert_child_path_to_path(row_ref.get_path())
self.devices_tv.expand_to_path(iter_path)
if self.options['followchanged']:
self.devices_tv.set_cursor(row_ref.get_path(), None, False)
self.logger.info(_('Device changed: %s') % device.nice_label)
# Log device updated info and propierties
for i in udevdiscover.device.find_differences(old_device, device):
if i[0] == 'changed': self.logger.debug('%s %s, %s: %s -> %s' % i)
else: self.logger.debug('%s %s, %s: %s' % i)
def populate(self, devices):
self.devices_treestore.clear()
self.rows = {}
for device in devices:
self.add_new_device(device)
def add_new_device(self, device):
device_icon = theme.load_icon(device.icon, 24, 0)
if device.parent == None:
treeiter = self.devices_treestore.append(None, [device.path,
device_icon, device.nice_label, device.subsystem, False,
device.name])
else:
if self.rows.has_key(device.parent.path):
parent_treeiter = self.devices_treestore.get_iter(
self.rows[device.parent.path].get_path())
treeiter = self.devices_treestore.append(parent_treeiter,
[device.path, device_icon, device.nice_label,
device.subsystem, False, device.name])
else:
treeiter = self.devices_treestore.append(None, [device.path,
device_icon, device.nice_label, device.subsystem, False,
device.name])
self.rows[device.path] = Gtk.TreeRowReference.new(self.devices_treestore,
self.devices_treestore.get_path(treeiter))
search_text = self.search_entry.get_text().lower()
if search_text:
treerow = self.devices_treestore[treeiter]
if udevdiscover.device.match_string(device, search_text):
treerow[VISIBLE_COL] = True
iter_parent = self.devices_treestore.iter_parent(treeiter)
if iter_parent:
self.set_branch_visible(iter_parent)
else:
treerow[VISIBLE_COL] = False
return self.rows[device.path]
def preferences_action_activated_cb(self, widget):
if self.subsys_dialog.run() == Gtk.ResponseType.ACCEPT:
chosen_preset = self.subsys_dialog.get_current_preset()
self.options['subsystem_preset'] = chosen_preset
if chosen_preset == CUSTOM_SUBSYS_PRESET:
self.options['custom_subsystem_preset'] = \
' '.join(self.subsys_dialog.get_chosen_subsystems())
self.reload_action_activate_cb()
self.subsys_dialog.destroy()
def showparents_toggleaction_toggled_cb(self, action):
self.options['parent_tree'] = action.get_active()
self.device_finder.scan_subsystems(
self.subsys_dialog.get_chosen_subsystems(),
self.options['parent_tree'])
self.populate(self.device_finder.get_devices())
self.expand_toggleaction_toggled_cb(self.expand_toggleaction)
def expand_toggleaction_toggled_cb(self, action):
if action.get_active():
self.devices_tv.expand_all()
else:
self.devices_tv.collapse_all()
self.options['expanded'] = self.expand_toggleaction.get_active()
def follownew_toggleaction_toggled_cb(self, action):
self.options['follownew'] = self.follownew_toggleaction.get_active()
def followchanged_toggleaction_toggled_cb(self, action):
self.options['followchanged'] = self.followchanged_toggleaction.get_active()
def reload_action_activate_cb(self, widget=None):
self.device_finder.scan_subsystems(
self.subsys_dialog.get_chosen_subsystems(),
self.options['parent_tree'])
self.populate(self.device_finder.get_devices())
self.expand_toggleaction_toggled_cb(self.expand_toggleaction)
def devices_tv_cursor_changed_cb(self, treeview):
selection = self.devices_tv.get_selection()
model, selected = selection.get_selected()
if selected:
row = model[selected]
device = self.device_finder.get_devices_tree()[row[PATH_COL]]
title = '<b>'+device.nice_label+'</b>'
if hasattr(device, 'vendor_name') and device.vendor_name:
title += '\n<i>%s</i>' % device.vendor_name.decode('UTF-8')
if hasattr(device, 'model_name') and device.model_name:
title += '\n<i>%s</i>' % device.model_name.decode('UTF-8')
self.devicename_label.set_label(title)
desc = '\n'.join([': '.join(('<b>'+key.capitalize()+'</b>',
str(val))) for key, val in device.get_info()])
self.devicedesc_label.set_label(desc)
self.device_image.set_from_icon_name(device.icon, Gtk.IconSize.DIALOG)
if hasattr(device, 'get_summary'):
self.summary_scrollwindow.set_visible(True)
desc = '\n'.join([': '.join(('<b>'+key.capitalize()+'</b>',
str(val))) for key, val in device.get_summary()])
self.devicepropsum_label.set_label(desc)
else:
self.summary_scrollwindow.set_visible(False)
self.deviceprop_store.clear()
for key, val in device.get_props().items():
self.deviceprop_store.append([key,
val.decode("string-escape")])
def search_entry_activate_cb(self, entry):
search_text = entry.get_text().lower()
if not search_text:
return
visible_iter = []
for path in self.rows.keys():
iter = self.devices_treestore.get_iter(self.rows[path].get_path())
treerow = self.devices_treestore[iter]
device = self.device_finder.get_devices_tree()[treerow[PATH_COL]]
if udevdiscover.device.match_string(device, search_text):
treerow[VISIBLE_COL] = True
visible_iter.append(iter)
else:
treerow[VISIBLE_COL] = False
if visible_iter:
for iter in visible_iter:
iter_parent = self.devices_treestore.iter_parent(iter)
if iter_parent:
self.set_branch_visible(iter_parent)
self.showparents_toggleaction.set_sensitive(False)
self.modelfilter = self.devices_treestore.filter_new(None)
self.modelfilter.set_visible_column(VISIBLE_COL)
self.devices_tv.set_model(self.modelfilter)
self.expand_toggleaction_toggled_cb(self.expand_toggleaction)
self.is_filtered = True
def set_branch_visible(self, itr):
""" Sets the whole tree-branch upward as visible """
tr = self.devices_treestore[itr]
tr[VISIBLE_COL] = True
itr_prnt = self.devices_treestore.iter_parent(itr)
if itr_prnt:
self.set_branch_visible(itr_prnt)
def search_entry_changed_cb(self, entry):
if entry.get_text():
entry.set_property('primary-icon-sensitive',True)
entry.set_property('secondary-icon-sensitive',True)
else:
self.devices_tv.set_model(self.devices_treestore)
self.expand_toggleaction_toggled_cb(self.expand_toggleaction)
self.showparents_toggleaction.set_sensitive(True)
entry.set_property('primary-icon-sensitive',False)
entry.set_property('secondary-icon-sensitive',False)
self.is_filtered = False
def search_entry_icon_release_cb(self, entry, icon_pos, event):
if icon_pos == 0:
entry.set_text('')
self.devices_tv.set_model(self.devices_treestore)
self.expand_toggleaction_toggled_cb(self.expand_toggleaction)
else:
self.search_entry_activate_cb(entry)
def about_action_activate_cb(self, widget):
self.about_dialog.run()
def help_action_activate_cb(self, widget):
pass
def quit_action_activate_cb(self, data=None):
self.options['devices_hpaned'] = self.devices_hpaned.get_position()
self.options['eventslog_vpaned'] = self.eventslog_vpaned.get_position()
self.options['eventslog_expanded'] = self.eventslog_expander.get_expanded()
self.options['follownew'] = self.follownew_toggleaction.get_active()
self.options['followchanged'] = self.followchanged_toggleaction.get_active()
self.saveconf()
sys.exit(0)
if __name__ == '__main__':
UDevDiscoverGUI()
GObject.MainLoop().run()
Jump to Line
Something went wrong with that request. Please try again.