3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 2 of the License, or
6 # (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software
15 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 # focus-link.pl - perl FvwmCommand script
25 # -v show version number and exit.
28 # This is a user programmable window focus script.
29 # It requires FvwmCommand version 1.5 or later.
30 # FvwmCommandS must be invoked from fvwm prior to this command.
32 # This script can be invoked from a shell or from .fvwm2rc. For example.
34 # AddToFunc "InitFunction" "I" Module FvwmBanner
35 # + "I" Module FvwmPager 0 8
36 # + "I" Exec xcb -n 4 -l vertical -g 240x180-0+530 &
37 # + "I" Exec sh -c "sleep 2;$HOME/scripts/focus-link.pl & "
39 # Sleep is used in order to avoid un-necessary reaction during initial
40 # window creation. A shell is invoked to avoid fvwm itself sleeps for
43 # Default behavior is listed below.
44 # In order to change the behavior, modify user_function using user
46 # 1. When a window is opened up, focus the window and move the pointer
47 # to it. The parent window regains focus when a window is closed.
48 # Parenthood is determined when a window is opened. It is the last
49 # focused window with the same X class.
50 # 2. #1 would not occur to AcroRead opening window.
51 # 3. #1 would not occur when SkipMapping is set and the window is the
52 # only window of its class.
53 # 4. For Netscape find dialog window, addition to #1, resize the window
54 # to 300x150 pixels and move it to East edge of the screen.
55 # Download/upload windows will not be focused nor be in focus link
57 # 5. Move appletviewer to NorthWest corner.
58 # 6. Xterm won't focus back to its parent after closed.
59 # 7. When a window is de-iconified, focus it and move the pointer.
63 # These are collection of functions a user can call from programmable
66 # user function description are comments that start with ##
68 # change path if necessary
69 $FVWMCOMMAND = "/usr/X11/lib/X11/fvwm/FvwmCommand";
70 # if not there, try this
71 if (! -x
$FVWMCOMMAND) {
72 $FVWMCOMMAND = "$ENV{HOME}/usr/X11/lib/X11/fvwm/FvwmCommand";
75 #********** user configurable function **************************
77 if (action_was
("add")) {
78 # don't do anything to opening window of acrobat reader
79 return if class_matches
("AcroRead", "splashScreen_popup");
81 # if skipmapping is specified with 'style' command and
82 # if the window is the first of its class, then don't focus
83 if (window_flag
('SkipMapping') && no_parent_window
()) {
87 # don't focus download/upload window. do not put it in focus link list
88 if (class_matches
("Netscape", "(Download|upload)")) {
95 # move Netscape find dialog to edge
96 if (class_matches
("Netscape", "find")) {
97 resize_window
('300p','150p'); # resize before move to be on the edge
99 # move_window ('-0p', '-100p'); # just an example
101 # move appletviewer to corner
102 move_window
("Northwest") if class_matches
("VendorShell", "AWTapp");
104 # if you don't want move the pointer, comment out the next line
105 warp_to_window
(50, 50); # center of the window
107 }elsif (action_was
("destroy")) {
108 #don't focus back to parent if its xterm
109 return if (class_matches
("XTerm"));
111 focus_window
(get_parent_window
());
112 # if you don't want move the pointer, comment out the next line
113 warp_to_window
(get_parent_window
(), 50, 50); #center of window
115 }elsif (action_was
("deiconify")) {
118 }elsif (action_was
("iconify")) {
119 # focus_window (get_parent_window());
123 #********** end of user configurable function **************************
129 #package FvwmFocusLink;
131 #@ISA = qw(Exporter);
133 #@EXPORT = qw( &move_window
149 # user callable functions
153 # move_window [<id>] <direction>
156 # move_window [<id>] <x> <y>
158 # If <id> is prensent in hex format, then move <id> window.
159 # Otherwise, move the window in question.
161 # If <y> is present, move window to <x> <y> in percentage of screen.
163 # If 'p' is appended to <width> or <height>, it specifies in
164 # pixel count. And, if <width'p'> or <height'p'> is lead with '-',
165 # it signifies that pixel count from right or bottom edge.
167 # If <y> does not exist, <dir> must be one of North Northeast East
168 # Southeast South Southwest West Northwest to move window to edge.
172 my ($height, $width, $x, $y, $w, $dir);
175 if ($id =~ /^$HEX$/) {
180 return undef if ($id eq $NULLWINDOW || $id eq ''
181 || !defined $Window{$id}
182 || defined $Window{$id}{destroy
} );
188 if ($x =~ s/^-(.*)p$/$1/) {
189 ($width) = ($Window{$id}{frame
} =~ /width (\d+)/);
190 $x = $SW - $width - $x . "p";
192 if ($y =~ s/^-(.*)p$/$1/) {
193 ($height) = ($Window{$id}{frame
} =~ /height (\d+)/);
194 $y = $SH - $height - $y . "p";
196 send_cmd
("windowid $id move $x $y", $id, "^$id $ACTPAIR{frame}");
199 ($x,$y,$width,$height) =
201 =~ /x (-?\d+), y (-?\d+), width (\d+), height (\d+)/);
203 if ($dir =~ /[Ee]ast/) {
205 }elsif ($dir =~ /[Ww]est/) {
208 if ($dir =~ /[Nn]orth/) {
210 }elsif ($dir =~ /[Ss]outh/) {
214 send_cmd
("windowid $id move ${x}p ${y}p\n", $id, "^$id $ACTPAIR{frame}");
216 !defined $Window{$id}{'destroy'};
220 # resize_window [<id>] <width> <height>
222 # Resize window to <width> and <height> in percentage of screen size.
224 # If <id> is not null, resize <id>. Otherwise resize the
225 # window in question.
227 # Letter 'p' can be appended to <width> and <height> to specify in
231 my ($id,$wd,$ht) = @_;
237 return undef if ($id eq $NULLWINDOW || $id eq ''
238 || !defined $Window{$id}
239 || defined $Window{$id}{destroy
} );
240 send_cmd
("windowid $id resize $wd $ht\n", $id, "^$id $ACTPAIR{frame}");
241 !defined $Window{$id}{'destroy'};
245 # focus_window [<id>]
247 # If <id> is not null, focus on <id>.
248 # Otherwise, focus on the window in question.
257 return undef if ($id eq $NULLWINDOW || $id eq ''
258 || !defined $Window{$id}
259 || defined $Window{$id}{destroy
} );
261 send_cmd
("windowid $id focus\n");
262 keep_last_focused
($id);
263 !defined $Window{$id}{'destroy'};
267 # warp_to_window [<id>] [<x> <y>]
269 # Move pointer to window.
271 # If <id> is a window id, warp to <id>.
272 # Otherwise, warp to the window in question.
274 # If <x> and <y> are present, warp to <x> and <y> percentage of window
275 # size down and in from the upper left hand corner.
277 # Letter 'p' can be appended to <width> and <height> to specify in pixel
284 if ($id !~ /^$HEX$/) {
289 return undef if ($id eq $NULLWINDOW || $id eq ''
290 || !defined $Window{$id}
291 || defined $Window{$id}{destroy
} );
295 # ensure both exists or none
296 if (!defined $x || !defined $y) {
299 send_cmd
("windowid $id WarpToWindow $x $y");
300 !defined $Window{$id}{'destroy'};
304 # class_matches <class> [<resource>]
306 # Check if window class and optional resource match.
308 # If arg1 is present, and if class matches with <class> and resource
309 # matches with <resource>, then return 1.
311 # If arg1 is not present, and if class matches with <class> then
313 # Otherwise, return null.
317 return $W->{class} =~ /$c/ && $W->{resource
} =~ /$r/;
319 return $W->{class} =~ /$c/;
323 # window_flag [<id>] <flag>
325 # Return 1 if <flag> is true in the window in question.
326 # If <id> is not null, check on <id>. Otherwise check on the
327 # window in question.
328 # <flag> must be a exact match to one of these:
368 return undef if ($id eq $NULLWINDOW || $id eq '');
369 return $Window{$id}{$f} eq 'yes';
373 # resource_matches <resource>
374 # Check if window resource matches pattern <resource>.
375 # If it matches, return 1.
376 # Otherwise return null.
377 sub resource_matches
{
379 return $W->{resource
} =~ /$r/;
383 # action_was <action>
384 # Check if <action> was taken place.
386 # <action> must be a exact match to one of these:
405 return $W->{act
} eq $act;
409 # get_parent_window [<id>]
411 # Return parent window id.
413 # If <id> is not null, check on <id>. Otherwise check on the
414 # window in question.
415 sub get_parent_window
{
420 return $NULLWINDOW if ($id eq $NULLWINDOW || $id eq ''
421 || !defined $Window{$id} );
423 if (defined $Window{$id}{parent
}) {
424 $Window{$id}{parent
};
426 $NULLWINDOW; # must be an orphan
431 # no_parent_window [<id>]
433 # Return 1 if no parent window exits.
435 # If <id> is not null, check on <id>. Otherwise check on the
436 # window in question.
437 sub no_parent_window
{
438 return get_parent_window
(@_) eq $NULLWINDOW;
444 # Delete the window from link list
445 sub delete_from_list
{
449 return if $W->{id
} eq $NULLWINDOW || $W->{id
} eq '';
451 $f = $Focus{$W->{class}};
453 if ($f->[$i] eq $id) {
454 $f->[$i] = $NULLWINDOW;
458 foreach $i (keys %Window) {
459 if ($Window{$i}{parent
} eq $id) {
460 # avoid being parent of itself
461 if ($Window{$id}{parent
} ne $i) {
462 $Window{$i}{parent
} = $Window{$id}{parent
};
464 $Window{$i}{parent
} = $NULLWINDOW;
468 undef %{$Window{$id}};
474 # Supporting routines
478 # Add the window to link list.
479 # Link points back to the last focused window among the same class
481 return if ( $W->{id
} eq '' || $W->{id
} eq $NULLWINDOW);
482 if (!defined @
{$Focus{$W->{class}}}) {
484 $W->{parent
} = $NULLWINDOW;
485 $Focus{$W->{class}}[0] = $NULLWINDOW;
487 $W->{parent
} = $Focus{$W->{class}}[0];
488 # can't be parent of itself
489 if ($W->{parent
} eq $W->{id
}) {
490 $W->{parent
} = $Focus{$W->{class}}[1];
491 if ($W->{parent
} eq $W->{id
}) {
492 $W->{parent
} = $NULLWINDOW;
500 # Optionally wait for a keyword to come back
502 my($cmd,$id,$key)=@_;
505 $cmd =~ s/\n*$/\n/; # ensure 1 cr at eol
506 # ensure not to send command for window already destroyed
507 if ($cmd =~ /^windowid ($HEX)/) {
509 return if ($cid eq $NULLWINDOW || $cid eq '' ||
510 !defined $Window{$cid} ||
511 defined $Window{$cid}{destory
} );
515 print STDERR
$cmd if $Debug & 2;
517 # if specified wait for the keyword
519 read_message
($id,$key);
523 # Wait until action defined in $STATUS occure, then call process.
524 # If a window focused, keep in focus list as the last window
526 # Create current window info from lines leaded with the same id
534 ($act) = /^\s+(\w+(\s\w+)?)/; # such as "new page"
536 ($id, $act) = (/^($HEX) ($STATUS)/);
542 # create new window info
543 $Window{"$id"} = {id
=>"$id"};
546 if (defined $ACTPAIR{$act}) {
548 read_message
($id, "$id $ACTPAIR{$act}");
557 # read message fifo or stack
558 # if $id is defined and the message is not for $id then
559 # push it on to the stack and read it again
560 # if $key is also defined, loop until $key is matched
567 if ($sk==0 && $#Message >= 0) {
568 $_ = shift (@Message);
571 if (/^$/ && eof(FCM
)) {
576 if (/^($HEX) ($STATUS)\s*(.*)/ && $1 ne $NULLWINDOW) {
577 $Window{$1}{$2} = $3;
579 $Window{$1}{id
} = $1;
580 $Window{$1}{parent
} = $NULLWINDOW;
581 push (@
{$Focus{$3}}, $1);
589 $sk = 1; # don't read from @Message
592 return undef if !defined $Window{$id};
593 return undef if defined $Window{$id}{destroy
};
597 if (defined $key && !/$key/) {
604 # Keep the last 2 focused windows of each class.
605 # This allows focus_window and add_to_list to be any order
606 sub keep_last_focused
{
610 return undef if ($id eq ''
611 || $id eq $NULLWINDOW
612 || !defined $Window{$id}
613 || defined $Window{$id}{destroy
} );
616 # consider for cases that Focus is not defined yet
617 if (!defined $Focus{$Window{$id}{class}}) {
618 @
{$Focus{$Window{$id}{class}}} = ($NULLWINDOW);
620 $l = $Focus{$Window{$id}{class}};
621 if ($l->[0] ne $id) {
629 send_cmd
("killme"); # kill FvwmCommandS
636 print STDERR
"FCM EOF ";
643 print STDERR
"signal $sig\n";
654 foreach $k (keys %Window) {
656 print (STDERR
"id $w->{id} parent $w->{parent} " ,
657 "class $w->{class} \tresource $w->{resource}\n");
662 # print the current list of parents for each class
663 sub print_focused_list
{
667 foreach $k (keys %Focus) {
668 print (STDERR
"class $k parents @{$Focus{$k}}\n");
676 # get screen size - to move
680 # 1 to print window info
681 # 2 to print command sent
682 # 4 to print focused window list
688 if ($ARGV[0] =~ /^-v/) {
689 print STDERR
"focus-link.pl version 2.0\n";
692 if ($ARGV[0] =~ /^-d(\d+)/) {
697 $SIG{'TTIN'} = "IGNORE";
698 $SIG{'TTOUT'} = "IGNORE";
699 $SIG{'QUIT'} = "sig_quit";
700 $SIG{'INT'} = "sig_quit";
701 $SIG{'HUP'} = "sig_quit";
702 $SIG{'PIPE'} = "sig_quit";
704 $STATUS = '(?:\w+(?:\s\w+)?)'; #status to be captured
705 # matching pair for some action
706 %ACTPAIR = ('add'=>'map',
711 $HEX = '0x[\da-f]+'; #for regex
712 $NULLWINDOW = '0x00000000'; # root or invalid window id
714 $FIFO = "$ENV{'HOME'}/.FCMfocus";
717 if( `xwininfo -root` =~ /Width: (\d+)\s+Height: (\d+)/ ) {
721 # some resonable number if xwininfo doesn't work
726 # start a dedicated server
727 # start a client monitoring (-m option ) all fvwm transaction (-i3 option )
728 # unlinking and verifying M FIFO ensures that new FvwmCommand is running
729 unlink( "${FIFO}M" );
730 open( FCM
, "$main::FVWMCOMMAND -S $FIFO -f $FIFO -m -i3 </dev/null|" )
732 while (! -p
"${FIFO}M") {
735 die "Can't open ${FIFO}M";
739 # send command through the new fifo which is "$FIFO" + "C"
740 open( FCC
, ">${FIFO}C" ) || die "FCC $FIFO" ;
742 # appearantly, it has to be unbuffered
743 select( FCC
); $| = 1;
744 select( STDOUT
); $| = 1;
746 %Focus = (); # last focused windows for each class
747 @Message = (); # message queue
751 #get current screen info
752 send_cmd
("Send_WindowList\n");
755 # last if /^end windowlist/;
757 # # create window list
758 # if (/^($HEX) ($STATUS)\s*(.*)/) {
759 # if ($1 ne $NULLWINDOW) {
760 # $Window{$1}{$2} = $3;
761 # # make focused window list
762 # if ($2 eq 'class') {
763 # $Window{$1}{id} = $1;
764 # $Window{$1}{parent} = $NULLWINDOW;
765 # push (@{$Focus{$3}}, $1);
771 print_list
() if ($Debug & 1);
772 print_focused_list
() if ($Debug & 4);
779 $Window{$W->{id
}} = $W if $W->{id
} ne '';
780 add_to_list
() if action_was
("add");
781 print STDERR
"Action $W->{act} $W->{id}\n" if $Debug & 8;
782 main
::user_function
();
783 if ($W->{act
} eq 'destroy') {
786 if ($W->{act
} eq 'focus change') {
787 keep_last_focused
($W->{id
});
789 print_list
() if ($Debug & 1 && !action_was
("focus change")) ;
790 print_focused_list
() if ($Debug & 4 && !action_was
("focus change"));
796 print STDERR
"end\n";
806 # Toshi Isogai isogai@ucsub.colorado.edu