From a3174a10a24326b568a8d9f864684f8db24f0d44 Mon Sep 17 00:00:00 2001 From: bdimych Date: Sat, 17 Oct 2009 02:36:09 +0400 Subject: [PATCH] contrib/stumpwm-windows-switcher-gtk2 --- contrib/stumpwm-windows-switcher-gtk2.lisp | 116 +++++ contrib/stumpwm-windows-switcher-gtk2.pl | 651 ++++++++++++++++++++++++++++ 2 files changed, 767 insertions(+), 0 deletions(-) create mode 100644 contrib/stumpwm-windows-switcher-gtk2.lisp create mode 100644 contrib/stumpwm-windows-switcher-gtk2.pl diff --git a/contrib/stumpwm-windows-switcher-gtk2.lisp b/contrib/stumpwm-windows-switcher-gtk2.lisp new file mode 100644 index 0000000..d9a332f --- /dev/null +++ b/contrib/stumpwm-windows-switcher-gtk2.lisp @@ -0,0 +1,116 @@ +;;; stumpwm-windows-switcher-gtk2.lisp +;;; +;;; This module 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, or (at your option) +;;; any later version. +;;; +;;; This module 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 software; see the file COPYING. If not, write to +;;; the Free Software Foundation, Inc., 59 Temple Place, Suite 330, +;;; Boston, MA 02111-1307 USA +;;; + +;;; USAGE: +;;; +;;; Put: +;;; +;;; (load-module "stumpwm-windows-switcher-gtk2") +;;; (define-key *top-map* (kbd "H-Tab") "run-stumpwm-windows-switcher-gtk2") +;;; +;;; in your ~/.stumpwmrc +;;; + + +(in-package :stumpwm) + + +;; sort frames by the right bottom corner +(defun frame-rb-x (f) (+ (frame-x f) (frame-width f))) +(defun frame-rb-y (f) (+ (frame-y f) (frame-height f))) +(defun tile-frame-list-sorted-by-right-bottom-corner () + (let ((flist (group-frames (current-group)))) + (sort flist #'(lambda (f1 f2) + (or + (< (frame-rb-x f1) (frame-rb-x f2)) + (and + (< (frame-rb-y f1) (frame-rb-y f2)) + (not (> (frame-rb-x f1) (frame-rb-x f2))) + ) + ) + )) + flist + ) + ) + + +(defcommand run-stumpwm-windows-switcher-gtk2 () () + "" + (if (not (eq 'TILE-GROUP (type-of (current-group)))) + (error "floating window group support is not yet implemented")) + (let ((stump_framelist "[")) + ;; all tile frames + (mapc + (lambda (f) + (setq + stump_framelist + (format + nil + "~a{frm_number => ~d, frm_x => ~d, frm_y => ~d, frm_width => ~d, frm_height => ~d, current => ~a, frm_windows => [" + stump_framelist + (frame-number f) + (frame-x f) + (frame-y f) + (frame-width f) + (frame-height f) + (if (eq f (tile-group-current-frame (current-group))) 1 0))) + + ;; all windows in frame f + (mapc + (lambda (w) + (setq + stump_framelist + (format + nil + "~a{wnd_xid => ~d, wnd_number => ~d, current => ~a, wnd_name => ~a},~%" + stump_framelist + (window-id w) + (window-number w) + (if (eq w (frame-window f)) 1 0) + (window-name w))) + ) + (frame-windows (current-group) f)) + + (setq stump_framelist (format nil "~a]}, " stump_framelist)) + ) + (tile-frame-list-sorted-by-right-bottom-corner)) + (setq stump_framelist (format nil "~a]" stump_framelist)) + + ;(echo-string (current-screen) stump_framelist) + (run-prog "perl" + :args (list (make-pathname :defaults *contrib-dir* + :name "stumpwm-windows-switcher-gtk2" + :type "pl") + stump_framelist) + :wait nil) + ) + ) + + +(defcommand switch-and-warp (target-frame &optional target-window-num) (:frame :string) + "" + (eval-command (format nil "fselect ~a" (frame-number target-frame))) + (if target-window-num (eval-command (concat "select-window-by-number " target-window-num))) + (let ((new-x (+ (frame-x target-frame) (floor (frame-width target-frame) 2)) ) + (new-y (+ (frame-y target-frame) (floor (frame-height target-frame) 2)) ) + ) + (warp-pointer (current-screen) new-x new-y) + ) + ) + + diff --git a/contrib/stumpwm-windows-switcher-gtk2.pl b/contrib/stumpwm-windows-switcher-gtk2.pl new file mode 100644 index 0000000..47cc5f6 --- /dev/null +++ b/contrib/stumpwm-windows-switcher-gtk2.pl @@ -0,0 +1,651 @@ +#!/usr/bin/perl +# +# This module 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, or (at your option) +# any later version. +# +# This module 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 software; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 59 Temple Place, Suite 330, +# Boston, MA 02111-1307 USA +# + +# USAGE: +# +# this script should be runned only from within stumpwm-windows-switcher-gtk2.lisp +# here you can set options +# + + +use strict; +use Data::Dumper; $Data::Dumper::Indent = 1; +use Glib qw/TRUE FALSE/; +use Gtk2 '-init'; +use Gnome2::Wnck; Gnome2::Wnck::Screen->get_default->force_update(); +use IO::Handle; +use Time::HiRes; + + + + + + + +### options + +# color names - see http://en.wikipedia.org/wiki/X11_color_names +# or '#rgb' or '#rrggbb' or '#rrrgggbbb' or '#rrrrggggbbbb' + +my $opt_icon_size = 16; +my $opt_font_size_in_points = 7.5; +my $opt_window_name_max_length = 50; +my @opt_thumbnail_coefficient_range = (0.1, 0.2); +my $opt_thumbnail_blank_border_size = 5; +my $opt_focused_thumbnail_color = 'green'; +my $opt_gtk_window_bgcolor = 'Peru'; +my $opt_gtk_window_translucent = FALSE; +my $opt_thumbnail_bgcolor = 'white'; +my $opt_where_to_show_gtk_window = 'monitor'; # possible values: 'frame', 'monitor', 'mouse' +my $opt_do_confine_mouse_pointer = TRUE; +my $opt_wrapw_change_step = 5; # the greater value the faster it will be calculated + + + + + + + +### debug +=x +BEGIN { + open DBG, ">>$0.dbg"; + DBG->autoflush(1); + + open STDERR, '>&DBG'; +} + +sub dbg { + print DBG "$_[0]\n"; +} + +dbg "=====\n" . localtime; +=cut + + + + + + + +### get stumpwm frame list + +utf8::decode($ARGV[0]); +my $stump_framelist = $ARGV[0]; +$stump_framelist =~ s|wnd_name => (.*)}|wnd_name => q\0$1\0}|g; +$stump_framelist = eval $stump_framelist; + + + + + + + + + + +### create frame thumbnails and calculate common thumbnail coefficient which will allow to display all window names with icons at once + +=a + +frame thumbnail: + + . . . . . . . . . . . . . . . + . (1) . + ._ # ______(2)________. + . |._____(3&4)_________.| . + (1)|| || || + . || ic window na || || . + || on me || || + . ||8(6)8(---7---)8|| || . + || || || + . || || 3| . + || 9| &| + . || || 4| . + || || || + . || ||5 || . + || || ||(1) + . ||_______________||__|| . + '---------------------' + . (1) . + . . . . . . . . . . . . . . . + + +(1)$opt_thumbnail_blank_border_size + +(2)$f->{gtk_frame} + | + '--(3)$scrolled_window + | + '--(4)$f->{listview} + +(5)$gtk_scrollbar_width + +(6)$opt_icon_size +(7)$wrapw +(8)$listview_additional_width + +(9)GtkScrolledWindow::scrollbar-spacing + +=cut + + +Gtk2::Rc->parse_string(' + style "scrolledwindow-style" { + GtkScrolledWindow::scrollbar-spacing = 0 + } + class "GtkScrolledWindow" style "scrolledwindow-style" +'); +my $gtk_scrollbar_width = Gtk2::VScrollbar->new->size_request->width; +my ($gtkfw, $gtkfh) = get_size_of_gtk_frame_around_thumbnail(); +my $listview_additional_width = 16; # i don't know how to determine the size of additional space which appears between and around cell renderers, its just experimental value +my $max_thumb_coef; +my $prev_frame; +foreach my $f (@{$stump_framelist}) +{ + my ($listview, $col, $renderer_i, $renderer_t, $store); + + # store window's icon, name, number to the gtk treemodel; determine initially selected_iter + $store = Gtk2::ListStore->new('Gtk2::Gdk::Pixbuf', 'Glib::String', 'Glib::String', 'Glib::Int'); + if (@{$f->{frm_windows}}) { + foreach my $w (sort {$a->{wnd_number} <=> $b->{wnd_number}} @{$f->{frm_windows}}) { + my $iter = $store->append; + my $font_weight = 400; + if ($w->{current}) { + $f->{selected_iter} = $iter; + $font_weight = 600; + } + $store->set + ( + $iter, + + 0 => Gnome2::Wnck::Window->get($w->{wnd_xid})->get_icon->scale_simple($opt_icon_size, $opt_icon_size, 'bilinear'), + + 1 => length($w->{wnd_name}) > $opt_window_name_max_length + ? substr($w->{wnd_name}, 0, $opt_window_name_max_length) . '...' + : $w->{wnd_name}, + + 2 => "$f->{frm_number} $w->{wnd_number}", + + 3 => $font_weight + ); + } + } + else { # empty stumpwm tile frame + $store->set($f->{selected_iter} = $store->append, 2 => $f->{frm_number}) + } + + # create renderers for icon and name + $col = Gtk2::TreeViewColumn->new; + + $renderer_i = Gtk2::CellRendererPixbuf->new; + $col->pack_start($renderer_i, FALSE); + $col->add_attribute($renderer_i, 'pixbuf', 0); + + $renderer_t = Gtk2::CellRendererText->new; + $renderer_t->set('wrap-mode', 'word'); + $renderer_t->set('size-points', $opt_font_size_in_points); + $col->pack_start($renderer_t, FALSE); + $col->add_attribute($renderer_t, 'text', 1); + $col->add_attribute($renderer_t, 'weight', 3); + + # determine minimal listview width which will allow to display all window names and will provide correct aspect ratio + $f->{frm_aspect_ratio} = $f->{frm_width} / $f->{frm_height}; + my ($reqw, $reqh); + my $wrapw_min = + int($f->{frm_width} * $opt_thumbnail_coefficient_range[0]) + - $opt_icon_size + - $listview_additional_width + - $gtk_scrollbar_width + - $gtkfw + - $opt_thumbnail_blank_border_size * 2 + ; + if ($wrapw_min < 20) {$wrapw_min = 20} # just arbitrary + my $wrapw_max = + int($f->{frm_width} * $opt_thumbnail_coefficient_range[1]) + - $opt_icon_size + - $listview_additional_width + - $gtk_scrollbar_width + - $gtkfw + - $opt_thumbnail_blank_border_size * 2 + ; + if ($wrapw_max < $wrapw_min) {$wrapw_max = $wrapw_min} + my $aspect_ok; + for (my $wrapw = $wrapw_min; $wrapw <= $wrapw_max; $wrapw += $opt_wrapw_change_step) + { + $renderer_t->set('wrap-width', $wrapw); + + $listview = Gtk2::TreeView->new_with_model($store); + $listview->append_column($col); + $listview->set_grid_lines('horizontal'); + $listview->set_headers_visible(0); + + $reqw = $listview->size_request->width + + $listview_additional_width + + $gtkfw + + $opt_thumbnail_blank_border_size * 2 + ; + $reqh = $listview->size_request->height + + $listview_additional_width + + $gtkfh + + $opt_thumbnail_blank_border_size * 2 + ; + if ($reqw/$reqh >= $f->{frm_aspect_ratio}) { + $aspect_ok = 1; + last; + } + } + + # update $max_thumb_coef + my $this_frame_thumb_coef = $reqw / $f->{frm_width}; + if ($this_frame_thumb_coef > $opt_thumbnail_coefficient_range[1]) { # in all probability GtkTreeView has some minimal gtk_widget_size_request values which hardcoded in Gtk and cannot be reduced with wrap-width. Therefore if frame has small width, then $this_frame_thumb_coef may become big + $this_frame_thumb_coef = $opt_thumbnail_coefficient_range[1]; + $aspect_ok = '?'; + } + elsif ($this_frame_thumb_coef < $opt_thumbnail_coefficient_range[0]) { + $this_frame_thumb_coef = $opt_thumbnail_coefficient_range[0] + } + if ($max_thumb_coef < $this_frame_thumb_coef) { + $max_thumb_coef = $this_frame_thumb_coef + } + + # save to the frame hash + $f->{listview} = $listview; + $f->{listview}->modify_base('normal', Gtk2::Gdk::Color->parse($opt_thumbnail_bgcolor)); + $f->{renderer_t} = $renderer_t; + $f->{this_frame_thumb_coef} = $this_frame_thumb_coef; + $f->{aspect_ok} = $aspect_ok; + + # create prev<->next links + if ($prev_frame) { + $f->{prev_frame} = $prev_frame; + $prev_frame->{next_frame} = $f; + } + $prev_frame = $f; +} + + + + + + + + +### create main gtk dialog window + +my $gtk_window; +if ($opt_where_to_show_gtk_window eq 'frame') { + $gtk_window = Gtk2::Window->new; + $gtk_window->set_type_hint('dialog'); +} +else { + $gtk_window = Gtk2::Window->new('popup'); + if ($opt_where_to_show_gtk_window eq 'mouse') { + $gtk_window->set_position('mouse'); + } +} +# TODO: show in task bar or not ??? $gtk_window->set('skip-taskbar-hint', TRUE); +$gtk_window->set_border_width($opt_thumbnail_blank_border_size); +$gtk_window->modify_bg('normal', Gtk2::Gdk::Color->parse($opt_gtk_window_bgcolor)); +$gtk_window->signal_connect( + destroy => + sub { + ungrab_and_restore(); + Gtk2->main_quit + } + ); +$gtk_window->signal_connect( + 'key-press-event' => + sub { + my $event = $_[1]; + if ($event->hardware_keycode == 9) { # escape + ungrab_and_restore(); + Gtk2->main_quit + } + }, + ); + +my $box = Gtk2::Fixed->new; +$gtk_window->add($box); + + + + + + + + + +### set frame thumbnails sizes accordingly to the common coefficient, set event handlers to them, and add them to the gtk window + +foreach my $f (@{$stump_framelist}) +{ + # if it's needed increase window names wrap width, else we will have unused empty space and needless line breaks + if ($max_thumb_coef > $f->{this_frame_thumb_coef}) { + $f->{renderer_t}->set( + 'wrap-width', + int($f->{frm_width} * $max_thumb_coef) + - $opt_icon_size + - $listview_additional_width + - ($f->{aspect_ok}==1 ? 0 : $gtk_scrollbar_width) + - $gtkfw + - $opt_thumbnail_blank_border_size * 2 + ); + } + + # get thumbnail size + $f->{thmb_width} = int($f->{frm_width} * $max_thumb_coef); + $f->{thmb_height} = int($f->{frm_height} * $max_thumb_coef); + + # add scroll bars + my $scrolled_window = Gtk2::ScrolledWindow->new; + $scrolled_window->set_policy('automatic', 'automatic'); + $scrolled_window->add($f->{listview}); + + # set thumbnail size + $scrolled_window->set_size_request( + $f->{thmb_width} - $gtkfw - $opt_thumbnail_blank_border_size * 2, + $f->{thmb_height} - $gtkfh - $opt_thumbnail_blank_border_size * 2 + ); + + # add gtk frame around listview + my $title_label = Gtk2::Label->new; + $title_label->set_markup(" $f->{frm_number} "); + $f->{gtk_frame} = Gtk2::Frame->new; + $f->{gtk_frame}->set_label_widget($title_label); + $f->{gtk_frame}->add($scrolled_window); + + # calculate coordinates where thumbnail should be placed + $f->{thmb_x} = int($f->{frm_x} * $max_thumb_coef); + $f->{thmb_y} = int($f->{frm_y} * $max_thumb_coef); + + # add all to the gtk window + $box->put($f->{gtk_frame}, + $f->{thmb_x} + $opt_thumbnail_blank_border_size, + $f->{thmb_y} + $opt_thumbnail_blank_border_size + ); + + # set event handlers + $f->{listview}->signal_connect( + 'key-press-event' => + sub { + my $dest_frame; + if ($_[1]->hardware_keycode == 23) { # tab + if ($_[1]->state =~ /shift-mask/) { + $_[1]->hardware_keycode(113); + } + else { + $_[1]->hardware_keycode(114); + } + $_[1]->set_state('release-mask'); # just to delete shift-mask + } + if ($_[1]->hardware_keycode == 113) { # left arrow + if ($f->{prev_frame}) {$dest_frame = $f->{prev_frame}} + } + elsif ($_[1]->hardware_keycode == 114) { # right arrow + if ($f->{next_frame}) {$dest_frame = $f->{next_frame}} + } + if ($dest_frame) + { + goto_frame_thumbnail($dest_frame); + return TRUE + } + } + ); + $f->{listview}->signal_connect( + 'focus-in-event' => + sub { + highlight_focused_thumbnail($f) + } + ); + $f->{listview}->signal_connect( + 'focus-out-event' => + sub { + unhighlight_focused_thumbnail($f); + + $f->{selected_iter} = $f->{listview}->get_selection->get_selected; + $f->{listview}->get_selection->unselect_all; + } + ); + $f->{listview}->signal_connect( + 'row-activated' => + sub { + ungrab_keyboard_and_mouse(); + my ($model, $iter) = $_[0]->get_selection->get_selected; + my $frm_number_and_wnd_number = $model->get($iter, 2); + system 'stumpish', 'switch-and-warp', $frm_number_and_wnd_number; + Gtk2->main_quit + } + ); + + # set focus if frame is current + if ($f->{current}) { + our $cur_frame = $f; + goto_frame_thumbnail($f); + } + else { + goto_selected_iter($f, 0); + } +} + + + + + + + +### set box size and, if needed, make borders of thumbnails transparent + +my $bottom_right_thumb = $stump_framelist->[-1]; +my $main_width = $bottom_right_thumb->{thmb_x} + $bottom_right_thumb->{thmb_width}; +my $main_height = $bottom_right_thumb->{thmb_y} + $bottom_right_thumb->{thmb_height}; +$box->set_size_request($main_width, $main_height); + +goto skip_make_translucent if not $opt_gtk_window_translucent; + +my $mask_bitmap = Gtk2::Gdk::Pixmap->new(undef, $main_width, $main_height, 1); +my $gc = Gtk2::Gdk::GC->new($mask_bitmap); + +$gc->set_rgb_foreground(0); +$mask_bitmap->draw_rectangle($gc, TRUE, 0, 0, $main_width, $main_height); + +$gc->set_rgb_foreground(255 *256*256 + 255 *256 + 255); +foreach my $f (@{$stump_framelist}) +{ + $mask_bitmap->draw_rectangle($gc, + TRUE, + + $f->{thmb_x} + $opt_thumbnail_blank_border_size + + $opt_thumbnail_blank_border_size, # gtk_window border + + $f->{thmb_y} + $opt_thumbnail_blank_border_size + + $opt_thumbnail_blank_border_size, # gtk_window border + + $f->{thmb_width} - 2*$opt_thumbnail_blank_border_size, + $f->{thmb_height} - 2*$opt_thumbnail_blank_border_size); +} + +$gtk_window->shape_combine_mask($mask_bitmap, 0, 0); + +skip_make_translucent: + + + + + + + + +### show window and enter gtk's main loop +if ($opt_where_to_show_gtk_window eq 'monitor') { + our $cur_frame; + my $screen = $gtk_window->get_screen; + my $monitor = $screen->get_monitor_at_point($cur_frame->{frm_x}, $cur_frame->{frm_y}); + my $monitor_geom = $screen->get_monitor_geometry($monitor); + $gtk_window->move( + int($monitor_geom->x + ($monitor_geom->width - $main_width)/2), + int($monitor_geom->y + ($monitor_geom->height - $main_height)/2), + ); +} +$gtk_window->show_all; +grab_keyboard_and_mouse(); +Gtk2->main; + + + + + + + +### SUBS + +sub goto_frame_thumbnail { + my $frame = shift; + $frame->{listview}->grab_focus; + goto_selected_iter($frame, 1); +} + +sub goto_selected_iter { + my $frame = shift; + my $do_select = shift; + + my $iter = $frame->{selected_iter}; + my $path = $frame->{listview}->get_model->get_path($iter); + if ($do_select) { + $frame->{listview}->set_cursor($path); + } + $frame->{listview}->scroll_to_cell($path); +} + +sub highlight_focused_thumbnail { + my $frame = shift; + + return if $frame->{highlighted}; + $frame->{highlighted} = 1; + + $frame->{gtk_frame}->modify_bg('normal', Gtk2::Gdk::Color->parse($opt_focused_thumbnail_color)); + my $markup = $frame->{gtk_frame}->get_label_widget->get_label; + $markup =~ s/{gtk_frame}->get_label_widget->set_markup($markup); +} + +sub unhighlight_focused_thumbnail { + my $frame = shift; + + return if ! $frame->{highlighted}; + $frame->{highlighted} = 0; + + $frame->{gtk_frame}->modify_bg('normal', undef); + my $markup = $frame->{gtk_frame}->get_label_widget->get_label; + $markup =~ s/ bgcolor='$opt_focused_thumbnail_color'//; + $frame->{gtk_frame}->get_label_widget->set_markup($markup); +} + +sub get_size_of_gtk_frame_around_thumbnail { + my ($w, $h); + + # get width + my $gtk_frame = Gtk2::Frame->new; + $w = $gtk_frame->size_request->width; + + # get height + my $title_label = Gtk2::Label->new; + $title_label->set_markup("0"); + my $gtk_frame = Gtk2::Frame->new; + $gtk_frame->set_label_widget($title_label); + $gtk_frame->show_all; + $h = $gtk_frame->size_request->height; + + return($w, $h) +} + +sub grab_keyboard_and_mouse { + # + # just copied from the libgksu.c + # + + if ($opt_do_confine_mouse_pointer) { + our @pointer_position_before_confined = ($gtk_window->get_screen->get_display->get_pointer)[0..2]; + } + + $gtk_window->window->set_cursor(Gtk2::Gdk::Cursor->new("arrow")); + + my $grab_tries = 0; + while (1) { + my $status = Gtk2::Gdk->pointer_grab( + $gtk_window->window, + TRUE, + ['button-press-mask', 'button-release-mask'], + $opt_do_confine_mouse_pointer ? $gtk_window->window : undef, + undef, + 0 + ); + last if $status eq 'success'; + + Time::HiRes::usleep(250000); + if (++$grab_tries > 16) { + die("could not grab mouse") + } + } + while (1) { + my $status = Gtk2::Gdk->keyboard_grab( + $gtk_window->window, + FALSE, + 0 + ); + last if $status eq 'success'; + + Time::HiRes::usleep(250000); + if (++$grab_tries > 16) { + die("could not grab keyboard") + } + } + + $gtk_window->set_keep_above(TRUE); + + while (Gtk2->events_pending) { + Gtk2->main_iteration + } +} + +sub ungrab_keyboard_and_mouse { + Gtk2::Gdk->pointer_ungrab(0); + Gtk2::Gdk->keyboard_ungrab(0); +} + +sub ungrab_and_restore { + ungrab_keyboard_and_mouse; + if ($opt_do_confine_mouse_pointer) { + $gtk_window->get_screen->get_display->warp_pointer(our @pointer_position_before_confined); + Gtk2->main_iteration; # do warp + } +} + + + + + + + + + + + + + + -- 1.6.0.4