2 # vim:ts=4:sw=4:expandtab:ft=perl
3 # © 2010 Michael Stapelberg, see LICENSE for license information
9 use IPC::Run qw(start pump);
16 my $socket_path = undef;
17 my ($workspaces, $outputs) = ([], {});
19 my $w = AnyEvent->timer(
22 say "Connection to i3 timed out. Verify socket path ($socket_path)";
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 },
42 say "i3-wsbar is only useful in combination with dzen2.";
43 say "Please specify -c (command)";
47 my $i3 = i3($socket_path);
49 my @input_on = split(/,/, $input_on);
50 my @output_on = split(/,/, $output_on);
55 # Wait a short amount of time and try to connect to i3 again
62 say "Connection to i3 timed out. Verify socket path ($socket_path)";
69 $timer = AnyEvent->timer(
71 cb => sub { $i3->connect->cb(\&connected) }
77 # Connection attempt succeeded or failed
89 workspace => \&ws_change,
90 output => \&output_change,
91 _error => sub { reconnect() }
97 # Called when a ws changes
99 # Request the current workspaces and update the output afterwards
100 $i3->get_workspaces->cb(
103 $workspaces = $cv->recv;
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)
112 my $reply = shift->recv;
113 my %old = %{$outputs};
114 my %new = map { ($_->{name}, $_) } grep { $_->{active} } @{$reply};
116 # If no command was given, we do not need to compare outputs
117 if ($command eq '') {
123 for my $name (keys %new) {
124 next if @output_on and !($name ~~ @output_on);
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);
133 # On mode changes, we re-start the command
134 $outputs->{$name}->{cmd}->finish;
135 delete $outputs->{$name};
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;
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};
153 for my $name (keys %old) {
154 next if defined($new{$name});
156 $outputs->{$name}->{cmd}->finish;
157 delete $outputs->{$name};
164 $i3->get_outputs->cb(\&got_outputs)
168 my $dzen_bg = "#111111";
172 for my $name (keys %{$outputs}) {
173 my $width = $outputs->{$name}->{rect}->{width};
175 $previous_output = undef;
177 for my $ws (@{$workspaces}) {
178 next if $ws->{output} ne $name and !$show_all;
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)|;
185 $previous_output = $ws->{output};
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};
191 my $cmd = q|i3-msg "workspace | . $ws->{name} . q|"|;
192 my $name = $ws->{name};
194 # Begin the clickable area
195 $out .= qq|^ca(1,$cmd)|;
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
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)|;
207 # End the clickable area
210 # Move to the next rect, reset Y coordinate
211 $out .= qq|^p(2)^pa(;2)|;
214 $out .= qq|^p(_LOCK_X)^fg($dzen_bg)^r(${width}x17)^p(_UNLOCK_X)^fg()|;
216 $out .= $last_line if (!@input_on or $name ~~ @input_on);
219 $outputs->{$name}->{cmd_input} = $out;
221 pump $outputs->{$name}->{cmd} while length $outputs->{$name}->{cmd_input};
223 warn "Could not write to dzen2";
229 $i3->connect->cb(\&connected);
231 $stdin = AnyEvent->io(
236 if (!defined($line)) {
245 # let AnyEvent do the rest ("endless loop")
246 AnyEvent->condvar->recv
252 i3-wsbar - sample implementation of a standalone workspace bar
256 i3-wsbar -c <dzen2-commandline> [options]
262 =item B<--command> <command>
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.
269 --command "dzen2 -dock -x %x -w %w"
271 =item B<--input-on> <list-of-RandR-outputs>
273 Specifies on which outputs the contents of stdin should be appended to the
279 =item B<--output-on> <list-of-RandR-outputs>
281 Specifies for which outputs i3-wsbar should start C<command>.
285 If enabled, all workspaces are shown (not only those of the current output).
286 Handy to use with C<--output-on>.