]> git.sur5r.net Git - i3/i3/blob - i3-wsbar
add socket path parameter to i3-wsbar script
[i3/i3] / i3-wsbar
1 #!/usr/bin/env perl
2 # vim:ts=4:sw=4:expandtab:ft=perl
3 # © 2010 Michael Stapelberg, see LICENSE for license information
4
5 use strict;
6 use warnings;
7 use Getopt::Long;
8 use Pod::Usage;
9 use IPC::Run qw(start pump);
10 use AnyEvent::I3;
11 use AnyEvent;
12 use v5.10;
13
14 my $stdin;
15 my $socket_path = undef;
16 my ($workspaces, $outputs) = ([], {});
17 my $last_line = "";
18
19 my $command = "";
20 my $input_on = "";
21 my $output_on = "";
22 my $show_all = 0;
23
24 my $result = GetOptions(
25     'command=s' => \$command,
26     'socket=s' => \$socket_path,
27     'input-on=s' => \$input_on,
28     'output-on=s' => \$output_on,
29     'show-all' => \$show_all,
30     'help' => sub { pod2usage(1); exit 0 },
31 );
32
33 if ($command eq '') {
34     say "i3-wsbar is only useful in combination with dzen2.";
35     say "Please specify -c (command)";
36     exit 1;
37 }
38
39 my $i3 = i3($socket_path);
40
41 my @input_on = split(/,/, $input_on);
42 my @output_on = split(/,/, $output_on);
43
44 # Disable buffering
45 $| = 1;
46
47 # Wait a short amount of time and try to connect to i3 again
48 sub reconnect {
49     my $timer;
50     my $c = sub {
51         $timer = AnyEvent->timer(
52             after => 0.01,
53             cb => sub { $i3->connect->cb(\&connected) }
54         );
55     };
56     $c->();
57 }
58
59 # Connection attempt succeeded or failed
60 sub connected {
61     my ($cv) = @_;
62
63     if (!$cv->recv) {
64         reconnect();
65         return;
66     }
67
68     $i3->subscribe({
69         workspace => \&ws_change,
70         output => \&output_change,
71         _error => sub { reconnect() }
72     });
73     ws_change();
74     output_change();
75 }
76
77 # Called when a ws changes
78 sub ws_change {
79     # Request the current workspaces and update the output afterwards
80     $i3->get_workspaces->cb(
81         sub {
82             my ($cv) = @_;
83             $workspaces = $cv->recv;
84             update_output();
85         });
86 }
87
88 # Called when the reply to the GET_OUTPUTS message arrives
89 # Compares old outputs with new outputs and starts/kills
90 # $command for each output (if specified)
91 sub got_outputs {
92     my $reply = shift->recv;
93     my %old = %{$outputs};
94     my %new = map { ($_->{name}, $_) } grep { $_->{active} } @{$reply};
95
96     # If no command was given, we do not need to compare outputs
97     if ($command eq '') {
98         update_output();
99         return;
100     }
101
102     # Handle new outputs
103     for my $name (keys %new) {
104         next if @output_on and !($name ~~ @output_on);
105
106         if (defined($old{$name})) {
107             # Check if the mode changed (by reversing the hashes so
108             # that we can check for equality using the smartmatch op)
109             my %oldrect = reverse %{$old{$name}->{rect}};
110             my %newrect = reverse %{$new{$name}->{rect}};
111             next if (%oldrect ~~ %newrect);
112
113             # On mode changes, we re-start the command
114             $outputs->{$name}->{cmd}->finish;
115             delete $outputs->{$name};
116         }
117
118         my $x = $new{$name}->{rect}->{x};
119         my $launch = $command;
120         $launch =~ s/([^%])%x/$1$x/g;
121         $launch =~ s/%%x/%x/g;
122
123         $new{$name}->{cmd_input} = '';
124         my @cmd = ('/bin/sh', '-c', $launch);
125         $new{$name}->{cmd} = start \@cmd, \$new{$name}->{cmd_input};
126         $outputs->{$name} = $new{$name};
127     }
128
129     # Handle old outputs
130     for my $name (keys %old) {
131         next if defined($new{$name});
132
133         $outputs->{$name}->{cmd}->finish;
134         delete $outputs->{$name};
135     }
136
137     update_output();
138 }
139
140 sub output_change {
141     $i3->get_outputs->cb(\&got_outputs)
142 }
143
144 sub update_output {
145     my $dzen_bg = "#111111";
146     my $out;
147
148     for my $name (keys %{$outputs}) {
149         my $width = $outputs->{$name}->{rect}->{width};
150
151         $out = qq|^pa(;2)|;
152         for my $ws (@{$workspaces}) {
153             next if $ws->{output} ne $name and !$show_all;
154
155             my ($bg, $fg) = qw(333333 888888);
156             ($bg, $fg) = qw(4c7899 ffffff) if $ws->{visible};
157             ($bg, $fg) = qw(900000 ffffff) if $ws->{urgent};
158
159             my $cmd = q|i3-msg "| . $ws->{num} . q|"|;
160             my $name = $ws->{name};
161
162             # Begin the clickable area
163             $out .= qq|^ca(1,$cmd)|;
164
165             # Draw the rest of the bar in the background color, but
166             # don’t move the "cursor"
167             $out .= qq|^p(_LOCK_X)^fg(#$bg)^r(${width}x17)^p(_UNLOCK_X)|;
168             # Draw the name of the workspace without overwriting the
169             # background color
170             $out .= qq|^p(+3)^fg(#$fg)^ib(1)$name^ib(0)^p(+5)|;
171             # Draw the rest of the bar in the normal background color
172             # without moving the "cursor"
173             $out .= qq|^p(_LOCK_X)^fg($dzen_bg)^r(${width}x17)^p(_UNLOCK_X)|;
174
175             # End the clickable area
176             $out .= qq|^ca()|;
177
178             # Move to the next rect, reset Y coordinate
179             $out .= qq|^p(2)^pa(;2)|;
180         }
181
182         $out .= qq|^p(_LOCK_X)^fg($dzen_bg)^r(${width}x17)^p(_UNLOCK_X)^fg(white)|;
183         $out .= qq|^p(+5)|;
184         $out .= $last_line if (!@input_on or $name ~~ @input_on);
185         $out .= "\n";
186
187         $outputs->{$name}->{cmd_input} = $out;
188         pump $outputs->{$name}->{cmd} while length $outputs->{$name}->{cmd_input};
189     }
190 }
191
192 $i3->connect->cb(\&connected);
193
194 $stdin = AnyEvent->io(
195     fh => \*STDIN,
196     poll => 'r',
197     cb => sub {
198         chomp (my $line = <STDIN>);
199         $last_line = $line;
200         update_output();
201     });
202
203 # let AnyEvent do the rest ("endless loop")
204 AnyEvent->condvar->recv
205
206 __END__
207
208 =head1 NAME
209
210 i3-wsbar - sample implementation of a standalone workspace bar
211
212 =head1 SYNOPSIS
213
214 i3-wsbar -c <dzen2-commandline> [options]
215
216 =head1 OPTIONS
217
218 =over 4
219
220 =item B<--command> <command>
221
222 This command (at the moment only dzen2 is supported) will be started for each
223 output. C<%x> will be replaced with the X coordinate of the output.
224
225 Example:
226     --command "dzen2 -dock -x %x"
227
228 =item B<--input-on> <list-of-RandR-outputs>
229
230 Specifies on which outputs the contents of stdin should be appended to the
231 workspace bar.
232
233 Example:
234     --input-on "LVDS1"
235
236 =item B<--output-on> <list-of-RandR-outputs>
237
238 Specifies for which outputs i3-wsbar should start C<command>.
239
240 =item B<--show-all>
241
242 If enabled, all workspaces are shown (not only those of the current output).
243 Handy to use with C<--output-on>.
244
245 =back
246
247 =cut