#!/usr/bin/perl -w
# Record the icon position of all iconified Firefox windows in a way that can
# be replayed as fvwm commands to re-iconify them and re-position their icons.
# This is descending into some sort of baroque hell, really.
#
# Arguments:
#	--file FILE		save to file instead of /tmp/WindowRecord.out
#	--name NAME		look for windows with NAME and CLASS instead
#	--class CLASS		of Navigator/Minefield
#	--allpos		restore uniconified position too
#
# BUGS: really only works well if it's invoked from page 0,0 of your
# virtual desktop. Doesn't cope at all with windows with the same name.
# Hardcodes my desktop size instead of getting it from the M_NEW_PAGE
# message. Probably many others.
#
# Copyright: GPL. See the end for the formal verbiage.

use strict;
use lib `/u/cks/lib/i386-linux/fvwm-cvs/bin/fvwm-perllib dir`;
use FVWM::Module;
use IO::File;

my $prog = "FvwmWindowRecorder";
my $fname = "/tmp/WindowRecord.out";
my $name = "Navigator";
my $class = "Minefield";
my $allpos;
my $module = new FVWM::Module(
	Name => $prog,
	Mask => M_WINDOW_NAME | M_RES_NAME | M_RES_CLASS | M_ICONIFY | M_END_WINDOWLIST | M_CONFIGURE_WINDOW,
	EnableOptions => { "file=s"	=> \$fname,
			   "class=s"	=> \$class, "name=s"	=> \$name,
			   "allpos"	=> \$allpos, },
	Debug => 1,
);

# ... it is probably obvious that I don't understand Perl OO.
{ package WinData;
  use Class::Struct;
  struct('wid' => '$', 'name' => '$', 'res' => '$', 'class' => '$',
	 'icon_x' => '$', 'icon_y' => '$', 'iconified' => '$',
	 'win_x' => '$', 'win_y' => '$');
}

# Track windows by FVWM Window ID. Each entry is a WinData structure,
# with fields that may or may not be defined.
my %byid;

sub setup_id {
	my $event = shift;
	my $wid = $event->_win_id;
	if (! defined $byid{$wid}) {
		$byid{$wid} = WinData->new(wid => $wid);
		$byid{$wid}->icon_x(0);
		$byid{$wid}->icon_y(0);
	}
	return $byid{$wid};
}

# Register the event handlers
# TODO: the first two lines of each of these functions are generic.
# Maybe Perl provides some way of condensing these.
$module->add_handler(M_CONFIGURE_WINDOW, sub {
	my ($self, $event) = @_;
	my $wi = setup_id($event);
	$wi->win_x($event->_frame_x);
	$wi->win_y($event->_frame_y);
});
$module->add_handler(M_WINDOW_NAME, sub {
	my ($self, $event) = @_;
	my $wi = setup_id($event);
	$wi->name($event->_name);
});
$module->add_handler(M_RES_NAME, sub {
	my ($self, $event) = @_;
	my $wi = setup_id($event);
	$wi->res($event->_name);
});
$module->add_handler(M_RES_CLASS, sub {
	my ($self, $event) = @_;
	my $wi = setup_id($event);
	$wi->class($event->_name);
});
$module->add_handler(M_ICONIFY, sub {
	my ($self, $event) = @_;
	my $wi = setup_id($event);
	$wi->iconified(1);
	$wi->icon_x($event->_icon_x);
	$wi->icon_y($event->_icon_y);
});

# " turns into \", $ turns into $$.
sub fvwm_quote {
    my $sstr = $_[0];
    $sstr =~ s'"'\"'g;
    $sstr =~ s'\$'$$'g;
    return $sstr;
}

# This one signals that the module should be terminated
$module->add_handler(M_END_WINDOWLIST, sub {
    my $fh = new IO::File "> $fname"	or die "Can't open $fname: $!";
    print $fh "# Written by $prog\n\n";

    # Order the windows by increasing X and then increasing Y.
    # This works out more or less the way I want.
    my @v = sort { $a->icon_x <=> $b->icon_x || $a->icon_y <=> $b->icon_y } values %byid;
    foreach my $wd (@v) {
	next	unless ($wd->iconified);
	next	unless ($wd->class eq $class && $wd->res eq $name);
	# This is not expected to work for out of screen icon
	# positions.
	next	if ($wd->icon_x > 2560 || $wd->icon_y > 1024);
	next	if ($wd->icon_x < 0 || $wd->icon_y < 0);

	my $nstr = sprintf('Next ("%s", "%s", "%s")', fvwm_quote($wd->name), fvwm_quote($wd->res), fvwm_quote($wd->class));
	if ($allpos && defined $wd->win_x && defined $wd->win_y) {
		print $fh "$nstr Iconify False\n";
		printf $fh "$nstr Move %d%s %d%s\n", $wd->win_x, 'p', $wd->win_y, 'p';
	}
	print $fh "$nstr Iconify True\n";
	printf $fh "$nstr Move %d%s %d%s\n", $wd->icon_x, 'p', $wd->icon_y, 'p';
    }
    print $fh "\n# Done\n";
    close $fh;
    $module->terminate;
});

# Ask fvwm to send us its list of windows
$module->send("Send_WindowList");

# Enter the main loop
$module->event_loop;

# What I get is a stream of
# M_WINDOW_NAME
# M_RES_NAME / M_RES_CLASS sets
# M_ICONIFY with icon_x and icon_y
# M_CONFIGURE_WINDOW with uniconified window position & size
# and some sundry others.
# how to see this:
# 'Module FvwmGtkDebug'
# Setup -> Unselect all events, Start monitoring events
# Tools -> Send_WindowList
# Stored Events -> page through them

# I could pluck pager information out of the M_NEW_PAGE message.

# ---
#
# Copyright (C) 2010 Chris Siebenmann
#
# 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
