#!/usr/bin/perl
use
5.032;
use
POSIX
"floor"
,
"mkfifo"
;
$| = 1;
$Data::Dumper::Sortkeys
= 1;
my
(
$pipe
,
$i3status
,
$debug
);
GetOptions
'debug!'
=> \
$debug
,
'i3status!'
=> \
$i3status
or
die
"failed to parse command line options"
;
sub
debugsay {
return
unless
$debug
;
my
@args
=
@_
> 1 ? (
sprintf
$_
[0],
@_
[1..
$#_
]) :
@_
;
print
STDERR
"papersway: "
,
@args
,
"\n"
;
}
sub
debugdump {
return
unless
$debug
;
print
STDERR Data::Dumper->Dump(
@_
) =~ s/^/papersway: /mgr;
}
if
(
$i3status
) {
$pipe
= IO::Pipe->new;
if
(
$i3status
=
fork
//
die
"couldn't fork: $!"
) {
$pipe
->reader;
}
else
{
$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(
"papersway-ign"
)->
recv
;
$_
[0]->();
$wmipc
->send_tick(
"papersway-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
),
"papersway.$&.pipe"
;
-e and
unlink
for
$cmdpipe
;
mkfifo
$cmdpipe
, 0700 or
die
"mkfifo $cmdpipe failed: $!"
;
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
{
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
"papersway-ign"
;
$ignore_events
= 0
if
$payload
eq
"papersway-unign"
;
},
window
=>
sub
{
return
if
$ignore_events
;
my
$e
=
shift
; state
$last_e
;
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
;
}
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();
}
}
elsif
(
$e
->{change} &&
$e
->{change} eq
"title"
&&
$caffe_id
&&
$caffe_id
==
$e
->{container}{id}) {
register_caffeinated(
$e
->{container});
}
elsif
(
$e
->{change} &&
$e
->{change} eq
"close"
) {
clear_caffeinated()
if
$caffe_id
&&
$caffe_id
==
$e
->{container}{id};
queue_event
$e
if
exists
$paper_ws
{
$focused_ws
};
}
elsif
(
$e
->{change} &&
exists
$paper_ws
{
$focused_ws
}
&& (
$e
->{change} eq
"floating"
&&
$e
->{container}{type} ne
"floating_con"
||
$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
(
exists
$paper_ws
{
$focused_ws
}) {
queue_event
$e
;
}
else
{
signal_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};
signal_i3status();
}
elsif
(
$e
->{change} eq
"empty"
&&
$e
->{current}) {
delete
$paper_ws
{
$e
->{current}{id}};
signal_i3status();
}
},
})->
recv
->{success} or
die
"couldn't subscribe to window manager events"
;
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
);
}
};
debugsay
"initialised"
;
debugdump [\
%paper_ws
], [
qw(*paper_ws)
];
my
$username
=
$ENV
{LOGNAME} ||
$ENV
{USER} ||
getpwuid
$<;
my
$hostinfo
= {
name
=>
"hostinfo"
,
full_text
=>
sprintf
"%s@%s"
,
$username
, hostname };
print
scalar
<
$pipe
>
if
$i3status
;
print
scalar
<
$pipe
>
if
$i3status
;
my
$i3status_wrapper
=
$i3status
&& AnyEvent->io(
fh
=>
$pipe
,
poll
=>
"r"
,
cb
=>
sub
{
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
?
"<b>%s</b>"
:
"%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
"<b>%s</b>"
,
$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"
;
});
cmd(
"bar bar-0 workspace_buttons no"
)
if
$have_sway
&&
$i3status
;
for
(;;) {
$have_pending
->
recv
;
$have_pending
= AnyEvent::condvar;
if
(
@pending_events
) {
debugsay
"{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{"
;
debugsay
"normalising for these changes: %s"
,
join
"; "
,
map
$_
->{change},
@pending_events
;
debugdump [\
%paper_ws
], [
"*paper_ws BEFORE"
];
normalise_ws_cols();
signal_i3status();
debugdump [\
%paper_ws
], [
"*paper_ws AFTER"
];
debugsay
"}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}"
;
@pending_events
= ();
}
process_msg(
shift
@pending_msgs
)
while
@pending_msgs
;
}
sub
register_caffeinated {
$caffe_id
=
$_
[0]->{id};
$caffe_name
=
$_
[0]->{name};
signal_i3status();
}
sub
clear_caffeinated {
undef
$caffe_id
;
undef
$caffe_name
;
signal_i3status();
}
sub
sync_cols {
my
(
$node
,
$entry
) =
@_
;
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
// focused_col_idx(
$ws
);
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
%still_hidden
=
map
+(
$_
, 1),
grep
defined
,
map
$_
->{name} =~ /\A\*(\d+)\*\z/,
$wmipc
->get_workspaces->
recv
->@*;
debugdump [\
%still_hidden
], [
"*still_hidden"
];
$ws
->{off_left} = [
grep
$still_hidden
{
$_
},
$ws
->{off_left}->@* ];
$ws
->{off_right} = [
grep
$still_hidden
{
$_
},
$ws
->{off_right}->@* ];
my
$cols
=
$ws
->{cols};
my
$i
= focused_col_idx(
$ws
);
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) {
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
)) {
my
(
$from_l
,
$from_r
);
my
$want
=
$ws
->{ncols} -
@$cols
;
if
(
$old_i
>
$i
) {
if
(
$old_i
==
$#$old_cols
) {
$from_l
= min
$avail_l
,
$want
;
}
else
{
$from_l
= min
$avail_l
,
$old_i
-
$i
;
}
}
else
{
if
(
$old_i
== 0) {
if
(
@$old_cols
== 1) {
$from_r
= min
$avail_r
,
$want
;
}
else
{
$from_l
= !!
$avail_l
;
}
}
else
{
$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}};
}
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
) }
}
}
{
$App::papersway::cmds::VERSION
=
'1.004'
;
sub
new {
bless
{
cmds
=> [],
need_signal
=> 0 } =>
shift
};
sub
cmd {
my
$self
=
shift
;
push
$self
->{cmds}->@*,
@_
}
sub
need_signal {
shift
->{need_signal}++ }
sub
run {
my
$self
=
shift
;
my
@cmds
=
$self
->{cmds}->@*;
if
(
@cmds
) {
main::with_ignored_events { main::cmd(
@cmds
) };
main::signal_i3status()
if
$self
->{need_signal};
$self
->{cmds} = [];
}
$self
->{need_signal} = 0;
}
}
sub
process_msg {
my
$cmd
=
shift
;
my
$ws
=
$paper_ws
{
$focused_ws
};
my
$cols
=
$ws
->{cols};
my
$i
= focused_col_idx(
$ws
);
state
$cmds
= App::papersway::cmds->new;
my
$mv
=
sub
{
my
(
$j
,
$move
) =
@_
;
my
$rows
=
$col_rows
{
$ws
->{focused_col}};
if
(
@$cols
>
$j
>= 0) {
if
(
$move
) {
$cmds
->cmd(
"[con_id=@$cols[$i]] swap container with con_id @$cols[$j]"
);
@$cols
[
$i
,
$j
] =
@$cols
[
$j
,
$i
];
}
else
{
$cmds
->cmd(
$j
>
$i
?
"focus right"
:
"focus left"
);
$ws
->{focused_col} =
@$cols
[
$j
];
}
}
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}->@*;
$cmds
->cmd(show_con(
$pulled
));
push
$ws
->{off_left}->@*,
$pushed
;
if
(
$move
) {
if
(
$rows
||
@$cols
) {
$cmds
->cmd(
$rows
?
"move left"
:
"swap container with con_id @$cols[-1]"
);
$cmds
->cmd(
"focus right"
);
}
if
(
@$cols
) {
my
$tem
=
pop
@$cols
;
push
@$cols
,
$pulled
,
$tem
;
}
else
{
push
@$cols
,
$pulled
;
}
}
else
{
$ws
->{focused_col} =
$pulled
;
$cmds
->cmd(
"move right"
)
if
$rows
;
push
@$cols
,
$pulled
;
$cmds
->cmd(
"focus child"
)
if
$col_rows
{
$pulled
};
}
$cmds
->cmd(hide_con(
$pushed
));
$cmds
->need_signal;
}
elsif
(
$j
== -1 &&
$ws
->{off_left}->@*) {
my
$pushed
=
pop
@$cols
;
my
$pulled
=
pop
$ws
->{off_left}->@*;
$cmds
->cmd(show_con(
$pulled
));
push
$ws
->{off_right}->@*,
$pushed
;
if
(
$move
) {
if
(
@$cols
) {
$cmds
->cmd(
"move right"
)
if
$rows
;
$cmds
->cmd(
"focus left"
);
my
$tem
=
shift
@$cols
;
unshift
@$cols
,
$tem
,
$pulled
;
}
else
{
unshift
@$cols
,
$pulled
;
}
}
else
{
if
(
$rows
) {
$cmds
->cmd(
"move left"
);
}
elsif
(
@$cols
) {
$cmds
->cmd(
"swap container with con_id @$cols[0]"
);
}
$ws
->{focused_col} =
$pulled
;
unshift
@$cols
,
$pulled
;
$cmds
->cmd(
"focus child"
)
if
$col_rows
{
$pulled
};
}
$cmds
->cmd(hide_con(
$pushed
));
$cmds
->need_signal;
}
};
if
(
$cmd
=~ /^(focus|move) (left|right)$/) {
my
(
$move
,
$dir
) = ($1, $2);
$mv
->(
$dir
eq
"right"
?
$i
+1 :
$i
-1,
$move
eq
"move"
);
$ws
->{last_dir} =
$dir
eq
"right"
? 1 : -1;
$cmds
->run;
}
elsif
(
$cmd
=~ /^cols (incr|decr)$/
&& (
$ws
->{ncols} > 2 || $1 eq
"incr"
)) {
$ws
->{ncols} += $1 eq
"incr"
? 1 : -1;
normalise_ws_cols();
signal_i3status();
}
elsif
(
$cmd
=~ /^other column$/) {
if
(
$i
== 0 ||
$ws
->{last_dir} == -1 &&
$i
<
$#$cols
) {
$mv
->(
$i
+1);
$ws
->{last_dir} = 1;
}
elsif
(
$i
==
$#$cols
||
$ws
->{last_dir} == 1) {
$mv
->(
$i
-1);
$ws
->{last_dir} = -1;
}
$cmds
->run;
}
elsif
(
$cmd
=~ /^scroll (left|right)$/
&&
$ws
->{
"off_$1"
}->@* && !
$ws
->{monocle}) {
if
($1 eq
"right"
) {
my
$old_i
=
$i
== 0 ?
$i
:
$i
-1;
$mv
->(
$i
+1),
$i
++
while
$i
<
$#$cols
;
$mv
->(
scalar
@$cols
);
$mv
->(
$i
-1),
$i
--
while
$i
>
$old_i
;
}
else
{
my
$old_i
=
$i
==
$#$cols
?
$i
:
$i
+1;
$mv
->(
$i
-1),
$i
--
while
$i
> 0;
$mv
->(-1);
$mv
->(
$i
+1),
$i
++
while
$i
<
$old_i
;
}
$cmds
->run;
}
elsif
(
$cmd
eq
"monocle toggle\n"
) {
unless
(ensure_disable_monocle(
$ws
)) {
$ws
->{monocle} = -
$i
-1;
normalise_ws_cols();
}
signal_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;
my
$rows
=
$col_rows
{
$ws
->{focused_col}};
if
(
$rows
> 1) {
cmd(
sprintf
"move %s"
,
$dir
> 0 ?
"right"
:
"left"
);
$ws
->{last_dir} =
$dir
;
}
else
{
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
;
signal_i3status();
}
}
}
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
$col_rows
{
$ws
->{focused_col}};
}
cmd(
@cmds
);
}
}
}
sub
fresh_workspace {
my
$next_free_workspace
= compact_workspaces(
leave_gap
=> 1);
if
(
$next_free_workspace
) {
my
@cmds
;
my
%opts
=
@_
;
ensure_disable_monocle(
my
$ws
=
$paper_ws
{
$focused_ws
})
if
$opts
{
send
};
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
}
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
signal_i3status {
kill
USR1
=>
$i3status
if
$i3status
}
sub
hide_con {
my
$ws
=
$paper_ws
{
$focused_ws
};
my
$ppt
= 100 / (!!
$ws
->{monocle} ||
$ws
->{ncols});
debugsay
"calculated hide_con resize width %dppt"
,
$ppt
;
sprintf
+(
"[con_id=%s] "
.
join
", "
,
"floating enable"
,
"resize set %dppt 100ppt"
,
"move container to workspace %s"
),
$_
[0],
$ppt
,
"*$_[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] }
sub
focused_col_idx {
first {
$_
[0]->{cols}->[
$_
] ==
$_
[0]->{focused_col} } 0..$
}