summaryrefslogtreecommitdiffstats
path: root/bin/hush/i3-switch-workspace
blob: bcbb8d27dd4e874f6ac60b6fab1bd4066beed94c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#!/usr/bin/env perl
use strict;
use warnings;
use 5.014;

use IO::Socket::UNIX;
use JSON::PP;

my $json = JSON::PP->new;

my %message_types = (
    command        => 0,
    get_workspaces => 1,
    subscribe      => 2,
    get_outputs    => 3,
    get_tree       => 4,
    get_marks      => 5,
    get_bar_config => 6,
    get_version    => 7,
);

my %message_codes = (reverse %message_types);

my $dir = $ARGV[0];

chomp(my $path = qx(i3 --get-socketpath));
my $sock = IO::Socket::UNIX->new(Peer => $path);

my $workspace_data = $json->decode(i3_msg('get_workspaces'));

my $next_workspace = $workspace_data->[-1]{num} + 1;
my $prev_workspace = $workspace_data->[0]{num} - 1;

if (@$workspace_data == 1) {
    my $current_workspace = $workspace_data->[0]{num};
    my $tree_data = $json->decode(i3_msg('get_tree'));
    my $workspace_tree = find_workspace($tree_data, $current_workspace);
    if ($dir eq 'prev') {
        i3_msg('command', "workspace number $prev_workspace");
    }
    else {
        i3_msg('command', "workspace number $next_workspace");
    }
}
elsif ($workspace_data->[0]{focused} && $dir eq 'prev') {
    my $current_workspace = $workspace_data->[0]{num};
    my $tree_data = $json->decode(i3_msg('get_tree'));
    my $workspace_tree = find_workspace($tree_data, $current_workspace);
    if (@{ $workspace_tree->{nodes} } || @{ $workspace_tree->{floating_nodes} }) {
        if ($prev_workspace > 0) {
            i3_msg('command', "workspace number $prev_workspace");
        }
    }
}
elsif ($workspace_data->[-1]{focused} && $dir eq 'next') {
    my $current_workspace = $workspace_data->[-1]{num};
    my $tree_data = $json->decode(i3_msg('get_tree'));
    my $workspace_tree = find_workspace($tree_data, $current_workspace);
    if (@{ $workspace_tree->{nodes} } || @{ $workspace_tree->{floating_nodes} }) {
        i3_msg('command', "workspace number $next_workspace");
    }
}
else {
    for my $i (0..$#$workspace_data) {
        if ($workspace_data->[$i]{focused}) {
            my $to = $workspace_data->[$i]{num} + ($dir eq 'prev' ? -1 : 1);
            i3_msg('command', "workspace number $to");
            last;
        }
    }
}

sub find_workspace {
    my ($tree, $num) = @_;
    if (exists $tree->{num} && $tree->{num} == $num) {
        return $tree;
    }
    for my $node (@{ $tree->{nodes} }) {
        my $found = find_workspace($node, $num);
        return $found if $found;
    }
    return;
}

sub build_i3_msg {
    my ($type, $payload) = @_;

    $payload = ''
        unless $type eq 'command' || $type eq 'subscribe';

    utf8::encode($payload);

    return 'i3-ipc'
         . pack('LL', length($payload), $message_types{$type})
         . $payload;
}

sub get_i3_msg {
    my $bytes = $sock->read(my $header, 14);
    die "invalid read" if $bytes != 14;
    die "invalid format: $header" if substr($header, 0, 6) ne 'i3-ipc';
    my ($length, $type) = unpack('LL', substr($header, 6, 8));

    if ($length) {
        my $bytes = $sock->read(my $payload, $length);
        die "invalid read" if $bytes != $length;
        utf8::decode($payload);
        return ($message_codes{$type}, $payload);
    }
    else {
        return ($message_codes{$type});
    }
}

sub i3_msg {
    my ($type, $payload) = @_;
    my $msg = build_i3_msg($type, $payload);
    $sock->write($msg);
    my ($got_type, $got_payload) = get_i3_msg();
    die "got $got_type message, expected $type message"
        unless $type eq $got_type;
    return $got_payload;
}