From acad4445b81048140a7d658ac43f8a0a457750ad Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Thu, 11 Apr 2024 21:11:12 +0800 Subject: i3status-wrapper: replace IPC::Shareable & Local::Desktop::WMIPC --- perl5/Local/Desktop.pm | 7 +- perl5/Local/Desktop/WMIPC.pm | 61 ----- scripts/desktop/i3status-wrapper | 497 +++++++++++++++++++-------------------- scripts/desktop/sway-ftp-master | 2 - 4 files changed, 246 insertions(+), 321 deletions(-) delete mode 100644 perl5/Local/Desktop/WMIPC.pm diff --git a/perl5/Local/Desktop.pm b/perl5/Local/Desktop.pm index 0460447e..b61f9270 100644 --- a/perl5/Local/Desktop.pm +++ b/perl5/Local/Desktop.pm @@ -20,7 +20,6 @@ package Local::Desktop; use 5.028; use strict; use warnings; -use lib "$ENV{HOME}/src/dotfiles/perl5"; use Carp; use JSON; @@ -30,14 +29,18 @@ use File::Spec::Functions "rel2abs"; use Exporter "import"; use File::Copy; use List::Util "first", "any"; -use Local::Desktop::WMIPC; our @EXPORT = qw( + $wmipc wmipc select_wallpaper_files ensure_resize_for_current_outputs resize_for_current_outputs pick_random_wallpapers ); +`sh -c "command -v i3-msg"`; +our $wmipc = $? == 0 ? "i3-msg" : "swaymsg"; +sub wmipc { system $wmipc, "-q", join "; ", @_ } + my $output_re = qr/ ([0-9]+)x([0-9]+)\+([0-9]+)\+([0-9]+) /; =head select_wallpaper_files(@files) diff --git a/perl5/Local/Desktop/WMIPC.pm b/perl5/Local/Desktop/WMIPC.pm deleted file mode 100644 index effa7797..00000000 --- a/perl5/Local/Desktop/WMIPC.pm +++ /dev/null @@ -1,61 +0,0 @@ -package Local::Desktop::WMIPC; -use 5.036; - -# Copyright (C) 2024 Sean Whitton -# -# 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 3 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, see . - -use IO::Socket::UNIX; -use parent "IO::Socket::UNIX"; -use Exporter "import"; -use overload "<>" => sub { shift->recv }; -use JSON; -use Encode "encode"; - -our @EXPORT_OK = (); - -sub new ($class, $socket = $ENV{SWAYSOCK} || $ENV{I3SOCK}) { - bless IO::Socket::UNIX->new(Type => SOCK_STREAM, Peer => $socket) - => $class; -} - -sub send ($self, $type, $payload = "") { - $payload = encode "UTF-8", $payload; - my $head = pack "A6ll", "i3-ipc", length $payload, $type; - $self->SUPER::send($head.$payload); - return $self->recv; -} - -sub recv ($self) { - my $buf; - $self->SUPER::recv($buf, 14); - length $buf or die "WMIPC socket EOF"; - my $len = (unpack "A6ll", $buf)[1]; - $self->SUPER::recv($buf, $len); - return decode_json $buf; -} - -sub subscribe ($self, @event_types) { - my $reply = $self->send(2, encode_json \@event_types); - $reply->{success} or die "WMIPC subscription failed"; -} - -sub cmd ($self, @cmds) { $self->send(0, join "; ", @cmds) } - -sub get_tree { shift->send(4) } -sub get_workspaces { shift->send(1) } - -sub send_tick ($self, $payload = "") { $self->send(10, $payload) } - -1; diff --git a/scripts/desktop/i3status-wrapper b/scripts/desktop/i3status-wrapper index 201cdd7e..22b78bd0 100755 --- a/scripts/desktop/i3status-wrapper +++ b/scripts/desktop/i3status-wrapper @@ -20,14 +20,14 @@ use 5.032; use strict; use warnings; -use lib "$ENV{HOME}/src/dotfiles/perl5"; use JSON; use IO::Pipe; -use IPC::Shareable ":lock"; -use Local::Desktop::WMIPC; +use AnyEvent; +use AnyEvent::I3 ":all"; use Sys::Hostname; use POSIX "floor", "mkfifo"; +use Fcntl; use File::Basename "basename", "dirname"; use File::Spec::Functions "catfile"; use List::Util qw(first min max zip); @@ -44,17 +44,19 @@ unless ($i3status) { exec "i3status"; } -tie my %info, "IPC::Shareable", undef, { destroy => 1 }; +my (%paper_ws, $focused_ws, %col_rows, $caffeinated_id, $caffeinated_name); -my $wmipc = Local::Desktop::WMIPC->new; +my $wm_ipc_socket = $ENV{SWAYSOCK} || $ENV{I3SOCK}; +my $wmipc = AnyEvent::I3->new($wm_ipc_socket); +$wmipc->connect->recv or die "couldn't connect to WM IPC socket"; sub with_ignored_events (&) { - $wmipc->send_tick("i3status-wrapper-ign"); + $wmipc->send_tick("i3status-wrapper-ign")->recv; $_[0]->(); - $wmipc->send_tick("i3status-wrapper-unign"); + $wmipc->send_tick("i3status-wrapper-unign")->recv; } sub for_each_node (&) { - my @trees = $wmipc->get_tree; + my @trees = $wmipc->get_tree->recv; while (@trees) { foreach my $node ((shift @trees)->{nodes}->@*) { $_[0]->($node); @@ -70,165 +72,133 @@ my @all_workspaces = ( "19:F9", "20:F10", "21:F11", "22:F12" ); -unless (fork // warn "couldn't fork monitoring loop") { - my $events = Local::Desktop::WMIPC->new; - $events->subscribe(qw(tick window workspace)); - - # Determine the initial state -- the WM might just have been reloaded. - # Move any previously-hidden containers to a fresh workspace for perusal. +my $have_pending = AnyEvent->condvar; +my (@pending_events, @pending_msgs); - tied(%info)->lock; - my @old_ids; - for ($wmipc->get_workspaces->@*) { - $info{focused_ws} = $_->{id} if $_->{focused}; - push @old_ids, $1 if $_->{name} =~ /\A\*(\d+)\*\z/; - } - if (@old_ids) { - fresh_workspace(go => 1); - $wmipc->cmd( - map("[con_id=$_] move container workspace current, floating disable", - @old_ids), - "focus child"); - } - for_each_node { - my $node = shift; - if ($node->{type} eq "workspace" - && grep $_ eq $node->{name}, @all_workspaces) { - my $entry = $info{paper_ws}{$node->{id}} - //= { name => $node->{name}, off_left => [], off_right => [], - last_dir => 1 }; - sync_cols($node => $entry); - $entry->{ncols} = max 2, scalar $entry->{cols}->@*; - } elsif (grep $_ eq "caffeinated", $node->{marks}->@*) { - register_caffeinated($node); +(basename $wm_ipc_socket) =~ /\d[\d.]*\d/; +my $cmdpipe = catfile dirname($wm_ipc_socket), "i3status-wrapper.$&.pipe"; +-e and unlink for $cmdpipe; +mkfifo $cmdpipe, 0700 or die "mkfifo $cmdpipe failed: $!"; + +# Hold the pipe open with a writer that won't write anything. +open(my $cmdpipe_w, ">", $cmdpipe), sleep + unless fork // die "couldn't fork: $!"; + +open my $cmdpipe_r, "<", $cmdpipe; +my $cmdpipe_reader = AnyEvent->io( + fh => $cmdpipe_r, poll => "r", cb => sub { + # There are a few cases where we can handle the command by only + # updating data structures, but for simplicity, always handle commands + # outside of the event loop. + push @pending_msgs, scalar <$cmdpipe_r>; + $have_pending->send; + }); + +my $ignore_events; +sub queue_event { push @pending_events, shift; $have_pending->send } +$wmipc->subscribe({ + tick => sub { + my $payload = shift->{payload}; + $ignore_events = 1 if $payload eq "i3status-wrapper-ign"; + $ignore_events = 0 if $payload eq "i3status-wrapper-unign"; + }, + + window => sub { + return if $ignore_events; + my $e = shift; state $last_e; + + # New containers: have to read two events to find out whether it's + # just a floating dialog that we'll ignore. + if ($last_e) { + undef $last_e; + queue_event $e unless $e->{change} && $e->{change} eq "floating"; + } elsif ($e->{change} && $e->{change} eq "new" + && exists $paper_ws{$focused_ws}) { + $last_e = $e; } - }; - tied(%info)->unlock; - - # Now loop forever reading events, assuming no exceptions. - - eval { - while (my $e = <$events>) { - state $last_e; - tied(%info)->lock; - - # New containers - if ($last_e && $last_e->{change} && $last_e->{change} eq "new") { - normalise_ws_cols() - unless $e->{change} && $e->{change} eq "floating"; - undef $last_e; - } elsif ($e->{change} && $e->{change} eq "new" - && exists $info{paper_ws}{$info{focused_ws}}) { - # We have to go round the loop once more to find out if it's - # just a floating dialog that we'll ignore. - $last_e = $e; - } elsif ($e->{change} && exists $info{paper_ws}{$info{focused_ws}} - && $e->{change} eq "floating" - && $e->{container}{type} ne "floating_con") { - # A container stopped floating -- it's as though it's new. - normalise_ws_cols(); - kill USR1 => $i3status; - } - # Other container changes - elsif ($e->{change} && exists $info{paper_ws}{$info{focused_ws}} - && ($e->{change} eq "close" - || $e->{change} eq "focus" && !$e->{current} - || $e->{change} eq "move" - && $e->{container} && $e->{container}{type} eq "con" - || $e->{change} eq "floating" - && $e->{container}{type} eq "floating_con")) { - # Generally we seek to update $info{paper_ws} with the - # information we receive by subscription, but in some cases we - # can't be sure of what has happened. - # For example, as we don't maintain a representation of the - # whole tree, on a change=move event, we don't know where the - # container has gone. Or a focus change might be due to a new - # container, in which case we might need to push one off. - normalise_ws_cols(); - kill USR1 => $i3status; - } - - # Ticks - elsif ($e->{payload} && $e->{payload} eq "i3status-wrapper-ign") { - # Ignore everything until tick telling us to unignore. - # Forked child that sent the ignore is responsible for - # updating data structures in the meantime. - while (my $next = <$events>) { - last if $next->{payload} - && $next->{payload} eq "i3status-wrapper-unign"; - } + # Mark changes -- can handle these without leaving event processing. + elsif ($e->{change} && $e->{change} eq "mark") { + if (grep $_ eq "caffeinated", $e->{container}{marks}->@*) { + register_caffeinated($e->{container}); + } elsif ($caffeinated_id + && $caffeinated_id == $e->{container}{id}) { + clear_caffeinated(); } + } - # Workspace changes - elsif ($e->{change} && $e->{change} eq "focus" && $e->{current}) { - $info{focused_ws} = $e->{current}{id}; - # Must normalise in case containers have moved to or from here - # in our absence. - normalise_ws_cols() - if exists $info{paper_ws}{$info{focused_ws}}; - kill USR1 => $i3status; - } elsif ($e->{change} && $e->{change} eq "init" && $e->{current} - && grep $_ eq $e->{current}{name}, @all_workspaces) { - $info{paper_ws}{$e->{current}{id}} - = { name => $e->{current}{name}, ncols => 2, cols => [], - off_left => [], off_right => [], last_dir => 1 }; - } elsif ($e->{change} && $e->{change} eq "rename" - && exists $info{paper_ws}{$e->{current}{id}}) { - $info{paper_ws}{$e->{current}{id}}{name} - = $e->{current}{name}; - kill USR1 => $i3status; - } elsif ($e->{change} && $e->{change} eq "empty" - && $e->{current}) { - delete $info{paper_ws}{$e->{current}{id}}; + # Other container changes we need to handle outside of any callback. + elsif ($e->{change} && exists $paper_ws{$focused_ws} + && (# A container stopped floating: it's as though it's new. + $e->{change} eq "floating" + && $e->{container}{type} ne "floating_con" + + || $e->{change} eq "close" || $e->{change} eq "focus" + + || $e->{change} eq "move" + && $e->{container} && $e->{container}{type} eq "con")) + { queue_event $e } + }, + + workspace => sub { + my $e = shift; + if ($ignore_events || !$e->{change}) { + return; + } elsif ($e->{change} eq "focus" && $e->{current}) { + $focused_ws = $e->{current}{id}; + # If this is one of our workspaces, then we must normalise: + # containers might have moved to or from here in our absence. + if (exists $paper_ws{$focused_ws}) { + queue_event $e; + } else { + # Update status bar display. kill USR1 => $i3status; } - - # Mark changes - elsif ($e->{change} && $e->{change} eq "mark") { - if (grep $_ eq "caffeinated", $e->{container}{marks}->@*) { - register_caffeinated($e->{container}); - } elsif ($info{caffeinated_id} - and $info{caffeinated_id} == $e->{container}{id}) { - clear_caffeinated(); - } - } - - tied(%info)->unlock; + } elsif ($e->{change} eq "init" && $e->{current} + && grep $_ eq $e->{current}{name}, @all_workspaces) { + $paper_ws{$e->{current}{id}} + = { name => $e->{current}{name}, ncols => 2, cols => [], + off_left => [], off_right => [], last_dir => 1 }; + } elsif ($e->{change} eq "rename" + && exists $paper_ws{$e->{current}{id}}) { + $paper_ws{$e->{current}{id}}{name} = $e->{current}{name}; + kill USR1 => $i3status; + } elsif ($e->{change} eq "empty" && $e->{current}) { + delete $paper_ws{$e->{current}{id}}; + kill USR1 => $i3status; } - }; - - # Give up if there's a decoding error. We can't ignore the problem - # because we don't want our ideas regarding what workspaces there are, and - # whether anything is caffeinated, to get out of sync. - # - # The user can use the WM's "reload" command to restart this loop. - $@ and wsbuttons("yes"), clear_caffeinated(); + }, +})->recv->{success} or die "couldn't subscribe to window manager events"; + +# Determine the initial state -- the WM might just have been reloaded. +# Move any previously-hidden containers to a fresh workspace for perusal. +my @old_ids; +for ($wmipc->get_workspaces->recv->@*) { + $focused_ws = $_->{id} if $_->{focused}; + push @old_ids, $1 if $_->{name} =~ /\A\*(\d+)\*\z/; } - -my $wm_ipc_socket = $ENV{SWAYSOCK} || $ENV{I3SOCK}; -(basename $wm_ipc_socket) =~ /\d[\d.]*\d/; -my $cmdpipe = catfile dirname($wm_ipc_socket), "i3status-wrapper.$&.pipe"; --e and unlink for $cmdpipe; - -unless (fork // warn "couldn't fork command pipe reader") { - mkfifo $cmdpipe, 0700 or die "mkfifo $cmdpipe failed: $!"; - open my $cmdpipe_r, "<", $cmdpipe; - - # Hold the pipe open with a writer that won't write anything. - open my $cmdpipe_w, ">", $cmdpipe; - - while (my $cmd = <$cmdpipe_r>) { - tied(%info)->lock; - process_msg($cmd); - tied(%info)->unlock; - } +if (@old_ids) { + fresh_workspace(go => 1); + cmd(map("[con_id=$_] move container workspace current, floating disable", + @old_ids), + "focus child"); } +for_each_node { + my $node = shift; + if ($node->{type} eq "workspace" + && grep $_ eq $node->{name}, @all_workspaces) { + my $entry = $paper_ws{$node->{id}} + //= { name => $node->{name}, + off_left => [], off_right => [], last_dir => 1 }; + sync_cols($node => $entry); + $entry->{ncols} = max 2, scalar $entry->{cols}->@*; + } elsif (grep $_ eq "caffeinated", $node->{marks}->@*) { + register_caffeinated($node); + } +}; $pipe->reader; -# Following based on Michael Stapelberg's sample i3status-wrapper script. - my $hostname = hostname; my $username = $ENV{LOGNAME} || $ENV{USER} || getpwuid($<); my $hostinfo @@ -242,74 +212,90 @@ print scalar <$pipe>; wsbuttons("no"); -# Read lines forever, ignore a comma at the beginning if it exists. -while (my ($statusline) = (<$pipe> =~ /^,?(.*)/)) { - # If there is a decoding error, just skip this line, to minimise status - # bar freezes. This should be fine here because this filtering loop is in - # itself stateless. It's only if the decoding error involves newlines in - # the wrong places, or similar, that this skip could cause us to produce - # invalid output. - my $blocks = eval { decode_json $statusline } // next; - - tied(%info)->lock(LOCK_SH); - - if ($info{focused_ws} - && $info{paper_ws} && keys $info{paper_ws}->%* > 1) { - my @disp; - my @keys = sorted_paper_ws(); - foreach my $key (@keys) { - push @disp, - sprintf +($info{focused_ws} == $key ? "%s" : "%s"), - ws_name($info{paper_ws}{$key}{name}) - +# Basic idea here from Michael Stapelberg's sample i3status-wrapper. +my $i3status_wrapper = AnyEvent->io( + fh => $pipe, poll => "r", cb => sub { + + # If there is a decoding error then we just skip this line, as it's + # not worth crashing this script over that. It should be fine to do + # this here because this filtering loop is in itself stateless. + # It's only if the decoding error involves newlines in wrong places, + # or similar, that this skip could cause us to produce invalid output. + my ($statusline) = (<$pipe> =~ /^,?(.*)/); + my $blocks = eval { decode_json $statusline } // next; + + if ($focused_ws && keys %paper_ws > 1) { + my @disp; + foreach my $key (sorted_paper_ws()) { + push @disp, + sprintf +($focused_ws == $key ? "%s" : "%s"), + ws_name($paper_ws{$key}{name}) + } + unshift @$blocks, { name => "ws", markup => "pango", + full_text => join " ", @disp }; } - unshift @$blocks, - { name => "ws", markup => "pango", full_text => join " ", @disp }; - } - if ($info{focused_ws} && exists $info{paper_ws}{ $info{focused_ws} }) { - sub nwin { join " ", ("\x{2021}")x$_[0] } + if ($focused_ws && exists $paper_ws{$focused_ws}) { + sub nwin { join " ", ("\x{2021}")x$_[0] } + + my $ws = $paper_ws{$focused_ws}; + my $left = $ws->{off_left}->@*; + my $right = $ws->{off_right}->@*; - my $ws = $info{paper_ws}{ $info{focused_ws} }; - my $left = $ws->{off_left}->@*; - my $right = $ws->{off_right}->@*; + my $disp = sprintf "%s", + $ws->{monocle} ? "\x{2020}" : nwin($ws->{ncols}); + $disp = sprintf "%s %s", nwin($left), $disp if $left; + $disp = sprintf "%s %s", $disp, nwin($right) if $right; - my $disp = sprintf "%s", - $ws->{monocle} ? "\x{2020}" : nwin($ws->{ncols}); - $disp = sprintf "%s %s", nwin($left), $disp if $left; - $disp = sprintf "%s %s", $disp, nwin($right) if $right; + unshift @$blocks, + { name => "cols", markup => "pango", full_text => $disp }; + } unshift @$blocks, - { name => "cols", markup => "pango", full_text => $disp }; + { + name => "caffeinated", + full_text => "Caffeinated: " . $caffeinated_name } + if $caffeinated_name; + + unshift @$blocks, $hostinfo; + + print encode_json($blocks) . ",\n"; + }); + +# Start main loop. +for (;;) { + $have_pending->recv; + $have_pending = AnyEvent::condvar; + + if (@pending_events) { + # Generally we would like to update %paper_ws with the information we + # receive by subscription, but in some cases we can't be sure of what + # has happened. For example, as we don't maintain a representation of + # the whole tree, on a change=move event, we don't know where the + # container has gone. Or a focus change might be due to a new + # container, in which case we might need to push one off. Currently, + # if we can't handle it within the callback, then we always normalise. + normalise_ws_cols(); + kill USR1 => $i3status; + @pending_events = (); } - - unshift @$blocks, - { - name => "caffeinated", - full_text => "Caffeinated: " . $info{caffeinated_name} } - if $info{caffeinated_name}; - - tied(%info)->unlock; - - unshift @$blocks, $hostinfo; - - print encode_json($blocks) . ",\n"; + process_msg(shift @pending_msgs) while @pending_msgs; } sub wsbuttons { return unless $ENV{XDG_CURRENT_DESKTOP} eq "sway"; - $wmipc->cmd("bar bar-0 workspace_buttons $_[0]"); + cmd("bar bar-0 workspace_buttons $_[0]"); } sub register_caffeinated { - $info{caffeinated_id} = $_[0]->{id}; - $info{caffeinated_name} = $_[0]->{name}; + $caffeinated_id = $_[0]->{id}; + $caffeinated_name = $_[0]->{name}; kill USR1 => $i3status; } sub clear_caffeinated { - undef $info{caffeinated_id}; - undef $info{caffeinated_name}; + undef $caffeinated_id; + undef $caffeinated_name; kill USR1 => $i3status; } @@ -328,19 +314,19 @@ sub sync_cols { foreach my $child_node ($node->{nodes}->@*) { next unless $child_node->{type} eq "con"; push $entry->{cols}->@*, $child_node->{id}; - $info{col_rows}{$child_node->{id}} = $child_node->{nodes}->@*; + $col_rows{$child_node->{id}} = $child_node->{nodes}->@*; } } sub normalise_ws_cols { - my $ws = $info{paper_ws}{$info{focused_ws}}; + my $ws = $paper_ws{$focused_ws}; my $floating_focus; my $old_cols = $ws->{cols}; my $old_i = shift // first { $old_cols->[$_] == $ws->{focused_col} } 0..$#$old_cols; for_each_node { my $node = shift; - if ($node->{id} == $info{focused_ws}) { + if ($node->{id} == $focused_ws) { sync_cols($node => $ws); my $first_focus = $node->{focus}->[0]; $floating_focus = ! grep $_ == $first_focus, $ws->{cols}->@*; @@ -360,18 +346,18 @@ sub normalise_ws_cols { $i = $old_i = !!$avail_l; } - if ($ws->{focused_col} && $info{col_rows}{$ws->{focused_col}} - && $info{col_rows}{$ws->{focused_col}} == 1) { + if ($ws->{focused_col} && $col_rows{$ws->{focused_col}} + && $col_rows{$ws->{focused_col}} == 1) { # Attempt to delete the vertically split container by moving the # single window it contains over one of its edges. # We can't always do this. We assume the default focus_wrapping. - if ($i < $#$cols && !$info{col_rows}{ @$cols[$i+1] }) { + if ($i < $#$cols && !$col_rows{ @$cols[$i+1] }) { push @cmds, "move right"; - delete $info{col_rows}{$ws->{focused_col}}; + delete $col_rows{$ws->{focused_col}}; $ws->{focused_col} = $cols->[$i] = node_first_child($cols->[$i]); - } elsif ($i > 0 && !$info{col_rows}{ @$cols[$i-1] }) { + } elsif ($i > 0 && !$col_rows{ @$cols[$i-1] }) { push @cmds, "move left"; - delete $info{col_rows}{$ws->{focused_col}}; + delete $col_rows{$ws->{focused_col}}; $ws->{focused_col} = $cols->[$i] = node_first_child($cols->[$i]); } } @@ -422,7 +408,7 @@ sub normalise_ws_cols { for (@to_pull) { push @cmds, show_con(@$_[0]); next unless @$_[1]; - push @cmds, $info{col_rows}{@$_[1]} + push @cmds, $col_rows{@$_[1]} ? "move left" : "swap container with con_id @$_[1]"; } @@ -437,7 +423,7 @@ sub normalise_ws_cols { push @cmds, ("focus right")x($#$cols-$i); for (@to_pull) { push @cmds, show_con(@$_[0]); - push @cmds, "move right" if @$_[1] && $info{col_rows}{@$_[1]}; + push @cmds, "move right" if @$_[1] && $col_rows{@$_[1]}; } push @$cols, @pulled; @@ -451,7 +437,7 @@ sub normalise_ws_cols { } $ws->{focused_col} = $cols->[$old_i]; - push @cmds, "focus child" if $info{col_rows}{$ws->{focused_col}}; + push @cmds, "focus child" if $col_rows{$ws->{focused_col}}; } # Push columns off if there are too many columns. # This should never change which container is focused. @@ -474,16 +460,16 @@ sub normalise_ws_cols { } if (@cmds) { push @cmds, "focus floating" if $floating_focus; - with_ignored_events { $wmipc->cmd("focus tiling", @cmds) } + with_ignored_events { cmd("focus tiling", @cmds) } } } sub process_msg { my $cmd = shift; - my $ws = $info{paper_ws}{$info{focused_ws}}; + my $ws = $paper_ws{$focused_ws}; my $cols = $ws->{cols}; - my $col_rows = $info{col_rows}{$ws->{focused_col}}; + my $rows = $col_rows{$ws->{focused_col}}; my $i = first { $cols->[$_] == $ws->{focused_col} } 0..$#$cols; my $mv = sub { @@ -491,12 +477,12 @@ sub process_msg { if (@$cols > $j >= 0) { if ($move) { # This does not trigger any events. - $wmipc->cmd( + cmd( "[con_id=@$cols[$i]] swap container with con_id @$cols[$j]" ); @$cols[$i, $j] = @$cols[$j, $i]; } else { - $wmipc->cmd($j > $i ? "focus right" : "focus left"); + cmd($j > $i ? "focus right" : "focus left"); } } elsif ($move && $ws->{monocle}) { if ($j > $i && $ws->{off_right}->@*) { @@ -511,8 +497,8 @@ sub process_msg { push $ws->{off_left}->@*, $pushed; if ($move) { - if ($col_rows || @$cols) { - push @cmds, $col_rows + if ($rows || @$cols) { + push @cmds, $rows ? "move left" : "swap container with con_id @$cols[-1]"; push @cmds, "focus right"; @@ -525,12 +511,12 @@ sub process_msg { } } else { $ws->{focused_col} = $pulled; - push @cmds, "move right" if $col_rows; + push @cmds, "move right" if $rows; push @$cols, $pulled; - push @cmds, "focus child" if $info{col_rows}{$pulled}; + push @cmds, "focus child" if $col_rows{$pulled}; } - with_ignored_events { $wmipc->cmd(@cmds, hide_con($pushed)) }; + with_ignored_events { cmd(@cmds, hide_con($pushed)) }; kill USR1 => $i3status; } elsif ($j == -1 && $ws->{off_left}->@*) { my $pushed = pop @$cols; @@ -541,7 +527,7 @@ sub process_msg { if ($move) { if (@$cols) { - push @cmds, "move right" if $col_rows; + push @cmds, "move right" if $rows; push @cmds, "focus left"; my $tem = shift @$cols; unshift @$cols, $tem, $pulled; @@ -549,17 +535,17 @@ sub process_msg { unshift @$cols, $pulled; } } else { - if ($col_rows) { + if ($rows) { push @cmds, "move left"; } elsif (@$cols) { push @cmds, "swap container with con_id @$cols[0]"; } $ws->{focused_col} = $pulled; unshift @$cols, $pulled; - push @cmds, "focus child" if $info{col_rows}{$pulled}; + push @cmds, "focus child" if $col_rows{$pulled}; } - with_ignored_events { $wmipc->cmd(@cmds, hide_con($pushed)) }; + with_ignored_events { cmd(@cmds, hide_con($pushed)) }; kill USR1 => $i3status; } $ws->{last_dir} = $j > $i ? 1 : -1; @@ -598,31 +584,31 @@ sub process_msg { }); } elsif ($cmd =~ /^absorb_expel ?(left|right)?$/) { my $dir = $1 eq "right" ? 1 : -1; - if ($col_rows > 1) { # expel + if ($rows > 1) { # expel # If the column to the right or left also has rows, we'll just # move the container into that column instead of expelling it. # Possibly we could float the container, select the # appropriate full column and unfloat it into place? - $wmipc->cmd(sprintf "move %s", $dir > 0 ? "right" : "left"); + cmd(sprintf "move %s", $dir > 0 ? "right" : "left"); $ws->{last_dir} = $dir; } else { # absorb my @cmds; if ($i == 0 && $dir < 0 && $ws->{off_left}->@*) { my $pulled = pop $ws->{off_left}->@*; push @cmds, show_con($pulled), "move left"; - push @cmds, "splitv" unless $info{col_rows}{$pulled}; + push @cmds, "splitv" unless $col_rows{$pulled}; push @cmds, "focus right", "move left"; - with_ignored_events { $wmipc->cmd(@cmds) }; + with_ignored_events { cmd(@cmds) }; normalise_ws_cols(); } elsif ($i == $#$cols && $dir > 0 && $ws->{off_right}->@*) { my $pulled = pop $ws->{off_right}->@*; push @cmds, show_con($pulled); - push @cmds, "move right" if $col_rows; - push @cmds, "splitv" unless $info{col_rows}{$pulled}; + push @cmds, "move right" if $rows; + push @cmds, "splitv" unless $col_rows{$pulled}; push @cmds, "focus left", "move right"; - with_ignored_events { $wmipc->cmd(@cmds) }; + with_ignored_events { cmd(@cmds) }; normalise_ws_cols(); } elsif ($i == $#$cols && $dir < 0 || $#$cols > $i > 0 @@ -630,10 +616,10 @@ sub process_msg { push @cmds, $dir > 0 ? ("focus right", "splitv", "focus left") : ("focus left", "splitv", "focus right") - unless $info{col_rows}{ @$cols[$i+$dir] }; + unless $col_rows{ @$cols[$i+$dir] }; push @cmds, $dir > 0 ? "move right" : "move left"; - with_ignored_events { $wmipc->cmd(@cmds) }; + with_ignored_events { cmd(@cmds) }; normalise_ws_cols($ws->{off_left}->@* && $dir > 0 || $ws->{off_right}->@* && $dir < 0 ? min($#$cols, max 0, $i+$dir) : $i); @@ -651,18 +637,17 @@ sub process_msg { my ($move, $dir) = (!!$1, $2); $move && ensure_disable_monocle($ws); my @keys = sorted_paper_ws(); - my $k = first { $keys[$_] == $info{focused_ws} } 0..$#keys; + my $k = first { $keys[$_] == $focused_ws } 0..$#keys; if ($dir eq "next" && $k < $#keys || $dir eq "prev" && $k > 0) { my @cmds = "workspace $dir"; - $info{focused_ws} = $keys[$dir eq "next" ? $k+1 : $k-1]; + $focused_ws = $keys[$dir eq "next" ? $k+1 : $k-1]; if ($move) { push @cmds, show_con($ws->{focused_col}); push @cmds, "move right" - if $info{col_rows} - { $info{paper_ws}{$info{focused_ws}}{focused_col} }; - push @cmds, "focus child" if $col_rows; + if $col_rows{ $paper_ws{$focused_ws}{focused_col} }; + push @cmds, "focus child" if $rows; } - $wmipc->cmd(@cmds); + cmd(@cmds); } } } @@ -685,7 +670,7 @@ sub fresh_workspace { # its monocle mode container, then that workspace will get an empty # event, and we'll lose track of any windows pushed off to the sides. # So turn off monocle mode first. - ensure_disable_monocle(my $ws = $info{paper_ws}{$info{focused_ws}}); + ensure_disable_monocle(my $ws = $paper_ws{$focused_ws}); # We need to ensure that the monitoring loop doesn't process the move # event before it knows about the workspace change. Otherwise, that @@ -706,7 +691,7 @@ sub fresh_workspace { push @cmds, show_con($ws->{focused_col}) if $opts{send}; push @cmds, "workspace back_and_forth" unless $opts{go}; - $wmipc->cmd(@cmds); + cmd(@cmds); } $next_free_workspace } @@ -731,9 +716,9 @@ sub compact_workspaces { my $workspace = $all_workspaces[$i++]; $opts{leave_gap} - and $next == $info{focused_ws} + and $next == $focused_ws and $gap_workspace = $all_workspaces[$i++]; - my $next_name = $info{paper_ws}{$next}{name}; + my $next_name = $paper_ws{$next}{name}; next if $next_name eq $workspace; my $pair = [$next, $workspace]; ws_num($next_name) > ws_num($workspace) @@ -742,12 +727,11 @@ sub compact_workspaces { } with_ignored_events { - $wmipc->cmd( - map sprintf("rename workspace %s to %s", - $info{paper_ws}{$_->[0]}{name}, $_->[1]), + cmd(map sprintf("rename workspace %s to %s", + $paper_ws{$_->[0]}{name}, $_->[1]), @pairs) }; - $info{paper_ws}{$_->[0]}{name} = $_->[1] for @pairs; + $paper_ws{$_->[0]}{name} = $_->[1] for @pairs; $opts{leave_gap} and $gap_workspace } @@ -775,11 +759,12 @@ sub ensure_disable_monocle { } sub sorted_paper_ws { - sort { ws_num($info{paper_ws}{$a}{name}) - <=> ws_num($info{paper_ws}{$b}{name}) } - keys $info{paper_ws}->%* + sort { ws_num($paper_ws{$a}{name}) <=> ws_num($paper_ws{$b}{name}) } + keys %paper_ws } +sub cmd { $wmipc->command(join "; ", @_)->recv } + sub hide_con { # Enable floating in order to preserve any rows the container might have. # Otherwise, Sway subsumes the rows to the hidden workspace and the diff --git a/scripts/desktop/sway-ftp-master b/scripts/desktop/sway-ftp-master index 5cb3f6a6..0a36feee 100755 --- a/scripts/desktop/sway-ftp-master +++ b/scripts/desktop/sway-ftp-master @@ -14,8 +14,6 @@ use lib "$ENV{HOME}/src/dotfiles/perl5"; use JSON; use Local::Desktop; -sub wmipc { system "swaymsg", "-q", join ", ", @_ } - sub walk_tree (&$) { my ($pred, $tree) = @_; $pred->($tree) and return $tree; -- cgit v1.2.3