2010-03-24 19:13:19 +01:00
|
|
|
|
#!/usr/bin/env perl
|
|
|
|
|
# vim:ts=4:sw=4:expandtab:ft=perl
|
|
|
|
|
# © 2010 Michael Stapelberg, see LICENSE for license information
|
|
|
|
|
|
|
|
|
|
use strict;
|
|
|
|
|
use warnings;
|
|
|
|
|
use Getopt::Long;
|
|
|
|
|
use Pod::Usage;
|
|
|
|
|
use IPC::Run qw(start pump);
|
2010-06-16 19:26:55 +02:00
|
|
|
|
use Try::Tiny;
|
2010-03-24 19:13:19 +01:00
|
|
|
|
use AnyEvent::I3;
|
|
|
|
|
use AnyEvent;
|
|
|
|
|
use v5.10;
|
|
|
|
|
|
|
|
|
|
my $stdin;
|
2010-04-11 21:00:57 +02:00
|
|
|
|
my $socket_path = undef;
|
2010-03-24 19:13:19 +01:00
|
|
|
|
my ($workspaces, $outputs) = ([], {});
|
|
|
|
|
my $last_line = "";
|
2010-05-26 23:40:05 +02:00
|
|
|
|
my $w = AnyEvent->timer(
|
|
|
|
|
after => 2,
|
|
|
|
|
cb => sub {
|
|
|
|
|
say "Connection to i3 timed out. Verify socket path ($socket_path)";
|
|
|
|
|
exit 1;
|
|
|
|
|
}
|
|
|
|
|
);
|
2010-03-24 19:13:19 +01:00
|
|
|
|
|
|
|
|
|
my $command = "";
|
|
|
|
|
my $input_on = "";
|
|
|
|
|
my $output_on = "";
|
|
|
|
|
my $show_all = 0;
|
|
|
|
|
|
|
|
|
|
my $result = GetOptions(
|
|
|
|
|
'command=s' => \$command,
|
2010-04-11 21:00:57 +02:00
|
|
|
|
'socket=s' => \$socket_path,
|
2010-03-24 19:13:19 +01:00
|
|
|
|
'input-on=s' => \$input_on,
|
|
|
|
|
'output-on=s' => \$output_on,
|
|
|
|
|
'show-all' => \$show_all,
|
|
|
|
|
'help' => sub { pod2usage(1); exit 0 },
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ($command eq '') {
|
|
|
|
|
say "i3-wsbar is only useful in combination with dzen2.";
|
|
|
|
|
say "Please specify -c (command)";
|
|
|
|
|
exit 1;
|
|
|
|
|
}
|
|
|
|
|
|
2010-04-11 21:00:57 +02:00
|
|
|
|
my $i3 = i3($socket_path);
|
|
|
|
|
|
2010-03-24 19:13:19 +01:00
|
|
|
|
my @input_on = split(/,/, $input_on);
|
|
|
|
|
my @output_on = split(/,/, $output_on);
|
|
|
|
|
|
|
|
|
|
# Disable buffering
|
|
|
|
|
$| = 1;
|
|
|
|
|
|
|
|
|
|
# Wait a short amount of time and try to connect to i3 again
|
|
|
|
|
sub reconnect {
|
|
|
|
|
my $timer;
|
2010-05-26 23:40:05 +02:00
|
|
|
|
if (!defined($w)) {
|
|
|
|
|
$w = AnyEvent->timer(
|
|
|
|
|
after => 2,
|
|
|
|
|
cb => sub {
|
|
|
|
|
say "Connection to i3 timed out. Verify socket path ($socket_path)";
|
|
|
|
|
exit 1;
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2010-03-24 19:13:19 +01:00
|
|
|
|
my $c = sub {
|
|
|
|
|
$timer = AnyEvent->timer(
|
|
|
|
|
after => 0.01,
|
|
|
|
|
cb => sub { $i3->connect->cb(\&connected) }
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
$c->();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Connection attempt succeeded or failed
|
|
|
|
|
sub connected {
|
|
|
|
|
my ($cv) = @_;
|
|
|
|
|
|
|
|
|
|
if (!$cv->recv) {
|
|
|
|
|
reconnect();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2010-05-26 23:40:05 +02:00
|
|
|
|
$w = undef;
|
|
|
|
|
|
2010-03-24 19:13:19 +01:00
|
|
|
|
$i3->subscribe({
|
|
|
|
|
workspace => \&ws_change,
|
|
|
|
|
output => \&output_change,
|
|
|
|
|
_error => sub { reconnect() }
|
|
|
|
|
});
|
|
|
|
|
ws_change();
|
|
|
|
|
output_change();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Called when a ws changes
|
|
|
|
|
sub ws_change {
|
|
|
|
|
# Request the current workspaces and update the output afterwards
|
|
|
|
|
$i3->get_workspaces->cb(
|
|
|
|
|
sub {
|
|
|
|
|
my ($cv) = @_;
|
|
|
|
|
$workspaces = $cv->recv;
|
|
|
|
|
update_output();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Called when the reply to the GET_OUTPUTS message arrives
|
|
|
|
|
# Compares old outputs with new outputs and starts/kills
|
|
|
|
|
# $command for each output (if specified)
|
|
|
|
|
sub got_outputs {
|
|
|
|
|
my $reply = shift->recv;
|
|
|
|
|
my %old = %{$outputs};
|
|
|
|
|
my %new = map { ($_->{name}, $_) } grep { $_->{active} } @{$reply};
|
|
|
|
|
|
|
|
|
|
# If no command was given, we do not need to compare outputs
|
|
|
|
|
if ($command eq '') {
|
|
|
|
|
update_output();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Handle new outputs
|
|
|
|
|
for my $name (keys %new) {
|
|
|
|
|
next if @output_on and !($name ~~ @output_on);
|
|
|
|
|
|
|
|
|
|
if (defined($old{$name})) {
|
|
|
|
|
# Check if the mode changed (by reversing the hashes so
|
|
|
|
|
# that we can check for equality using the smartmatch op)
|
|
|
|
|
my %oldrect = reverse %{$old{$name}->{rect}};
|
|
|
|
|
my %newrect = reverse %{$new{$name}->{rect}};
|
|
|
|
|
next if (%oldrect ~~ %newrect);
|
|
|
|
|
|
|
|
|
|
# On mode changes, we re-start the command
|
|
|
|
|
$outputs->{$name}->{cmd}->finish;
|
|
|
|
|
delete $outputs->{$name};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
my $x = $new{$name}->{rect}->{x};
|
2010-06-19 11:48:47 +02:00
|
|
|
|
my $w = $new{$name}->{rect}->{width};
|
2010-03-24 19:13:19 +01:00
|
|
|
|
my $launch = $command;
|
|
|
|
|
$launch =~ s/([^%])%x/$1$x/g;
|
2010-06-19 11:48:47 +02:00
|
|
|
|
$launch =~ s/([^%])%w/$1$w/g;
|
2010-03-24 19:13:19 +01:00
|
|
|
|
$launch =~ s/%%x/%x/g;
|
2010-06-19 11:48:47 +02:00
|
|
|
|
$launch =~ s/%%w/%w/g;
|
2010-03-24 19:13:19 +01:00
|
|
|
|
|
|
|
|
|
$new{$name}->{cmd_input} = '';
|
|
|
|
|
my @cmd = ('/bin/sh', '-c', $launch);
|
|
|
|
|
$new{$name}->{cmd} = start \@cmd, \$new{$name}->{cmd_input};
|
|
|
|
|
$outputs->{$name} = $new{$name};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Handle old outputs
|
|
|
|
|
for my $name (keys %old) {
|
|
|
|
|
next if defined($new{$name});
|
|
|
|
|
|
|
|
|
|
$outputs->{$name}->{cmd}->finish;
|
|
|
|
|
delete $outputs->{$name};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
update_output();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sub output_change {
|
|
|
|
|
$i3->get_outputs->cb(\&got_outputs)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sub update_output {
|
|
|
|
|
my $dzen_bg = "#111111";
|
|
|
|
|
my $out;
|
2011-08-03 03:54:00 +02:00
|
|
|
|
my $previous_output;
|
2010-03-24 19:13:19 +01:00
|
|
|
|
|
|
|
|
|
for my $name (keys %{$outputs}) {
|
|
|
|
|
my $width = $outputs->{$name}->{rect}->{width};
|
|
|
|
|
|
2011-08-03 03:54:00 +02:00
|
|
|
|
$previous_output = undef;
|
2010-03-24 19:13:19 +01:00
|
|
|
|
$out = qq|^pa(;2)|;
|
|
|
|
|
for my $ws (@{$workspaces}) {
|
|
|
|
|
next if $ws->{output} ne $name and !$show_all;
|
|
|
|
|
|
2011-08-03 03:54:00 +02:00
|
|
|
|
# Display a separator if we are on a different output now
|
|
|
|
|
if (defined($previous_output) and
|
|
|
|
|
($ws->{output} ne $previous_output)) {
|
|
|
|
|
$out .= qq|^fg(#900000)^ib(1)\|^ib(0)^p(+4)|;
|
|
|
|
|
}
|
|
|
|
|
$previous_output = $ws->{output};
|
|
|
|
|
|
2010-03-24 19:13:19 +01:00
|
|
|
|
my ($bg, $fg) = qw(333333 888888);
|
|
|
|
|
($bg, $fg) = qw(4c7899 ffffff) if $ws->{visible};
|
|
|
|
|
($bg, $fg) = qw(900000 ffffff) if $ws->{urgent};
|
|
|
|
|
|
2011-08-02 23:25:29 +02:00
|
|
|
|
my $cmd = q|i3-msg "workspace | . $ws->{name} . q|"|;
|
2010-03-24 19:13:19 +01:00
|
|
|
|
my $name = $ws->{name};
|
|
|
|
|
|
|
|
|
|
# Begin the clickable area
|
|
|
|
|
$out .= qq|^ca(1,$cmd)|;
|
|
|
|
|
|
|
|
|
|
# Draw the rest of the bar in the background color, but
|
|
|
|
|
# don’t move the "cursor"
|
|
|
|
|
$out .= qq|^p(_LOCK_X)^fg(#$bg)^r(${width}x17)^p(_UNLOCK_X)|;
|
|
|
|
|
# Draw the name of the workspace without overwriting the
|
|
|
|
|
# background color
|
|
|
|
|
$out .= qq|^p(+3)^fg(#$fg)^ib(1)$name^ib(0)^p(+5)|;
|
|
|
|
|
# Draw the rest of the bar in the normal background color
|
|
|
|
|
# without moving the "cursor"
|
|
|
|
|
$out .= qq|^p(_LOCK_X)^fg($dzen_bg)^r(${width}x17)^p(_UNLOCK_X)|;
|
|
|
|
|
|
|
|
|
|
# End the clickable area
|
|
|
|
|
$out .= qq|^ca()|;
|
|
|
|
|
|
|
|
|
|
# Move to the next rect, reset Y coordinate
|
|
|
|
|
$out .= qq|^p(2)^pa(;2)|;
|
|
|
|
|
}
|
|
|
|
|
|
2010-06-21 00:20:04 +02:00
|
|
|
|
$out .= qq|^p(_LOCK_X)^fg($dzen_bg)^r(${width}x17)^p(_UNLOCK_X)^fg()|;
|
2010-03-24 19:13:19 +01:00
|
|
|
|
$out .= qq|^p(+5)|;
|
|
|
|
|
$out .= $last_line if (!@input_on or $name ~~ @input_on);
|
|
|
|
|
$out .= "\n";
|
|
|
|
|
|
|
|
|
|
$outputs->{$name}->{cmd_input} = $out;
|
2010-06-16 19:26:55 +02:00
|
|
|
|
try {
|
|
|
|
|
pump $outputs->{$name}->{cmd} while length $outputs->{$name}->{cmd_input};
|
|
|
|
|
} catch {
|
|
|
|
|
warn "Could not write to dzen2";
|
|
|
|
|
exit 1;
|
|
|
|
|
}
|
2010-03-24 19:13:19 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$i3->connect->cb(\&connected);
|
|
|
|
|
|
|
|
|
|
$stdin = AnyEvent->io(
|
|
|
|
|
fh => \*STDIN,
|
|
|
|
|
poll => 'r',
|
|
|
|
|
cb => sub {
|
2011-08-02 23:31:03 +02:00
|
|
|
|
my $line = <STDIN>;
|
|
|
|
|
if (!defined($line)) {
|
|
|
|
|
undef $stdin;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
chomp($line);
|
2010-03-24 19:13:19 +01:00
|
|
|
|
$last_line = $line;
|
|
|
|
|
update_output();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
# let AnyEvent do the rest ("endless loop")
|
|
|
|
|
AnyEvent->condvar->recv
|
|
|
|
|
|
|
|
|
|
__END__
|
|
|
|
|
|
|
|
|
|
=head1 NAME
|
|
|
|
|
|
|
|
|
|
i3-wsbar - sample implementation of a standalone workspace bar
|
|
|
|
|
|
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
|
|
|
|
|
|
i3-wsbar -c <dzen2-commandline> [options]
|
|
|
|
|
|
|
|
|
|
=head1 OPTIONS
|
|
|
|
|
|
|
|
|
|
=over 4
|
|
|
|
|
|
|
|
|
|
=item B<--command> <command>
|
|
|
|
|
|
|
|
|
|
This command (at the moment only dzen2 is supported) will be started for each
|
2010-06-19 11:48:47 +02:00
|
|
|
|
output. C<%x> will be replaced with the X coordinate of the output, C<%w> will
|
|
|
|
|
be replaced with the width of the output.
|
2010-03-24 19:13:19 +01:00
|
|
|
|
|
|
|
|
|
Example:
|
2010-06-19 11:48:47 +02:00
|
|
|
|
--command "dzen2 -dock -x %x -w %w"
|
2010-03-24 19:13:19 +01:00
|
|
|
|
|
|
|
|
|
=item B<--input-on> <list-of-RandR-outputs>
|
|
|
|
|
|
|
|
|
|
Specifies on which outputs the contents of stdin should be appended to the
|
|
|
|
|
workspace bar.
|
|
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
--input-on "LVDS1"
|
|
|
|
|
|
|
|
|
|
=item B<--output-on> <list-of-RandR-outputs>
|
|
|
|
|
|
|
|
|
|
Specifies for which outputs i3-wsbar should start C<command>.
|
|
|
|
|
|
|
|
|
|
=item B<--show-all>
|
|
|
|
|
|
|
|
|
|
If enabled, all workspaces are shown (not only those of the current output).
|
|
|
|
|
Handy to use with C<--output-on>.
|
|
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
|
|
=cut
|