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