#!/usr/bin/perl
# metacity-direction - move to a window in a given direction
#
# Copyright (c) 2009 Thomas Thurman <tthurman@gnome.org>
#
# 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.

use strict;
use warnings;

# save ourselves some support questions...
eval { require X11::Protocol; };
die "Please install X11::Protocol first, by typing 'sudo cpan X11::Protocol'.\n"
    if $@;

my %mapping;

mkdir $ENV{HOME}."/.config";
mkdir $ENV{HOME}."/.config/metacity";
my $filename = $ENV{HOME}."/.config/metacity/bookmark.ini";

if (-e $filename) {
    open INI, "<$filename";
    while (<INI>) {
	chomp;
	my ($key, $value) = /^(.*)=(.*)$/;
	$mapping{$key} = $value;
    }
}

my $x11 = X11::Protocol->new();
my $root = $x11->{root};

sub get_prop {
    my ($win, $atom) = @_;
    my ($p) = $x11->GetProperty($win, $x11->atom($atom),
				'AnyPropertyType', 0, -1, 0);
    return unpack("L*", $p);
}

sub get_string_prop {
    my ($win, $atom) = @_;
    my ($p) = $x11->GetProperty($win, $x11->atom($atom),
				'AnyPropertyType', 0, -1, 0);
    return $p;
}

sub get_active_window { return get_prop($root, '_NET_ACTIVE_WINDOW'); }
sub get_client_list { return get_prop($root, '_NET_CLIENT_LIST_STACKING'); }

sub get_current_time {
    # really what we ought to do here is create a window, update its
    # properties with an empty list of properties, catch the
    # PropertyNotify event, get the most recent timestamp out of it,
    # and kill the window.  This is way too much work, so we'll pretend
    # we only know the old version of the spec and return CurrentTime (zero).
    return 0;
}

sub get_centre_of_window {
  my ($win) = @_;

  my %geom = $x11->GetGeometry($win);
  my @xlat = $x11->TranslateCoordinates($win, $root, $geom{x}, $geom{y});

  return (
    $geom{width}/2+$xlat[2],
    $geom{height}/2+$xlat[3],
  );
}

# really basic window matching
sub get_unique_id {

    my ($win) = @_;

    my $role = get_string_prop ($win, 'WM_WINDOW_ROLE');
    my $wmclass = get_string_prop ($win, 'WM_CLASS');
    my $i = index ($wmclass, "\0");
    my $wmclass1 = substr ($wmclass, 0, $i);
    my $wmclass2 = substr ($wmclass, $i+1);

    return "$role|$wmclass1|$wmclass2";
}

sub show_window {
    my ($message, $timeout) = @_;

    my $font = $x11->new_rsrc;
    $x11->OpenFont ($font, 'fixed');

    my %extents = $x11->QueryTextExtents($font, $message);

    $x11->event_handler('queue');

    my $w = $extents{overall_width}*2;
    my $h = $extents{font_ascent}+5;

    my $win = $x11->new_rsrc();
    $x11->CreateWindow($win, $x11->root, 'InputOutput', $x11->root_depth,
		       'CopyFromParent',
		       (0, 0),
		       $w, $h, 1,
		       'background_pixel' => $x11->white_pixel,
		       'event_mask' => $x11->pack_event_mask('KeyPress', 'Exposure'));

    $x11->MapWindow($win);

    my $gc = $x11->new_rsrc;
    $x11->CreateGC ($gc, $win, font => $font, foreground => $x11->black_pixel, graphics_exposures=>0);

    my %keypress = $x11->next_event();
    die "unexpected event" unless $keypress{name} eq 'Expose';

    $x11->PolyText8($win, $gc, (0, $extents{font_ascent}), [0, $message]);

    my $result = 0;

    %keypress = $x11->next_event();
    $result = $keypress{detail};

    $x11->UnmapWindow($win);

    return $result;
}

my $r = show_window ('Press a bookmark key, or Space to bookmark this window');

if ($r==65) {
    # pressed space
    my $newcode;

    my $active = get_active_window();
    my $active_id;

    $active_id = get_unique_id ($active) if $active;

    if (!$active_id || $active_id =~ /desktop_window/) {
	show_window ("No window is selected; please select one first.  Press Space.");
	exit;
    }

    print "[[$active_id]]\n";

    while (1) {
	$newcode = show_window ('Press the key you will use to represent this window');
	last if $newcode != 65;
	show_window ("You can't represent a window with Space.  Press Space and try again.");
    }
    $mapping{$newcode} = $active_id;

    open INI, ">$filename" or die "Couldn't open: $!";
    for (sort keys %mapping) {
	print INI "$_=$mapping{$_}\n";
    }
    close INI or die "Couldn't close: $!";

    show_window ("Okay, you can come back to this window later with that key.  Press Space.");
} else {

    # okay, so search for it

    my $target = $mapping{$r};

    if ($target) {
	# well, it existed at *some* point...

	my $found = 0;
	for (get_client_list()) {
	    if (get_unique_id($_) eq $target) {
		$found = $_;
		last;
	    }
	}

	if ($found) {
	    # got it!  let's go there.
	    $x11->SendEvent($root, 0,
			    $x11->pack_event_mask('SubstructureNotify',
						  'SubstructureRedirect'),
			    $x11->pack_event(
				name => 'ClientMessage',
				window => $found,
				type => $x11->atom('_NET_ACTIVE_WINDOW'),
				format => 32,
                                data => pack('LLLLL',
                                             2, # we are a pager
                                             get_current_time(),
                                $found, 0, 0)));
	} else {
	    show_window ("Sorry, that window is no longer on this display.  Press Space.");
	}

    } else {
	show_window ("Sorry, you haven't set any window to have that keypress.  Press Space.");
    }
}

# eof metacity-bookmark
