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