Patch to rpws for multi screen support

Matthew King <[email protected]>
Newsgroups gmane.comp.window-managers.ratpoison.devel
Message-ID <CAFDK8z7wfmeZGJExZKVB7bkJFHh+g_Kv-UfF86zCgBE0604+TA@mail.gmail.com>
Also I documented the hell out of it and simplified a few things.

Matthew

_______________________________________________
Ratpoison-devel mailing list
[email protected]
https://lists.nongnu.org/mailman/listinfo/ratpoison-devel
rpws.diff (application/octet-stream, 11.1 KB)
--- /usr/bin/rpws	2012-06-30 15:47:30.000000000 +0100
+++ src/rpws	2012-10-08 22:38:44.306312102 +0100
@@ -32,6 +32,7 @@
     print( "for more detailed documentation run \"perldoc $0\"\n" );
 }
 
+## Run the given ratpoison command, returning the result in a string.
 sub rp_call
 {
     my $result = `$ratpoison -c "@_"`;
@@ -40,51 +41,83 @@
     return $result;
 }
 
-sub ws_init_ws
+## Return the frame configuration (ratpoison: fdump).
+sub fdump
 {
+    return rp_call( "fdump " . shift );
+}
 
-    my $num = shift;
-
-    rp_call( "gnew wspl$num" );
-    my $fd = fdump();
-    rp_call( "setenv fspl$num $fd" )
+## Returns the number of the currently active screen by parsing ratpoison's
+## sdump output.
+sub get_active_screen
+{
+    # Don't ask. A cleaner way to do this would be nice.
+    return (grep { $_->[1] == 1 }
+            map { [(split)[0,5]] }
+            split /,/, rp_call( "sdump" )
+           )[0]->[0];
 }
 
-sub fdump
+## Hook to run when the active screen is changed. Re-activates the new screen's
+## workspace and thus selects its group.
+sub screen_change
 {
-    return rp_call( "fdump" );
+    my $ws = rp_call( "getenv ws_screen_" . get_active_screen() . "_group" );
+    rp_call( "exec rpws $ws" );
 }
 
+## Initialize a given number of workspaces.
 sub ws_init
 {
     my $num = shift;
+    my $screens = scalar (split /,/, rp_call( "sdump" )); # No. of screens.
 
-    $num >= 2 || die "You must create at least 2 workspaces.";
+    die "You must create at least $screens workspaces."
+        unless $num >= $screens;
 
-    # Backup the frames
-    my $fd = fdump();
+    # Create a group (ws_<n>) for each workspace.
+    rp_call( "gnew ws_$_" ) for 1..$num;
 
-    rp_call( "select -" );
-    rp_call( "only" );
+    # Initialise each screen s (counting from 0 because X does) to display
+    # workspace s+1, which corresponds to group ws_<n> (counting from 1 because
+    # then 1 or F1 can be bound to the first workspace/group).
+    #
+    # I have no idea if this works unless it's run on startup.
+    for (0..($screens-1)) {
+        # For each screen record its current frame configuration
+        my $fd = fdump($_);
+        #  ... store it in ws_<s>_framedef.
+        rp_call( "setenv ws_" . ($_+1) . "_framedef $fd" );
+
+        # I have no idea if this is necessary but it was here before.
+        rp_call( "sselect $_" );
+        rp_call( "select -" ); # Empty the screen of windows
+        rp_call( "only" );     #  ... and frames.
+        rp_call( "gselect ws_" . ($_+1) ); # Select this screen's new group.
+
+        # Record which workspace (default: s+1) is displayed on each screen
+        # in ws_screen_<s>_group.
+        rp_call( "setenv ws_screen_${_}_group " . ($_ + 1) );
 
-    my $i;
-    for my $i (2..$num)
-    {
-        ws_init_ws( $i );
+        # Restore the frames recorded previously.
+        rp_call( "frestore $fd" );
     }
 
-    # Workspace 1 uses the 'default' group.
-    # Start in workspace 1.
-    $fd = fdump();
-    rp_call( "gselect default" );
-    rp_call( "setenv fspl1 $fd" );
-    rp_call( "setenv wspl 1" );
-
-    # Keep track of workspace count
-    rp_call( "setenv wspc $num" );
-
-    # restore the frames
-    rp_call( "frestore $fd" );
+    # Activate screen 0
+    rp_call( "sselect 0" );
+    rp_call( "gselect ws_1" );
+
+    # Move the 'default' group into ws_1 and delete it.
+    rp_call( "gmerge default" );
+    rp_call( "gdelete default" );
+
+    # Keep track of workspace count.
+    rp_call( "setenv ws_count $num" );
+
+    # Add a hook to follow screen changes if it's not already there.
+    if (rp_call( "listhook switchscreen" ) !~ /rpws screenchange$/m) {
+        rp_call( "addhook switchscreen exec $0 screenchange" );
+    }
 
     if( -e "$lockfile" )
     {
@@ -92,81 +125,128 @@
     }
 }
 
+## Save the workspace of the screen given (as the only argument)
+## to ws_<n>_framedef.
 sub ws_save
 {
-    my $ws = rp_call( "getenv wspl" );
-    my $fd = fdump();
-    rp_call( "setenv fspl$ws $fd" );
+    my $screen = shift;
+    my $ws = rp_call( "getenv ws_screen_${screen}_group" );
+    rp_call( "setenv ws_${ws}_framedef " . fdump($screen) );
 }
 
+## Restore the workspace configuration given (as the only argument) to the
+## active screen from ws_<n>_framedef.
+##  * If workspace n is active the current screen, reactivate it (useful if
+##    the current screen has changed).
+##  * If workspace n is active on another screen return an error.
+##  * If workspace n has not been used, create a new frame filling the whole screen.
 sub ws_restore
 {
     my $which = shift;
+    my $screen = get_active_screen();
 
-    ws_save();
+    # An array of each screen's id and size.
+    my @sdump = map { [split] } split /,/, rp_call( "sdump" );
 
+    # Make next/prev increase or decrease by one. Wraps around at each end.
     if ( $which =~ /^(?:next|prev)$/ )
     {
-        my $ws = rp_call( "getenv wspl" );
-        my $wspc = rp_call( "getenv wspc" );
+        my $ws = rp_call( "getenv ws_screen_${screen}_group" );
+        my $ws_count = rp_call( "getenv ws_count" );
 
-        if ( $which eq 'next' )
-        {
-            $ws++;
-        }
-        else
-        {
-            $ws--;
-        }
-        $which = ( ( $ws - 1 ) % $wspc ) + 1;
+        $ws += ($which eq 'next') ? 1 : -1;
+        $which = ( ( $ws - 1 ) % $ws_count ) + 1;
     }
 
-    if( $which == 1 )
-    {
-        rp_call( "gselect default" );
+    # Check that the workspace isn't already active.
+    #
+    # TODO: Integrate with the above so next/prev will skip active workspaces.
+    my %ws_screen = map { rp_call( "getenv ws_screen_$_->[0]_group" )
+                            => $_->[0]
+                        } @sdump;
+    if (exists $ws_screen{$which} and $ws_screen{$which} != $screen) {
+        rp_call( "echo Not changing to workspace $which which is"
+                 . " already on $ws_screen{$which}." );
+        return;
     }
-    else
-    {
-        rp_call( "gselect wspl$which");
+
+    # Save the current configuration before destroying it.
+    ws_save($screen);
+
+    # Activate the new workspace's group. This is also called when changing the
+    # active screen.
+    rp_call( "gselect ws_$which");
+
+    # Load the new workspace's configuration
+    my $fd = rp_call( "getenv ws_${which}_framedef" );
+    if ($fd ne '') {
+        #  ... activate it if there is one
+        rp_call( "frestore $fd" );
+
+    } else {
+        #  ... or create a new fullscreen frame if there isn't.
+
+        # List all frames
+        my @sfdump = sort map { (split)[2] } split /,/, rp_call( "sfdump" );
+        # Next unused frame id.
+        my $fid = $sfdump[$#sfdump] + 1; 
+        rp_call( "frestore (frame :number $fid :x 0 :y 0"
+                 . " :width $sdump[$screen][3] :height $sdump[$screen][4]"
+                 . " :window 0) $screen"
+               );
     }
 
+    # Record the screen's new group.
+    rp_call( "setenv ws_screen_${screen}_group $which" );
+
     rp_call( "echo Workspace $which" );
-    my $last = rp_call( "getenv fspl$which" );
-    rp_call( "frestore $last" );
-    rp_call( "setenv wspl $which" );
 }
 
+## Add aliases:
+##  * rpws<n>: activate workspace n.
+##  * rpwsn; rpwsnext: activate the next workspace (execute rpws next).
+##  * rpwsp; rpwsprev: activate the previous workspace (execute rpws prev).
 sub add_aliases
 {
     my $n = shift;
-    foreach my $i (1..$n) {
-        rp_call ( "alias rpws$i exec $0 $i" );
-    }
+    rp_call ( "alias rpws$_ exec $0 $_" ) for 1..$n;
     rp_call ( "alias rpwsn exec $0 next" );
+    rp_call ( "alias rpwsnext exec $0 next" );
     rp_call ( "alias rpwsp exec $0 prev" );
+    rp_call ( "alias rpwsprev exec $0 prev" );
 }
 
-sub add_keys
+## Bind keys to the above aliases:
+##  * M-F<n> (function keys); rpws<n>. (BUG? Is there a maximm no. of F-keys?)
+##  * C-M-Right; rpwsnext
+##  * C-M-Left; rpwsprev
+sub bind_keys
 {
     my $n = shift;
-    foreach my $i (1..$n) {
-        rp_call ( "definekey top M-F$i rpws$i" );
-    }
-    rp_call ( "definekey top C-M-Right rpwsn" );
-    rp_call ( "definekey top C-M-Left rpwsp" );
+    rp_call ( "definekey top M-F$_ rpws$_" ) for 1..$n;
+    rp_call ( "definekey top C-M-Right rpwsnext" );
+    rp_call ( "definekey top C-M-Left rpwsprev" );
 }
 
+
 my $arg = shift @ARGV || 'help';
 
-if( $arg eq "help" ) {
+if( $arg =~ /^-*(h(elp)?|\?)/ ) { # A few different ways of asking for help.
     help();
+
+} elsif( $arg eq "screenchange" or $arg eq "screen_change" ) {
+    screen_change();
+
 } elsif( $arg eq "init" ) {
-    my $num = shift @ARGV;
     my %opts;
-    ws_init( $num );
     getopts('ka', \%opts);
+
+    my $num = shift @ARGV;
+    ws_init( $num );
+
     add_aliases( $num ) if $opts{'a'} || $opts{'k'};
-    add_keys ( $num ) if $opts{'k'};
+    bind_keys  ( $num ) if $opts{'k'};
+
 } else {
    open LOCK, ">>$lockfile" or die "Cannot open lockfile: $lockfile";
    flock(LOCK, LOCK_EX);
@@ -177,38 +257,44 @@
 
 =head1 NAME
 
-rpws - Implements multiple workspaces in ratpoison
+ rpws - Implements multiple workspaces in ratpoison
 
 =head1 SYNOPSIS
 
- rpws init n [-k] [-a]  - setup rpws with n workspaces.
-                            -a sets up command aliases;
+ rpws init <n> [-k] [-a] - Setup rpws with n workspaces.
+                            -a sets up command aliases.
                             -k sets up key bindings and aliases.
- rpws help              - this documentation
- rpws n                 - switch to this workspace
-
+ rpws help               - This documentation.
+ rpws <n>                - Switch to workspace n.
+ rpws next               - Switch to the next workspace.
+ rpws prev               - Switch to the previous workspace.
 
 =head1 DESCRIPTION
 
- B<rpws> implements multiple workspaces in ratpoison by making calls
- to fdump, freestore.  It was adapted from rpws which comes with
- ratpoison in the contrib directory.
+ B<rpws> implements multiple workspaces in ratpoison by saving and loading
+ frame configuration with fdump and freestore, then 'hiding' inactive
+ workspaces with ratpoison's groups. It was adapted from rpws which comes with
+ ratpoison in the contrib directory. Then adapted again to include support for
+ multiple screens.
 
 =head1 USAGE
 
-Add the following line in ~/.ratpoisonrc
+ Add the following line in ~/.ratpoisonrc
+
+     exec /path/to/rpws init 6 -a -k
+
+ This creates 6 workspaces. The current configration is stored in workspace 1.
 
-     exec /path/to/rpws init 6 -k
+ The -a flag creates aliases rpws1, rpws2 ... rpws6, rpwsprev and rpwsnext.
 
-This creates 6 aliases rpws1, rpws2, etc. It also binds the keys M-F1,
-M-F2, etc to each rpwsN alias. Moreover, rpwsn (Next) and rpwsp (Prev) are
-created, and C-M-{Right,Left} are bound to rpws{n,p}.
+ The -k flag binds the keys M-F1, M-F2 ... M-F6 to each rpwsN alias and
+ C-M-{Left,Right} to rpws{prev,next}.
 
 =head1 FILES
 
- rpws requires use of a lockfile.  It defaults to using
-/tmp/rpws.<UID>.lock but this can be changed by setting the
-environment variable RPWS_LOCKFILE to your desired lockfile.
+ rpws requires use of a lockfile. It defaults to using /tmp/rpws.<UID>.lock but
+ this can be changed by setting the environment variable RPWS_LOCKFILE to your
+ desired lockfile.
 
 =head1 AUTHOR
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.