From 3201ac5b6531c8348664adfc40815c3be926e762 Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Fri, 12 Apr 2024 16:08:30 +0800 Subject: i3status-wrapper -> papersway --- .config/sway/config | 61 +- hooks/git/src/papersway/pre-push_01signed-off-by | 1 + scripts/desktop/i3status-wrapper | 776 ----------------------- scripts/desktop/i3status-wrapper-msg | 10 - 4 files changed, 22 insertions(+), 826 deletions(-) create mode 120000 hooks/git/src/papersway/pre-push_01signed-off-by delete mode 100755 scripts/desktop/i3status-wrapper delete mode 100755 scripts/desktop/i3status-wrapper-msg diff --git a/.config/sway/config b/.config/sway/config index 20e965ff..dbd62231 100644 --- a/.config/sway/config +++ b/.config/sway/config @@ -97,8 +97,7 @@ mode "C-i-" { # bindsym o focus_wrapping workspace, focus parent, focus right, \ # focus_wrapping yes, mode "default" - bindsym o exec ~/src/dotfiles/scripts/desktop/i3status-wrapper-msg \ - other column, mode "default" + bindsym o exec papersway-msg other column, mode "default" # switch to workspace bindsym 1 workspace 1, mode "default" @@ -154,24 +153,15 @@ mode "C-i-" { workspace back_and_forth, mode "default" # next unused workspace - bindsym j exec \ - ~/src/dotfiles/scripts/desktop/i3status-wrapper-msg \ - fresh-workspace, mode "default" + bindsym j exec papersway-msg fresh-workspace, mode "default" # sends window to next unused but doesn't change view - bindsym Mod1+j exec \ - ~/src/dotfiles/scripts/desktop/i3status-wrapper-msg \ - fresh-workspace send, mode "default" + bindsym Mod1+j exec papersway-msg fresh-workspace send, mode "default" # sends window to next unused and changes view bindsym Mod1+Ctrl+j exec \ - ~/src/dotfiles/scripts/desktop/i3status-wrapper-msg \ - fresh-workspace take, mode "default" + papersway-msg fresh-workspace take, mode "default" - bindsym comma exec \ - ~/src/dotfiles/scripts/desktop/i3status-wrapper-msg \ - absorb_expel left, mode "default" - bindsym period exec \ - ~/src/dotfiles/scripts/desktop/i3status-wrapper-msg \ - absorb_expel right, mode "default" + bindsym comma exec papersway-msg absorb_expel left, mode "default" + bindsym period exec papersway-msg absorb_expel right, mode "default" # reload the configuration file bindsym Ctrl+Mod1+c reload, mode "default" @@ -229,12 +219,8 @@ mode "C-i-" { # # height 120 by `spw/maybe-scale-basic-faces'. # bindsym equal resize set width 43 ppt, mode "default" - bindsym minus exec \ - ~/src/dotfiles/scripts/desktop/i3status-wrapper-msg cols decr, \ - mode "default" - bindsym equal exec \ - ~/src/dotfiles/scripts/desktop/i3status-wrapper-msg cols incr, \ - mode "default" + bindsym minus exec papersway-msg cols decr, mode "default" + bindsym equal exec papersway-msg cols incr, mode "default" bindsym Ctrl+y exec ~/src/dotfiles/scripts/desktop/sway-ftp-master-cut-note , mode "default" @@ -251,24 +237,21 @@ for_window [title="ftp-master dak command session"] mark dak focus_wrapping no # change focus -bindsym Ctrl+7 exec \ - ~/src/dotfiles/scripts/desktop/i3status-wrapper-msg focus left \ +bindsym Ctrl+7 exec papersway-msg focus left \ || swaymsg focus left -bindsym Ctrl+0 exec \ - ~/src/dotfiles/scripts/desktop/i3status-wrapper-msg focus right \ +bindsym Ctrl+0 exec papersway-msg focus right \ || swaymsg focus right bindsym Ctrl+8 focus down bindsym Ctrl+9 focus up # move focused window -bindsym Mod1+Ctrl+7 exec ~/src/dotfiles/scripts/desktop/i3status-wrapper-msg move left || swaymsg move left -bindsym Mod1+Ctrl+0 exec ~/src/dotfiles/scripts/desktop/i3status-wrapper-msg move right || swaymsg move right +bindsym Mod1+Ctrl+7 exec papersway-msg move left || swaymsg move left +bindsym Mod1+Ctrl+0 exec papersway-msg move right || swaymsg move right bindsym Mod1+Ctrl+8 move down bindsym Mod1+Ctrl+9 move up bindsym Ctrl+5 kill -bindsym Ctrl+6 exec \ - ~/src/dotfiles/scripts/desktop/i3status-wrapper-msg monocle toggle +bindsym Ctrl+6 exec papersway-msg monocle toggle # bindsym Ctrl+apostrophe layout toggle splith splitv tabbed @@ -276,14 +259,12 @@ bindsym Ctrl+6 exec \ # Warp the cursor to a point on the screen which is hopefully a titlebar. # This means that cycling through workspaces does not abruptly stop because, # e.g., a VNC client has grabbed the keyboard upon entry to the workspace. -bindsym Ctrl+1 seat - cursor set 0 0, exec \ - ~/src/dotfiles/scripts/desktop/i3status-wrapper-msg workspace prev -bindsym Ctrl+4 seat - cursor set 0 0, exec \ - ~/src/dotfiles/scripts/desktop/i3status-wrapper-msg workspace next +bindsym Ctrl+1 seat - cursor set 0 0, exec papersway-msg workspace prev +bindsym Ctrl+4 seat - cursor set 0 0, exec papersway-msg workspace next bindsym Mod1+Ctrl+1 seat - cursor set 0 0, exec \ - ~/src/dotfiles/scripts/desktop/i3status-wrapper-msg move_workspace prev + papersway-msg move_workspace prev bindsym Mod1+Ctrl+4 seat - cursor set 0 0, exec \ - ~/src/dotfiles/scripts/desktop/i3status-wrapper-msg move_workspace next + papersway-msg move_workspace next bindsym Ctrl+2 focus output left bindsym Mod1+Ctrl+2 move workspace to output left @@ -295,9 +276,9 @@ bindsym Mod1+Ctrl+3 move workspace to output right bar { # i3status is designed to be highly performant so it continues # to be refreshed when the system is under load; we're giving - # up some of that with i3status-wrapper, but let's see if it + # up some of that with papersway, but let's see if it # actually matters - status_command ~/src/dotfiles/scripts/desktop/i3status-wrapper + status_command papersway --i3status # default behaviour is to cycle through workspaces when scroll # wheel is used on the bar. However, don't think I've ever @@ -319,8 +300,8 @@ bar { binding_mode #000000 #EECD82 #000000 } - # Leave i3status-wrapper to disable the workspace buttons once it has - # started handling workspaces. + # Leave papersway to disable the workspace buttons once it has started + # handling workspaces. position bottom strip_workspace_numbers yes diff --git a/hooks/git/src/papersway/pre-push_01signed-off-by b/hooks/git/src/papersway/pre-push_01signed-off-by new file mode 120000 index 00000000..60ea0562 --- /dev/null +++ b/hooks/git/src/papersway/pre-push_01signed-off-by @@ -0,0 +1 @@ +../../pre-push_signed-off-by \ No newline at end of file diff --git a/scripts/desktop/i3status-wrapper b/scripts/desktop/i3status-wrapper deleted file mode 100755 index feb9a2d7..00000000 --- a/scripts/desktop/i3status-wrapper +++ /dev/null @@ -1,776 +0,0 @@ -#!/usr/bin/env perl - -# i3status-wrapper -- wrapper for i3status(1), plus other monitoring -# -# Copyright (C) 2019-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 5.032; -use strict; -use warnings; - -use JSON; -use IO::Pipe; -use AnyEvent; -use AnyEvent::I3 ":all"; -use Sys::Hostname; -use POSIX "floor", "mkfifo"; -use File::Basename "basename", "dirname"; -use File::Spec::Functions "catfile"; -use List::Util qw(first min max zip); - -$| = 1; - -my $pipe = IO::Pipe->new; - -my $i3status = fork // die "couldn't fork: $!"; -unless ($i3status) { - $pipe->writer; - open STDOUT, ">&=", $pipe->fileno - or die "couldn't re-open i3status's STDOUT: $!"; - exec "i3status"; -} - -my (%paper_ws, $focused_ws, %col_rows, $caffe_id, $caffe_name); - -my $have_sway = !!$ENV{SWAYSOCK}; -my $wm_ipc_socket = $have_sway ? $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")->recv; - $_[0]->(); - $wmipc->send_tick("i3status-wrapper-unign")->recv; -} - -sub for_each_node (&) { - my @trees = $wmipc->get_tree->recv; - while (@trees) { - foreach my $node ((shift @trees)->{nodes}->@*) { - $_[0]->($node); - unshift @trees, $node; - } - } -} - -my @all_workspaces = ( - "1", "2", "3", "4", "5", "6", - "7", "8", "9", "10", "11:F1", "12:F2", - "13:F3", "14:F4", "15:F5", "16:F6", "17:F7", "18:F8", - "19:F9", "20:F10", "21:F11", "22:F12" -); - -my $have_pending = AnyEvent->condvar; -my (@pending_events, @pending_msgs); - -(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; - } - - # 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 ($caffe_id && $caffe_id == $e->{container}{id}) { - clear_caffeinated(); - } - } - - # 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; - } - } 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; - } - }, -})->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/; -} -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; - -my $username = $ENV{LOGNAME} || $ENV{USER} || getpwuid $<; -my $hostinfo = { name => "hostinfo", - full_text => sprintf "%s@%s", $username, hostname }; - -# Skip the first line which contains the version header. -print scalar <$pipe>; - -# The second line contains the start of the infinite array. -print scalar <$pipe>; - -# 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 => "@disp" }; - } - - 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 $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 => "caffeinated", full_text => "Caffeinated: $caffe_name" } - if $caffe_name; - - unshift @$blocks, $hostinfo; - - print encode_json($blocks) . ",\n"; - }); - -# Start main loop. -cmd("bar bar-0 workspace_buttons no") if $have_sway; -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 = (); - } - process_msg(shift @pending_msgs) while @pending_msgs; -} - -sub register_caffeinated { - $caffe_id = $_[0]->{id}; - $caffe_name = $_[0]->{name}; - kill USR1 => $i3status; -} - -sub clear_caffeinated { - undef $caffe_id; - undef $caffe_name; - kill USR1 => $i3status; -} - -sub sync_cols { - my ($node, $entry) = @_; - - # Here we assume that the containers for the columns are directly below - # the type=workspace node. That won't be true if workspace_layout is not - # configured to 'default'. - foreach my $child_id ($node->{focus}->@*) { - my $child_node = first { $_->{id} == $child_id } $node->{nodes}->@*; - $entry->{focused_col} = $child_id, last - if $child_node->{type} eq "con"; - } - $entry->{cols} = []; - foreach my $child_node ($node->{nodes}->@*) { - next unless $child_node->{type} eq "con"; - push $entry->{cols}->@*, $child_node->{id}; - $col_rows{$child_node->{id}} = $child_node->{nodes}->@*; - } -} - -sub normalise_ws_cols { - 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} == $focused_ws) { - sync_cols($node => $ws); - my $first_focus = $node->{focus}->[0]; - $floating_focus = ! grep $_ == $first_focus, $ws->{cols}->@*; - goto DONE; - } - }; - DONE: - my $cols = $ws->{cols}; - my $i = first { $cols->[$_] eq $ws->{focused_col} } 0..$#$cols; - - my @cmds; - my $avail_l = scalar $ws->{off_left}->@*; - my $avail_r = scalar $ws->{off_right}->@*; - - if ($ws->{monocle} && !@$cols) { - undef $ws->{monocle}; - $i = $old_i = !!$avail_l; - } - - 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 && !$col_rows{ @$cols[$i+1] }) { - push @cmds, "move right"; - delete $col_rows{$ws->{focused_col}}; - $ws->{focused_col} = $cols->[$i] = node_first_child($cols->[$i]); - } elsif ($i > 0 && !$col_rows{ @$cols[$i-1] }) { - push @cmds, "move left"; - delete $col_rows{$ws->{focused_col}}; - $ws->{focused_col} = $cols->[$i] = node_first_child($cols->[$i]); - } - } - - if (!$ws->{monocle} && $ws->{ncols} > @$cols && ($avail_l || $avail_r)) { - # Pull columns in if there are too few columns but some available. - # Want the focused column, after pulls, to be the $old_i'th. - my ($from_l, $from_r); - my $want = $ws->{ncols} - @$cols; - # When we lose columns, the focused column either moves left or - # stays the same. So always $old_i >= $i. - if ($old_i > $i) { - if ($old_i == $#$old_cols) { - # We were in the final column. Either we closed the - # rightmost column, or we lost arbitrary columns from the - # left (e.g. monocle from the last column). - # In either case it is fine to pull more from the left. - $from_l = min $avail_l, $want; - } else { - # We have $i < $old_i < $#$old_cols. - # We must have lost at least $old_i-$i from the left. - $from_l = min $avail_l, $old_i-$i; - } - } else { # $old_i == $i. - if ($old_i == 0) { - # We were in the first column. Either we closed the leftmost - # column, or we lost arbitrary columns from the left - # (e.g. monocle from the first column). We prefer to pull - # from the left in the former case. If we are indeed exiting - # monocle mode, we must pull from the right. - if (@$cols == 1) { - $from_r = min $avail_r, $want; - } else { - $from_l = !!$avail_l; - } - } else { - # It must be that we lost columns from the right. - $from_r = min $avail_r, $want; - } - } - - if ($from_l //= min $avail_l, $want-$from_r) { - my @pulled = splice $ws->{off_left}->@*, -$from_l, $from_l; - my @to_pull = reverse @pulled; - @to_pull = zip \@to_pull, [@$cols[0], @to_pull[0..$#to_pull-1]]; - - push @cmds, ("focus left")x$i; - for (@to_pull) { - push @cmds, show_con(@$_[0]); - next unless @$_[1]; - push @cmds, $col_rows{@$_[1]} - ? "move left" - : "swap container with con_id @$_[1]"; - } - - unshift @$cols, @pulled; - $i = 0; - } - if ($from_r //= min $avail_r, $want-$from_l) { - my @pulled - = reverse splice $ws->{off_right}->@*, -$from_r, $from_r; - my @to_pull = zip \@pulled, [@$cols[-1], @pulled[1..$#pulled]]; - push @cmds, ("focus right")x($#$cols-$i); - for (@to_pull) { - push @cmds, show_con(@$_[0]); - push @cmds, "move right" if @$_[1] && $col_rows{@$_[1]}; - } - - push @$cols, @pulled; - $i = $#$cols; - } - - if ($i > $old_i) { - push @cmds, ("focus left")x($i-$old_i); - } elsif ($old_i > $i) { - push @cmds, ("focus right")x($old_i-$i); - } - - $ws->{focused_col} = $cols->[$old_i]; - 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. - elsif (my $n = $ws->{monocle} ? @$cols-1 : @$cols-$ws->{ncols} > 0) { - my $left = $i; - my $right = $#$cols-$i; - if ($left >= $right) { - $left = min $left, $n; - $right = $n-$left; - } else { - $right = min $right, $n; - $left = $n-$right; - } - my @to_left = splice @$cols, 0, $left; - my @to_right = reverse splice @$cols, -$right, $right; - - push @cmds, map hide_con($_), @to_left, @to_right; - push $ws->{off_left}->@*, @to_left; - push $ws->{off_right}->@*, @to_right; - } - if (@cmds) { - push @cmds, "focus floating" if $floating_focus; - with_ignored_events { cmd("focus tiling", @cmds) } - } -} - -sub process_msg { - my $cmd = shift; - - my $ws = $paper_ws{$focused_ws}; - my $cols = $ws->{cols}; - my $rows = $col_rows{$ws->{focused_col}}; - my $i = first { $cols->[$_] == $ws->{focused_col} } 0..$#$cols; - - my $mv = sub { - my ($j, $move) = @_; - if (@$cols > $j >= 0) { - if ($move) { - # This does not trigger any events. - cmd( -"[con_id=@$cols[$i]] swap container with con_id @$cols[$j]" - ); - @$cols[$i, $j] = @$cols[$j, $i]; - } else { - cmd($j > $i ? "focus right" : "focus left"); - } - } elsif ($move && $ws->{monocle}) { - if ($j > $i && $ws->{off_right}->@*) { - push $ws->{off_left}->@*, pop $ws->{off_right}->@*; - } elsif ($j < $i && $ws->{off_left}->@*) { - push $ws->{off_right}->@*, pop $ws->{off_left}->@*; - } - } elsif ($j == @$cols && $ws->{off_right}->@*) { - my $pushed = shift @$cols; - my $pulled = pop $ws->{off_right}->@*; - my @cmds = show_con($pulled); - push $ws->{off_left}->@*, $pushed; - - if ($move) { - if ($rows || @$cols) { - push @cmds, $rows - ? "move left" - : "swap container with con_id @$cols[-1]"; - push @cmds, "focus right"; - } - if (@$cols) { - my $tem = pop @$cols; - push @$cols, $pulled, $tem; - } else { - push @$cols, $pulled; - } - } else { - $ws->{focused_col} = $pulled; - push @cmds, "move right" if $rows; - push @$cols, $pulled; - push @cmds, "focus child" if $col_rows{$pulled}; - } - - with_ignored_events { cmd(@cmds, hide_con($pushed)) }; - kill USR1 => $i3status; - } elsif ($j == -1 && $ws->{off_left}->@*) { - my $pushed = pop @$cols; - my $pulled = pop $ws->{off_left}->@*; - my @cmds = show_con($pulled); - - push $ws->{off_right}->@*, $pushed; - - if ($move) { - if (@$cols) { - push @cmds, "move right" if $rows; - push @cmds, "focus left"; - my $tem = shift @$cols; - unshift @$cols, $tem, $pulled; - } else { - unshift @$cols, $pulled; - } - } else { - 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 $col_rows{$pulled}; - } - - with_ignored_events { cmd(@cmds, hide_con($pushed)) }; - kill USR1 => $i3status; - } - $ws->{last_dir} = $j > $i ? 1 : -1; - }; - - # Command dispatch - - if ($cmd =~ /^(focus|move) (left|right)$/) { - $mv->($2 eq "right" ? $i+1 : $i-1, $1 eq "move"); - } elsif ($cmd =~ /^cols (incr|decr)$/) { - $ws->{ncols} += $1 eq "incr" ? 1 : -1; - normalise_ws_cols(); - kill USR1 => $i3status; - } elsif ($cmd =~ /^other column$/) { - # This is meant to be similar to my custom Emacs C-x o. - if ($i == 0 || $ws->{last_dir} == -1 && $i < $#$cols) { - $mv->($i+1); - } elsif ($i == $#$cols || $ws->{last_dir} == 1) { - $mv->($i-1); - } - } elsif ($cmd eq "monocle toggle\n") { - unless (ensure_disable_monocle($ws)) { - $ws->{monocle} = -$i-1; - normalise_ws_cols(); - } - kill USR1 => $i3status; - } elsif ($cmd =~ /^fresh-workspace ?(take|send)?$/) { - fresh_workspace(do { - if ($1 && $1 eq "take") { - go => 1, send => 1; - } elsif ($1 && $1 eq "send") { - send => 1; - } else { - go => 1; - } - }); - } elsif ($cmd =~ /^absorb_expel ?(left|right)?$/) { - my $dir = $1 eq "right" ? 1 : -1; - 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? - 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 $col_rows{$pulled}; - push @cmds, "focus right", "move left"; - - 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 $rows; - push @cmds, "splitv" unless $col_rows{$pulled}; - push @cmds, "focus left", "move right"; - - with_ignored_events { cmd(@cmds) }; - normalise_ws_cols(); - } elsif ($i == $#$cols && $dir < 0 - || $#$cols > $i > 0 - || $i == 0 && $dir > 0) { - push @cmds, $dir > 0 - ? ("focus right", "splitv", "focus left") - : ("focus left", "splitv", "focus right") - unless $col_rows{ @$cols[$i+$dir] }; - push @cmds, $dir > 0 ? "move right" : "move left"; - - 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); - } - if (@cmds) { - $ws->{last_dir} = $dir; - kill USR1 => $i3status; - } - } - } - - # Basic purpose of this wrapper command is to prevent accidentally - # moving to one of the holding workspaces using Sway's own commands. - elsif ($cmd =~ /^(move_)?workspace (prev|next)$/) { - my ($move, $dir) = (!!$1, $2); - $move && ensure_disable_monocle($ws); - my @keys = sorted_paper_ws(); - my $k = first { $keys[$_] == $focused_ws } 0..$#keys; - if ($dir eq "next" && $k < $#keys || $dir eq "prev" && $k > 0) { - my @cmds = "workspace $dir"; - $focused_ws = $keys[$dir eq "next" ? $k+1 : $k-1]; - if ($move) { - push @cmds, show_con($ws->{focused_col}); - push @cmds, "move right" - if $col_rows{ $paper_ws{$focused_ws}{focused_col} }; - push @cmds, "focus child" if $rows; - } - cmd(@cmds); - } - } -} - -=head fresh_workspace(%opts) - -Switch to the next free workspace, if any. Return the name of that workspace, -or undef if no workspace was available. - -=cut - -sub fresh_workspace { - my $next_free_workspace = compact_workspaces(leave_gap => 1); - - if ($next_free_workspace) { - my @cmds; - my %opts = @_; - - # Special case: if we're about to leave a workspace empty by removing - # 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 = $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 - # loop might try to unhide containers from the old workspace onto the - # new one. We do need it to process the workspace init event, else we - # don't know the ID of the new workspace without making our own query. - # - # We also want to ensure that the fresh workspace is the one that - # C-i ; will take us to. In the case that !$opts{go}, can use C-i M-; - # to move any other wanted containers over, before a final C-i ;. - # - # There is a relevant i3/Sway difference here: - # . - # (Our use of hide_con elsewhere assumes Sway's behaviour. Possibly - # we should write wrapper code that can handle either case.) - - push @cmds, "workspace $next_free_workspace"; - push @cmds, show_con($ws->{focused_col}) if $opts{send}; - push @cmds, "workspace back_and_forth" unless $opts{go}; - - cmd(@cmds); - } - $next_free_workspace -} - -=head compact_workspaces(%opts) - -Rename workspaces so as to remove gaps in the sequence of workspaces. - -If C<$opts{leave_gap}>, ensure there is a gap of one workspace after the -currently focused workspace and return the name of the gap workspace, or just -return undef if there is no space for a gap. - -=cut - -sub compact_workspaces { - my %opts = @_; - my @workspaces = sorted_paper_ws(); - @workspaces < @all_workspaces or return; - my ($i, $gap_workspace, @pairs); - - while (my $next = shift @workspaces) { - my $workspace = $all_workspaces[$i++]; - - $opts{leave_gap} - and $next == $focused_ws - and $gap_workspace = $all_workspaces[$i++]; - my $next_name = $paper_ws{$next}{name}; - next if $next_name eq $workspace; - my $pair = [$next, $workspace]; - ws_num($next_name) > ws_num($workspace) - ? push @pairs, $pair - : unshift @pairs, $pair - } - - with_ignored_events { - cmd(map sprintf("rename workspace %s to %s", - $paper_ws{$_->[0]}{name}, $_->[1]), - @pairs) - }; - $paper_ws{$_->[0]}{name} = $_->[1] for @pairs; - - $opts{leave_gap} and $gap_workspace -} - -sub node_first_child { - my $node_id = shift; - my $child_id; - for_each_node { - my $node = shift; - if ($node->{id} == $node_id) { - $child_id = $node->{nodes}[0]{id}; - goto DONE; - } - }; - DONE: - return $child_id; -} - -sub ensure_disable_monocle { - my $ws = shift; - my $m = $ws->{monocle} or return 0; - undef $ws->{monocle}; - normalise_ws_cols(abs ++$m); - return 1; -} - -sub sorted_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 - # container with our known ID ceases to exist, s.t. we can't unhide it. - sprintf "[con_id=%s] floating enable, move container to workspace %s", - $_[0], "*$_[0]*" -} - -sub show_con { - sprintf "[con_id=%s] %s", - $_[0], join ", ", "move container to workspace current", - "floating disable", "focus"; -} - -sub ws_name { - my ($before, $after) = split /:/, $_[0]; - $after // $before -} -sub ws_num { (split /:/, $_[0])[0] } diff --git a/scripts/desktop/i3status-wrapper-msg b/scripts/desktop/i3status-wrapper-msg deleted file mode 100755 index 602014e3..00000000 --- a/scripts/desktop/i3status-wrapper-msg +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/sh - -[ -n "$1" ] || exit 127 -socket=${SWAYSOCK:-$I3SOCK} -pipe="$(printf "%s/i3status-wrapper.%s.pipe" \ - "$(dirname "$socket")" \ - "$(basename "$socket" \ - | sed -n 's/^[^0-9]*\([0-9][0-9.]*[0-9]\).*/\1/p')")" -[ -w "$pipe" ] || exit 127 -echo "$@" >"$pipe" -- cgit v1.2.3