dotfiles/dot_i3/i3-focus-next
2024-05-18 19:08:15 -07:00

196 lines
4.4 KiB
Perl
Executable file

#!/usr/bin/env perl
#
# needs package perl-json
#
use IO::Socket::UNIX;
use JSON;
use Encode;
use Getopt::Long;
use constant MAGIC_STRING => 'i3-ipc';
use constant TYPE_COMMAND => 0;
use constant TYPE_GET_WORKSPACES => 1;
use constant TYPE_GET_TREE => 4;
#
# ipc functions
#
sub message {
my ($type, $content) = @_;
my $payload = '';
if ($content) {
$payload = encode_utf8($content);
}
return(MAGIC_STRING . pack('LL', length($payload), $type) . $payload);
}
sub ipc_request {
my ($sock, $type, $content) = @_;
# send request
$sock->write(message($type, $content));
# read response header
my $header = '';
$sock->read($header, length(MAGIC_STRING) + 4 + 4);
# get payload length
my ($len, $type) = unpack('LL', substr($header, length(MAGIC_STRING)));
# read payload
my $payload = '';
$sock->read($payload, $len);
# return decoded json
return decode_json $payload;
}
#
# tree functions
#
sub get_node_by_name {
my ($ref, $name) = @_;
# search for a direct child node by name
foreach my $node (@{${${ref}}{'nodes'}}) {
if (${${node}}{'name'} eq $name) {
return $node;
}
}
}
sub pop_window_array {
my ($ref, $arr) = @_;
my @node_types = qw(nodes floating_nodes);
# iterate through all child nodes
foreach my $node_type (@node_types) {
foreach my $node (@{${${ref}}{$node_type}}) {
# if child node is not a window
if (${${node}}{'window'} eq undef) {
# go one level down
pop_window_array($node, \@{$arr});
} else {
# add window id to array
push(@{$arr}, ${${node}}{'id'});
}
}
}
}
sub get_focused_node {
my $ref = shift;
my $ret = '';
my @node_types = qw(nodes floating_nodes);
# iterate through all child nodes
foreach my $node_type (@node_types) {
foreach my $node (@{${${ref}}{$node_type}}) {
# if child node is not a window
if (${${node}}{'window'} eq undef) {
# go one level down
$ret = get_focused_node($node);
if ($ret != '') {
return $ret;
}
} else {
# return focused window id
if (${${node}}{'focused'}) {
return ${${node}}{'id'};
}
}
}
}
return '';
}
#
# main
#
my $reverse;
GetOptions("reverse" => \$reverse);
# get socket path
chomp(my $path = qx(i3 --get-socketpath));
# open connection
my $sock = IO::Socket::UNIX->new(Peer => $path);
# get current workspaces
my $response = '';
$response = ipc_request($sock, TYPE_GET_WORKSPACES);
# get focused output and workspace
my $i3output = '';
my $i3workspace = '';
foreach my $node (@{$response}) {
if (${$node}{'focused'}) {
$i3output = ${$node}{output};
$i3workspace = ${$node}{num};
}
}
# get layout tree
my $response = '';
$response = ipc_request($sock, TYPE_GET_TREE);
my @windows, $focused, $prev, $next;
if (${$response}{'name'} eq 'root') {
my $output = get_node_by_name($response, $i3output);
my $content = get_node_by_name($output, 'content');
my $workspace = get_node_by_name($content, $i3workspace);
# populate window array
pop_window_array($workspace, \@windows);
# get focused window
$focused = get_focused_node($workspace);
# find window key for focused window
my $focused_key;
while (my ($key, $val) = each @windows) {
if ($val eq $focused) {
# get previous window
if ($key == 0) {
$prev = $windows[$#windows];
} else {
$prev = $windows[$key-1];
}
# get next window
if ($key == $#windows) {
$next = $windows[0];
} else {
$next = $windows[$key+1];
}
}
}
}
#print $prev . "\n" . $focused . "\n" . $next . "\n";
my $response = '';
if ($reverse) {
# set focus to previous window
$response = ipc_request($sock, TYPE_COMMAND, '[con_id="' . $prev . '"] focus');
} else {
# set focus to next window
$response = ipc_request($sock, TYPE_COMMAND, '[con_id="' . $next . '"] focus');
}
close($sock);
# exit with ipc command status
if (${$response}[0]{'success'}) {
exit(0);
} else {
exit(1);
}