From a3114123727162db8ffa2be533f938cd6ba658bb Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Tue, 20 Feb 2024 11:30:52 +0800 Subject: i3status-wrapper: add absorb/expel command --- .config/sway/config | 13 +-- scripts/desktop/i3status-wrapper | 201 ++++++++++++++++++++++++++++++++------- 2 files changed, 175 insertions(+), 39 deletions(-) diff --git a/.config/sway/config b/.config/sway/config index f9b4c5d6..857f14da 100644 --- a/.config/sway/config +++ b/.config/sway/config @@ -91,12 +91,6 @@ mode "C-i-" { # change focus between tiling / floating windows bindsym Tab focus mode_toggle, mode "default" - # focus the parent container - bindsym u focus parent, mode "default" - - # focus the child container - bindsym d focus child, mode "default" - # # when screen is divided into two containers where at least one might # # have several tabs, as I usually have it, this works well to go back # # and forth @@ -172,6 +166,13 @@ mode "C-i-" { ~/src/dotfiles/scripts/desktop/i3status-wrapper-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" + # reload the configuration file bindsym Ctrl+Mod1+c reload, mode "default" # exit i3 (logs you out of your X session) diff --git a/scripts/desktop/i3status-wrapper b/scripts/desktop/i3status-wrapper index dbc02bad..4e0ddca9 100755 --- a/scripts/desktop/i3status-wrapper +++ b/scripts/desktop/i3status-wrapper @@ -30,7 +30,7 @@ use Sys::Hostname; use POSIX "floor", "mkfifo"; use File::Basename "basename", "dirname"; use File::Spec::Functions "catfile"; -use List::Util qw(first min max); +use List::Util qw(first min max zip); $| = 1; @@ -86,7 +86,8 @@ unless (fork // warn "couldn't fork monitoring loop") { if (@old_ids) { fresh_workspace(go => 1); $wmipc->cmd( - map("[con_id=$_] move container workspace current", @old_ids), + map("[con_id=$_] move container workspace current, floating disable", + @old_ids), "focus child"); } for_each_node { @@ -221,52 +222,84 @@ unless (fork // warn "couldn't fork command pipe reader") { my $ws = $info{paper_ws}{$info{focused_ws}}; my $cols = $ws->{cols}; + my $col_rows = $info{col_rows}{$ws->{focused_col}}; my $i = first { $cols->[$_] == $ws->{focused_col} } 0..$#$cols; state $last_dir = 1; my $mv = sub { my ($j, $move) = @_; if (@$cols > $j >= 0) { - $wmipc->cmd(sprintf "%s %s", - $move ? "move" : "focus", - $j > $i ? "right" : "left"); + if ($move) { + # This does not trigger any events. + $wmipc->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"); + } + } 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}->@*) { - with_ignored_events { - my $pushed = shift @$cols; - my $pulled = pop $ws->{off_right}->@*; - my @cmds = show_con($pulled); - push $ws->{off_left}->@*, $pushed; - if ($move) { - push @cmds, "focus left", "move right"; + my $pushed = shift @$cols; + my $pulled = pop $ws->{off_right}->@*; + my @cmds = show_con($pulled); + push $ws->{off_left}->@*, $pushed; + + if ($move) { + if ($col_rows || @$cols) { + push @cmds, $col_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 { - $ws->{focused_col} = $pulled; push @$cols, $pulled; } + } else { + $ws->{focused_col} = $pulled; + push @cmds, "move right" if $col_rows; + push @$cols, $pulled; + push @cmds, "focus child" if $info{col_rows}{$pulled}; + } - $wmipc->cmd(@cmds, hide_con($pushed)); - }; + with_ignored_events { $wmipc->cmd(@cmds, hide_con($pushed)) }; kill USR1 => $i3status; } elsif ($j == -1 && $ws->{off_left}->@*) { - with_ignored_events { - my $pushed = pop @$cols; - my $pulled = pop $ws->{off_left}->@*; - my @cmds = show_con($pulled); - push $ws->{off_right}->@*, $pushed; + my $pushed = pop @$cols; + my $pulled = pop $ws->{off_left}->@*; + my @cmds = show_con($pulled); - if ($move) { + push $ws->{off_right}->@*, $pushed; + + if ($move) { + if (@$cols) { + push @cmds, "move right" if $col_rows; push @cmds, "focus left"; my $tem = shift @$cols; unshift @$cols, $tem, $pulled; } else { - push @cmds, "move left"; - $ws->{focused_col} = $pulled; unshift @$cols, $pulled; } + } else { + if ($col_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}; + } - $wmipc->cmd(@cmds, hide_con($pushed)); - }; + with_ignored_events { $wmipc->cmd(@cmds, hide_con($pushed)) }; kill USR1 => $i3status; } $last_dir = $j > $i ? 1 : -1; @@ -313,6 +346,55 @@ unless (fork // warn "couldn't fork command pipe reader") { }); } + elsif ($cmd =~ /^absorb_expel ?(left|right)?$/) { + my $dir = $1 eq "right" ? 1 : -1; + if ($col_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"); + $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, "focus right", "move left"; + + with_ignored_events { $wmipc->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, "focus left", "move right"; + + with_ignored_events { $wmipc->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 $info{col_rows}{ @$cols[$i+$dir] }; + push @cmds, $dir > 0 ? "move right" : "move left"; + + with_ignored_events { $wmipc->cmd(@cmds) }; + normalise_ws_cols($ws->{off_left}->@* && $dir > 0 + || $ws->{off_right}->@* && $dir < 0 + ? min($#$cols, max 0, $i+$dir) : $i); + } + if (@cmds) { + $last_dir = $dir; + kill USR1 => $i3status; + } + } + } + tied(%info)->unlock; } } @@ -419,8 +501,9 @@ sub sync_cols { } $entry->{cols} = []; foreach my $child_node ($node->{nodes}->@*) { - push $entry->{cols}->@*, $child_node->{id} - if $child_node->{type} eq "con"; + next unless $child_node->{type} eq "con"; + push $entry->{cols}->@*, $child_node->{id}; + $info{col_rows}{$child_node->{id}} = $child_node->{nodes}->@*; } } @@ -452,6 +535,22 @@ 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) { + # 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] }) { + push @cmds, "move right"; + delete $info{col_rows}{$ws->{focused_col}}; + $ws->{focused_col} = $cols->[$i] = node_first_child($cols->[$i]); + } elsif ($i > 0 && !$info{col_rows}{ @$cols[$i-1] }) { + push @cmds, "move left"; + delete $info{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. @@ -491,16 +590,31 @@ sub normalise_ws_cols { if ($from_l //= min $avail_l, $want-$from_r) { my @pulled = splice $ws->{off_left}->@*, -$from_l, $from_l; - push @cmds, ("focus left")x$i, - map +(show_con($_), "move left"), reverse @pulled; + 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, $info{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; - push @cmds, ("focus right")x($#$cols-$i), - map show_con($_), @pulled; + 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] && $info{col_rows}{@$_[1]}; + } + push @$cols, @pulled; $i = $#$cols; } @@ -512,6 +626,7 @@ sub normalise_ws_cols { } $ws->{focused_col} = $cols->[$old_i]; + push @cmds, "focus child" if $info{col_rows}{$ws->{focused_col}}; } # Push columns off if there are too many columns. # This should never change which container is focused. @@ -628,6 +743,20 @@ sub compact_workspaces { $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 sorted_paper_ws { sort { ws_num($info{paper_ws}{$a}{name}) <=> ws_num($info{paper_ws}{$b}{name}) } @@ -635,11 +764,17 @@ sub sorted_paper_ws { } sub hide_con { - sprintf "[con_id=%s] move container to workspace %s", $_[0], "*$_[0]*" + # 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] move container to workspace current, focus", $_[0] + sprintf "[con_id=%s] %s", + $_[0], join ", ", "move container to workspace current", + "floating disable", "focus"; } sub ws_name { -- cgit v1.2.3