###################################################
#
#  Copyright (C) 2008-2013 Mario Kemper <mario.kemper@gmail.com>
#
#  This file is part of Shutter.
#
#  Shutter 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 3 of the License, or
#  (at your option) any later version.
#
#  Shutter 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 Shutter; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
#
###################################################

package Shutter::Screenshot::Window;

#modules
#--------------------------------------
use utf8;
use strict;
use warnings;

#File operations
use IO::File();

use Shutter::Screenshot::Main;
use Shutter::Screenshot::History;
use Data::Dumper;
our @ISA = qw(Shutter::Screenshot::Main);

#Glib
use Gtk3;
use Glib qw/TRUE FALSE/;

#--------------------------------------

sub new {
	my $class = shift;

	#call constructor of super class (shutter_common, include_cursor, delay, notify_timeout)
	my $self = $class->SUPER::new(shift, shift, shift, shift);

	#get params
	$self->{_include_border} = shift;
	$self->{_windowresize}   = shift;
	$self->{_windowresize_w} = shift;
	$self->{_windowresize_h} = shift;
	$self->{_hide_time}      = shift;    #a short timeout to give the server a chance to redraw the area that was obscured
	$self->{_mode}           = shift;
	$self->{_auto_shape}     = shift;    #shape the window without XShape support
	$self->{_is_hidden}      = shift;
	$self->{_show_visible}   = shift;    #show user-visible windows only when selecting a window
	$self->{_ignore_type}    = shift;    #Ignore possibly wrong type hints

	#X11 protocol and XSHAPE ext
	require X11::Protocol;

	$self->{_x11} = X11::Protocol->new($ENV{'DISPLAY'});
	$self->{_x11}{ext_shape} = $self->{_x11}->init_extension('SHAPE');

	#main window
	$self->{_main_gtk_window} = $self->{_sc}->get_mainwindow;
	$self->{_dpi_scale} = $self->{_main_gtk_window}->get('scale-factor');

	#only used when selecting a window
	if (defined $self->{_mode} && $self->{_mode} =~ m/(window|section)/ig) {

		#check if compositing is available
		my $compos = $self->{_main_gtk_window}->get_screen->is_composited;

		#higlighter (borderless gtk window)
		$self->{_highlighter} = Gtk3::Window->new('popup');
		if ($compos) {
			my $screen = $self->{_main_gtk_window}->get_screen;
			# Glib::Object::Introspection doesn't support method call via
			# cross-package inheritance, call it as a free function instead
			# (X11Screen inherits from Screen)
			$self->{_highlighter}->set_visual(Gtk3::Gdk::Screen::get_rgba_visual($screen) || Gtk3::Gdb::Screen::get_system_visual($screen));
		}

		$self->{_highlighter}->set_app_paintable(TRUE);
		$self->{_highlighter}->set_decorated(FALSE);
		$self->{_highlighter}->set_skip_taskbar_hint(TRUE);
		$self->{_highlighter}->set_skip_pager_hint(TRUE);
		$self->{_highlighter}->set_keep_above(TRUE);
		$self->{_highlighter}->set_accept_focus(FALSE);

		#obtain current colors and font_desc from the main window
		my $style     = $self->{_main_gtk_window}->get_style_context;
		my $sel_bg    = $style->get_background_color('selected');
		my $font_fam  = $style->get_font('normal')->get_family;
		my $font_size = $style->get_font('normal')->get_size / Pango->scale;

		#get current monitor
		my $mon = $self->get_current_monitor;

		$self->{_highlighter_expose} = $self->{_highlighter}->signal_connect(
			'draw' => sub {
				return FALSE unless $self->{_highlighter}->get_window;

				#Place window and resize it
				$self->{_highlighter}->get_window->move_resize($self->{_c}{'cw'}{'x'} / $self->{_dpi_scale} - 3, $self->{_c}{'cw'}{'y'} / $self->{_dpi_scale} - 3, $self->{_c}{'cw'}{'width'} / $self->{_dpi_scale} + 6, $self->{_c}{'cw'}{'height'} / $self->{_dpi_scale} + 6);

				print $self->{_c}{'cw'}{'window'}->get_name, "\n" if $self->{_sc}->get_debug;

				my $text = Glib::Markup::escape_text($self->{_c}{'cw'}{'window'}->get_name);
				utf8::decode $text;

				my $sec_text = "\n" . $self->{_c}{'cw'}{'width'} . "x" . $self->{_c}{'cw'}{'height'};

				#window size and position
				my ($w, $h) = $self->{_highlighter}->get_size;
				my ($x, $y) = $self->{_highlighter}->get_position;

				#app icon
				my $icon = $self->{_c}{'cw'}{'window'}->get_icon;

				#create cairo context
				my $cr = $_[1];

				#pango layout
				my $layout = Pango::Cairo::create_layout($cr);
				$layout->set_width(($w - $icon->get_width - $font_size * 3) * Pango->scale);
				$layout->set_alignment('left');
				$layout->set_wrap('char');

				#warning if there are no subwindows
				#when we are in section mode and
				#a toplevel window was already selected
				if ($self->{_c}{'ws'}) {
					my $xwindow = $self->{_c}{'ws'}->get_xid;
					if (scalar @{$self->{_c}{'cw'}{$xwindow}} <= 1) {

						#error icon
						$icon = Gtk3::Widget::render_icon(Gtk3::Invisible->new, "gtk-dialog-error", 'dialog');

						#error message
						my $d = $self->{_sc}->get_gettext;
						$text     = $d->get("No subwindow detected");
						$sec_text = "\n" . $d->get("Maybe this window is using client-side windows (or similar).\nShutter is not yet able to query the tree information of such windows.");

						#wrap nicely
						$layout->set_wrap('word-char');
					}
				}

				#set text
				$layout->set_markup(
					"<span font_desc=\"$font_fam $font_size\" weight=\"bold\" foreground=\"#FFFFFF\">$text</span><span font_desc=\"$font_fam $font_size\" foreground=\"#FFFFFF\">$sec_text</span>");

				#get layout size
				my ($lw, $lh) = $layout->get_pixel_size;

				#adjust values
				$lw += $icon->get_width;
				$lh = $icon->get_height if $icon->get_height > $lh;

				#calculate values for rounded/shaped rectangle
				my $wi = $lw + $font_size * 3;
				my $hi = $lh + $font_size * 2;
				my $xi = int(($w - $wi) / 2);
				my $yi = int(($h - $hi) / 2);
				my $ri = 20;

				#two different ways - compositing or not
				if ($compos) {

					#fill window
					$cr->set_operator('source');
					$cr->set_source_rgba($sel_bg->red, $sel_bg->green, $sel_bg->blue, 0.3);
					$cr->paint;

					#Parent window with text and icon
					if ($self->{_c}{'cw'}{'is_parent'}) {

						$cr->set_operator('over');

						#create small frame (window outlines)
						$cr->set_source_rgba($sel_bg->red, $sel_bg->green, $sel_bg->blue, 0.75);
						$cr->set_line_width(6);
						$cr->rectangle(0, 0, $w, $h);
						$cr->stroke;

						if ($lw <= $w && $lh <= $h) {

							#rounded rectangle to display the window name
							$cr->move_to($xi + $ri, $yi);
							$cr->line_to($xi + $wi - $ri, $yi);
							$cr->curve_to($xi + $wi, $yi, $xi + $wi, $yi, $xi + $wi, $yi + $ri);
							$cr->line_to($xi + $wi, $yi + $hi - $ri);
							$cr->curve_to($xi + $wi, $yi + $hi, $xi + $wi, $yi + $hi, $xi + $wi - $ri, $yi + $hi);
							$cr->line_to($xi + $ri, $yi + $hi);
							$cr->curve_to($xi, $yi + $hi, $xi, $yi + $hi, $xi, $yi + $hi - $ri);
							$cr->line_to($xi, $yi + $ri);
							$cr->curve_to($xi, $yi, $xi, $yi, $xi + $ri, $yi);
							$cr->fill;

							#app icon
							Gtk3::Gdk::cairo_set_source_pixbuf($cr, $icon, $xi + $font_size, $yi + $font_size);
							$cr->paint;

							#draw the pango layout
							$cr->move_to($xi + $font_size * 2 + $icon->get_width, $yi + $font_size);
							Pango::Cairo::show_layout($cr, $layout);

						}

					} else {

						#create small frame
						$cr->set_source_rgba($sel_bg->red, $sel_bg->green, $sel_bg->blue, 0.75);
						$cr->set_line_width(6);
						$cr->rectangle(0, 0, $w, $h);
						$cr->stroke;
					}

					#no compositing
				} else {

					#fill window
					$cr->set_operator('over');
					$cr->set_source_rgba($sel_bg->red, $sel_bg->green, $sel_bg->blue, 0.75);
					$cr->paint;

					#Parent window with text and icon
					if ($self->{_c}{'cw'}{'is_parent'}) {

						if ($lw <= $w && $lh <= $h) {

							#app icon
							Gtk3::Gdk::cairo_set_source_pixbuf($cr, $icon, $xi + $font_size, $yi + $font_size);
							$cr->paint;

							#draw the pango layout
							$cr->move_to($xi + $font_size * 2 + $icon->get_width, $yi + $font_size);
							Pango::Cairo::show_layout($cr, $layout);
						}

					}

					my $shape_region1 = Cairo::Region->create({
						x=>0, y=>0, width=>$w, height=>$h,
					});
					my $shape_region2 = Cairo::Region->create({
						x=>3, y=>3, width=>$w - 6, height=>$h - 6,
					});
					my $shape_region3 = Cairo::Region->create({
						x=>$xi, y=>$yi, width=>$wi, height=>$hi,
					});

					#Parent window with text and icon
					if ($self->{_c}{'cw'}{'is_parent'}) {
						if ($lw <= $w && $lh <= $h) {
							$shape_region2->subtract($shape_region3);
						}
					}

					$shape_region1->subtract($shape_region2);
					$self->{_highlighter}->get_window->shape_combine_region($shape_region1, 0, 0);

				}

				return TRUE;
			});

	}

	bless $self, $class;
	return $self;
}

#~ sub DESTROY {
#~ my $self = shift;
#~ print "$self dying at\n";
#~ }
#~

sub find_wm_window {
	my $self = shift;
	my $xid  = shift;

	do {
		my ($qroot, $qparent, @qkids) = $self->{_x11}->QueryTree($xid);
		return undef unless ($qroot || $qparent);
		return $xid if ($qroot == $qparent);
		$xid = $qparent;
	} while (TRUE);
}

sub get_shape {
	my $self      = shift;
	my $xid       = shift;
	my $orig      = shift;
	my $l_cropped = shift;
	my $r_cropped = shift;
	my $t_cropped = shift;
	my $b_cropped = shift;

	print "$l_cropped, $r_cropped, $t_cropped, $b_cropped cropped\n" if $self->{_sc}->get_debug;

	print "Calculating window shape\n" if $self->{_sc}->get_debug;

	#check if extenstion is available and use it
	my ($ordering, @r) = (undef, undef);
	if ($self->{_x11}{ext_shape}) {
		($ordering, @r) = $self->{_x11}->ShapeGetRectangles($self->find_wm_window($xid), 'Bounding');
	}

	my $manually_shaped = FALSE;

	#create shape manually when option is set and the shape was not detected automatically
	if (scalar @r <= 1 && defined $self->{_auto_shape} && $self->{_auto_shape}) {

		my $shf = Shutter::App::HelperFunctions->new($self->{_sc});

		my $shape_path = undef;
		$shape_path = $self->{_sc}->get_root . "/share/shutter/resources/conf/shape.conf" if $shf->file_exists($self->{_sc}->get_root . "/share/shutter/resources/conf/shape.conf");
		$shape_path = "$ENV{'HOME'}/.shutter/shape.conf" if $shf->file_exists("$ENV{'HOME'}/.shutter/shape.conf");

		if (defined $shape_path && $shape_path) {

			my @fregion;

			my $fh = new IO::File;
			if ($fh->open("< $shape_path")) {
				while (my $line = <$fh>) {

					#skip on comments
					next if $line =~ /^#/;
					chomp($line);
					push @fregion, $line;
				}
				$fh->close;
			} else {
				print "Unable to open file $shape_path" if $self->{_sc}->get_debug;
				return $orig;
			}

			print "Window shape not detected - using $shape_path\n" if $self->{_sc}->get_debug;

			#remove current entry
			pop @r;

			my $width  = $orig->get_width;
			my $height = $orig->get_height;

			foreach my $line (@fregion) {
				$line =~ s/width/$width/;
				$line =~ s/height/$height/;
				$line =~ s/(\d+)-(\d+)/$1-$2/eg;
				my @temp = split(' ', $line);
				push @r, \@temp;
			}

			$manually_shaped = TRUE;

		} else {

			print "Unable to locate shape.conf\n" if $self->{_sc}->get_debug;

		}

		#do nothing if there are no
		#shape rectangles (or only one)
	} elsif (scalar @r <= 1) {
		return $orig;
	}

	#create a region from the bounding rectangles
	my $bregion = Cairo::Region->create;
	foreach my $r (@r) {
		my @rect = @{$r};

		next unless defined $rect[0];
		next unless defined $rect[1];
		next unless defined $rect[2];
		next unless defined $rect[3];

		unless ($manually_shaped) {

			#adjust rectangle if window is only partially visible
			if ($l_cropped) {
				$rect[2] -= $l_cropped - $rect[0];
				$rect[0] = 0;
			}
			if ($t_cropped) {
				$rect[3] -= $t_cropped - $rect[1];
				$rect[1] = 0;
			}
		}

		print "Current $rect[0],$rect[1],$rect[2],$rect[3]\n" if $self->{_sc}->get_debug;
		$bregion->union_rectangle({x=>$rect[0], y=>$rect[1], width=>$rect[2], height=>$rect[3]});
	}

	if (defined $orig) {

		#create target pixbuf with dimensions if selected/current window
		my $target = Gtk3::Gdk::Pixbuf->new($orig->get_colorspace, TRUE, 8, $orig->get_width, $orig->get_height);

		#whole pixbuf is transparent
		$target->fill(0x00000000);

		#copy all rectangles of bounding region to the target pixbuf
		my $len = $bregion->num_rectangles-1;
		for my $i (0..$len) {
			my $r = $bregion->get_rectangle($i);
			print $r->{x} . " " . $r->{y} . " " . $r->{width} . " " . $r->{height} . "\n" if $self->{_sc}->get_debug;

			next if ($r->{x} > $orig->get_width);
			next if ($r->{y} > $orig->get_height);

			$r->{width} = $orig->get_width - $r->{x}   if ($r->{x} + $r->{width} > $orig->get_width);
			$r->{height} = $orig->get_height - $r->{y} if ($r->{y} + $r->{height} > $orig->get_height);

			if ($r->{x} >= 0 && $r->{x} + $r->{width} <= $orig->get_width && $r->{y} >= 0 && $r->{y} + $r->{height} <= $orig->get_height) {
				$orig->copy_area($r->{x}, $r->{y}, $r->{width}, $r->{height}, $target, $r->{x}, $r->{y});
			} else {
				warn "WARNING: There was an error while calculating the window shape\n";
				return $orig;
			}
		}

		return $target;
	} else {
		return $bregion;
	}

}

sub get_window_size {
	my ($self, $wnck_window, $gdk_window, $border, $no_resize) = @_;

	#windowresize is active
	if ($self->{_mode} eq "window" || $self->{_mode} eq "tray_window" || $self->{_mode} eq "awindow" || $self->{_mode} eq "tray_awindow") {
		unless ($no_resize) {
			if (defined $self->{_windowresize} && $self->{_windowresize}) {

				#windows can usually not be resized when maximized
				if ($wnck_window->is_maximized) {
					$wnck_window->unmaximize;
				}

				$self->quit_eventh_only;

				Glib::Timeout->add(
					$self->{_hide_time},
					sub {
						Gtk3->main_quit;
						return FALSE;
					});
				Gtk3->main();

				my ($xc, $yc, $wc, $hc) = $self->get_window_size($wnck_window, $gdk_window, $border, TRUE);

				if (defined $self->{_windowresize_w} && $self->{_windowresize} > 0) {
					$wc = $self->{_windowresize_w};
				}

				if (defined $self->{_windowresize_h} && $self->{_windowresize_h} > 0) {
					$hc = $self->{_windowresize_h};
				}

				if ($border) {
					$wnck_window->set_geometry('current', [qw/width height/], $xc, $yc, $wc, $hc);
				} else {
					$gdk_window->resize($wc, $hc);
				}

				Glib::Timeout->add(
					$self->{_hide_time},
					sub {
						Gtk3->main_quit;
						return FALSE;
					});
				Gtk3->main();
			}
		}
	}

	#calculate size of the window
	my ($xp, $yp, $wp, $hp) = (0, 0, 0, 0);
	if ($border) {
		($xp, $yp, $wp, $hp) = $wnck_window->get_geometry;
	} else {
		($xp, $yp, $wp, $hp) = $gdk_window->get_geometry;
		($xp, $yp) = $gdk_window->get_origin;
	}

	return ($xp, $yp, $wp, $hp);
}

sub update_highlighter {
	my $self = shift;

	if (defined $self->{_c}{'cw'}{'gdk_window'} && defined $self->{_c}{'cw'}{'window'}) {

		#and show highlighter window at current cursor position
		$self->{_highlighter}->show_all;
		$self->{_highlighter}->queue_draw;

		Gtk3::Gdk::keyboard_grab($self->{_highlighter}->get_window, 0, Gtk3::get_current_event_time());

		#save last window objects
		$self->{_c}{'lw'}{'window'}     = $self->{_c}{'cw'}{'window'};
		$self->{_c}{'lw'}{'gdk_window'} = $self->{_c}{'cw'}{'gdk_window'};

	}

}

sub find_current_parent_window {
	my $self             = shift;
	my $event            = shift;
	my $active_workspace = shift;

	#get all toplevel windows
	my @wnck_windows = @{$self->{_wnck_screen}->get_windows_stacked};

	#show user-visible windows only when selecting a window
	if (defined $self->{_show_visible} && $self->{_show_visible}) {
		@wnck_windows = reverse @wnck_windows;
	}

	foreach my $cwdow (@wnck_windows) {

		my $drawable = Gtk3::GdkX11::X11Window->foreign_new_for_display(Gtk3::Gdk::Display::get_default(), $cwdow->get_xid);
		if (defined $drawable) {

			#do not detect shutter window when it is hidden
			if ($self->{_main_gtk_window}->get_window && $self->{_is_hidden}) {
				next if ($cwdow->get_xid == $self->{_main_gtk_window}->get_window->get_xid);
			}

			my ($xp, $yp, $wp, $hp) = $self->get_window_size($cwdow, $drawable, $self->{_include_border}, TRUE);

			my $wr = Cairo::Region->create({
				x=>$xp, y=>$yp, width=>$wp, height=>$hp,
			});

			if (   $cwdow->is_visible_on_workspace($active_workspace)
				&& $wr->contains_point($event->x * $self->{_dpi_scale}, $event->y * $self->{_dpi_scale})
				&& $wp * $hp <= $self->{_min_size})
			{

				$self->{_c}{'cw'}{'window'}     = $cwdow;
				$self->{_c}{'cw'}{'gdk_window'} = $drawable;
				$self->{_c}{'cw'}{'x'}          = $xp;
				$self->{_c}{'cw'}{'y'}          = $yp;
				$self->{_c}{'cw'}{'width'}      = $wp;
				$self->{_c}{'cw'}{'height'}     = $hp;
				$self->{_c}{'cw'}{'is_parent'}  = TRUE;
				$self->{_min_size}              = $wp * $hp;

				#show user-visible windows only when selecting a window
				if (defined $self->{_show_visible} && $self->{_show_visible}) {
					last;
				}

			}    #size and geometry check

		}    #not defined gdk::window

	}    #end if toplevel window loop

	return TRUE;
}

sub find_current_child_window {
	my ($self, $event, $xwindow, $xparent, $depth, $limit, $type_hint) = @_;

	#reparenting depth and recursion limit
	$depth = 0 unless defined $depth;
	$limit = 0 unless defined $limit;
	if ($depth > $limit) {
		return TRUE;
	}

	my ($qroot, $qparent, @qkids);
	unless (defined $self->{_c}{'cw'}{$xwindow} && scalar @{$self->{_c}{'cw'}{$xwindow}}) {

		#query all child windows of xwindow
		($qroot, $qparent, @qkids) = $self->{_x11}->QueryTree($xwindow);

		#and save them, so we don't have to query them again
		@{$self->{_c}{'cw'}{$xwindow}} = @qkids;

	} else {

		#we can use the cached children information
		@qkids = @{$self->{_c}{'cw'}{$xwindow}};

	}

	foreach my $kid (reverse @qkids) {

		my $gdk_window = Gtk3::GdkX11::X11Window->foreign_new_for_display(Gtk3::Gdk::Display::get_default(), $kid);
		if (defined $gdk_window) {

			#window needs to be viewable and visible
			next unless $gdk_window->is_visible;
			next unless $gdk_window->is_viewable;

			#check type_hint
			if (defined $type_hint) {
				my $curr_type_hint = $gdk_window->get_type_hint;
				next unless $curr_type_hint =~ /$type_hint/;
			}

			#~ print $curr_type_hint, " - passed \n";

			#min size
			my ($xp, $yp, $wp, $hp, $depthp) = $gdk_window->get_geometry;
			($xp, $yp) = $gdk_window->get_origin;
			next if ($wp * $hp < 4);

			my $sr = Cairo::Region->create({x=>$xp, y=>$yp, width=>$wp, height=>$hp});

			if ($sr->contains_point($event->x, $event->y) && $wp * $hp <= $self->{_min_size}) {

				$self->{_c}{'cw'}{'gdk_window'} = $gdk_window;
				$self->{_c}{'cw'}{'x'}          = $xp;
				$self->{_c}{'cw'}{'y'}          = $yp;
				$self->{_c}{'cw'}{'width'}      = $wp;
				$self->{_c}{'cw'}{'height'}     = $hp;
				$self->{_c}{'cw'}{'is_parent'}  = FALSE;
				$self->{_min_size}              = $wp * $hp;

				#~ print $self->{_c}{'cw'}{'x'}, " - ",
				#~ $self->{_c}{'cw'}{'y'}, " - ",
				#~ $self->{_c}{'cw'}{'width'}, " - ",
				#~ $self->{_c}{'cw'}{'height'}, " \n " if $self->{_sc}->get_debug;

				#check next depth
				unless ($gdk_window->get_xid == $xwindow) {
					$self->find_current_child_window($event, $gdk_window->get_xid, $xparent, $depth++, $limit, $type_hint);
				} else {
					last;
				}

				#~ last;

			}
		}
	}

	return TRUE;
}

sub find_active_window {
	my $self = shift;

	my $gdk_window = $self->{_gdk_screen}->get_active_window;

	if (defined $gdk_window) {
		my $wnck_window = Wnck::Window::get($gdk_window->get_xid);
		if (defined $wnck_window) {
			return ($wnck_window, $gdk_window);
		}
	}

	return FALSE;
}

sub find_region_for_window_type {
	my ($self, $xwindow, $type_hint) = @_;

	#XQueryTree - query window tree information
	my ($qroot, $qparent, @qkids) = $self->{_x11}->QueryTree($xwindow);

	foreach my $kid (reverse @qkids) {

		my $gdk_window = Gtk3::GdkX11::X11Window->foreign_new_for_display(Gtk3::Gdk::Display::get_default(), $kid);

		if (defined $gdk_window) {

			#check type_hint
			my $curr_type_hint = $gdk_window->get_type_hint;
			if (defined $type_hint) {
				next unless $curr_type_hint =~ /$type_hint/;
			}

			#XGetWindowAttributes, XGetGeometry, XWindowAttributes - get current
			#window attribute or geometry and current window attributes structure
			my @atts = $self->{_x11}->GetWindowAttributes($kid);
			return unless @atts;

			#window needs to be viewable
			return FALSE unless $atts[19] eq 'Viewable';

			#min size
			my ($xp, $yp, $wp, $hp, $depthp) = $gdk_window->get_geometry;
			($xp, $yp) = $gdk_window->get_origin;

			#~ print $xp, " - ", $yp, " - ", $wp, " - ", $hp, "\n";

			#create region
			my $sr = Cairo::Region->create({x=>$xp, y=>$yp, width=>$wp * $self->{_dpi_scale}, height=>$hp * $self->{_dpi_scale}});

			#init region
			unless (defined $self->{_c}{'cw'}{'window_region'}) {
				$self->{_c}{'cw'}{'window_region'} = Cairo::Region->create;
			}
			$self->{_c}{'cw'}{'window_region'}->union($sr);

			#store clipbox geometry
			#~ my $cbox = $self->{_c}{'cw'}{'window_region'}->get_clipbox;
			my $cbox = $self->get_clipbox($self->{_c}{'cw'}{'window_region'});

			$self->{_c}{'cw'}{'gdk_window'} = $gdk_window;
			$self->{_c}{'cw'}{'x'}          = $cbox->{x};
			$self->{_c}{'cw'}{'y'}          = $cbox->{y};
			$self->{_c}{'cw'}{'width'}      = $cbox->{width};
			$self->{_c}{'cw'}{'height'}     = $cbox->{height};
			$self->{_c}{'cw'}{'is_parent'}  = FALSE;

			#~ print $self->{_c}{'cw'}{'x'}, " - ",
			#~ $self->{_c}{'cw'}{'y'}, " - ",
			#~ $self->{_c}{'cw'}{'width'}, " - ",
			#~ $self->{_c}{'cw'}{'height'}, " \n ";
		}
	}

	return TRUE;
}

sub select_window {
	my $self             = shift;
	my $event            = shift;
	my $active_workspace = shift;

	#select child window
	my $depth     = shift;
	my $limit     = shift;
	my $type_hint = shift;

	#root window size is minimum at startup
	$self->{_min_size} = $self->{_root}->{w} * $self->{_root}->{h} * $self->{_dpi_scale} * $self->{_dpi_scale};

	#if there is no window already selected
	unless ($self->{_c}{'ws'}) {

		$self->find_current_parent_window($event, $active_workspace);

		#parent window selected/no grab, search for children now
	} elsif (($self->{_mode} eq "section" || $self->{_mode} eq "tray_section") && $self->{_c}{'ws'}) {

		$self->find_current_child_window($event, $self->{_c}{'ws'}->get_xid, $self->{_c}{'ws'}->get_xid, $depth, $limit, $type_hint);
	}

	#draw highlighter if needed
	if (   (Gtk3::Gdk::pointer_is_grabbed() && ($self->{_c}{'lw'}{'gdk_window'} ne $self->{_c}{'cw'}{'gdk_window'}))
		|| (Gtk3::Gdk::pointer_is_grabbed() && $self->{_c}{'ws_init'}))
	{
		$self->update_highlighter();

		#reset flag
		$self->{_c}{'ws_init'} = FALSE;
	}

	return TRUE;
}

sub window {
	my $self = shift;

	#return value

	my $output = 5;

	#current workspace
	my $active_workspace = $self->{_wnck_screen}->get_active_workspace;

	#something went wrong here, no active workspace detected
	unless ($active_workspace) {
		$output = 0;
		return $output;
	}

	#grab pointer and keyboard
	#when mode is section or window
	unless ($self->{_mode} eq "menu"
		|| $self->{_mode} eq "tray_menu"
		|| $self->{_mode} eq "tooltip"
		|| $self->{_mode} eq "tray_tooltip"
		|| $self->{_mode} eq "awindow"
		|| $self->{_mode} eq "tray_awindow")
	{

		$self->{_highlighter}->realize;

		my $grab_counter = 0;
		while (!Gtk3::Gdk::pointer_is_grabbed() && $grab_counter < 100) {
			Gtk3::Gdk::pointer_grab($self->{_root}, FALSE, [qw/pointer-motion-mask button-press-mask button-release-mask/], undef, Gtk3::Gdk::Cursor->new('GDK_HAND2'), Gtk3::get_current_event_time());
			Gtk3::Gdk::keyboard_grab($self->{_highlighter}->get_window, 0, Gtk3::get_current_event_time());
			$grab_counter++;
		}

	}

	#init
	$self->{_c}                     = ();
	$self->{_c}{'ws'}               = undef;
	$self->{_c}{'ws_init'}          = FALSE;
	$self->{_c}{'lw'}{'gdk_window'} = 0;

	#root window size is minimum at startup
	$self->{_min_size}              = $self->{_root}->{w} * $self->{_root}->{h} * $self->{_dpi_scale} * $self->{_dpi_scale};
	$self->{_c}{'cw'}{'gdk_window'} = $self->{_root};
	$self->{_c}{'cw'}{'x'}          = $self->{_root}->{x};
	$self->{_c}{'cw'}{'y'}          = $self->{_root}->{y};
	$self->{_c}{'cw'}{'width'}      = $self->{_root}->{w};
	$self->{_c}{'cw'}{'height'}     = $self->{_root}->{h};

	#get initial window under cursor
	my ($window_at_pointer, $initx, $inity, $mask) = $self->{_root}->get_pointer;

	#create event for current coordinates
	my $initevent = Gtk3::Gdk::Event->new('motion-notify');
	$initevent->time(Gtk3::get_current_event_time());
	$initevent->window($self->{_root});
	$initevent->x($initx);
	$initevent->y($inity);

	if (
		Gtk3::Gdk::pointer_is_grabbed()
		&& !(
			   $self->{_mode} eq "menu"
			|| $self->{_mode} eq "tray_menu"
			|| $self->{_mode} eq "tooltip"
			|| $self->{_mode} eq "tray_tooltip"
			|| $self->{_mode} eq "awindow"
			|| $self->{_mode} eq "tray_awindow"
		))
	{

		#simulate mouse movement
		$self->select_window($initevent, $active_workspace);

		Gtk3::Gdk::Event::handler_set(
			sub {
				my ($event, $data) = @_;
				return FALSE unless defined $event;

				#KEY-PRESS
				if ($event->type eq 'key-press') {
					next unless defined $event->keyval;

					if ($event->keyval == Gtk3::Gdk::keyval_from_name('Escape')) {

						#destroy highlighter window
						$self->{_highlighter}->destroy;

						$self->quit;

						$output = 5;
					}

					#BUTTON-PRESS
				} elsif ($event->type eq 'button-press') {
					print "Type: " . $event->type . "\n"
						if (defined $event && $self->{_sc}->get_debug);

					#user selects window or section
					$self->select_window($event, $active_workspace);

					#BUTTON-RELEASE
				} elsif ($event->type eq 'button-release') {
					print "Type: " . $event->type . "\n" if (defined $event && $self->{_sc}->get_debug);

					my ($xp, $yp, $wp, $hp, $xc, $yc, $wc, $hc) = (0, 0, 0, 0, 0, 0, 0, 0);

					if (defined $self->{_c}{'lw'} && $self->{_c}{'lw'}{'gdk_window'}) {

						#size (we need to do this again because of autoresizing)
						if (($self->{_mode} eq "window" || $self->{_mode} eq "tray_window" || $self->{_mode} eq "awindow" || $self->{_mode} eq "tray_awindow")) {
							($xc, $yc, $wc, $hc) = $self->get_window_size($self->{_c}{'lw'}{'window'}, $self->{_c}{'lw'}{'gdk_window'}, $self->{_include_border}, TRUE);
							($xp, $yp, $wp, $hp) = $self->get_window_size($self->{_c}{'lw'}{'window'}, $self->{_c}{'lw'}{'gdk_window'}, $self->{_include_border});

							$self->{_c}{'cw'}{'x'}      = $xp;
							$self->{_c}{'cw'}{'y'}      = $yp;
							$self->{_c}{'cw'}{'width'}  = $wp;
							$self->{_c}{'cw'}{'height'} = $hp;
						}

						#focus selected window (maybe it is hidden)
						$self->{_c}{'lw'}{'gdk_window'}->focus($event->time);
						Gtk3::Gdk::flush();

						#something went wrong here, no window on screen detected
					} else {

						$output = 0;
						$self->quit;
						return $output;

					}

					#looking for a section of a window?
					#keep current window in mind and search for children
					if (($self->{_mode} eq "section" || $self->{_mode} eq "tray_section")
						&& !$self->{_c}{'ws'})
					{

						#mark as selected parent window
						$self->{_c}{'ws'}      = $self->{_c}{'cw'}{'gdk_window'};
						$self->{_c}{'ws_init'} = TRUE;

						#and select current subwindow
						$self->select_window($event);

						#we don't take the screenshot yet
						return TRUE;
					}

					#stop event handler
					$self->quit_eventh_only;

					#destroy highlighter window
					$self->{_highlighter}->destroy;

					#A short timeout to give the server a chance to
					#redraw the area
					Glib::Timeout->add(
						$self->{_hide_time},
						sub {
							Gtk3->main_quit;
							return FALSE;
						});
					Gtk3->main();

					my ($output_new, $l_cropped, $r_cropped, $t_cropped, $b_cropped) =
						$self->get_pixbuf_from_drawable($self->{_root}, $self->{_c}{'cw'}{'x'} / $self->{_dpi_scale}, $self->{_c}{'cw'}{'y'} / $self->{_dpi_scale}, $self->{_c}{'cw'}{'width'}/$self->{_dpi_scale}, $self->{_c}{'cw'}{'height'}/$self->{_dpi_scale});

					#save return value to current $output variable
					#-> ugly but fastest and safest solution now
					$output = $output_new;

					#respect rounded corners of wm decorations (metacity for example - does not work with compiz currently)
					if ($self->{_include_border}) {
						my $xid = $self->{_c}{'cw'}{'gdk_window'}->get_xid;

						#do not try this for child windows
						foreach my $win (@{$self->{_wnck_screen}->get_windows}) {
							if ($win->get_xid == $xid) {
								$output = $self->get_shape($xid, $output, $l_cropped, $r_cropped, $t_cropped, $b_cropped);
								last;
							}
						}
					}

					#restore window size when autoresizing was used
					if ($self->{_mode} eq "window" || $self->{_mode} eq "tray_window" || $self->{_mode} eq "awindow" || $self->{_mode} eq "tray_awindow") {
						if (defined $self->{_windowresize} && $self->{_windowresize}) {
							if ($wc != $wp || $hc != $hp) {
								if ($self->{_include_border}) {
									$self->{_c}{'lw'}{'window'}->set_geometry('current', [qw/width height/], $xc, $yc, $wc, $hc);
								} else {
									$self->{_c}{'lw'}{'gdk_window'}->resize($wc, $hc);
								}
							}
						}
					}

					#set name of the captured window
					#e.g. for use in wildcards
					if ($output =~ /Gtk3/ && defined $self->{_c}{'cw'}{'window'}) {
						$self->{_action_name} = $self->{_c}{'cw'}{'window'}->get_name;
					}

					#set history object
					$self->{_history} = Shutter::Screenshot::History->new(
						$self->{_sc},           $self->{_root},                       $self->{_c}{'cw'}{'x'},
						$self->{_c}{'cw'}{'y'}, $self->{_c}{'cw'}{'width'},           $self->{_c}{'cw'}{'height'},
						undef,                  $self->{_c}{'cw'}{'window'}->get_xid, $self->{_c}{'cw'}{'gdk_window'}->get_xid
					);

					$self->quit;

					#MOTION-NOTIFY
				} elsif ($event->type eq 'motion-notify') {
					print "Type: " . $event->type . "\n"
						if (defined $event && $self->{_sc}->get_debug);

					#user selects window or section
					$self->select_window($event, $active_workspace);

				} else {
					Gtk3::main_do_event($event);
				}
			});

		Gtk3->main;

		#pointer not grabbed
	} else {

		$output = 0;

		my ($xp, $yp, $wp, $hp, $xc, $yc, $wc, $hc) = (0, 0, 0, 0, 0, 0, 0, 0);

		if (($self->{_mode} eq "window" || $self->{_mode} eq "tray_window" || $self->{_mode} eq "awindow" || $self->{_mode} eq "tray_awindow")) {

			#and select current parent window
			my ($wnck_window, $gdk_window) = $self->find_active_window;

			if (defined $wnck_window && $wnck_window && defined $gdk_window && $gdk_window) {

				#get_size of it
				($xc, $yc, $wc, $hc) = $self->get_window_size($wnck_window, $gdk_window, $self->{_include_border}, TRUE);
				($xp, $yp, $wp, $hp) = $self->get_window_size($wnck_window, $gdk_window, $self->{_include_border});

				$self->{_c}{'cw'}{'window'}     = $wnck_window;
				$self->{_c}{'cw'}{'gdk_window'} = $gdk_window;
				$self->{_c}{'cw'}{'x'}          = $xp;
				$self->{_c}{'cw'}{'y'}          = $yp;
				$self->{_c}{'cw'}{'width'}      = $wp;
				$self->{_c}{'cw'}{'height'}     = $hp;
				$self->{_c}{'cw'}{'is_parent'}  = TRUE;

			}

		} elsif (($self->{_mode} eq "menu" || $self->{_mode} eq "tray_menu")) {

			#and select current menu
			$self->find_region_for_window_type($self->{_root}->get_xid, 'menu');

			#no window with type_hint eq 'menu' detected
			unless (defined $self->{_c}{'cw'}{'window_region'}) {
				if ($self->{_ignore_type}) {
					warn "WARNING: No window with type hint 'menu' detected -> window type hint will be ignored, because workaround is enabled\n";
					$self->find_region_for_window_type($self->{_root}->get_xid);
				} else {
					return 2;
				}
			}

		} elsif (($self->{_mode} eq "tooltip" || $self->{_mode} eq "tray_tooltip")) {

			#and select current tooltip
			$self->find_region_for_window_type($self->{_root}->get_xid, 'tooltip');

			#no window with type_hint eq 'tooltip' detected
			unless (defined $self->{_c}{'cw'}{'window_region'}) {
				if ($self->{_ignore_type}) {
					warn "WARNING: No window with type hint 'tooltip' detected -> window type hint will be ignored, because workaround is enabled\n";
					$self->find_region_for_window_type($self->{_root}->get_xid);
				} else {
					return 2;
				}
			}

			#looking for a section of a window?
			#keep current window in mind and search for children
		} elsif (($self->{_mode} eq "section" || $self->{_mode} eq "tray_section")) {

			#mark as selected parent window
			$self->{_c}{'ws'} = $self->{_root};

			#and select current subwindow
			$self->select_window($initevent);

		}

		my ($output_new, $l_cropped, $r_cropped, $t_cropped, $b_cropped) =
			$self->get_pixbuf_from_drawable($self->{_root}, $self->{_c}{'cw'}{'x'}, $self->{_c}{'cw'}{'y'}, $self->{_c}{'cw'}{'width'}, $self->{_c}{'cw'}{'height'},
			$self->{_c}{'cw'}{'window_region'});

		#save return value to current $output variable
		#-> ugly but fastest and safest solution now
		$output = $output_new;

		#respect rounded corners of wm decorations
		#(metacity for example - does not work with compiz currently)
		#only if toplevel window was selected
		if (($self->{_mode} eq "window" || $self->{_mode} eq "tray_window" || $self->{_mode} eq "awindow" || $self->{_mode} eq "tray_awindow")) {
			if ($self->{_include_border}) {
				my $xid = $self->{_c}{'cw'}{'gdk_window'}->get_xid;

				#do not try this for child windows
				foreach my $win (@{$self->{_wnck_screen}->get_windows}) {
					if ($win->get_xid == $xid) {
						$output = $self->get_shape($xid, $output, $l_cropped, $r_cropped, $t_cropped, $b_cropped);
						last;
					}
				}
			}
		}

		#restore window size when autoresizing was used
		if ($self->{_mode} eq "window" || $self->{_mode} eq "tray_window" || $self->{_mode} eq "awindow" || $self->{_mode} eq "tray_awindow") {
			if (defined $self->{_windowresize} && $self->{_windowresize}) {
				if ($wc != $wp || $hc != $hp) {
					if ($self->{_include_border}) {
						$self->{_c}{'cw'}{'window'}->set_geometry('current', [qw/width height/], $xc, $yc, $wc, $hc);
					} else {
						$self->{_c}{'cw'}{'gdk_window'}->resize($wc, $hc);
					}
				}
			}
		}

		#set name of the captured window
		#e.g. for use in wildcards
		my $d = $self->{_sc}->get_gettext;

		if (($self->{_mode} eq "window" || $self->{_mode} eq "tray_window" || $self->{_mode} eq "awindow" || $self->{_mode} eq "tray_awindow")) {

			if ($output =~ /Gtk3/ && defined $self->{_c}{'cw'}{'window'}) {
				$self->{_action_name} = $self->{_c}{'cw'}{'window'}->get_name;
			}

		} elsif (($self->{_mode} eq "section" || $self->{_mode} eq "tray_section")) {

			if ($output =~ /Gtk3/ && defined $self->{_c}{'cw'}{'window'}) {
				$self->{_action_name} = $self->{_action_name} = $self->{_c}{'cw'}{'window'}->get_name;
			}

		} elsif (($self->{_mode} eq "menu" || $self->{_mode} eq "tray_menu")) {

			if ($output =~ /Gtk3/) {
				$self->{_action_name} = $d->get("Menu");
			}

		} elsif (($self->{_mode} eq "tooltip" || $self->{_mode} eq "tray_tooltip")) {

			if ($output =~ /Gtk3/) {
				$self->{_action_name} = $d->get("Tooltip");
			}

		}

		if (defined $self->{_c}{'cw'}{'window'} && $self->{_c}{'cw'}{'gdk_window'}) {

			#set history object
			$self->{_history} = Shutter::Screenshot::History->new(
				$self->{_sc},                       $self->{_root},                       $self->{_c}{'cw'}{'x'},
				$self->{_c}{'cw'}{'y'},             $self->{_c}{'cw'}{'width'},           $self->{_c}{'cw'}{'height'},
				$self->{_c}{'cw'}{'window_region'}, $self->{_c}{'cw'}{'window'}->get_xid, $self->{_c}{'cw'}{'gdk_window'}->get_xid
			);

		} else {

			#set history object
			$self->{_history} = Shutter::Screenshot::History->new(
				$self->{_sc}, $self->{_root},
				$self->{_c}{'cw'}{'x'},
				$self->{_c}{'cw'}{'y'},
				$self->{_c}{'cw'}{'width'},
				$self->{_c}{'cw'}{'height'},
				$self->{_c}{'cw'}{'window_region'},
			);

		}

	}
	return $output;
}

sub get_mode {
	my $self = shift;
	return $self->{_mode};
}

sub redo_capture {
	my $self   = shift;
	my $output = 3;

	if (defined $self->{_history}) {
		my ($last_drawable, $lxp, $lyp, $lwp, $lhp, $lregion, $wxid, $gxid) = $self->{_history}->get_last_capture;

		if (defined $gxid && defined $wxid) {

			#create windows
			my $gdk_window = Gtk3::GdkX11::X11Window->foreign_new_for_display(Gtk3::Gdk::Display::get_default(), $gxid);
			my $wnck_window = Wnck::Window::get($wxid);

			if (defined $gdk_window && defined $wnck_window) {

				#store size
				my ($xp, $yp, $wp, $hp, $xc, $yc, $wc, $hc) = (0, 0, 0, 0, 0, 0, 0, 0);

				if ($self->{_mode} eq "section" || $self->{_mode} eq "tray_section") {

					($xp, $yp, $wp, $hp) = $gdk_window->get_geometry;
					($xp, $yp) = $gdk_window->get_origin;

					#find parent window
					my $pxid   = $self->find_wm_window($gxid);
					my $parent = Gtk3::GdkX11::X11Window->foreign_new_for_display(Gtk3::Gdk::Display::get_default(), $pxid);
					if (defined $parent && $parent) {

						#and focus parent window (maybe it is hidden)
						$parent->focus(Gtk3::get_current_event_time());
						Gtk3::Gdk::flush();
					}

				} elsif ($self->{_mode} eq "window" || $self->{_mode} eq "tray_window" || $self->{_mode} eq "awindow" || $self->{_mode} eq "tray_awindow") {

					#get_size of it
					($xc, $yc, $wc, $hc) = $self->get_window_size($wnck_window, $gdk_window, $self->{_include_border}, TRUE);
					($xp, $yp, $wp, $hp) = $self->get_window_size($wnck_window, $gdk_window, $self->{_include_border});

				}

				#focus selected window (maybe it is hidden)
				$gdk_window->focus(Gtk3::get_current_event_time());
				Gtk3::Gdk::flush();

				#A short timeout to give the server a chance to
				#redraw the area
				Glib::Timeout->add(
					$self->{_hide_time},
					sub {
						Gtk3->main_quit;
						return FALSE;
					});
				Gtk3->main();

				my ($output_new, $l_cropped, $r_cropped, $t_cropped, $b_cropped) = $self->get_pixbuf_from_drawable($self->{_root}, $xp, $yp, $wp, $hp);

				#save return value to current $output variable
				#-> ugly but fastest and safest solution now
				$output = $output_new;

				if ($self->{_mode} eq "window" || $self->{_mode} eq "tray_window" || $self->{_mode} eq "awindow" || $self->{_mode} eq "tray_awindow") {
					if ($self->{_include_border}) {
						$output = $self->get_shape($gxid, $output, $l_cropped, $r_cropped, $t_cropped, $b_cropped);
					}
				}

				#restore window size when autoresizing was used
				if ($self->{_mode} eq "window" || $self->{_mode} eq "tray_window" || $self->{_mode} eq "awindow" || $self->{_mode} eq "tray_awindow") {
					if (defined $self->{_windowresize} && $self->{_windowresize}) {
						if ($wc != $wp || $hc != $hp) {
							if ($self->{_include_border}) {
								$wnck_window->set_geometry('current', [qw/width height/], $xc, $yc, $wc, $hc);
							} else {
								$gdk_window->resize($wc, $hc);
							}
						}
					}
				}

				$self->quit_eventh_only;

			} else {
				warn "WARNING: Could not get window with id $gxid\n";
				$output = 4;
			}

			#no xid
		} else {
			($output) = $self->get_pixbuf_from_drawable($self->{_history}->get_last_capture);
		}

	}

	return $output;
}

sub get_history {
	my $self = shift;
	return $self->{_history};
}

sub get_error_text {
	my $self = shift;
	return $self->{_error_text};
}

sub get_action_name {
	my $self = shift;
	return $self->{_action_name};
}

sub quit {
	my $self = shift;

	$self->ungrab_pointer_and_keyboard(FALSE, TRUE, TRUE);
	Gtk3::Gdk::flush();

}

sub quit_eventh_only {
	my $self = shift;

	$self->ungrab_pointer_and_keyboard(FALSE, TRUE, FALSE);
	Gtk3::Gdk::flush();

}

1;
