Subject: PATCH: Window List Tracker & FvwmWindowMenu

From: Scott Smedley <[email-masked]>
Date: Wed, 2 Jun 2004 21:34:46 +1000

Hi Mikhael, :)

Find attached a patch that includes the WindowList tracker &
updated FvwmWindowMenu module which actually utilises the new
tracker.

Comments most welcome.

SCoTT. :)
Index: modules/ChangeLog
===================================================================
RCS file: /home/cvs/fvwm/fvwm/modules/ChangeLog,v
retrieving revision 1.1110
diff -u -r1.1110 ChangeLog
--- modules/ChangeLog	1 Jun 2004 11:46:54 -0000	1.1110
+++ modules/ChangeLog	2 Jun 2004 11:28:48 -0000
@@ -1,3 +1,10 @@
+2004-06-01  Scott Smedley  <[email-masked]>
+
+	* FvwmWindowMenu/FvwmWindowMenu.in:
+	* FvwmWindowMenu/FvwmWindowMenu.1:
+	Rewrote module to use new WindowList tracker.
+	Updated man page.
+
 2004-06-01  Dominik Vogt  <[email-masked]>
 
 	* FvwmConsole/FvwmConsoleC.c (main):
Index: modules/FvwmWindowMenu/FvwmWindowMenu.1
===================================================================
RCS file: /home/cvs/fvwm/fvwm/modules/FvwmWindowMenu/FvwmWindowMenu.1,v
retrieving revision 1.2
diff -u -r1.2 FvwmWindowMenu.1
--- modules/FvwmWindowMenu/FvwmWindowMenu.1	12 Nov 2003 21:27:41 -0000	1.2
+++ modules/FvwmWindowMenu/FvwmWindowMenu.1	2 Jun 2004 11:28:50 -0000
@@ -128,8 +128,8 @@
 .rm #[ #] #H #V #F C
 .\" ========================================================================
 .\"
-.IX Title "FVWMWINDOWMENU 1"
-.TH FVWMWINDOWMENU 1 "2003-11-12" "perl v5.8.1" "FVWM Modules"
+.IX Title "FVWMWINDOWMENU.IN 1"
+.TH FVWMWINDOWMENU.IN 1 "2004-06-02" "perl v5.8.1" "FVWM Module"
 .SH "NAME"
 FvwmWindowMenu \- open configurable fvwm menu listing current windows
 .SH "SYNOPSIS"
@@ -149,13 +149,14 @@
 module does not draw its own window, but instead creates an
 \&\fIfvwm\fR menu and asks \fIfvwm\fR to pop it up.
 .PP
-By defining a set of regular expressions using Show, windows may
-be sorted into sections based on the regexp matching their
-name, icon name, class or resource.
+By defining a set of regular expressions, windows may
+be sorted into sections based on a regexp matching the window
+name, class or resource and included in the menu.
 .PP
-Similarly, matches in DontShow will be excluded from the list.
+Similarly, another set of regular expressions can be used to exclude
+items from the menu.
 .PP
-Any windows not matching an instance of Show or DontShow will
+Any windows not matching an instance of the include or exclude list will
 be placed in the last section of the menu.
 .SH "USAGE"
 .IX Header "USAGE"
@@ -186,41 +187,44 @@
 \&\fBMenu\fR and \fBPopup\fR. If the module was started with \*(L"\-g\*(R" switch, it
 additionally supports \fBPostBar\fR (not implemented yet).
 .PP
-Set module options for windows to show or not show. The syntax is:
+Set module options for windows to include (Show) or exclude (DontShow).
+The syntax is:
 .PP
-.Vb 2
-\&    *FvwmWindowMenu: Show type = pattern
-\&    *FvwmWindowMenu: DontShow type = pattern
+.Vb 6
+\&    *FvwmWindowMenu: ShowName pattern
+\&    *FvwmWindowMenu: ShowClass pattern
+\&    *FvwmWindowMenu: ShowResource pattern
+\&    *FvwmWindowMenu: DontShowName pattern
+\&    *FvwmWindowMenu: DontShowClass pattern
+\&    *FvwmWindowMenu: DontShowResource pattern
 .Ve
 .PP
-where type is one of \fIname\fR, \fIicon\fR, \fIclass\fR or \fIresource\fR. Pattern is
-a perl regular expression that will be evaluated in m// context.
+Pattern is a perl regular expression that will be evaluated in m// context.
 See \fIperlre\fR\|(1).
 .PP
 For example:
 .PP
 .Vb 2
-\&    *FvwmWindowMenu: Show resource = Galeon|Navigator|mozilla-bin
-\&    *FvwmWindowMenu: Show name = ^emacs
+\&    *FvwmWindowMenu: ShowResource ^gvim
+\&    *FvwmWindowMenu: ShowName Galeon|Navigator|mozilla-bin|Firefox
 .Ve
 .PP
-will define two sections containing respectively browsers, and emacs.
-Number of sections is unlimited. The strings are perl regular
-expressions that will be evaluated in m// context. See \fIperlre\fR\|(1).
+will define two sections containing respectively browsers, and GVim. A third
+section will contain all other windows.
 .PP
 Similarly:
 .PP
 .Vb 2
-\&    *FvwmWindowMenu: DontShow name = ^Fvwm
-\&    *FvwmWindowMenu: DontShow class = Gkrellm
+\&    *FvwmWindowMenu: DontShowName ^Fvwm
+\&    *FvwmWindowMenu: DontShowClass Gkrellm
 .Ve
 .PP
 will cause the menu to ignore windows with name beginning with Fvwm
 or class gkrellm.
 .PP
 Other options:
-.IP "*FvwmWindowMenu: \fIOnlyIconic\fR {on|off}" 4
-.IX Item "*FvwmWindowMenu: OnlyIconic {on|off}"
+.IP "*FvwmWindowMenu: \fIOnlyIconified\fR {on|off}" 4
+.IX Item "*FvwmWindowMenu: OnlyIconified {on|off}"
 show only iconified windows
 .IP "*FvwmWindowMenu: \fIAllDesks\fR {on|off}" 4
 .IX Item "*FvwmWindowMenu: AllDesks {on|off}"
@@ -228,8 +232,8 @@
 .IP "*FvwmWindowMenu: \fIAllPages\fR {on|off}" 4
 .IX Item "*FvwmWindowMenu: AllPages {on|off}"
 show windows from all pages
-.IP "*FvwmWindowMenu: \fIMaxlen\fR 32" 4
-.IX Item "*FvwmWindowMenu: Maxlen 32"
+.IP "*FvwmWindowMenu: \fIMaxLen\fR 32" 4
+.IX Item "*FvwmWindowMenu: MaxLen 32"
 max length in chars of entry
 .IP "*FvwmWindowMenu: \fIMenuName\fR MyMenu" 4
 .IX Item "*FvwmWindowMenu: MenuName MyMenu"
@@ -254,7 +258,11 @@
 .ie n .IP "%x, %y" 4
 .el .IP "%x, \f(CW%y\fR" 4
 .IX Item "%x, %y"
-the window x or y coordinates on current page
+the window x or y coordinates w.r.t. the page the window is on.
+.ie n .IP "%X, %Y" 4
+.el .IP "%X, \f(CW%Y\fR" 4
+.IX Item "%X, %Y"
+the window x or y coordinates w.r.t. the desk the window is on.
 .IP "%d" 4
 .IX Item "%d"
 the window desk number
@@ -293,6 +301,12 @@
 .Vb 1
 \&    Key Super_R A A SendToModule FvwmWindowMenu Post Root c c WarpTitle
 .Ve
-.SH "AUTHOR"
-.IX Header "AUTHOR"
-Ric Lister <http://cns.georgetown.edu/~ric/>
+.SH "AUTHORS"
+.IX Header "AUTHORS"
+.RE
+.IP "Ric Lister <http://cns.georgetown.edu/~ric/>"
+.IX Item "Ric Lister <http://cns.georgetown.edu/~ric/>"
+.RE
+.PD 0
+.IP "Scott Smedley"
+.IX Item "Scott Smedley"
Index: modules/FvwmWindowMenu/FvwmWindowMenu.in
===================================================================
RCS file: /home/cvs/fvwm/fvwm/modules/FvwmWindowMenu/FvwmWindowMenu.in,v
retrieving revision 1.3
diff -u -r1.3 FvwmWindowMenu.in
--- modules/FvwmWindowMenu/FvwmWindowMenu.in	12 Nov 2003 21:27:41 -0000	1.3
+++ modules/FvwmWindowMenu/FvwmWindowMenu.in	2 Jun 2004 11:28:51 -0000
@@ -28,9 +28,7 @@
 
 use lib "@FVWM_PERLLIBDIR@";
 use FVWM::Module;
-use General::FileSystem "-quiet";
 use General::Parse;
-use Getopt::Long;
 
 my $moduleType = "";
 my $moduleClass = "FVWM::Module";
@@ -47,293 +45,178 @@
 	}
 }
 
-# default user options
-my $opt = {
-	onlyiconic   => 0,      # only list iconified windows
-	alldesks     => 0,      # list windows on all desks
-	allpages     => 0,      # list windows on all pages
-	maxlen       => 32,     # max chars for window name in menu
-	menuname     => "MenuFvwmWindowMenu",  # name of fvwm menu to create
-	menustyle    => undef,  # name of fvwm menu style to use
-	itemformat   => "%m%n%t%t(+%x+%y) - Desk %d",  # format of menu items
-	function     => "WindowListFunc",
-};
-
-# get size of your screen
-my $screen;
-
-# I would like a better way of getting screen dimensions!
-open(XWININFO, "xwininfo -root|") || die "can't run xwininfo";
-while (<XWININFO>) {
-	$screen->{w} = $1 if /Width:\s*(\d+)/;
-	$screen->{h} = $1 if /Height:\s*(\d+)/;
-}
-close(XWININFO);
-
-
-# some global vars
-my %window;
-my $desk = 0;
-my $page_nx = 0;
-my $page_ny = 0;
-
-# show = window matches to display in order, each entry defines a section
-# dontshow = window matches to ignore, none of these will show up in the menu
-my (@show, @dontshow);
-
 # init the module
 # set Debug = 0 for no messages at all
-# set Debug = 1 to see my messages about window decisions
+# set Debug = 1 to see messages about window decisions
 # set Debug = 2 to see also perllib messages about communication
 my $module = new $moduleClass(
 	Name => "FvwmWindowMenu",
-	Mask => M_WINDOW_NAME | M_STRING |
-		M_NEW_PAGE | M_NEW_DESK |
-		M_ADD_WINDOW | M_CONFIGURE_WINDOW |
-		M_RES_NAME | M_RES_CLASS | M_ICON_NAME |
-		M_DESTROY_WINDOW | M_END_WINDOWLIST |
-		M_ICONIFY | M_DEICONIFY | M_MINI_ICON |
-		M_CONFIG_INFO | M_SENDCONFIG,
+	Mask => M_STRING,
 	Debug => 0,
 );
 
 $module->debug("starting " . $module->name);
 
-# process module options
-$module->addHandler(M_CONFIG_INFO, sub {
-	my ($module, $event) = @_;
-
-	my $modname = $module->name;
-	return unless $event->_text =~ /^\*$modname(.*)$/;
-	$_ = $1;
-
-	# get show array
-	if ( /^show\s+(\w+)\s*=\s*(.*)$/i ) {
-		(my $type = $1) =~ tr/A-Z/a-z/;
-		push(@show, { type => $type, pattern => $2 });
-	}
-
-	# get dontshow array
-	elsif ( /^dontshow\s+(\w+)\s*=\s*(.*)$/i ) {
-		(my $type = $1) =~ tr/A-Z/a-z/;
-		push(@dontshow, { type => $type, pattern => $2 });
-	}
-
-	# match format string in quotes
-	elsif ( /^itemformat\s+([\"\'])(.*)\1/i ) {
-		$opt->{itemformat} = $2;
-	}
-
-	# get all other options, arg of 'off' sets opt to 0
-	elsif (  /^(\w+)\s*(\w+)/i ) {
-		(my $arg1 = $1) =~ tr/A-Z/a-z/;
-		$opt->{$arg1} = ($2 =~ /^off$/i) ? 0 : $2;
-	}
-
-	# should probably do more error checking
-});
+my $configTracker = $module->track('ModuleConfig',
+	DefaultConfig => {
+		OnlyIconified => 'off',
+		AllDesks => 'off',
+		AllPages => 'off',
+		MaxLen => 32,
+		MenuName => 'MyMenu',
+		MenuStyle => '',
+		Debug => 0,
+		Function => 'MyWindowListFunc',
+		ItemFormat => '%m%n%t%t(+%x+%y) - Desk %d',
+		ShowName => '',
+		ShowClass => '',
+		ShowResource => '',
+		DontShowName => '',
+		DontShowClass => '',
+		DontShowResource => '',
+	});
 
+my $pConfig = $configTracker->data();
+my $winTracker = $module->track("WindowList", "!stack icons names winfo");
 
-# handler to get page and desk info
-$module->addHandler(M_NEW_PAGE, sub {
-	my ($module, $event) = @_;
-
-	$desk = $event->_desk;
-
-	my $width  = $event->_vp_width || 1;
-	my $height = $event->_vp_height || 1;
-
-	$page_nx = int($event->_vp_x / $width);
-	$page_ny = int($event->_vp_y / $height);
-});
-
-
-$module->addHandler(M_NEW_DESK, sub {
-	my ($module, $event) = @_;
-	$desk = $event->_desk;
-});
-
-
-$module->addHandler(M_STRING, sub{
+$module->addHandler(M_STRING, sub {
 	my ($module, $event) = @_;
 	my ($action, $args) = getToken($event->_text);
 	return unless $action;
 	if ($action =~ /^Post|Menu|Popup$/i) {
-		&PopupMenu($action, $args);
+		PopupMenu($action, $args);
 	} elsif ($action =~ /^ShowBar$/i) {
 		if ($moduleType ne "gtk") {
 			$module->debug("Not started with Gtk support", 0);
 			return;
 		}
-		&PopupTaskBar();
+		PopupTaskBar();
 	} else {
 		$module->debug("Unknown action $action", 0);
 	}
 });
 
-
-$module->addHandler(M_ADD_WINDOW|M_CONFIGURE_WINDOW, sub {
-	my ($module, $event) = @_;
-
-	$window{$event->_win_id}->{desk} = $event->_desk;
-	$window{$event->_win_id}->{x} = $event->_frame_x;
-	$window{$event->_win_id}->{y} = $event->_frame_y;
-});
-
-
-$module->addHandler(M_DESTROY_WINDOW, sub {
-	my ($module, $event) = @_;
-	delete $window{$event->_win_id};
-});
-
-
-$module->addHandler(M_WINDOW_NAME, sub {
-	my ($module, $event) = @_;
-	$window{$event->_win_id}->{name} = $event->_name;
-});
-
-$module->addHandler(M_ICON_NAME, sub {
-	my ($module, $event) = @_;
-	$window{$event->_win_id}->{icon} = $event->_name;
-});
-
-$module->addHandler(M_RES_CLASS, sub {
-	my ($module, $event) = @_;
-	$window{$event->_win_id}->{class} = $event->_name;
-});
-
-$module->addHandler(M_RES_NAME, sub {
-	my ($module, $event) = @_;
-	$window{$event->_win_id}->{resource} = $event->_name;
-});
-
-
-
-$module->addHandler(M_ICONIFY, sub {
-	my ($module, $event) = @_;
-	$window{$event->_win_id}->{iconic} = 1;
-});
-
-
-$module->addHandler(M_DEICONIFY, sub {
-	my ($module, $event) = @_;
-	$window{$event->_win_id}->{iconic} = 0;
-});
-
-
-$module->addHandler(M_MINI_ICON, sub {
-	my ($module, $event) = @_;
-	$window{$event->_win_id}->{mini_icon} = $event->_name;
-});
-
-
 # does all the work and pops up the menu
-sub PopupMenu {
+sub PopupMenu ($$) {
 	my ($action, $args) = @_;
-	my $command = $action =~ /^Popup$/i? "Popup": "Menu";
-
-	my @section;
+	my $command = ($action =~ /^Popup$/i ? "Popup" : "Menu");
 
-	# loop on list of windows
-	foreach my $id ( keys %window ) {
+	my @aSection;
 
-		$module->debug("\t$id - " . $window{$id}->{name});
+	# loop on list of all windows
+	my $pData = $winTracker->data();
+	foreach my $id (keys(%{$pData}))
+	{
+		my $p = $pData->{$id};
+		$module->debug("\t$id - " . $p->{window_name});
+
+		if ($pConfig->{AllDesks} =~ /off/i &&
+			$p->{desk} != $winTracker->pageInfo('desk_n'))
+		{
+			$module->debug("\t\tnot on this desk");
+			next;
+		}
 
-		# skip windows in the dontshow array
-		my $dont_show_me;
-		foreach ( @dontshow ) {
-			my $p = $_->{pattern};
-			$dont_show_me = 1, last
-				if $window{$id}->{$_->{type}} =~ /$p/;
+		if ($pConfig->{AllPages} =~ /off/i &&
+			($p->{page_x} != $winTracker->pageInfo('page_x') ||
+			 $p->{page_y} != $winTracker->pageInfo('page_y')))
+		{
+			$module->debug("\t\tnot on this page");
+			next;
 		}
 
-		$module->debug("\t\tin dontshow list"), next
-			if $dont_show_me;
+		if ($pConfig->{OnlyIconified} =~ /on/i && !$p->{iconified})
+		{
+			$module->debug("\t\tnot iconified");
+			next;
+		}
 
-		# skip windows not on this desk if alldesks not set
-		$module->debug("\t\tnot on this desk"), next
-			unless $opt->{alldesks} || $window{$id}->{desk} == $desk;
-
-		# show only iconic windows if onlyiconic set
-		$module->debug("\t\tskipping because not iconified"), next
-			if $opt->{onlyiconic} && ! $window{$id}->{iconic};
-
-		# skip window if its top left is not on this page
-		$module->debug("\t\tnot on this page"), next
-			if ! $opt->{allpages} && (
-				$window{$id}->{x} < 0 ||
-				$window{$id}->{x} >= $screen->{w} ||
-				$window{$id}->{y} < 0 ||
-				$window{$id}->{y} >= $screen->{h});
-
-		# loop on sections and choose which one to file in
-		my $sectctr = 0;
-		foreach ( @show ) {
- 			my $p = $_->{pattern};
- 			last if $window{$id}->{$_->{type}} =~ /$p/ ;
-			++$sectctr;
+		if (($pConfig->{DontShowName} ne '' &&
+			$p->{window_name} =~ /$pConfig->{DontShowName}/i) ||
+			($pConfig->{DontShowClass} ne '' &&
+			$p->{res_class_name} =~ /$pConfig->{DontShowClass}/i) ||
+			($pConfig->{DontShowResource} ne '' &&
+			$p->{res_name} =~ /$pConfig->{DontShowResource}/i))
+		{
+			$module->debug("\t\tin dontshow list");
+			next;
 		}
 
-		# if we got here add the window to the last section
-		# which is 'none of the above'
-		$module->debug("\t\tadding to section $sectctr");
-		&AddToSection(\$section[$sectctr], $id);
+ 		my $section = 3;
+		if ($pConfig->{ShowName} ne '' &&
+ 			$p->{window_name} =~ /$pConfig->{ShowName}/i) {
+			$section = 0;
+		} elsif ($pConfig->{ShowClass} ne '' &&
+			$p->{res_class_name} =~ /$pConfig->{ShowClass}/i) {
+			$section = 1;
+		} elsif ($pConfig->{ShowResource} ne '' &&
+			$p->{res_name} =~ /$pConfig->{ShowResource}/i) {
+			$section = 2;
+		}
+		$module->debug("\t\tadding to section $section");
+		AddToSection(\$aSection[$section], $id);
 	}

 	# tell fvwm to start the menu
-	$module->send("DestroyMenu $opt->{menuname}");
-	$module->send("AddToMenu $opt->{menuname} 'Desk $desk, "
-		. "Page $page_nx $page_ny' Title");
+	$module->send("DestroyMenu recreate $pConfig->{MenuName}");
+	$module->send("AddToMenu " . $pConfig->{MenuName} . " 'Desk " .
+		$winTracker->pageInfo('desk_n') . ", Page " .
+		$winTracker->pageInfo('page_x') . ' ' .
+		$winTracker->pageInfo('page_y') . "' Title");
 
 	# now loop on sections sending menu entries to fvwm
-	while ( @section ) {
-		my $s = shift @section;
+	while ( @aSection ) {
+		my $s = shift @aSection;
 
 		if ($s) {
 			$module->send($s);
-
 			# add separator after section unless it is the last
-			$module->send("+ \"\" Nop") if @section;
+			$module->send("+ \"\" Nop") if @aSection;
 		}
 	}
 
 	# set a menustyle if one given
-	$module->send("ChangeMenuStyle $opt->{menustyle} $opt->{menuname}")
-		if $opt->{menustyle};
+	$module->send("ChangeMenuStyle $pConfig->{MenuStyle} $pConfig->{MenuName}")
+		if ($pConfig->{MenuStyle} ne '');
 
 	# popup the menu with args we were sent
-	$module->send("$command $opt->{menuname} $args");
+	$module->send("$command $pConfig->{MenuName} $args");
 }

 # build a line containing the fvwm menu entry for a window
-# then add it to the appropriate member of the global array @section
+# then add it to the appropriate member of the global array @aSection
 # args: pointer to section, window id
-sub AddToSection {
+sub AddToSection ($$) {
 	my ($s, $id) = @_;
 
-	my $format = $opt->{itemformat};
+	my $format = $pConfig->{ItemFormat};
+	my $p = $winTracker->data($id);
 
 	# hack: insert __%__ instead of % to avoid bogus substitution later
 	$format =~ s/%%/__%____%__/g;
 
 	# make format string substitutions
 	$format =~ s/%t/\t/g;
-	$format =~ s/%n/&Shorten($window{$id}->{name}, $opt->{maxlen})/ge;
-	$format =~ s/%i/&Shorten($window{$id}->{icon}, $opt->{maxlen})/ge;
-	$format =~ s/%c/&Shorten($window{$id}->{class}, $opt->{maxlen})/ge;
-	$format =~ s/%r/&Shorten($window{$id}->{resource}, $opt->{maxlen})/ge;
-	$format =~ s/%x/$window{$id}->{x}/g;
-	$format =~ s/%y/$window{$id}->{y}/g;
-	$format =~ s/%d/$window{$id}->{desk}/g;
-
-	$format =~ s/%m/__%__$window{$id}->{mini_icon}__%__/g;
-
-	if ($window{$id}->{iconic}) {
-		$format =~ s/%M/__%__$window{$id}->{mini_icon}__%__/g;
-	}
-	else {
+	$format =~ s/%n/&Shorten($p->{window_name})/ge;
+	$format =~ s/%i/&Shorten($p->{icon_name})/ge;
+	$format =~ s/%c/&Shorten($p->{res_class_name})/ge;
+	$format =~ s/%r/&Shorten($p->{res_name})/ge;
+	$format =~ s/%X/$p->{x}/g;
+	$format =~ s/%Y/$p->{y}/g;
+	$format =~ s/%x/$p->{frame_x}/g;
+	$format =~ s/%y/$p->{frame_y}/g;
+	$format =~ s/%d/$p->{desk}/g;
+
+	# TODO: doesn't handle EWMH icons yet.
+	$format =~ s/%m// if ($p->{mini_icon_name} eq 'ewmh_mini_icon');
+	$format =~ s/%m/__%__$p->{mini_icon_name}__%__/g;
+
+	# %M is strange - does anyone really want this behaviour? -- SS.
+	if ($p->{iconified}) {
+		$format =~ s/%M/__%__$p->{mini_icon_name}__%__/g;
+	} else {
 		$format =~ s/%M//g;
 	}
 	
@@ -345,13 +228,14 @@
 
 	# add the entry to the section
 	# support two ways for now: window context (new), window id param (old)
-	$$s .= qq(+ "$format" WindowId $id $opt->{function} $id\n);
+	$$s .= qq(+ "$format" WindowId $id $pConfig->{Function} $id\n);
 }

 # shorten a string to given length and append ellipses
-sub Shorten {
-	my($string, $length) = @_;
+sub Shorten ($) {
+	my ($string) = @_;
+	my $length = $pConfig->{MaxLen};
 
 	my $r = substr($string, 0, $length);
 	$r .= "..." if length($string) > $length;
@@ -359,7 +243,6 @@
 	return $r;
 }
 
-
 sub PopupTaskBar () {
 	my ($w, $h) = (180, 60);
 
@@ -367,7 +250,10 @@
 	$window->set_title("FvwmWindowMenuBar");
 	$window->set_border_width(5);
 	$window->set_usize($w, $h);
-	$window->set_uposition(($screen->{w} - $w) / 2, ($screen->{h} - $h) / 2);
+
+	my $screenW = $winTracker->pageInfo('vp_width');
+	my $screenH = $winTracker->pageInfo('vp_height');
+	$window->set_uposition(($screenW - $w) / 2, ($screenH - $h) / 2);
 
 	my $frame = new Gtk::Frame();
 	$window->add($frame);
@@ -390,9 +276,6 @@
 }

-# main loop
-$module->send("Send_ConfigInfo");
-$module->send("Send_WindowList");
 $module->send(
 	"Style FvwmWindowMenuBar UsePPosition, !Title, !Borders, " .
 	"StaysOnTop, WindowListSkip, CascadePlacement, SloppyFocus"
@@ -425,13 +308,14 @@
 module does not draw its own window, but instead creates an
 I<fvwm> menu and asks I<fvwm> to pop it up.
 
-By defining a set of regular expressions using Show, windows may
-be sorted into sections based on the regexp matching their
-name, icon name, class or resource.
+By defining a set of regular expressions, windows may
+be sorted into sections based on a regexp matching the window
+name, class or resource and included in the menu.
 
-Similarly, matches in DontShow will be excluded from the list.
+Similarly, another set of regular expressions can be used to exclude
+items from the menu.
 
-Any windows not matching an instance of Show or DontShow will
+Any windows not matching an instance of the include or exclude list will
 be placed in the last section of the menu.
 
 =head1 USAGE
@@ -457,28 +341,32 @@
 B<Menu> and B<Popup>. If the module was started with "-g" switch, it
 additionally supports B<PostBar> (not implemented yet).
 
-Set module options for windows to show or not show. The syntax is:
+Set module options for windows to include (Show) or exclude (DontShow).
+The syntax is:
 
-    *FvwmWindowMenu: Show type = pattern
-    *FvwmWindowMenu: DontShow type = pattern
+    *FvwmWindowMenu: ShowName pattern
+    *FvwmWindowMenu: ShowClass pattern
+    *FvwmWindowMenu: ShowResource pattern
+    *FvwmWindowMenu: DontShowName pattern
+    *FvwmWindowMenu: DontShowClass pattern
+    *FvwmWindowMenu: DontShowResource pattern
 
-where type is one of I<name>, I<icon>, I<class> or I<resource>. Pattern is
-a perl regular expression that will be evaluated in m// context.
+Pattern is a perl regular expression that will be evaluated in m// context.
 See perlre(1).
 
 For example:
 
-    *FvwmWindowMenu: Show resource = Galeon|Navigator|mozilla-bin
-    *FvwmWindowMenu: Show name = ^emacs
+    *FvwmWindowMenu: ShowResource ^gvim
+    *FvwmWindowMenu: ShowName Galeon|Navigator|mozilla-bin|Firefox
+
+will define two sections containing respectively browsers, and GVim. A third
+section will contain all other windows.
 
-will define two sections containing respectively browsers, and emacs.
-Number of sections is unlimited. The strings are perl regular
-expressions that will be evaluated in m// context. See perlre(1).
 
 Similarly:
 
-    *FvwmWindowMenu: DontShow name = ^Fvwm
-    *FvwmWindowMenu: DontShow class = Gkrellm
+    *FvwmWindowMenu: DontShowName ^Fvwm
+    *FvwmWindowMenu: DontShowClass Gkrellm
 
 will cause the menu to ignore windows with name beginning with Fvwm
 or class gkrellm.
@@ -487,7 +375,7 @@
 
 =over 4
 
-=item *FvwmWindowMenu: I<OnlyIconic> {on|off}
+=item *FvwmWindowMenu: I<OnlyIconified> {on|off}
 
 show only iconified windows
 
@@ -499,7 +387,7 @@
 
 show windows from all pages
 
-=item *FvwmWindowMenu: I<Maxlen> 32
+=item *FvwmWindowMenu: I<MaxLen> 32
 
 max length in chars of entry
 
@@ -531,7 +419,11 @@
 
 =item %x, %y
 
-the window x or y coordinates on current page
+the window x or y coordinates w.r.t. the page the window is on.
+
+=item %X, %Y
+
+the window x or y coordinates w.r.t. the desk the window is on.
 
 =item %d
 
@@ -573,8 +465,10 @@
 
     Key Super_R A A SendToModule FvwmWindowMenu Post Root c c WarpTitle
 
-=head1 AUTHOR
+=head1 AUTHORS
+
+=item Ric Lister <http://cns.georgetown.edu/~ric/>
 
-Ric Lister <http://cns.georgetown.edu/~ric/>
+=item Scott Smedley
 
 =cut
Index: perllib/ChangeLog
===================================================================
RCS file: /home/cvs/fvwm/fvwm/perllib/ChangeLog,v
retrieving revision 1.46
diff -u -r1.46 ChangeLog
--- perllib/ChangeLog	24 Apr 2004 19:13:13 -0000	1.46
+++ perllib/ChangeLog	2 Jun 2004 11:28:52 -0000
@@ -1,3 +1,8 @@
+2004-06-01  Scott Smedley  <[email-masked]>
+
+	* FVWM/Tracker/WindowList.pm:
+	actually implemented tracker & all observables.
+
 2004-04-24  Mikhael Goikhman  <[email-masked]>
 
 	* FVWM/Tracker.pm:
Index: perllib/FVWM/Tracker/WindowList.pm
===================================================================
RCS file: /home/cvs/fvwm/fvwm/perllib/FVWM/Tracker/WindowList.pm,v
retrieving revision 1.4
diff -u -r1.4 WindowList.pm
--- perllib/FVWM/Tracker/WindowList.pm	25 Oct 2003 03:00:01 -0000	1.4
+++ perllib/FVWM/Tracker/WindowList.pm	2 Jun 2004 11:28:53 -0000
@@ -1,4 +1,4 @@
-# Copyright (c) 2003 Mikhael Goikhman
+# Copyright (c) 2004 Mikhael Goikhman, Scott Smedley
 #
 # 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
@@ -20,11 +20,25 @@
 
 use FVWM::Tracker qw(base);
 
+my $windowEvents = M_ADD_WINDOW | M_CONFIGURE_WINDOW | M_DESTROY_WINDOW |
+	M_ICONIFY | M_DEICONIFY;
+my $nameEvents = M_RES_NAME | M_RES_CLASS | M_WINDOW_NAME | M_VISIBLE_NAME |
+	M_ICON_NAME;
+my $stackEvents = M_RESTACK | M_RAISE_WINDOW | M_LOWER_WINDOW;
+my $iconEvents = M_ICON_LOCATION | M_ICON_FILE | M_DEFAULTICON | M_MINI_ICON;
+
 sub observables ($) {
 	return [
 		"window added",
 		"window deleted",
 		"window properties updated",
+		"window moved",
+		"window resized",
+		"window iconified",
+		"window deiconified",
+		"window name updated",
+		"window stack updated",
+		"window icon updated",
 	];
 }
 
@@ -36,41 +50,38 @@
 	my $self = $class->FVWM::Tracker::new($module);
 
 	$self->{options} = [ @options ];
-
 	return $self;
 }
 
-sub addRequestedInfoHandlers ($$$$) {
+sub addRequestedInfoHandlers ($$) {
 	my $self = shift;
-	my $handlerC = shift;
-	my $handlerD = shift;
-	my $handlerP = shift;
+	my $handler = shift;
 
 	my $useWInfo = 1;
 	my $useNames = 1;
 	my $useStack = 0;
 	my $useIcons = 0;
-	foreach my $option (@{$self->{options}}) {  
+	foreach (@{$self->{options}}) {  
 		/^(\!?)winfo$/ and $useWInfo = $1 ne '!';
 		/^(\!?)names$/ and $useNames = $1 ne '!';
 		/^(\!?)stack$/ and $useStack = $1 ne '!';
 		/^(\!?)icons$/ and $useIcons = $1 ne '!';
 	}
-	my $mask1  = 0;
-	my $mask2  = 0;
-	my $xmask2 = 0;
-	$mask1 |= M_ADD_WINDOW | M_CONFIGURE_WINDOW if $useWInfo;
-	$mask2 |= M_RES_NAME | M_RES_CLASS | M_WINDOW_NAME | M_VISIBLE_NAME | M_ICON_NAME
-		if $useNames;
-	$mask2 |= M_RESTACK | M_RAISE_WINDOW | M_LOWER_WINDOW if $useStack;  
-	$mask2 |= M_ICON_LOCATION | M_ICON_FILE | M_DEFAULTICON | M_MINI_ICON
-		if $useIcons;
-	$xmask2 |= MX_VISIBLE_ICON_NAME if $useNames;
-
-	$self->addHandler($mask1, $handlerC) if $mask1;
-	$self->addHandler(M_DESTROY_WINDOW, $handlerD);
-	$self->addHandler($mask2, $handlerP) if $mask2;
-	$self->addHandler($xmask2, $handlerP) if $xmask2;
+	my $mask  = 0;
+	$mask |= $windowEvents if $useWInfo;
+	$mask |= $nameEvents if $useNames;
+	$mask |= $stackEvents if $useStack;  
+	$mask |= $iconEvents if $useIcons;
+
+	# Adding MX_VISIBLE_ICON_NAME to $nameEvents does not work.
+	my $xmask = 0;
+	$xmask |= MX_VISIBLE_ICON_NAME if $useNames;
+
+	$self->addHandler($mask, $handler) if $mask;
+	$self->addHandler($xmask, $handler) if $xmask;
+	$self->addHandler(M_NEW_PAGE | M_NEW_DESK, sub {
+		$self->handlerPageInfo($_[1]);
+	}) if $useWInfo;
 }
 
 sub start ($) {
@@ -78,37 +89,55 @@
 
 	$self->{data} = {};
 
-	### TODO
 	$self->addRequestedInfoHandlers(sub {
 		my $event = $_[1];
-		$self->calculateInternals($event->args);
-	}, sub {
-		my $event = $_[1];
-		$self->calculateInternals($event->args);
-	}, sub {
-		my $event = $_[1];
-		$self->calculateInternals($event->args);
+		$self->calculateInternals($event);
 	});
 
 	$self->requestWindowListEvents;
-
-	### temporary
-	$self->deleteHandlers;
-
 	my $result = $self->SUPER::start;
-
 	$self->deleteHandlers;
 
-	### TODO
 	$self->addRequestedInfoHandlers(sub {
 		my $event = $_[1];
-		$self->calculateInternals($event->args);
-	}, sub {
-		my $event = $_[1];
-		$self->calculateInternals($event->args);
-	}, sub {
-		my $event = $_[1];
-		$self->calculateInternals($event->args);
+		my ($winId, $oldHash) = $self->calculateInternals($event);
+		return unless defined $winId;
+		my $type = $event->type();
+		if ($type & M_ADD_WINDOW) {
+			$self->notify("window added", $winId);
+		} elsif ($type & M_CONFIGURE_WINDOW) {
+			$self->notify("window properties updated", $winId, $oldHash);
+			# The "window properties updated" observable is very broad &
+			# occurs as a result of many different operations - here we
+			# try & determine if some common operations occur & invoke more
+			# specific observables. This may reduce the amount of parsing
+			# external modules have to do.
+			my $p = $self->{data}->{$winId};
+			if ($p->{frame_width} != $oldHash->{frame_width} ||
+				$p->{frame_height} != $oldHash->{frame_height})
+			{
+				$self->notify("window resized", $winId, $oldHash);
+			}
+			elsif ($p->{desk} != $oldHash->{desk} ||
+				$p->{x} != $oldHash->{x} ||
+				$p->{y} != $oldHash->{y})
+			{
+				$self->notify("window moved", $winId, $oldHash);
+			}
+			# We can easily add other specific observables here as required.
+		} elsif ($type & M_DESTROY_WINDOW) {
+			$self->notify("window deleted", $winId, $oldHash);
+		} elsif ($type & M_ICONIFY) {
+			$self->notify("window iconified", $winId, $oldHash);
+		} elsif ($type & M_DEICONIFY) {
+			$self->notify("window deiconified", $winId, $oldHash);
+		} elsif ($type & $nameEvents || $type & MX_VISIBLE_ICON_NAME) {
+			$self->notify("window name updated", $winId, $oldHash);
+		} elsif ($type & $stackEvents) {
+			$self->notify("window stack updated", $winId, $oldHash);
+		} elsif ($type & $iconEvents) {
+			$self->notify("window icon updated", $winId, $oldHash);
+		}
 	});
 
 	return $result;
@@ -116,12 +145,83 @@
 
 sub calculateInternals ($$) {
 	my $self = shift;
-	my $args = shift;
+	my $event = shift;
+	my $args = $event->args;
 	my $data = $self->{data};
+	my $winId = $args->{win_id};
+
+	my $oldHash = undef;
+	if (defined $data->{$winId}) {
+		# make a copy of the hash cos we are about to modify it.
+		my %tmp = %{$data->{$winId}};
+		$oldHash = \%tmp;
+	}
+
+	# There are some fields that are not unique to all events. To ensure
+	# we don't clobber them, we rename some fields. For example, the 'name'
+	# field of M_MINI_ICON events is renamed to 'mini_icon_name'.
+	foreach ('name', 'x', 'y', 'width', 'height') {
+		if (defined $args->{$_}) {
+			(my $name = lc($event->name())) =~ s/^.*?_//;
+			$name .= "_$_" if ($name !~ /$_$/);
+			$args->{$name} = $args->{$_};
+			delete $args->{$_};
+		}
+	}
+
+	# Please leave this (commented-out) code here. I often enable it
+	# when debugging -- SS.
+#	print(STDERR "Got " . $event->name() . " event\n");
+#	foreach (sort(keys %{$args})) {
+#		print(STDERR "    $_=" . $args->{$_} . "\n");
+#	}
+
+	@{$data->{$winId}}{keys %{$args}} = values %{$args};
+
+	if (defined $args->{frame_x} && defined $self->{page_info}) {
+		# frame_x & frame_y are _relative_ coords of the window to the
+		# current page - calculate the _absolute_ coords - x & y.
+		my $pW = $data->{$winId};
+		$pW->{x} = $self->pageInfo('vp_x') + $args->{frame_x};
+		$pW->{y} = $self->pageInfo('vp_y') + $args->{frame_y};
+		$pW->{page_x} = int($pW->{x} / $self->pageInfo('vp_width'));
+		$pW->{page_y} = int($pW->{y} / $self->pageInfo('vp_height'));
+	}
 
-	### TODO
+	my $type = $event->type();
+	if ($type & M_ADD_WINDOW || $type & M_DEICONIFY) {
+		$data->{$winId}->{iconified} = 0;
+	} elsif ($type & M_ICONIFY) {
+		$data->{$winId}->{iconified} = 1;
+	} elsif ($type & M_DESTROY_WINDOW) {
+		delete $data->{$winId};
+	}
+
+	return wantarray ? ($winId, $oldHash) : $winId;
 }
 
+sub handlerPageInfo ($$) {
+	my $self = shift;
+	my $event = shift;
+	my $args = $event->args;
+
+	@{$self->{page_info}}{keys %{$args}} = values %{$args};
+
+	if ($event->type() & M_NEW_PAGE) {
+		$self->{page_info}->{page_x} = int($args->{vp_x} / $args->{vp_width});
+		$self->{page_info}->{page_y} = int($args->{vp_y} / $args->{vp_height});
+	}
+}
+
+sub pageInfo ($;$) {
+	my $self = shift;
+	my $id = shift;
+	my $data = $self->{page_info};
+	return $data unless defined $id;
+	return $data->{$id};
+}
+
+
 sub data ($;$) { 
 	my $self = shift;
 	my $id = shift;
@@ -158,24 +258,31 @@
 
 This tracker defines the following observables:
 
-    "window added",
-    "window deleted",
-    "window properties updated",
-
-NOT USABLE YET.
+	"window added",
+	"window deleted",
+	"window properties updated",
+	"window moved",
+	"window resized",
+	"window iconified",
+	"window deiconified",
+	"window name updated",
+	"window stack updated",
+	"window icon updated",
 
 =head1 SYNOPSYS
  
 Using B<FVWM::Module> $module object (preferably):
 
-    my $windowsTracker = $module->track("ModuleConfig");
-    my $windows = $windowsTracker->data;
-    my $windowSizeX = $windows->{$winId}->{'x'};
+	my $windowsTracker = $module->track("WindowList", $options);
+	my $windows = $windowsTracker->data;
+	my $windowSizeX = $windows->{$winId}->{'x'};
 
 or:
 
-    my $windowsTracker = $module->track("WindowList");
-    my $windowSizeX = $windowsTracker->data($winId)->{'x'};
+	my $windowsTracker = $module->track("WindowList", $options);
+	my $windowSizeX = $windowsTracker->data($winId)->{'x'};
+
+Default $options string is: "!stack !icons names winfo"
 
 =head1 OVERRIDDEN METHODS
 
@@ -190,7 +297,7 @@
 =item B<data> [I<window-id>]
 
 Returns array ref of window hash refs. or one window hash ref if
-I<window-id> is given. The hash keys are not finalized yet.
+I<window-id> is given.
 
 =item B<dump> [I<window-id>]
 
@@ -198,9 +305,25 @@
 
 =back
 
-=head1 AUTHOR
+=head1 METHODS
 
-Mikhael Goikhman <[email-masked]>.
+=over 4
+
+=item B<pageInfo> [I<field>]
+
+Returns hash ref of page/desk info, or actual hash value using B<field> as a key (if specified).
+
+=back
+
+=head1 AUTHORS
+
+=over 4
+
+=item Mikhael Goikhman <[email-masked]>
+
+=item Scott Smedley
+
+=back
 
 =head1 SEE ALSO