#!@PERL@ -w # 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 2 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, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # Filter this script to pod2man to get a man page: # pod2man -c "Fvwm Module" FvwmGtkDebug | nroff -man | less -e use 5.003; use strict; BEGIN { use vars qw($prefix $datarootdir $datadir); $prefix = "@prefix@"; $datarootdir = "@datarootdir@"; $datadir = "@datadir@"; } use lib "@FVWM_PERLLIBDIR@"; use FVWM::Module::Gtk; use FVWM::EventNames; use Gtk; init Gtk; my $defaultMask = MAX_MSG_MASK & ~(M_FOCUS_CHANGE | M_CONFIGURE_WINDOW | M_VISIBLE_NAME | M_ICON_NAME); my $defaultXMask = MAX_XMSG_MASK & ~(MX_ENTER_WINDOW | MX_LEAVE_WINDOW | MX_VISIBLE_ICON_NAME); $defaultXMask &= ~M_EXTENDED_MSG; my $mask = $defaultMask; my $xmask = $defaultXMask; my $debug = 0; my $options = { 'm|mask=i' => \$mask, 'x|xmask=i' => \$xmask, 'd|debug=i' => \$debug, }; my $module = new FVWM::Module::Gtk( Name => "FvwmGtkDebug", EnableOptions => $options, Debug => \$debug, ); $mask = MAX_MSG_MASK if $mask == -1; $xmask = MAX_XMSG_MASK if $xmask == -1; my $newMask = $mask; my $newXMask = $xmask; # ---------------------------------------------------------------------------- # functions my $monitoring = 0; my $storedEventDatas = []; my $currentEventNum = -1; my $eventListSizeChanged = 0; my $stickToLastEvent = 1; sub eventArgTypeToName ($) { my $type = shift; return $type == FVWM::EventNames::number? "number": $type == FVWM::EventNames::bool? "boolean": $type == FVWM::EventNames::window? "window": $type == FVWM::EventNames::pixel? "color": $type == FVWM::EventNames::string? "string": $type == FVWM::EventNames::looped? "looped": $type == FVWM::EventNames::wflags? "wflags": "unknown"; } sub storeEvent ($$) { my ($module, $event) = @_; my @argNames = @{$event->argNames}; my @argTypes = @{$event->argTypes}; my @argValues = @{$event->argValues}; # print STDERR $event->name, "\n"; my $eventData = { type => $event->type, name => $event->name, time => time(), args => [], }; while (@argNames) { my $name = shift @argNames; my $type = shift @argTypes; my $value = shift @argValues; my $text; if ($type == FVWM::EventNames::number) { $text = $value; $text = "*undefined*" unless defined $value; } elsif ($type == FVWM::EventNames::bool) { $text = $value? "True": "False"; } elsif ($type == FVWM::EventNames::window) { $text = sprintf("0x%07lx", $value); } elsif ($type == FVWM::EventNames::pixel) { $text = "rgb:" . join('/', sprintf("%06lx", $value) =~ /(..)(..)(..)/); } elsif ($type == FVWM::EventNames::string) { $text = qq("$value"); } elsif ($type == FVWM::EventNames::looped) { my $loopArgNames = $event->loopArgNames; my $loopArgTypes = $event->loopArgTypes; my $j = 0; while ($j < @$value) { my $k = 0; foreach (@$loopArgNames) { my $i = int($j / @$loopArgNames) + 1; push @argNames, "[$i] $_"; push @argTypes, $loopArgTypes->[$k]; push @argValues, $value->[$j]; $j++; $k++; } } $text = sprintf("(%d)", @$value / @$loopArgNames); } elsif ($type == FVWM::EventNames::wflags) { my @words = unpack("l*", $value); my $label = join(" ", map { sprintf("%08x", $_) } @words); $text = { label => $label, value => \@words }; } else { $text = qq([unsupported arg type $type] "$value"); } push @{$eventData->{args}}, { name => $name, type => $type, text => $text, }; } push @$storedEventDatas, $eventData; $eventListSizeChanged = 1; &updateCurrentEventWidgets(); } sub sendModuleEventMask () { if ($monitoring) { $module->mask($mask); $module->xmask($xmask); } else { $module->mask(0); $module->xmask(0); } } my $updateEventMaskButton; my $revertEventMaskButton; sub updateEventMaskChangeButtons () { my $isChanged = $mask != $newMask || $xmask != $newXMask; $updateEventMaskButton->set_sensitive($isChanged); $revertEventMaskButton->set_sensitive($isChanged); } # ---------------------------------------------------------------------------- # creating gui my $tmp; # there is a Gtk::Frame bug regarding set_border_width, so use tmp box my $tooltips = Gtk::Tooltips->new; my $window = new Gtk::Window; $window->set_title($module->name); $window->set_border_width(4); my $notebook = new Gtk::Notebook(); $notebook->set_homogeneous_tabs(1); $window->add($notebook); # ---- setup page ---- my $setupPage = new Gtk::VBox(0, 0); $notebook->append_page($setupPage, new Gtk::Label(" Setup ")); my $eventMaskBox = new Gtk::HBox(0, 0); $setupPage->pack_start($eventMaskBox, 1, 1, 10); my $eventMaskScroll = new Gtk::ScrolledWindow("", ""); $eventMaskScroll->set_policy("automatic", "always"); my $eventMaskScrollFrame = new Gtk::Frame(" Event mask "); $tmp = new Gtk::VBox(0, 0); $tmp->add($eventMaskScroll); $tmp->set_border_width(5); $eventMaskScrollFrame->add($tmp); $eventMaskBox->pack_start($eventMaskScrollFrame, 1, 1, 10); my $eventTypeBox = new Gtk::VButtonBox(); $eventTypeBox->set_spacing(0); $eventTypeBox->set_child_size(0, 0); my $eventTypeCheckButtons = {}; my $type; foreach $type (@{allEventTypes()}) { my $checkButton = new Gtk::CheckButton(eventName($type)); $checkButton->set_border_width(0); $eventTypeBox->pack_start($checkButton, 0, 0, 0); $eventTypeCheckButtons->{$type} = $checkButton; $checkButton->signal_connect("clicked", sub { ($type & M_EXTENDED_MSG? $newXMask: $newMask) ^= ($type & ~M_EXTENDED_MSG); updateEventMaskChangeButtons(); }); } $eventMaskScroll->add_with_viewport($eventTypeBox); sub updateCheckButtonsFromNewMask () { my $curMask = $newMask; my $curXMask = $newXMask; my ($type, $checkButton); while (($type, $checkButton) = each %$eventTypeCheckButtons) { $checkButton->set_active(($type & M_EXTENDED_MSG? $newXMask: $newMask) & $type & ~M_EXTENDED_MSG); } # unfortunately set_active triggers "clicked" signal, so correct this $newMask = $curMask; $newXMask = $curXMask; updateEventMaskChangeButtons(); } my $eventMaskButtonBox = new Gtk::VButtonBox(); $eventMaskButtonBox->set_spacing_default(10); $eventMaskButtonBox->set_child_size(190, 0); $eventMaskButtonBox->set_child_ipadding(0, 2); $eventMaskButtonBox->set_layout('start'); $eventMaskBox->pack_start($eventMaskButtonBox, 0, 0, 10); my $selectAllEventsButton = new Gtk::Button(" Select all events "); $eventMaskButtonBox->pack_start($selectAllEventsButton, 1, 1, 6); $selectAllEventsButton->signal_connect("clicked", sub { $newMask = MAX_MSG_MASK; $newXMask = MAX_XMSG_MASK; updateCheckButtonsFromNewMask(); }); my $unselectAllEventsButton = new Gtk::Button(" Unselect all events "); $eventMaskButtonBox->pack_start($unselectAllEventsButton, 1, 1, 6); $unselectAllEventsButton->signal_connect("clicked", sub { $newMask = 0; $newXMask = 0; updateCheckButtonsFromNewMask(); }); my $setDefaultEventsButton = new Gtk::Button(" Select default events "); $eventMaskButtonBox->pack_start($setDefaultEventsButton, 1, 1, 6); $setDefaultEventsButton->signal_connect("clicked", sub { $newMask = $defaultMask; $newXMask = $defaultXMask; updateCheckButtonsFromNewMask(); }); $revertEventMaskButton = new Gtk::Button(" Restore current events "); $eventMaskButtonBox->pack_start($revertEventMaskButton, 1, 1, 6); $revertEventMaskButton->signal_connect("clicked", sub { $newMask = $mask; $newXMask = $xmask; updateCheckButtonsFromNewMask(); }); my $setupButtonBox = new Gtk::HButtonBox(); $setupButtonBox->set_border_width(10); $setupButtonBox->set_child_size(190, 0); $setupButtonBox->set_child_ipadding(0, 2); $setupButtonBox->set_layout('edge'); $setupPage->pack_end($setupButtonBox, 0, 0, 0); $updateEventMaskButton = new Gtk::Button(" Update event mask "); $setupButtonBox->pack_start($updateEventMaskButton, 1, 1, 40); $updateEventMaskButton->signal_connect("clicked", sub { $mask = $newMask; $xmask = $newXMask; sendModuleEventMask() if $monitoring; updateEventMaskChangeButtons(); }); my $startMonitoringButton = new Gtk::Button(" Start watching events "); $setupButtonBox->pack_start($startMonitoringButton, 1, 1, 40); $startMonitoringButton->signal_connect("clicked", \&switchMonitoring); # ---- event page ---- my $eventPage = new Gtk::VBox(0, 0); $eventPage->set_border_width(10); $notebook->append_page($eventPage, new Gtk::Label(" Stored Events ")); my $eventNameLine = new Gtk::HBox(0, 0); $eventPage->pack_start($eventNameLine, 0, 0, 0); my $eventNumBox = new Gtk::HBox(0, 0); $eventNumBox->set_border_width(5); my $eventNumFrame = new Gtk::Frame(" Event num "); $eventNumFrame->add($eventNumBox); $eventNameLine->pack_start($eventNumFrame, 0, 0, 0); my $eventNumAdj = new Gtk::Adjustment(0, 0, 0, 0, 0, 0); my $eventNum = new Gtk::SpinButton($eventNumAdj, 0, 1); $eventNum->set_usize(57, 0); $eventNum->signal_connect("changed", \&updateCurrentEventNumber); $eventNumBox->pack_start($eventNum, 0, 0, 0); my $eventTotalNum = new Gtk::Entry(); $eventTotalNum->set_editable(0); $eventTotalNum->set_usize(42, 0); $eventNumBox->pack_start($eventTotalNum, 0, 0, 0); my $eventName = new Gtk::Entry(); $eventName->set_editable(0); $eventName->set_usize(154, 0); my $eventNameFrame = new Gtk::Frame(" Event type "); $tmp = new Gtk::VBox(0, 0); $tmp->add($eventName); $tmp->set_border_width(5); $eventNameFrame->add($tmp); $eventNameLine->pack_start($eventNameFrame, 0, 0, 10); my $eventTime = new Gtk::Entry(); $eventTime->set_usize(46, 0); $eventTime->set_editable(0); my $eventTimeFrame = new Gtk::Frame(" Time "); $tmp = new Gtk::VBox(0, 0); $tmp->add($eventTime); $tmp->set_border_width(5); $eventTimeFrame->add($tmp); $eventNameLine->pack_start($eventTimeFrame, 0, 0, 0); my $eventRunOptsButtonBox = new Gtk::VButtonBox(); $eventRunOptsButtonBox->set_spacing(0); $eventRunOptsButtonBox->set_child_size(0, 0); $eventNameLine->pack_end($eventRunOptsButtonBox, 0, 0, 0); my $activeCheckButton = new Gtk::CheckButton("Active"); $activeCheckButton->signal_connect("clicked", \&switchMonitoring); $eventRunOptsButtonBox->pack_start($activeCheckButton, 0, 0, 0); my $stickCheckButton = new Gtk::CheckButton("Stick to last"); $stickCheckButton->set_active($stickToLastEvent); $stickCheckButton->signal_connect("clicked", sub { $stickToLastEvent ^= 1; &updateCurrentEventWidgets() if $stickToLastEvent && $currentEventNum != @$storedEventDatas; }); $eventRunOptsButtonBox->pack_start($stickCheckButton, 0, 0, 0); # ---- next event page row ---- my $eventArgsList = new_with_titles Gtk::CList("Name", "Value"); $eventArgsList->column_titles_passive(); $eventArgsList->set_shadow_type("none"); $eventArgsList->set_selection_mode("extended"); $eventArgsList->set_column_width(0, 140); my $lastRowSelected = -1; $eventArgsList->signal_connect("select_row", sub { my ($widget, $row) = @_; my $currRowSelected = $lastRowSelected; $lastRowSelected = $row; return unless $currRowSelected == $row; my $data = $widget->get_row_data($row); $stickCheckButton->set_active(0); my $text = $data->{text}; if (ref($text) eq 'HASH') { $text = join("", map { sprintf("\n%032b", $_) } @{$text->{value}} ); } $module->showMessage( "$data->{name} (" . eventArgTypeToName($data->{type}) . "): $text", $eventName->get_text() . " event argument" ); }); $eventArgsList->signal_connect("unselect_row", sub { $lastRowSelected = -1; }); my $eventArgsListScroll = new Gtk::ScrolledWindow("", ""); $eventArgsListScroll->set_policy("automatic", "automatic"); $eventArgsListScroll->add_with_viewport($eventArgsList); my $eventArgsListScrollFrame = new Gtk::Frame(" Event arguments "); $tmp = new Gtk::VBox(0, 0); $tmp->add($eventArgsListScroll); $tmp->set_border_width(5); $eventArgsListScrollFrame->add($tmp); $eventPage->pack_start($eventArgsListScrollFrame, 1, 1, 10); my $eventListButtonBox = new Gtk::HButtonBox(); $eventListButtonBox->set_child_size(90, 0); $eventListButtonBox->set_child_ipadding(0, 2); $eventListButtonBox->set_layout('edge'); $eventPage->pack_end($eventListButtonBox, 0, 0, 0); my $currentEventPossiblyDirty = 0; sub filterStoredEvents ($) { my $func = shift; my $initialNum = @$storedEventDatas; my $count = 0; my $index = 0; for ($count = 1; $count <= $initialNum; $count++) { if (&$func($count, $storedEventDatas->[$index]->{type})) { $index++; } else { splice(@$storedEventDatas, $index, 1); } } if ($initialNum != @$storedEventDatas) { $eventListSizeChanged = 1; $currentEventPossiblyDirty = 1; updateCurrentEventWidgets(); } } my $clearThisOneButton = new Gtk::Button(" Clear one "); $eventListButtonBox->pack_start($clearThisOneButton, 1, 1, 6); $clearThisOneButton->signal_connect("clicked", sub { filterStoredEvents(sub { $_[0] != $currentEventNum }); }); my $clearThisTypeButton = new Gtk::Button(" Clear type "); $eventListButtonBox->pack_start($clearThisTypeButton, 1, 1, 6); $clearThisTypeButton->signal_connect("clicked", sub { my $currentType = $storedEventDatas->[$currentEventNum - 1]->{type}; filterStoredEvents(sub { $_[1] != $currentType }); }); my $clearAllButton = new Gtk::Button(" Clear all "); $eventListButtonBox->pack_start($clearAllButton, 1, 1, 6); $clearAllButton->signal_connect("clicked", sub { filterStoredEvents(sub { 0 }); }); my $leaveThisTypeButton = new Gtk::Button(" Leave type "); $eventListButtonBox->pack_start($leaveThisTypeButton, 1, 1, 6); $leaveThisTypeButton->signal_connect("clicked", sub { my $currentType = $storedEventDatas->[$currentEventNum - 1]->{type}; filterStoredEvents(sub { $_[1] == $currentType }); }); my $leaveThisOneButton = new Gtk::Button(" Leave one "); $eventListButtonBox->pack_start($leaveThisOneButton, 1, 1, 6); $leaveThisOneButton->signal_connect("clicked", sub { filterStoredEvents(sub { $_[0] == $currentEventNum }); }); sub updateCurrentEventWidgets (;$) { # update event number my $maxNum = @$storedEventDatas; my $num = shift || ($stickToLastEvent? $maxNum: $currentEventNum); $num = 1 if $num <= 0; $num = $maxNum if $num > $maxNum; my $currentEventNumChanged = $currentEventNum != $num; $currentEventNum = $num; my $eventNumAdj = new Gtk::Adjustment($num, 1, $maxNum, 1, 10, 0); # this line does not work with Gtk 0.6123, no idea how to replace it $eventNum->configure($eventNumAdj, 0.5, 0) if $Gtk::VERSION > 0.62; $eventTotalNum->set_text($maxNum); return unless $currentEventNumChanged || $eventListSizeChanged || $currentEventPossiblyDirty; $eventListSizeChanged = 0; $lastRowSelected = -1; # update event name $eventName->set_text($num? $storedEventDatas->[$num - 1]->{name}: ""); # update event args if ($currentEventNumChanged || $currentEventPossiblyDirty) { $eventArgsList->freeze(); $eventArgsList->clear(); my $row = 0; foreach ($num? @{$storedEventDatas->[$num - 1]->{args}}: ()) { my $data = $_; my $text = $data->{text}; $text = $text->{label} if ref($text) eq 'HASH'; $eventArgsList->append($data->{name}, $text); $eventArgsList->set_row_data($row++, $data); } $eventArgsList->thaw(); $currentEventPossiblyDirty = 0; } # update event time my $timeString1 = ""; my $timeString2 = ""; if ($num) { my $time = $storedEventDatas->[$num - 1]->{time}; my ($sec, $min, $hour, $day, $mon, $year) = localtime($time); $mon++; $year += 1900 if $year < 1900; $timeString1 = sprintf("%02d:%02d", $hour, $min); $timeString2 = sprintf("%s-%02d-%02d %02d:%02d:%02d", $year, $mon, $day, $hour, $min, $sec); } $eventTime->set_text($timeString1); $tooltips->set_tip($eventTime, $timeString2); # update event buttons my $currentType = $num? $storedEventDatas->[$num - 1]->{type}: 0; my $hasOtherTypes = 0; foreach ($num = 1; $num <= $maxNum; $num++) { if ($currentType != $storedEventDatas->[$num - 1]->{type}) { $hasOtherTypes = 1; last; } } $clearThisOneButton->set_sensitive($maxNum > 0); $clearThisTypeButton->set_sensitive($maxNum > 0); $clearAllButton->set_sensitive($maxNum > 0); $leaveThisTypeButton->set_sensitive($hasOtherTypes); $leaveThisOneButton->set_sensitive($maxNum > 1); } sub updateCurrentEventNumber () { return if $eventNum->value == $currentEventNum; updateCurrentEventWidgets($eventNum->value); } my $inSwitchMonitoring = 0; sub switchMonitoring () { return if $inSwitchMonitoring; $inSwitchMonitoring = 1; $monitoring ^= 1; sendModuleEventMask(); $startMonitoringButton->child->set($monitoring? " Stop watching events ": " Start watching events "); $activeCheckButton->set_active($monitoring); $inSwitchMonitoring = 0; } # ---- tools page ---- my $toolsPage = new Gtk::VBox(0, 0); $toolsPage->set_border_width(10); $notebook->append_page($toolsPage, new Gtk::Label(" Tools ")); my $requestButtonBox = new Gtk::HButtonBox(); $requestButtonBox->set_child_size(190, 0); $requestButtonBox->set_child_ipadding(0, 2); $requestButtonBox->set_layout('edge'); my $requestButtonBoxFrame = new Gtk::Frame(" Request module info "); $tmp = new Gtk::VBox(0, 0); $tmp->add($requestButtonBox); $tmp->set_border_width(5); $requestButtonBoxFrame->add($tmp); $toolsPage->pack_start($requestButtonBoxFrame, 0, 0, 0); my $sendConfigInfoButton = new Gtk::Button(" Send_ConfigInfo "); $requestButtonBox->pack_end($sendConfigInfoButton, 1, 1, 6); $sendConfigInfoButton->signal_connect("clicked", sub { $module->send("Send_ConfigInfo"); }); my $sendWindowListButton = new Gtk::Button(" Send_WindowList "); $requestButtonBox->pack_end($sendWindowListButton, 1, 1, 6); $sendWindowListButton->signal_connect("clicked", sub { $module->send("Send_WindowList"); }); my $commandToSend = new Gtk::Entry(); $commandToSend->signal_connect("activate", sub { $module->send($commandToSend->get_text); $commandToSend->set_text(""); }); my $commandToSendFrame = new Gtk::Frame(" Command console "); $tmp = new Gtk::VBox(0, 0); $tmp->add($commandToSend); $tmp->set_border_width(5); $commandToSendFrame->add($tmp); $toolsPage->pack_start($commandToSendFrame, 0, 0, 10); my $quitButtonBox = new Gtk::HButtonBox(); $quitButtonBox->set_child_size(190, 0); $quitButtonBox->set_child_ipadding(0, 2); $quitButtonBox->set_layout('edge'); $toolsPage->pack_end($quitButtonBox, 0, 0, 0); my $helpButton = new Gtk::Button(" Help "); $quitButtonBox->pack_end($helpButton, 1, 1, 6); $helpButton->signal_connect("clicked", sub { $module->showMessage(<pack_end($quitButton, 1, 1, 6); $quitButton->signal_connect("clicked", sub { Gtk->main_quit; }); # ---- last GUI preparations ---- updateCheckButtonsFromNewMask(); updateCurrentEventWidgets(); $window->signal_connect("destroy" => \&Gtk::main_quit); $window->signal_connect("delete_event" => \&Gtk::false); $window->set_usize(520, 600); $window->show_all; # ---------------------------------------------------------------------------- # main sendModuleEventMask(); $module->addHandler(MAX_MSG_MASK, \&storeEvent); $module->addHandler(MAX_XMSG_MASK | M_EXTENDED_MSG, \&storeEvent); sub emulateSomeEvents () { $module->emulateEvent(M_NEW_DESK, [ $currentEventNum ]); $module->emulateEvent(M_MAP, [ 0x123, 0x456, 789 ]); $module->emulateEvent(M_NEW_PAGE, [ 0, 0, 2, 800, 600, 3, 3 ]); } if ($module->isDummy) { $module->showMessage("This module is executed in the dummy mode.\n\n" . "Every 20 seconds fake events are generated.\n\n" . "There are known alarm problems with perl-5.8.0 + Gtk-Perl."); emulateSomeEvents(); emulateSomeEvents(); my $scheduler = $module->track('Scheduler'); $scheduler->schedule(20, sub { emulateSomeEvents() if $monitoring; $scheduler->reschedule; }); } $module->eventLoop; __END__ # ---------------------------------------------------------------------------- # man page =head1 NAME FvwmGtkDebug - graphical interactive fvwm module debugger =head1 SYNOPSIS FvwmGtkDebug should be spawned by fvwm(1). To run this module, place this command somewhere in the configuration: Module FvwmGtkDebug To stop this module, just close the GUI window, the usual KillModule works too. You may also run this application in the dummy mode from the command line, but this only shows the GUI with dummy event data and not the real data. =head1 DESCRIPTION This module monitors all fvwm event information and shows it nicely in the interactive gtk+ application. Good for debugging purposes. =head1 INVOCATION There are several command line switches: B [ B<--mask> I ] [ B<--xmask> I ] [ B<--debug> I ] Long switches may be abbreviated to shorter switches. B<-m>|B<--mask> I - set the initial module mask, 31 bit integer. This mask may be changed interactively at any time. By default almost all events are monitored (except for some flood events like I or I. The special value of I<-1> sets the maximal mask. B<-x>|B<--xmask> I - set the initial module extended mask, 31 bit integer. This mask may be changed interactively at any time. By default almost all events are monitored (except for some flood events like I or I. The special value of I<-1> sets the maximal extended mask. B<-d>|B<--debug> I - use the Perl library debugging mechanism. The useful Is are 2 to 4. =head1 SEE ALSO See also L. =head1 AUTHOR Mikhael Goikhman . =cut