#!/usr/bin/perl -w # USBInfo 0.1 # Graphical display of USB devices # (C) 2005 Arjan Opmeer # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 # as published by the Free Software Foundation. use strict; use Getopt::Long; # Built in default configuration that can be overridden with a personal # configuration file, or with command line parameters # my %default_configuration = ( devices_file => '/proc/bus/usb/devices', poll_time => 20, show_free_ports => 0, mark_no_driver => 0, use_usb_ids => 0, usb_ids_file => '/usr/share/misc/usb.ids', ); # # These are the built in defaults that can be tweaked # my $configuration_file = "$ENV{HOME}/.usbinfo"; my $poll_time_max = 1000; my @device_markup = ("", ""); my @free_port_markup = ("", ""); my @driver_missing_markup = ("", ""); my $driver_missing_text = "(*DRIVER MISSING*)"; my $indent_width = 4; my $value_column = 40; # # Static classinfo that could be updated when the USB-IF assigns more # official class numbers # my %classinfo = ( '00' => { class_name => 'Defined at Interface level' }, '01' => { class_name => 'Audio', '01' => { subclass_name => 'Control Device' }, '02' => { subclass_name => 'Streaming' }, '03' => { subclass_name => 'MIDI Streaming' }, }, '02' => { class_name => 'Communications', '01' => { subclass_name => 'Direct Line' }, '02' => { subclass_name => 'Abstract (modem)', '00' => 'None', '01' => 'AT-commands (v.25ter)', '02' => 'AT-commands (PCCA101)', '03' => 'AT-commands (PCCA101 + wakeup)', '04' => 'AT-commands (GSM)', '05' => 'AT-commands (3G)', '06' => 'AT-commands (CDMA)', 'fe' => 'Defined by command set descriptor', 'ff' => 'Vendor Specific (MSFT RNDIS?)', }, '03' => { subclass_name => 'Telephone' }, '04' => { subclass_name => 'Multi-Channel' }, '05' => { subclass_name => 'CAPI Control' }, '06' => { subclass_name => 'Ethernet Networking' }, '07' => { subclass_name => 'ATM Networking' }, '08' => { subclass_name => 'Wireless Handset Control' }, '09' => { subclass_name => 'Device Management' }, '0a' => { subclass_name => 'Mobile Direct Line' }, '0b' => { subclass_name => 'OBEX' }, }, '03' => { class_name => 'Human Interface Devices', '00' => { subclass_name => 'No Subclass', '00' => 'None', '01' => 'Keyboard', '02' => 'Mouse', }, '01' => { subclass_name => 'Boot Interface Subclass', '00' => 'None', '01' => 'Keyboard', '02' => 'Mouse', }, }, '06' => { class_name => 'Imaging', '01' => { subclass_name => 'Still Image Capture', '01' => 'Picture Transfer Protocol (PIMA 15470)', }, }, '07' => { class_name => 'Printer', '01' => { subclass_name => 'Printer', '00' => 'Reserved/Undefined', '01' => 'Unidirectional', '02' => 'Bidirectional', '03' => 'IEEE 1284.4 compatible bidirectional', 'ff' => 'Vendor Specific', }, }, '08' => { class_name => 'Mass Storage', '01' => { subclass_name => 'RBC (typically Flash)', '00' => 'Control/Bulk/Interrupt', '01' => 'Control/Bulk', '50' => 'Bulk (Zip)', }, '02' => { subclass_name => 'SFF-8020i, MMC-2 (ATAPI)' }, '03' => { subclass_name => 'QIC-157' }, '04' => { subclass_name => 'Floppy (UFI)', '00' => 'Control/Bulk/Interrupt', '01' => 'Control/Bulk', '50' => 'Bulk (Zip)', }, '05' => { subclass_name => 'SFF-8070i' }, '06' => { subclass_name => 'SCSI', '00' => 'Control/Bulk/Interrupt', '01' => 'Control/Bulk', '50' => 'Bulk (Zip)', }, }, '09' => { class_name => 'Hub', '00' => { subclass_name => 'Unused', '01' => 'Single TT', '02' => 'TT per port', }, }, '0a' => { class_name => 'Data', '00' => { subclass_name => 'Unused', '30' => 'I.430 ISDN BRI', '31' => 'HDLC', '32' => 'Transparent', '50' => 'Q.921M', '51' => 'Q.921', '52' => 'Q.921TM', '90' => 'V.42bis', '91' => 'Q.932 EuroISDN', '92' => 'V.120 V.24 rate ISDN', '93' => 'CAPI 2.0', 'fd' => 'Host Based Driver', 'fe' => 'CDC PUF', 'ff' => 'Vendor specific', }, }, '0b' => { class_name => 'Chip/SmartCard' }, '0d' => { class_name => 'Content Security' }, '0e' => { class_name => 'Video', '00' => { subclass_name => 'Undefined' }, '01' => { subclass_name => 'Video Control' }, '02' => { subclass_name => 'Video Streaming' }, '03' => { subclass_name => 'Video Interface Collection' }, }, 'dc' => { class_name => 'Diagnostic', '01' => { subclass_name => 'Reprogrammable Diagnostics', '01' => 'USB2 Compliance', }, }, 'e0' => { class_name => 'Wireless', '01' => { subclass_name => 'Radio Frequency', '01' => 'Bluetooth', }, }, 'ef' => { class_name => 'Miscellaneous Device', '02' => { subclass_name => 'Common Class', '01' => 'Interface Association', }, }, 'fe' => { class_name => 'Application Specific Interface', '01' => { subclass_name => 'Device Firmware Update' }, '02' => { subclass_name => 'IRDA Bridge' }, '03' => { subclass_name => 'Test and Measurement' }, }, 'ff' => { class_name => 'Vendor Specific Class', 'ff' => { subclass_name => 'Vendor Specific Subclass', 'ff' => 'Vendor Specific Protocol', }, }, ); # from Glib.pm use constant { TRUE => 1, FALSE => !1, G_PRIORITY_DEFAULT => 0, }; my $usegraphics = 0; if (defined $ENV{DISPLAY}) { $usegraphics = 1; } if ($usegraphics) { eval 'use Gtk2'; if ($@) { $usegraphics = 0; } } use constant { DEVICE_NAME_COLUMN => 0, BUS_NUM_COLUMN => 1, DEVICE_NUM_COLUMN => 2, PROPERTY_NAME_COLUMN => 0, PROPERTY_VALUE_COLUMN => 1, }; my %configuration = %default_configuration; my (@devices_file_contents, @buslist, %vendorinfo); my ($timer_source, $lastsize, $lastmtime, $datamodel); my ($mainwindow, $configwindow, $actions, $tooltips, $configopen); my @infowindows = ([]); my @mainmenu_entries = ( [ "USBInfoMenu", undef, "_USBInfo" ], [ "ConfigurationMenu", undef, "_Configuration" ], [ "HelpMenu", undef, "_Help" ], [ "Refresh", # name undef, # stock-id "_Refresh", # label "R", # accelerator "Force refresh", # tooltip \&refresh_callback # callback ], [ "SaveAs", "gtk-save-as", "_Save as text", "S", "Save as text", \&save_callback ], [ "Quit", "gtk-quit", "_Quit", "Q", "Quit", \&quit_callback ], [ "Configuration", "gtk-preferences", "_Configuration", "C", "Configuration", \&configuration_callback ], [ "About", "gtk-about", "_About", undef, "About", \&about_callback ] ); my $ui_info = ' '; sub refresh_callback { $actions->get_action("Refresh")->set_property('sensitive', FALSE); $actions->get_action("SaveAs")->set_property('sensitive', FALSE); $datamodel->clear; if (read_devices_file()) { if (parse_devices_file_contents()) { fill_datamodel(); $actions->get_action("Refresh")->set_property('sensitive', TRUE); $actions->get_action("SaveAs")->set_property('sensitive', TRUE); } } } sub check_overwrite { my ($filename) = @_; if (-e $filename) { my $dialog = Gtk2::MessageDialog->new_with_markup($mainwindow, [qw(destroy-with-parent)], 'question', 'ok-cancel', "The file \"$filename\" already exists.\n\n". "Are you sure you want to overwrite it?"); my $response = $dialog->run; $dialog->destroy; if ($response eq 'cancel') { return 0; } } return 1; } sub save_callback { my $dialog = Gtk2::FileChooserDialog->new("Enter a file name", $mainwindow, 'save', 'gtk-cancel' => 'cancel', 'gtk-save' => 'accept'); if ($dialog->run eq 'accept') { my $filename = $dialog->get_filename; if (check_overwrite($filename)) { if (open(OUTPUT, ">", $filename)) { my $oldfh = select(OUTPUT); text_output(); select($oldfh); close(OUTPUT); } else { warning_message("Cannot open text output file \"$filename\".\n" . "($!)\n\nText output is not saved."); } } } $dialog->destroy; } sub quit_callback { Gtk2->main_quit; } sub open_file_chooser { my (undef, $udata) = @_; my ($window, $entry) = @{ $udata }; my $dialog = Gtk2::FileChooserDialog->new("Select a file", $window, 'open', 'gtk-cancel' => 'cancel', 'gtk-open' => 'accept'); $dialog->set_filename($entry->get_text); if ($dialog->run eq 'accept') { $entry->set_text($dialog->get_filename); } $dialog->destroy; } sub close_info_windows { for my $bus (1 .. $#buslist) { for my $dev (0 .. $#{ $infowindows[$bus] }) { if (defined $infowindows[$bus][$dev]) { $infowindows[$bus][$dev]->destroy; } } } } sub configuration_callback { if ($configopen) { $configwindow->present; return; } $configwindow = Gtk2::Window->new('toplevel'); $configwindow->set_title("Edit configuration"); $configwindow->set_resizable(FALSE); $configwindow->set_type_hint('dialog'); $configwindow->set_transient_for($mainwindow); $configwindow->set_destroy_with_parent(TRUE); $configwindow->signal_connect(destroy => sub { $configwindow->destroy; $configopen = 0 }); $configwindow->signal_connect(key_press_event => sub { my ($widget, $event) = @_; if (Gtk2::Gdk->keyval_name($event->keyval) eq 'Escape') { $configwindow->destroy; $configopen = 0; } return FALSE; }); my $table = Gtk2::Table->new(8, 3, FALSE); $table->set_border_width(15); $table->set_row_spacings(10); $table->set_col_spacings(10); $configwindow->add($table); my $label = Gtk2::Label->new("USB devices file:"); $label->set_alignment(0, 0.5); $table->attach_defaults($label, 0, 1, 0, 1); my $dentry = Gtk2::Entry->new; $dentry->set_text($configuration{devices_file}); $tooltips->set_tip($dentry, "The file from which the USB devices information will be read", undef); $table->attach_defaults($dentry, 1, 2, 0, 1); my $image = Gtk2::Image->new_from_stock('gtk-open', 'button'); my $button = Gtk2::Button->new; $button->add($image); $button->signal_connect(clicked => \&open_file_chooser, [$configwindow, $dentry]); $tooltips->set_tip($button, "Browse the filesystem for the USB devices file", undef); $table->attach($button, 2, 3, 0, 1, [], [], 0, 0); $label = Gtk2::Label->new("Poll time:"); $label->set_alignment(0, 0.5); $table->attach_defaults($label, 0, 1, 1, 2); my $align = Gtk2::Alignment->new(0, 0.5, 0, 0); $table->attach_defaults($align, 1, 2, 1, 2); my $spin = Gtk2::SpinButton->new_with_range(0, $poll_time_max, 1); $spin->set_value($configuration{poll_time}); $tooltips->set_tip($spin, "The time in 10ths of a second between checks for changes to the USB devices file. " . "A value of 0 means not to check for changes at all", undef); $align->add($spin); my $fcheck = Gtk2::CheckButton->new_with_label("Show free ports in the USB device tree"); $fcheck->set_active($configuration{show_free_ports} ? TRUE : FALSE); $tooltips->set_tip($fcheck, "Whether ports not used by a device will be shown in the device tree", undef); $table->attach_defaults($fcheck, 0, 3, 2, 3); my $dcheck = Gtk2::CheckButton->new_with_label("Mark devices without a driver"); $dcheck->set_active($configuration{mark_no_driver} ? TRUE : FALSE); $tooltips->set_tip($dcheck, "Whether devices that have not been claimed by a driver will be marked with a visual clue", undef); $table->attach_defaults($dcheck, 0, 3, 3, 4); my $icheck = Gtk2::CheckButton->new_with_label("Use a file with USB ID's to name mappings"); $icheck->set_active($configuration{use_usb_ids} ? TRUE : FALSE); $tooltips->set_tip($icheck, "Whether a file with USB ID's to product name mappings will be used to display more userfriendly device names", undef); $table->attach_defaults($icheck, 0, 3, 4, 5); my $active = $icheck->get_active; my $ulabel = Gtk2::Label->new("USB ID's file:"); $ulabel->set_alignment(0, 0.5); $ulabel->set_sensitive($active ? TRUE : FALSE); $table->attach_defaults($ulabel, 0, 1, 5, 6); my $uentry = Gtk2::Entry->new; $uentry->set_text($configuration{usb_ids_file}); $uentry->set_sensitive( $active ? TRUE : FALSE ); $tooltips->set_tip($uentry, "The file containing the USB ID's to product name mappings", undef); $table->attach_defaults($uentry, 1, 2, 5, 6); $image = Gtk2::Image->new_from_stock('gtk-open', 'button'); my $ubutton = Gtk2::Button->new; $ubutton->add($image); $ubutton->signal_connect(clicked => \&open_file_chooser, [$configwindow, $uentry]); $ubutton->set_sensitive($active ? TRUE : FALSE); $tooltips->set_tip($ubutton, "Browse the filesystem for the USB ID's file", undef); $table->attach($ubutton, 2, 3, 5, 6, [], [], 0, 0); $icheck->signal_connect(toggled => sub { my $active = $icheck->get_active; $ulabel->set_sensitive($active ? TRUE : FALSE); $uentry->set_sensitive($active ? TRUE : FALSE); $ubutton->set_sensitive($active ? TRUE : FALSE); }); my $sepa = Gtk2::HSeparator->new; $table->attach_defaults($sepa, 0, 3, 6, 7); my $butbox = Gtk2::HButtonBox->new; $button = Gtk2::Button->new_from_stock('gtk-cancel'); $button->signal_connect(clicked => sub { $configwindow->destroy; $configopen = 0 }); $tooltips->set_tip($button, "Do not apply any changes made to your configuration", undef); $butbox->add($button); $button = Gtk2::Button->new_from_stock('gtk-save'); $button->signal_connect(clicked => sub { $configuration{devices_file} = $dentry->get_text; $configuration{poll_time} = $spin->get_value; $configuration{show_free_ports} = ($fcheck->get_active ? 1 : 0); $configuration{mark_no_driver} = ($dcheck->get_active ? 1 : 0); $configuration{use_usb_ids} = ($icheck->get_active ? 1 : 0); $configuration{usb_ids_file} = $uentry->get_text; $configwindow->destroy; $configopen = 0; write_configuration(); }); $tooltips->set_tip($button, "Save your current configuration to a configuration file", undef); $butbox->add($button); my $okbutton = Gtk2::Button->new_from_stock('gtk-ok'); $okbutton->signal_connect(clicked => sub { if (defined $timer_source) { Glib::Source->remove($timer_source); } close_info_windows(); $datamodel->clear; $actions->get_action("Refresh")->set_property('sensitive', FALSE); $actions->get_action("SaveAs")->set_property('sensitive', FALSE); $configuration{devices_file} = $dentry->get_text; $configuration{poll_time} = $spin->get_value; $configuration{show_free_ports} = ($fcheck->get_active ? 1 : 0); $configuration{mark_no_driver} = ($dcheck->get_active ? 1 : 0); $configuration{use_usb_ids} = ($icheck->get_active ? 1 : 0); $configuration{usb_ids_file} = $uentry->get_text; $configwindow->destroy; $configopen = 0; if ($configuration{use_usb_ids}) { read_usb_ids_file(); } if (read_devices_file()) { if (parse_devices_file_contents()) { fill_datamodel(); setup_file_watcher(); $actions->get_action("Refresh")->set_property('sensitive', TRUE); $actions->get_action("SaveAs")->set_property('sensitive', TRUE); } } }); $tooltips->set_tip($okbutton, "Apply changes made to your configuration", undef); $butbox->add($okbutton); $table->attach_defaults($butbox, 0, 3, 7, 8); $dentry->signal_connect(key_press_event => sub { my ($widget, $event) = @_; if (Gtk2::Gdk->keyval_name($event->keyval) eq 'Return') { $okbutton->signal_emit('clicked'); } return FALSE; }); $configwindow->show_all; $configopen = 1; } sub about_callback { Gtk2->show_about_dialog($mainwindow, authors => 'Arjan Opmeer ', comments => 'Graphical display of USB devices', copyright => '© 2005 Arjan Opmeer', name => 'USBInfo', version => '0.1'); } sub error_message { my ($msg) = @_; if ($usegraphics) { my $dialog = Gtk2::MessageDialog->new_with_markup($mainwindow, [qw(destroy-with-parent)], 'error', 'close', $msg); $dialog->run; $dialog->destroy; } else { $msg =~ s/<.*?>//g; print STDERR "Error: $msg\n"; } } sub warning_message { my ($msg) = @_; if ($usegraphics) { my $dialog = Gtk2::MessageDialog->new_with_markup($mainwindow, [qw(destroy-with-parent)], 'warning', 'close', $msg); $dialog->run; $dialog->destroy; } else { $msg =~ s/<.*?>//g; print STDERR "Warning: $msg\n"; } } sub read_configuration { if (-e $configuration_file) { if (open(INPUT, "<", $configuration_file)) { while (my $line = ) { chomp $line; if (length($line) && ($line !~ /^#/)) { $line =~ s/\s//g; my ($key, $val) = split("=", $line); $configuration{$key} = $val; } } close(INPUT); } else { warning_message("Cannot open configuration file \"$configuration_file\".\n" . "($!)\n\nContinuing with default values."); } } } sub write_configuration { if (open(OUTPUT, ">", $configuration_file)) { print OUTPUT "# Configuration file for USBInfo\n"; for my $key (keys %configuration) { print OUTPUT "$key = $configuration{$key}\n"; } close(OUTPUT); } else { warning_message("Cannot open configuration file \"$configuration_file\".\n" . "($!)\n\nConfiguration are not saved."); } } sub read_usb_ids_file { if (! -e $configuration{usb_ids_file}) { warning_message("USB ID's file \"$configuration{usb_ids_file}\" does not exist." . "\n\nContinuing without using a USB ID's file."); $configuration{use_usb_ids} = 0; return; } if (open(INPUT, "<", $configuration{usb_ids_file})) { %vendorinfo = (); my ($vendor, $match1, $match2); while (my $line = ) { chomp $line; if ($line =~ /^(\w\w\w\w)\s+(.*)/) { my ($vendor_id, $vendor_name) = ($1, $2); $match1 = 1; $vendor = {}; $vendorinfo{$vendor_id} = $vendor; $vendor->{vendor_name} = $vendor_name; } elsif ($line =~ /^\t(\w\w\w\w)\s+(.*)/) { my ($device_id, $device_name) = ($1, $2); $match2 = 1; $vendor->{$device_id} = $device_name; } } close(INPUT); if (!($match1 && $match2)) { warning_message("Failed to parse USB ID's file \"$configuration{usb_ids_file}\"." . "\n\nContinuing without using a USB ID's file."); $configuration{use_usb_ids} = 0; } } else { warning_message("Cannot open USB ID's file \"$configuration{usb_ids_file}\".\n" . "($!)\n\nContining without using a USB ID's file."); $configuration{use_usb_ids} = 0; } } sub why_no_proc_devices_file { my ($procfs_mounted, $usbfs_mounted, $usbfs_support); if (open(INPUT, "<", "/etc/mtab")) { my @contents = ; close(INPUT); $procfs_mounted = grep(/^proc/, @contents); $usbfs_mounted = grep(/^usbfs/, @contents); } else { return 0; } if (open(INPUT, "<", "/proc/filesystems")) { my @contents = ; close(INPUT); $usbfs_support = grep(/usbfs/, @contents); } else { return 0; } if (!$procfs_mounted) { error_message("Your system does not seem to have the proc filing system mounted." . "\n\nHence cannot find the USB devices file \"$configuration{devices_file}\"."); return 1; } elsif (!$usbfs_support) { error_message("Your kernel does not seem to have usbfs support." . "\n\nHence cannot find the USB devices file \"$configuration{devices_file}\"."); return 1; } elsif (!$usbfs_mounted) { error_message("Your system does not seem to have the usbfs filing system mounted." . "\n\nHence cannot find the USB devices file \"$configuration{devices_file}\"."); return 1; } else { return 0; } } sub read_devices_file { if (! -e $configuration{devices_file}) { if ($configuration{devices_file} eq $default_configuration{devices_file}) { if (!why_no_proc_devices_file()) { error_message("USB devices file \"$configuration{devices_file}\" does not exist." . "\n\nFailed to discover why. Sorry."); } } else { error_message("USB devices file \"$configuration{devices_file}\" does not exist." . "\n\nEdit your configuration."); } return 0; } if (open(INPUT, "<", $configuration{devices_file})) { # slurp in devices file as quickly as possible @devices_file_contents = ; close(INPUT); } else { error_message("Cannot open USB devices file \"$configuration{devices_file}\".\n" . "($!)\n\nEdit your configuration."); return 0; } chomp(@devices_file_contents); return 1; } sub parse_devices_file_contents { my ($match, $device, $config, $interface, $endpoint); @buslist = ([]); for my $line (@devices_file_contents) { if ($line =~ /^T:.*Bus=(\d+) .*Lev=(\d+) .*Prnt=(\d+) .*Port=(\d+) .*Cnt=(\d+) .*Dev\#=\s*(\d+) .*Spd=([\d.]+) .*MxCh=\s*(\d+).*/x) { # Topology info my ($bus, $lev, $prnt, $port) = ($1 + 0, $2 + 0, $3 + 0, $4 + 0); my ($cnt, $devn, $spd, $mxch) = ($5 + 0, $6 + 0, $7 + 0, $8 + 0); $match = 1; $device = {}; $device->{configlist} = []; $device->{bus_num} = $bus; $device->{device_num} = $devn; $device->{speed} = $spd; $device->{max_children} = $mxch; $device->{driver_missing} = 0; $device->{drivers} = {}; if ($mxch > 0) { $device->{portlist} = []; } if ($lev > 0) { # append device to buslist push @{ $buslist[$bus] }, $device; # search parent device on same bus LOOP: for my $dev (@{ $buslist[$bus] }) { if ($dev->{device_num} == $prnt) { $dev->{portlist}[$port] = $device; last LOOP; } } } else { # make sure root hub is first device on buslist unshift @{ $buslist[$bus] }, $device; } } elsif ($line =~ /^B:.*Alloc=\s*(\d+)\/(\d+) .*\#Int=\s*(\d+) .*\#Iso=\s*(\d+).*/x) { # Bandwidth info my ($sch, $rsv, $nint, $niso) = ($1 + 0, $2 + 0, $3 + 0, $4 + 0); $device->{scheduled} = $sch; $device->{reserved} = $rsv; $device->{num_int_req} = $nint; $device->{num_iso_req} = $niso; } elsif ($line =~ /^D:.*Ver=\s*([\d.]+) .*Cls=(\w\w) .*Sub=(\w\w) .*Prot=(\w\w) .*MxPS=\s*(\d+) .*\#Cfgs=\s*(\d+).*/x) { # Device descriptor info my ($ver, $cls, $sub) = ($1, $2, $3); my ($prot, $mxps, $ncfgs) = ($4, $5 + 0, $6 + 0); $device->{version} = $ver; $device->{class} = $cls; $device->{subclass} = $sub; $device->{protocol} = $prot; $device->{max_packetsize} = $mxps; $device->{num_configs} = $ncfgs; } elsif ($line =~ /^P:.*Vendor=(\w\w\w\w) .*ProdID=(\w\w\w\w) .*Rev=\s*([\w.]+)/x) { # Product ID info my ($vendor, $prodid, $rev) = ($1, $2, $3); $device->{vendor_id} = $vendor; $device->{product_id} = $prodid; $device->{revision} = $rev; } elsif ($line =~ /^S:/) { # String descriptor info if ($line =~ /Manufacturer/) { $line =~ m/.*Manufacturer=(.*)/; $device->{manufacturer} = $1; } elsif ($line =~ /Product/) { $line =~ m/.*Product=(.*)/; $device->{product} = $1; } elsif ($line =~ /SerialNumber/) { $line =~ m/.*SerialNumber=(.*)/; $device->{serial_number} = $1; } } elsif ($line =~ /^C:.*\#Ifs=\s*(\d+) .*Cfg\#=\s*(\d+) .*Atr=(\w\w) .*MxPwr=\s*(\d+)mA.*/x) { # Configuration info my ($nifs, $cfgn, $atr, $mxpwr) = ($1 + 0, $2 + 0, hex($3), $4 + 0); $config = {}; $config->{interfacelist} = ([]); $config->{num_interfaces} = $nifs; $config->{attributes} = $atr; $config->{max_power} = $mxpwr; $device->{configlist}[$cfgn] = $config; if ($line =~ /^C:\*/) { $device->{current_config} = $cfgn; } } elsif ($line =~ /^I:.*If\#=\s*(\d+) .*Alt=\s*(\d+) .*\#EPs=\s*(\d+) .*Cls=(\w\w) .*Sub=(\w\w) .*Prot=(\w\w) .*Driver=(.*)/x) { # Interface info my ($ifn, $alt, $neps) = ($1 + 0, $2 + 0, $3 + 0); my ($cls, $sub, $prot, $driver) = ($4, $5, $6, $7); $interface = {}; $interface->{endpointlist} = []; $interface->{num_endpoints} = $neps; $interface->{class} = $cls; $interface->{subclass} = $sub; $interface->{protocol} = $prot; $interface->{driver} = $driver; if ($driver =~ /\(none\)/) { $device->{driver_missing} = 1; } else { if (!defined $device->{drivers}->{$driver}) { $device->{drivers}->{$driver} = $ifn; } } $config->{interfacelist}[$ifn][$alt] = $interface; } elsif ($line =~ /^E:.*Ad=(\w\w) .*Atr=(\w\w) .*MxPS=\s*(\d+) .*Ivl=\s*(\d+[um]s).*/x) { # Endpoint info my ($ad, $atr, $mxps, $ivl) = (hex($1), hex($2), $3 + 0, $4); $endpoint = {}; $endpoint->{address} = $ad; $endpoint->{attributes} = $atr; $endpoint->{max_packetsize} = $mxps; $ivl =~ s/(\d+)([um]s)/$1 $2/; $endpoint->{interval} = $ivl; push @{ $interface->{endpointlist} }, $endpoint; } } if (!$match) { error_message("Failed to parse USB devices file \"$configuration{devices_file}\"." . "\n\nEdit your configuration."); return 0; } return 1; } sub fill_datamodel { $datamodel->clear; for my $bus (1 .. $#buslist) { my $iter = $datamodel->append(undef); $datamodel->set($iter, DEVICE_NAME_COLUMN, "USB bus $bus"); $iter = $datamodel->append($iter); model_add_device($buslist[$bus][0], $iter, -1); } $datamodel->{treeview}->expand_all; } sub model_add_device { my ($dev, $iter, $atport) = @_; create_device_name($dev); if ($atport >= 0) { if (($configuration{mark_no_driver}) && ($dev->{driver_missing})) { $datamodel->set($iter, DEVICE_NAME_COLUMN, "Port $atport: " . join(join($dev->{name}, @device_markup), @driver_missing_markup)); } else { $datamodel->set($iter, DEVICE_NAME_COLUMN, "Port $atport: " . join($dev->{name}, @device_markup)); } } else { $datamodel->set($iter, DEVICE_NAME_COLUMN, $dev->{name}); } $datamodel->set($iter, BUS_NUM_COLUMN, $dev->{bus_num}); $datamodel->set($iter, DEVICE_NUM_COLUMN, $dev->{device_num}); if ($dev->{max_children} > 0) { for my $port (0 .. $dev->{max_children} - 1) { if (defined $dev->{portlist}[$port]) { my $port_iter = $datamodel->append($iter); model_add_device($dev->{portlist}[$port], $port_iter, $port); } else { if ($configuration{show_free_ports}) { my $port_iter = $datamodel->append($iter); $datamodel->set($port_iter, DEVICE_NAME_COLUMN, join("Port $port: -", @free_port_markup)); } } } } } sub create_device_name { my ($dev) = @_; if ($configuration{use_usb_ids}) { if (defined $vendorinfo{$dev->{vendor_id}}) { if (defined $vendorinfo{$dev->{vendor_id}}{$dev->{product_id}}) { $dev->{name} = $vendorinfo{$dev->{vendor_id}}{vendor_name} . " - " . $vendorinfo{$dev->{vendor_id}}{$dev->{product_id}}; } else { if (defined $dev->{product}) { $dev->{name} = $vendorinfo{$dev->{vendor_id}}{vendor_name} . " - " . $dev->{product}; } else { $dev->{name} = $vendorinfo{$dev->{vendor_id}}{vendor_name} . " - [$dev->{product_id}]"; } } } else { if (defined $dev->{manufacturer}) { if (defined $dev->{product}) { $dev->{name} = "$dev->{manufacturer} - $dev->{product}"; } else { $dev->{name} = "$dev->{manufacturer} - [$dev->{product_id}]"; } } else { if (defined $dev->{product}) { $dev->{name} = "$dev->{product}"; } else { $dev->{name} = "[$dev->{vendor_id}] - [$dev->{product_id}] (" . join(" / ", (keys %{ $dev->{drivers} })) . ")"; } } } } else { if (defined $dev->{manufacturer}) { if (defined $dev->{product}) { $dev->{name} = "$dev->{manufacturer} - $dev->{product}"; } else { $dev->{name} = "$dev->{manufacturer} - [$dev->{product_id}]"; } } else { if (defined $dev->{product}) { $dev->{name} = "$dev->{product}"; } else { $dev->{name} = "[$dev->{vendor_id}] - [$dev->{product_id}] (" . join(" / ", (keys %{ $dev->{drivers} })) . ")"; } } } } sub text_output { print "USB tree overview\n"; print "=================\n\n"; for my $bus (1 .. $#buslist) { print "USB bus $bus\n"; text_add_device($buslist[$bus][0], 1, -1); } print "\n\nUSB devices information\n"; print "=======================\n\n"; for my $bus (1 .. $#buslist) { for my $dev (@{ $buslist[$bus] }) { text_show_device_info($dev); print "\n"; } } } sub iprint { my ($indent, $prop, $val) = @_; if ($indent > 0) { print " " x ($indent * $indent_width) ; } print $prop; if (defined $val) { print " " x ($value_column - length($prop) - $indent_width * $indent); print $val; } } sub text_add_device { my ($dev, $indent, $atport) = @_; create_device_name($dev); if ($atport >= 0) { if (($configuration{mark_no_driver}) && ($dev->{driver_missing})) { iprint($indent, "Port $atport: $dev->{name} $driver_missing_text\n"); } else { iprint($indent, "Port $atport: $dev->{name}\n"); } } else { iprint($indent, "$dev->{name}\n"); } if ($dev->{max_children} > 0) { for my $port (0 .. $dev->{max_children} - 1) { if (defined $dev->{portlist}[$port]) { text_add_device($dev->{portlist}[$port], $indent + 1, $port); } else { if ($configuration{show_free_ports}) { iprint($indent + 1, "Port $port: -\n"); } } } } } sub text_show_device_info { my ($dev) = @_; my ($classstr, $subclassstr, $protocolstr) = ("", "", ""); my ($vendorstr, $productstr) = ("", ""); print "USB device info for $dev->{name}:\n"; iprint(0, " Bus number:", "$dev->{bus_num}\n"); iprint(0, " Device number:", "$dev->{device_num}\n"); iprint(0, " Speed:", "$dev->{speed} Mb/s"); if ($dev->{speed} == 1.5) { print " (Low)\n"; } elsif ($dev->{speed} == 12) { print " (Full)\n"; } elsif ($dev->{speed} == 480) { print " (High)\n"; } iprint(0, " Max children:", "$dev->{max_children}\n"); if (defined $dev->{scheduled}) { iprint(0, " Bandwidth allocated:", "$dev->{scheduled}/$dev->{reserved} us (" . int(100 * $dev->{scheduled} / $dev->{reserved} + 0.5) . "%)\n"); iprint(0, " Number of interrupt requests:", "$dev->{num_int_req}\n"); iprint(0, " Number of isochronous requests:", "$dev->{num_iso_req}\n"); } iprint(0, " USB version:", "$dev->{version}\n"); if (defined $classinfo{$dev->{class}}) { $classstr = " ($classinfo{$dev->{class}}{class_name})"; if (defined $classinfo{$dev->{class}}{$dev->{subclass}}) { $subclassstr = " ($classinfo{$dev->{class}}{$dev->{subclass}}{subclass_name})"; if (defined $classinfo{$dev->{class}}{$dev->{subclass}}{$dev->{protocol}}) { $protocolstr = " ($classinfo{$dev->{class}}{$dev->{subclass}}{$dev->{protocol}})"; } } } iprint(0, " Class:", "$dev->{class}$classstr\n"); iprint(0, " Subclass:", "$dev->{subclass}$subclassstr\n"); iprint(0, " Protocol:", "$dev->{protocol}$protocolstr\n"); iprint(0, " Max default packet size:", "$dev->{max_packetsize}\n"); if ($configuration{use_usb_ids}) { if (defined $vendorinfo{$dev->{vendor_id}}) { $vendorstr = " ($vendorinfo{$dev->{vendor_id}}{vendor_name})"; if (defined $vendorinfo{$dev->{vendor_id}}{$dev->{product_id}}) { $productstr = " ($vendorinfo{$dev->{vendor_id}}{$dev->{product_id}})"; } } } iprint(0, " Vendor ID:", "$dev->{vendor_id}$vendorstr\n"); iprint(0, " Product ID:", "$dev->{product_id}$productstr\n"); iprint(0, " Revision:", "$dev->{revision}\n"); if (defined $dev->{manufacturer}) { iprint(0, " Manufacturer string:", "$dev->{manufacturer}\n"); } if (defined $dev->{product}) { iprint(0, " Product string:", "$dev->{product}\n"); } if (defined $dev->{serial_number}) { iprint(0, " Serialnumber string:", "$dev->{serial_number}\n"); } for my $confnum (1 .. $dev->{num_configs}) { print " Configuration $confnum"; if ($confnum == $dev->{current_config}) { print " (active)"; } print "\n"; my $config = $dev->{configlist}[$confnum]; iprint(1, " Attributes:", sprintf("%02x", $config->{attributes})); my $test = (($config->{attributes} & 0x60) >> 5); if ($test == 0x03) { print " (self powered, remote wakeup)"; } elsif ($test == 0x02) { print " (self powered)"; } elsif ($test == 0x01) { print " (remote wakeup)"; } print "\n"; iprint(1, " Max power:", "$config->{max_power} mA\n"); for my $intnum (0 .. $config->{num_interfaces} - 1) { iprint(1, " Interface $intnum\n"); for my $altnum (0 .. $#{@{ $config->{interfacelist}[$intnum] }}) { iprint(2, " Alternative $altnum\n"); my $interface = $config->{interfacelist}[$intnum][$altnum]; if (defined $classinfo{$interface->{class}}) { $classstr = " ($classinfo{$interface->{class}}{class_name})"; if (defined $classinfo{$interface->{class}}{$interface->{subclass}}) { $subclassstr = " ($classinfo{$interface->{class}}{$interface->{subclass}}{subclass_name})"; if (defined $classinfo{$interface->{class}}{$interface->{subclass}}{$interface->{protocol}}) { $protocolstr = " ($classinfo{$interface->{class}}{$interface->{subclass}}{$interface->{protocol}})"; } } } iprint(3, " Class:", "$interface->{class}$classstr\n"); iprint(3, " Subclass:", "$interface->{subclass}$subclassstr\n"); iprint(3, " Protocol:", "$interface->{protocol}$protocolstr\n"); iprint(3, " Driver:", "$interface->{driver}"); if (($interface->{driver} =~ /\(none\)/) && ($configuration{mark_no_driver})) { print " $driver_missing_text"; } print "\n"; for my $endnum ( 0 .. $interface->{num_endpoints} - 1) { iprint(3, " Endpoint $endnum\n"); my $endpoint = $interface->{endpointlist}[$endnum]; iprint(4, " Address:", sprintf("%02x", $endpoint->{address})); if ($endpoint->{address} & 0x80) { print " (input)\n"; } else { print " (output)\n"; } iprint(4, " Attributes:", sprintf("%02x", $endpoint->{attributes})); $test = ($endpoint->{attributes} & 0x03); if ($test == 0x03) { print " (interrupt)\n"; } elsif ($test == 0x02) { print " (bulk)\n"; } elsif ($test == 0x01) { print " (isochronous ("; $test = (($endpoint->{attributes} & 0x30) >> 4); if ($test == 0x00) { print "data endpoint"; } elsif ($test == 0x01) { print "feedback endpoint"; } elsif ($test == 0x02) { print "implicit feedback data endpoint"; } else { print "reserved"; } $test = (($endpoint->{attributes} & 0x0c) >> 2); if ($test == 0x00) { print ", no synchronization))\n"; } elsif ($test == 0x01) { print ", asynchronous))\n"; } elsif ($test == 0x02) { print ", adaptive))\n"; } else { print ", synchronous))\n"; } } else { print " (control)\n"; } iprint(4, " Max packet size:", "$endpoint->{max_packetsize}\n"); iprint(4, " Interval:", "$endpoint->{interval}\n"); } } } } } sub show_device_info_window { my (undef, $udata) = @_; my ($bus_num, $device_num) = @{ $udata }; if (defined $infowindows[$bus_num][$device_num]) { $infowindows[$bus_num][$device_num]->present; return; } my ($dev, $str); my ($classstr, $subclassstr, $protocolstr) = ("", "", ""); my ($vendorstr, $productstr) = ("", ""); LOOP: for my $d (@{ $buslist[$bus_num] }) { if ($d->{device_num} == $device_num) { $dev = $d; last LOOP; } } my $window = Gtk2::Window->new('toplevel'); $infowindows[$bus_num][$device_num] = $window; $window->set_title("USB device information"); $window->signal_connect(destroy => sub{ $window->destroy; $infowindows[$bus_num][$device_num] = undef}); my $vbox = Gtk2::VBox->new(FALSE, 0); $window->add($vbox); my $label = Gtk2::Label->new("Information for $dev->{name}:"); $vbox->pack_start($label, FALSE, FALSE, 10); my $scrwin = Gtk2::ScrolledWindow->new; $scrwin->set_shadow_type('etched-in'); $scrwin->set_policy('automatic', 'automatic'); $vbox->add($scrwin); my $model = Gtk2::TreeStore->new(qw(Glib::String Glib::String)); my $treeview = Gtk2::TreeView->new($model); $model->{treeview} = $treeview; $treeview->set_rules_hint(TRUE); my $renderer = Gtk2::CellRendererText->new; $treeview->insert_column_with_attributes(-1, "Property", $renderer, markup => PROPERTY_NAME_COLUMN); $renderer = Gtk2::CellRendererText->new; $treeview->insert_column_with_attributes(-1, "Value", $renderer, markup => PROPERTY_VALUE_COLUMN); $scrwin->add($treeview); my $butbox = Gtk2::HButtonBox->new; my $button = Gtk2::Button->new_from_stock('gtk-close'); $button->signal_connect(clicked => sub { $window->destroy; $infowindows[$bus_num][$device_num] = undef}); $butbox->add($button); $vbox->pack_start($butbox, FALSE, FALSE, 10); $window->set_default_size(600, 450); $window->show_all; my $iter = $model->append(undef); $model->set($iter, PROPERTY_NAME_COLUMN, "Bus number:"); $model->set($iter, PROPERTY_VALUE_COLUMN, $bus_num); $iter = $model->append(undef); $model->set($iter, PROPERTY_NAME_COLUMN, "Device number:"); $model->set($iter, PROPERTY_VALUE_COLUMN, $device_num); $iter = $model->append(undef); $model->set($iter, PROPERTY_NAME_COLUMN, "Speed:"); $str = "$dev->{speed} Mb/s"; if ($dev->{speed} == 1.5) { $str .= " (Low)"; } elsif ($dev->{speed} == 12) { $str .= " (Full)"; } elsif ($dev->{speed} == 480) { $str .= " (High)"; } $model->set($iter, PROPERTY_VALUE_COLUMN, $str); $iter = $model->append(undef); $model->set($iter, PROPERTY_NAME_COLUMN, "Max children"); $model->set($iter, PROPERTY_VALUE_COLUMN, $dev->{max_children}); if (defined $dev->{scheduled}) { $iter = $model->append(undef); $model->set($iter, PROPERTY_NAME_COLUMN, "Bandwidth allocated:"); $str = "$dev->{scheduled}/$dev->{reserved} us (" . int(100 * $dev->{scheduled} / $dev->{reserved} + 0.5) . "%)"; $model->set($iter, PROPERTY_VALUE_COLUMN, $str); $iter = $model->append(undef); $model->set($iter, PROPERTY_NAME_COLUMN, "Number of interrupt requests:"); $model->set($iter, PROPERTY_VALUE_COLUMN, $dev->{num_int_req}); $iter = $model->append(undef); $model->set($iter, PROPERTY_NAME_COLUMN, "Number of isochronous requests:"); $model->set($iter, PROPERTY_VALUE_COLUMN, $dev->{num_iso_req}); } $iter = $model->append(undef); $model->set($iter, PROPERTY_NAME_COLUMN, "USB version:"); $model->set($iter, PROPERTY_VALUE_COLUMN, $dev->{version}); $iter = $model->append(undef); $model->set($iter, PROPERTY_NAME_COLUMN, "Class:"); if (defined $classinfo{$dev->{class}}) { $classstr = " ($classinfo{$dev->{class}}{class_name})"; if (defined $classinfo{$dev->{class}}{$dev->{subclass}}) { $subclassstr = " ($classinfo{$dev->{class}}{$dev->{subclass}}{subclass_name})"; if (defined $classinfo{$dev->{class}}{$dev->{subclass}}{$dev->{protocol}}) { $protocolstr = " ($classinfo{$dev->{class}}{$dev->{subclass}}{$dev->{protocol}})"; } } } $model->set($iter, PROPERTY_VALUE_COLUMN, $dev->{class} . $classstr); $iter = $model->append(undef); $model->set($iter, PROPERTY_NAME_COLUMN, "Subclass:"); $model->set($iter, PROPERTY_VALUE_COLUMN, $dev->{subclass} . $subclassstr); $iter = $model->append(undef); $model->set($iter, PROPERTY_NAME_COLUMN, "Protocol:"); $model->set($iter, PROPERTY_VALUE_COLUMN, $dev->{protocol} . $protocolstr); $iter = $model->append(undef); $model->set($iter, PROPERTY_NAME_COLUMN, "Max default packet size:"); $model->set($iter, PROPERTY_VALUE_COLUMN, $dev->{max_packetsize}); $iter = $model->append(undef); $model->set($iter, PROPERTY_NAME_COLUMN, "Vendor ID:"); if ($configuration{use_usb_ids}) { if (defined $vendorinfo{$dev->{vendor_id}}) { $vendorstr = " ($vendorinfo{$dev->{vendor_id}}{vendor_name})"; if (defined $vendorinfo{$dev->{vendor_id}}{$dev->{product_id}}) { $productstr = " ($vendorinfo{$dev->{vendor_id}}{$dev->{product_id}})"; } } } $model->set($iter, PROPERTY_VALUE_COLUMN, $dev->{vendor_id} . $vendorstr); $iter = $model->append(undef); $model->set($iter, PROPERTY_NAME_COLUMN, "Product ID:"); $model->set($iter, PROPERTY_VALUE_COLUMN, $dev->{product_id} . $productstr); $iter = $model->append(undef); $model->set($iter, PROPERTY_NAME_COLUMN, "Revision:"); $model->set($iter, PROPERTY_VALUE_COLUMN, $dev->{revision}); if (defined $dev->{manufacturer}) { $iter = $model->append(undef); $model->set($iter, PROPERTY_NAME_COLUMN, "Manufacturer string:"); $model->set($iter, PROPERTY_VALUE_COLUMN, $dev->{manufacturer}); } if (defined $dev->{product}) { $iter = $model->append(undef); $model->set($iter, PROPERTY_NAME_COLUMN, "Product string:"); $model->set($iter, PROPERTY_VALUE_COLUMN, $dev->{product}); } if (defined $dev->{serial_number}) { $iter = $model->append(undef); $model->set($iter, PROPERTY_NAME_COLUMN, "Serialnumber string:"); $model->set($iter, PROPERTY_VALUE_COLUMN, $dev->{serial_number}); } for my $confnum (1 .. $dev->{num_configs}) { $iter = $model->append(undef); $str = "Configuration $confnum"; if ($confnum == $dev->{current_config}) { $str .= " (active)"; } $model->set($iter, PROPERTY_NAME_COLUMN, $str); my $confiter = $model->append($iter); my $config = $dev->{configlist}[$confnum]; $model->set($confiter, PROPERTY_NAME_COLUMN, "Attributes:"); $str = sprintf("%02x", $config->{attributes}); my $test = (($config->{attributes} & 0x60) >> 5); if ($test == 0x03) { $str .= " (self powered, remote wakeup)"; } elsif ($test == 0x02) { $str .= " (self powered)"; } elsif ($test == 0x01) { $str .= " (remote wakeup)"; } $model->set($confiter, PROPERTY_VALUE_COLUMN, $str); $confiter = $model->append($iter); $model->set($confiter, PROPERTY_NAME_COLUMN, "Max power:"); $str = "$config->{max_power} mA"; $model->set($confiter, PROPERTY_VALUE_COLUMN, $str); for my $intnum (0 .. $config->{num_interfaces} - 1) { $confiter = $model->append($iter); $model->set($confiter, PROPERTY_NAME_COLUMN, "Interface $intnum"); for my $altnum (0 .. $#{@{ $config->{interfacelist}[$intnum] }}) { my $intiter = $model->append($confiter); $model->set($intiter, PROPERTY_NAME_COLUMN, "Alternative $altnum"); my $altiter = $model->append($intiter); my $interface = $config->{interfacelist}[$intnum][$altnum]; $model->set($altiter, PROPERTY_NAME_COLUMN, "Class:"); if (defined $classinfo{$interface->{class}}) { $classstr = " ($classinfo{$interface->{class}}{class_name})"; if (defined $classinfo{$interface->{class}}{$interface->{subclass}}) { $subclassstr = " ($classinfo{$interface->{class}}{$interface->{subclass}}{subclass_name})"; if (defined $classinfo{$interface->{class}}{$interface->{subclass}}{$interface->{protocol}}) { $protocolstr = " ($classinfo{$interface->{class}}{$interface->{subclass}}{$interface->{protocol}})"; } } } $model->set($altiter, PROPERTY_VALUE_COLUMN, $interface->{class} . $classstr); $altiter = $model->append($intiter); $model->set($altiter, PROPERTY_NAME_COLUMN, "Subclass:"); $model->set($altiter, PROPERTY_VALUE_COLUMN, $interface->{subclass} . $subclassstr); $altiter = $model->append($intiter); $model->set($altiter, PROPERTY_NAME_COLUMN, "Protocol:"); $model->set($altiter, PROPERTY_VALUE_COLUMN, $interface->{protocol} . $protocolstr); $altiter = $model->append($intiter); $model->set($altiter, PROPERTY_NAME_COLUMN, "Driver:"); if (($interface->{driver} =~ /\(none\)/) && ($configuration{mark_no_driver})) { $model->set($altiter, PROPERTY_VALUE_COLUMN, join($interface->{driver}, @driver_missing_markup)); } else { $model->set($altiter, PROPERTY_VALUE_COLUMN, $interface->{driver}); } for my $endnum ( 0 .. $interface->{num_endpoints} - 1) { my $altiter = $model->append($intiter); $model->set($altiter, PROPERTY_NAME_COLUMN, "Endpoint $endnum"); my $enditer = $model->append($altiter); my $endpoint = $interface->{endpointlist}[$endnum]; $model->set($enditer, PROPERTY_NAME_COLUMN, "Address:"); $str = sprintf("%02x", $endpoint->{address}); if ($endpoint->{address} & 0x80) { $str .= " (input)"; } else { $str .= " (output)"; } $model->set($enditer, PROPERTY_VALUE_COLUMN, $str); $enditer = $model->append($altiter); $model->set($enditer, PROPERTY_NAME_COLUMN, "Attributes:"); $str = sprintf("%02x", $endpoint->{attributes}); $test = ($endpoint->{attributes} & 0x03); if ($test == 0x03) { $str .= " (interrupt)"; } elsif ($test == 0x02) { $str .= " (bulk)"; } elsif ($test == 0x01) { $str .= " (isochronous ("; $test = (($endpoint->{attributes} & 0x30) >> 4); if ($test == 0x00) { $str .= "data endpoint"; } elsif ($test == 0x01) { $str .= "feedback endpoint"; } elsif ($test == 0x02) { $str .= "implicit feedback data endpoint"; } else { $str .= "reserved"; } $test = (($endpoint->{attributes} & 0x0c) >> 2); if ($test == 0x00) { $str .= ", no synchronization))"; } elsif ($test == 0x01) { $str .= ", asynchronous))"; } elsif ($test == 0x02) { $str .= ", adaptive))"; } else { $str .= ", synchronous))"; } } else { $str .= " (control)"; } $model->set($enditer, PROPERTY_VALUE_COLUMN, $str); $enditer = $model->append($altiter); $model->set($enditer, PROPERTY_NAME_COLUMN, "Max packet size:"); $model->set($enditer, PROPERTY_VALUE_COLUMN, $endpoint->{max_packetsize}); $enditer = $model->append($altiter); $model->set($enditer, PROPERTY_NAME_COLUMN, "Interval:"); $model->set($enditer, PROPERTY_VALUE_COLUMN, $endpoint->{interval}); } } } if ($confnum == $dev->{current_config}) { my $path = $model->get_path($iter); $treeview->expand_row($path, TRUE); } } } sub treeview_row_activated { my ($treeview, $path, $column) = @_; my $iter = $datamodel->get_iter($path); if (defined $iter) { my ($name, $bus_num, $device_num) = $datamodel->get($iter, DEVICE_NAME_COLUMN, BUS_NUM_COLUMN, DEVICE_NUM_COLUMN); if (($bus_num > 0) && ($device_num > 0)) { show_device_info_window(undef, [$bus_num, $device_num]); } } } sub treeview_button_press { my ($treeview, $event) = @_; if (($event->type eq 'button-press') && ($event->button == 3)) { my $selection = $treeview->get_selection; my $path = $treeview->get_path_at_pos($event->x, $event->y); if (defined $path) { $selection->select_path($path); my $iter = $datamodel->get_iter($path); my ($name, $bus_num, $device_num) = $datamodel->get($iter, DEVICE_NAME_COLUMN, BUS_NUM_COLUMN, DEVICE_NUM_COLUMN); if (($bus_num > 0) && ($device_num > 0)) { my $menu = Gtk2::Menu->new; my $menuitem = Gtk2::MenuItem->new_with_label("Show device information"); $menuitem->signal_connect(activate => \&show_device_info_window, [$bus_num, $device_num]); $menu->append($menuitem); $menu->show_all; $menu->popup(undef, undef, undef, undef, $event->button, $event->time); } return TRUE; } } return FALSE; } sub file_watch_function { my ($size, $mtime) = ( stat($configuration{devices_file}) )[7, 9]; if (($size != $lastsize) || ($mtime != $lastmtime)) { if (read_devices_file()) { if (parse_devices_file_contents()) { close_info_windows(); fill_datamodel(); } } ($lastsize, $lastmtime) = ($size, $mtime); } return TRUE; } sub setup_file_watcher { if (($configuration{poll_time} < 0) || ($configuration{poll_time} > $poll_time_max)) { warning_message("Poll time $configuration{poll_time} out of range 0 - $poll_time_max.\n\n" . "Continuing with default value: $default_configuration{poll_time}."); $configuration{poll_time} = $default_configuration{poll_time}; } ($lastsize, $lastmtime) = ( stat($configuration{devices_file}) )[7, 9]; if ($configuration{poll_time} > 0) { $timer_source = Glib::Timeout->add(100 * $configuration{poll_time}, \&file_watch_function, undef, G_PRIORITY_DEFAULT); } } sub show_help { print <init; $tooltips = Gtk2::Tooltips->new; $actions = Gtk2::ActionGroup->new("Actions"); $actions->add_actions(\@mainmenu_entries, undef); $actions->get_action("Refresh")->set_property('sensitive', FALSE); $actions->get_action("SaveAs")->set_property('sensitive', FALSE); my $uimanager = Gtk2::UIManager->new; $uimanager->insert_action_group($actions, 0); $uimanager->add_ui_from_string($ui_info); $mainwindow = Gtk2::Window->new('toplevel'); $mainwindow->set_title("USBInfo"); $mainwindow->add_accel_group($uimanager->get_accel_group); $mainwindow->signal_connect(destroy => \&quit_callback); my $mainvbox = Gtk2::VBox->new(FALSE, 0); $mainwindow->add($mainvbox); $mainvbox->pack_start($uimanager->get_widget("/MenuBar"), FALSE, FALSE, 0); my $scrwin = Gtk2::ScrolledWindow->new; $scrwin->set_shadow_type('etched-in'); $scrwin->set_policy('automatic', 'automatic'); $mainvbox->add($scrwin); $datamodel = Gtk2::TreeStore->new(qw(Glib::String Glib::Int Glib::Int)); my $treeview = Gtk2::TreeView->new($datamodel); $datamodel->{treeview} = $treeview; my $renderer = Gtk2::CellRendererText->new; $treeview->insert_column_with_attributes(-1, "USB tree overview", $renderer, markup => DEVICE_NAME_COLUMN); $treeview->signal_connect(row_activated => \&treeview_row_activated); $treeview->signal_connect(button_press_event => \&treeview_button_press); $scrwin->add($treeview); $mainwindow->set_default_size(600, 450); $mainwindow->show_all; } read_configuration(); GetOptions( 'devices_file=s' => \$configuration{devices_file}, 'poll_time=i' => \$configuration{poll_time}, 'free_ports' => \$configuration{show_free_ports}, 'no_driver' => \$configuration{mark_no_driver}, 'use_usb_ids' => \$configuration{use_usb_ids}, 'ids_file=s' => \$configuration{usb_ids_file}, 'help' => \$needhelp) or show_help(); if ($needhelp) { show_help(); } if ($configuration{use_usb_ids}) { read_usb_ids_file(); } if (read_devices_file()) { if (parse_devices_file_contents()) { if ($usegraphics) { fill_datamodel(); setup_file_watcher(); $actions->get_action("Refresh")->set_property('sensitive', TRUE); $actions->get_action("SaveAs")->set_property('sensitive', TRUE); } else { text_output(); exit(0); } } } if ($usegraphics) { Gtk2->main; } }