0

Initial commit

This commit is contained in:
Eleksir
2021-04-09 16:13:25 +03:00
committed by Sergei Fedosov
commit c5ce3773ad
4 changed files with 1146 additions and 0 deletions

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Sergei Fedosoff <eleksir@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

7
cpanfile Normal file
View File

@@ -0,0 +1,7 @@
requires 'AnyEvent';
requires 'AnyEvent::Timer::Cron';
requires 'AnyEvent::I3';
requires 'JSON::XS';
requires 'Encode';
requires 'IO::Socket';
requires 'POSIX';

824
i3status-anyevent Normal file
View File

@@ -0,0 +1,824 @@
#!/usr/bin/perl
# TODO: on i3wm reload (i3status restart), restore active windows status
# TODO: make battery plugin take into account charge level, add ability to call suspend2disk on critical charge level
# TODO: More complex PA-plugin with volume adjustment of default sink
# we need to disable hash shuffling in order to make as less prints to stdout as possible
BEGIN {
unless (defined $ENV{PERL_PERTURB_KEYS}) {
$ENV{PERL_PERTURB_KEYS} = 0;
exec ( $^X, $0, @ARGV );
exit 0; # we actually never get here
}
}
use 5.018;
use strict;
use warnings;
use utf8;
use open qw (:std :utf8);
use Fcntl qw (:seek);
use AnyEvent;
use AnyEvent::Timer::Cron;
use AnyEvent::I3;
use JSON::XS;
use Encode;
use IO::Socket qw (AF_INET SOCK_STREAM SHUT_WR);
use POSIX qw (setsid);
local $| = 1;
sub add_events ();
sub spawn (@);
sub find_caps_led ();
sub to_hours ($);
sub readconf ();
sub active_win_borders ();
my $CONF = readconf ();
my $i3 = i3 ();
my $done = AnyEvent->condvar;
$i3->connect->recv or die 'Unable to connect to i3';
# event pool
my @EP;
# reload config and re-apply it on SIGHUP
local $SIG{HUP} = sub {
$CONF = readconf ();
active_win_borders ();
$#EP = -1;
add_events ();
};
my @month = ('Янв', 'Фев', 'Мар', 'Апр', 'Май', 'Июн', 'Июл', 'Авг', 'Сен', 'Окт', 'Ноя', 'Дек');
my @week_day = ('Вс', 'Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб');
# File handler pool
my $LAHANDLE;
my $MEMINFOHANDLE;
my $CPUTEMPHANDLE;
my $CAPSHANDLE;
# everything for text labels on i3bar
my $wallclock = '';
my $la = '';
my $meminfo = '';
my $cputemp = '';
my $caps_state = '';
my $charge = '';
my $tcp_check = 0;
my $file_check = 0;
my $file_check_mtime = 0;
my $simple_volume_pa = '<big>♪</big> ??%';
my $interfaces = '??';
my $output_string;
my $old_output_string = '';
my $STDIN_FIRST_LINE_DROPPED = 0;
# hp, kernel 5.4.x
my $BATTERY_CHARGE_NOW_FILE = sprintf '%s/charge_now', $CONF->{battery}->{sys_dir};
unless (-f $BATTERY_CHARGE_NOW_FILE) {
# lenovo, kernel 4.4.x
$BATTERY_CHARGE_NOW_FILE = sprintf '%s/energy_now', $CONF->{battery}->{sys_dir};
}
# hp, kernel 5.4.x
my $BATTERY_CHARGE_FULL_FILE = sprintf '%s/charge_full', $CONF->{battery}->{sys_dir};
unless (-f $BATTERY_CHARGE_FULL_FILE) {
# lenovo, kernel 4.4.x
$BATTERY_CHARGE_FULL_FILE = sprintf '%s/energy_full', $CONF->{battery}->{sys_dir};
}
my $CAPS_LED_PATH = find_caps_led ();
# opened windows info storage ref
my $win;
# capture click events and do not override sigstop and sigcont numbers
print << 'EOJSON';
{"version": 1, "click_events": true}
[ [],
EOJSON
# i3 events should persist over config relads
my $i3_subscription->{window} = sub {
my $w = shift;
if (defined $w->{change}) {
if ($w->{change} eq 'new' ) {
if (defined $w->{container}->{window_properties}->{instance}) {
if (defined $win->{instance}->{$w->{container}->{window_properties}->{instance}}) {
$win->{instance}->{$w->{container}->{window_properties}->{instance}}++;
} else {
$win->{instance}->{$w->{container}->{window_properties}->{instance}} = 1;
}
}
} elsif ($w->{change} eq 'close') {
if (defined $w->{container}->{window_properties}->{instance}) {
if (defined $win->{instance}->{$w->{container}->{window_properties}->{instance}}) {
if ($win->{instance}->{$w->{container}->{window_properties}->{instance}} > 1) {
$win->{instance}->{$w->{container}->{window_properties}->{instance}} --;
} else {
delete $win->{instance}->{$w->{container}->{window_properties}->{instance}};
}
}
}
}
active_win_borders ();
}
};
$i3_subscription->{_error} = sub {
my ($msg) = @_;
die "Connection to i3 closed: $msg";
};
$i3->subscribe($i3_subscription)->recv->{success} || die 'Unable to subscribe to i3 events';
# now we can add other events
add_events ();
# and start eventloop
$done->recv ();
exit 0;
sub add_events () {
# Register callbacks/event handlers
push @EP, AnyEvent->timer (
after => 1,
interval => 120,
cb => sub {
AnyEvent->now_update;
},
);
# Actually print messages there
push @EP, AnyEvent->timer (
after => 1, # sleep 1 second
interval => 0.3, # then invoke every ~1/3 second
cb => sub { # the callback to invoke
my $OUTPUT;
if ($CONF->{app_buttons}) {
@{$OUTPUT} = @{$CONF->{apps}};
}
if ($CONF->{capslock}->{enabled}) {
if ($caps_state) {
push @{$OUTPUT}, {
background => $CONF->{capslock}->{color},
color => $CONF->{capslock}->{background},
border => $CONF->{capslock}->{background},
full_text => 'caps',
markup => 'pango'
};
} else {
push @{$OUTPUT}, {
color => $CONF->{capslock}->{color},
background => $CONF->{capslock}->{background},
border => $CONF->{capslock}->{background},
full_text => 'caps',
markup => 'pango'
};
}
}
if ($CONF->{cpu_temp}->{enabled}) {
push @{$OUTPUT}, { color => $CONF->{color}, full_text => $cputemp };
}
if ($CONF->{mem}) {
push @{$OUTPUT}, { color => $CONF->{color}, full_text => $meminfo };
}
if ($CONF->{la}) {
push @{$OUTPUT}, { color => $CONF->{color}, full_text => "LA: $la" };
}
my $vpn_str = 'VPN:';
if ($CONF->{vpn}->{enabled}) {
if ($file_check) {
$vpn_str .= '⍋';
} else {
$vpn_str .= '⍒';
}
}
if ($CONF->{vpn}->{tcp_check}->{enabled}) {
if ($CONF->{vpn}->{enabled}) {
$vpn_str .= ':';
}
if ($tcp_check) {
$vpn_str .= '✔';
} else {
$vpn_str .= '✘';
}
}
if ($CONF->{vpn}->{tcp_check}->{enabled} || $CONF->{vpn}->{enabled}) {
push @{$OUTPUT}, { color => $CONF->{color}, full_text => $vpn_str };
}
if ($CONF->{'net-if'}->{enabled}) {
push @{$OUTPUT}, { color => $CONF->{color}, full_text => $interfaces, markup => 'pango' };
}
if ($CONF->{battery}->{enabled}) {
push @{$OUTPUT}, { color => $CONF->{color}, full_text => $charge, markup => 'pango' };
}
if ($CONF->{'simple-volume-pa'}) {
push @{$OUTPUT}, {
color => $CONF->{color},
full_text => sprintf ('<big>♪</big> %s%%', $simple_volume_pa),
markup => 'pango'
};
}
if ($CONF->{clock}->{enabled}) {
push @{$OUTPUT}, { name => 'wallclock', color => $CONF->{clock}->{color}, full_text => "<big>$wallclock</big>", markup => 'pango'};
}
$output_string = eval { encode_json $OUTPUT };
if (defined $output_string) {
if ($output_string ne $old_output_string) {
print decode ('UTF-8', $output_string) . ",\n";
$old_output_string = $output_string;
}
} else {
warn $@;
}
@{$OUTPUT} = -1;
}
);
# read STDIN
push @EP, AnyEvent->io (
fh => \*STDIN,
poll => 'r',
cb => sub {
my $msg = <>;
unless ($STDIN_FIRST_LINE_DROPPED) {
$msg = <>;
$STDIN_FIRST_LINE_DROPPED = 1;
}
if ($msg) {
# drop leading ',' if present
if (substr ($msg, 0, 1) eq ',') {
$msg = substr $msg, 1;
}
my $j = eval { decode_json $msg };
if (defined $j) {
# left button click, without shift, ctrl, alt, etc
if (($j->{button} == 1) && ($#{$j->{modifiers}} < 0)) {
foreach (@{$CONF->{apps}}) {
unless (defined $_->{name}) {
next;
}
unless (defined $j->{name}) {
next;
}
if ($j->{name} eq $_->{name}) {
spawn ($_->{cmd});
last;
}
}
}
if (($j->{name} eq 'wallclock') && ($CONF->{clock}->{enabled})) {
if ($CONF->{clock}->{left_click}->{enabled}) {
if (($j->{button} == 1) && ($#{$j->{modifiers}} < 0)) {
spawn ($CONF->{clock}->{left_click}->{cmd});
}
} elsif ($CONF->{clock}->{right_click}->{enabled}) {
if (($j->{button} == 2) && ($#{$j->{modifiers}} < 0)) {
spawn ($CONF->{clock}->{right_click}->{cmd});
}
}
}
}
}
}
);
# poll caps lock status, on one of my laptop there is no led for this :(
if ($CONF->{capslock}->{enabled}) {
push @EP, AnyEvent->timer (
after => 0, # first invoke ASAP
interval => 0.3, # then invoke every 0.3 seconds
cb => sub { # the callback to invoke
if (defined $CAPS_LED_PATH) {
if (defined $CAPSHANDLE) {
if (tell ($CAPSHANDLE) == -1) {
unless (open $CAPSHANDLE, '<', $CAPS_LED_PATH) {
$CAPS_LED_PATH = undef;
$caps_state = 2;
}
}
} else {
unless (open $CAPSHANDLE, '<', $CAPS_LED_PATH) {
$CAPS_LED_PATH = undef;
$caps_state = 2
}
}
} else {
$caps_state = 2;
}
if ($caps_state ne '2') {
$caps_state = eval { readline ($CAPSHANDLE) };
seek $CAPSHANDLE, SEEK_SET, 0;
chomp $caps_state;
}
}
);
}
# poll clock by timer
if ($CONF->{clock}->{enabled}) {
push @EP, AnyEvent->timer (
after => 0, # first invoke ASAP
interval => 1, # then invoke every 1 second
cb => sub { # the callback to invoke
# Fetch time for clock
my ($sec, $min, $hour, $mday, $mon, $year, $wday) = (localtime)[0,1,2,3,4,5,6];
$year = $year + 1900;
$wallclock = sprintf ' %s, %s %s %s %02s:%02s ', $week_day[$wday], $mday, $month[$mon], $year, $hour, $min;
}
);
}
# poll battery status via upower or /sys
if ($CONF->{battery}->{enabled}) {
# borrow data from upower output
if ($CONF->{battery}->{driver} eq 'upower') {
push @EP, AnyEvent->timer (
after => 0, # first invoke ASAP
interval => 10, # then invoke every 10 seconds
cb => sub { # the callback to invoke
my @up;
my $str = `upower -i $CONF->{battery}->{upower_device}`;
$charge = '<big>⚡</big> ??% • 00:00';
if ($?) {
return;
}
@up = split /\n/, $str;
my $percentage;
my $time_to; # full or empty
my $state;
my $hours = 0;
my $minutes = 0;
while ($str = pop (@up)) {
if (substr ($str, 4, 10) eq 'percentage') {
$percentage = substr $str, 25;
chomp $percentage;
} elsif (substr ($str, 4, 5) eq 'state') {
if (substr ($str, 25, 11) eq 'discharging') {
$state = '▼';
} elsif (substr ($str, 25, 8) eq 'charging') {
$state = '▲';
} else {
$state = '•';
}
} elsif (substr ($str, 4, 13) eq 'time to empty') {
$time_to = substr $str, 25;
} elsif (substr ($str, 4, 12) eq 'time to full') {
$time_to = substr $str, 25;
}
}
unless (defined $percentage && defined $state) {
return;
}
if (defined $time_to) {
chomp $time_to;
if (substr ($time_to, -7) eq 'minutes') {
$minutes = substr $time_to, 0, -8;
$minutes =~ tr/\,/./;
$minutes = int $minutes;
} elsif (substr ($time_to, -5) eq 'hours') {
$hours = substr $time_to, 0, -6;
$hours =~ tr/\,/./;
$minutes = int (60 * ($hours - int ($hours)));
}
$time_to = sprintf '%02d:%02d', $hours, $minutes;
} else {
$time_to = '00:00';
}
$charge = sprintf '<big>⚡</big> %s %s %s', $percentage, $state, $time_to;
}
);
} else {
# fallback borrow data from /sys
push @EP, AnyEvent->timer (
after => 0, # first invoke ASAP
interval => 10, # then invoke every 10 seconds
cb => sub { # the callback to invoke
my $current_charge;
my $charge_max;
my $state;
my ($CH, $CCM, $STH);
unless (open $CH, '<', $BATTERY_CHARGE_NOW_FILE) {
$charge = '<big>⚡</big> ??% •';
return;
}
$current_charge = <$CH>;
chomp $current_charge;
close $CH;
unless (open $CCM, '<', $BATTERY_CHARGE_FULL_FILE) {
$charge = '<big>⚡</big> ??% •';
return;
}
$charge_max = <$CCM>;
chomp $charge_max;
close $CCM;
unless (open $STH, '<', sprintf '%s/status', $CONF->{battery}->{sys_dir}) {
$charge = '<big>⚡</big> ??% •';
return;
}
$state = <$STH>;
chomp $state;
close $STH;
if ($state eq 'Charging') {
$state = '▲';
} elsif ($state eq 'Discharging') {
$state = '▼';
} else {
$state = '•';
}
my $capacity = int (100 * ($current_charge / $charge_max));
$charge = sprintf '<big>⚡</big> %s%% %s', $capacity, $state;
}
);
}
}
# put all fast accessable stats in one callback
push @EP, AnyEvent->timer (
after => 0, # first invoke ASAP
interval => 3, # then invoke every 3 second
cb => sub { # the callback to invoke
# Fetch Load Average
if ($CONF->{la}) {
if (defined $LAHANDLE) {
if (tell ($LAHANDLE) == -1) {
open $LAHANDLE, '<', '/proc/loadavg';
}
} else {
open $LAHANDLE, '<', '/proc/loadavg';
}
$la = readline $LAHANDLE;
seek $LAHANDLE, SEEK_SET, 0;
$la = (split / /ms, $la, 2)[0];
}
# populate memory statistics
if ($CONF->{mem}) {
if (defined $MEMINFOHANDLE) {
if (tell ($MEMINFOHANDLE) == -1) {
open $MEMINFOHANDLE, '<', '/proc/meminfo';
}
} else {
open $MEMINFOHANDLE, '<', '/proc/meminfo';
}
my @mem;
while ( ! eof ($MEMINFOHANDLE) ) {
push @mem, readline ($MEMINFOHANDLE);
}
seek $MEMINFOHANDLE, SEEK_SET, 0;
@mem = map { substr $_, 0, -4 } @mem;
my $memtotal = (split /\s+/xms, $mem[0])[1];
my $memavail = (split /\s+/xms, $mem[2])[1];
my $swaptotal = (split /\s+/xms, $mem[14])[1];
my $swapfree = (split /\s+/xms, $mem[15])[1];
my $shmem = (split /\s+/xms, $mem[20])[1];
my $memused = int (($memtotal - $memavail) / ($memtotal / 100));
$meminfo = sprintf (
'M:%d%% SHM:%dM SW:%dM',
$memused,
int ($shmem / 1024),
int (($swaptotal - $swapfree) / 1024)
);
}
# populate cpu temperature info
if ($CONF->{cpu_temp}->{enabled}) {
my $hwmon = 1;
if (defined $CPUTEMPHANDLE) {
if (tell $CPUTEMPHANDLE == -1) {
if (open $CPUTEMPHANDLE, '<', $CONF->{cpu_temp}->{file}) {
$hwmon = 1;
} else {
$hwmon = 0;
}
}
} else {
unless (open $CPUTEMPHANDLE, '<', $CONF->{cpu_temp}->{file}) {
$hwmon = 0;
};
}
if ($hwmon) {
$cputemp = readline $CPUTEMPHANDLE;
seek $CPUTEMPHANDLE, SEEK_SET, 0;
chomp $cputemp;
$cputemp = int $cputemp;
if ($cputemp > 1000) {
$cputemp = int ($cputemp / 1000);
}
$cputemp = sprintf '%s°', $cputemp;
}
}
},
);
# check openvpn state-file mtime
if ($CONF->{vpn}->{enabled}) {
push @EP, AnyEvent->timer (
after => 0, # first invoke ASAP
interval => 3, # then invoke every 3 seconds
cb => sub { # the callback to invoke
my $mtime = (stat $CONF->{vpn}->{statusfile}) [9];
if (defined $mtime) {
if ((time () - $mtime) < $CONF->{vpn}->{mtime_threshold}) {
$file_check = 1;
} else {
$file_check = 0;
}
$file_check_mtime = $mtime;
} else {
$mtime = 0;
$file_check = 0;
}
}
);
# Try to tcp connect to server in vpn network
if ($CONF->{vpn}->{tcp_check}->{enabled}) {
push @EP, AnyEvent->timer (
after => 0, # first invoke ASAP
interval => 5, # then invoke every 5 seconds
cb => sub { # the callback to invoke
if (
my $sock = IO::Socket->new (
Domain => AF_INET,
proto => 'tcp',
PeerHost => $CONF->{vpn}->{tcp_check}->{host},
PeerPort => $CONF->{vpn}->{tcp_check}->{port},
Type => SOCK_STREAM,
Timeout => $CONF->{vpn}->{tcp_check}->{timeout}
)
) {
$sock->shutdown (SHUT_WR);
$sock->close ();
$tcp_check = 1;
} else {
$tcp_check = 0;
}
}
);
}
}
# poll pulseaudio volume on first sink in list
if ($CONF->{'simple-volume-pa'}) {
push @EP, AnyEvent->timer (
after => 0, # first invoke ASAP
interval => 1, # then invoke every 1 second
cb => sub { # the callback to invoke
my $pactl = `LC_ALL=C pactl list sinks`;
if ($?) {
# we've some error here, so quit further processing
return;
}
foreach my $str (split /\n/, $pactl) {
if ($str =~ /^\s+Volume\:\s+[a-z|A-Z\\-]+\:\s+\d+\s+\/\s+(\d+)% /xms ) {
$simple_volume_pa = $1;
last;
}
}
}
);
}
# poll operstate for given net ifs
if ($CONF->{'net-if'}->{enabled}) {
push @EP, AnyEvent->timer (
after => 0, # first invoke ASAP
interval => 1, # then invoke every 1 second
cb => sub { # the callback to invoke
my @ifstate;
foreach my $if (@{$CONF->{'net-if'}->{if}}) {
if (open my $STATEFH, '<', "$if->{dir}/operstate") {
my $state = <$STATEFH>;
close $STATEFH;
chomp $state;
if ($state eq 'up') {
push @ifstate, sprintf '%s:<span foreground="%s">▲</span>', $if->{name}, $CONF->{'net-if'}->{up_color};
} else {
push @ifstate, sprintf '%s:<span foreground="%s">▼</span>', $if->{name}, $CONF->{'net-if'}->{down_color};
}
}
}
if ($#ifstate >=0 ) {
$interfaces = join ' ', @ifstate;
}
}
);
}
# run all cron jobs from config
if ($CONF->{'cron'}->{enabled}) {
foreach my $task (@{$CONF->{'cron'}->{tasks}}) {
push @EP, AnyEvent::Timer::Cron->new (
time_zone => $CONF->{cron}->{timezone},
cron => $task->{time},
cb => sub {
foreach my $cmd (@{$task->{cmd}}) {
`$cmd`;
}
}
)
}
}
return;
}
sub spawn (@) {
my @args = @_;
my $pid = fork ();
if ($pid) {
# Parent, work done, return
return;
} elsif ($pid == 0) { # Child
setsid ();
# it seems that at least vscode creashes if we close stdio handles
open STDOUT, '>', '/dev/null';
open STDERR, '>', '/dev/null';
open STDIN, '<', '/dev/null';
$pid = fork ();
if ($pid) { # Parent
# in this place parent quits
exit 0;
} elsif ($pid == 0) { # Child
setsid ();
my $prg = shift @args;
# weird situation, silently quit here :)
exit 0 unless (defined $prg);
if (@args > 0) {
exec $prg, @args;
} else {
exec $prg;
}
}
# else - we just unable to fork, so silently ignore event
}
return;
}
sub find_caps_led () {
my $str;
opendir my $C, '/sys/class/input';
my $cnt = 0;
while (readdir $C) {
if (-f sprintf ('/sys/class/input/%s/input0::capslock/brightness', $_)) {
$str = sprintf '/sys/class/input/%s/input0::capslock/brightness', $_;
last;
}
if (-f sprintf ('/sys/class/input/%s/input%s::capslock/brightness', $_, $cnt)) {
$str = sprintf '/sys/class/input/%s/input%s::capslock/brightness', $_, $cnt;
last;
}
$cnt++;
}
closedir $C;
return $str;
}
sub to_hours ($) {
my $sec = shift;
my $hrs = int ($sec / (60 * 60));
my $min = int (($sec - ($hrs * 60 * 60)) / 60);
return sprintf '%02d:%02d', $hrs, $min;
}
sub readconf () {
my $i3dir = "$ENV{HOME}/.config/i3";
unless (-d $i3dir) {
$i3dir = "$ENV{HOME}/.i3";
}
my $file = "$i3dir/i3status-anyevent.json";
unless (-f $file) {
$file = "$i3dir/config.json";
}
my $FH;
unless (open $FH, '<', $file) {
die "Unable to open $file: $!\n";
}
my $len = (stat $file)[7];
my $data;
use bytes;
my $readlen = read $FH, $data, $len;
no bytes;
close $FH;
unless (defined $readlen) {
die "Unable to read $file: $!\n";
}
if ($readlen != $len) {
die "Unable to read $file: read $readlen, but stat() reports that file is $len bytes\n";
}
my $config = eval { JSON::XS->new->utf8->relaxed->decode ($data) };
unless ($config) {
die "Unable to decode $file: $@\n";
}
return $config;
}
sub active_win_borders () {
foreach my $app (@{$CONF->{apps}}) {
unless (defined $app->{border_inactive}) {
$app->{border_inactive} = $app->{border};
}
unless (defined $app->{border_active}) {
$app->{border_active} = $app->{border};
}
if (defined ($app->{instance}) && defined ($win->{instance}->{$app->{instance}})) {
$app->{border} = $app->{border_active};
} else {
$app->{border} = $app->{border_inactive};
}
}
return;
}

294
i3status-anyevent.json Normal file
View File

@@ -0,0 +1,294 @@
# Enable "plugin" 1, disable "plugin" 0
{
# default text and background colors
"color" : "#3e78fd",
"background" : "#edeceb",
# Load Average
"la" : 1,
# Memory stats
"mem" : 1,
# Just clock
"clock": {
"enabled" : 1,
# html (actually, pango) color notation
"color" : "#000000",
"left_click" : {
"enabled" : 1,
"cmd" : "orage"
},
"right_click" : {
"enabled" : 0,
"cmd" : "true"
}
},
# Battery
"battery": {
"enabled" : 1,
# can be upower or something else to use /sys polling instead of periodic run of upower binary
"driver" : "upower",
# where we can find charge_{now,full} and status files
"sys_dir" : "/sys/class/power_supply/BAT0",
# invoke 'upower --enumerate' and choose one
"upower_device" : "/org/freedesktop/UPower/devices/battery_BAT0"
},
# 0-th core temperature
"cpu_temp": {
"enabled": 1,
"file": "/sys/class/hwmon/hwmon0/temp1_input"
},
# Caps Lock status
"capslock": {
"enabled" : 1,
"background" : "#edeceb",
"color" : "#3e78fd"
},
# OpenVPN status. Note that directive status in openvpn.conf should point to
# state file and update its timestamp at least as frequent as mtime_threshold in seconds
"vpn": {
"enabled": 0,
"statusfile" : "/tmp/openvpn-status.stat",
"mtime_threshold": 10,
# Also we can tcp-ping some service inside vpn network to ensure that connection is not stoned
"tcp_check" : {
"enabled": 0,
"host": "somehost.tld",
"port": 443,
"timeout" : 3
}
},
# Simplified volume indicator
# it periodically polls pulseaudio via "pactl list sinks" command
# so it's not light on system resources as i'd like
"simple-volume-pa": 0,
"net-if" : {
"enabled" : 0,
# html (actually, pango) notation
"down_color" : "red",
"up_color" : "green",
"if" : [
{
"name" : "eth0",
"dir" : "/sys/class/net/eth0"
},
{
"name" : "wlan0",
"dir" : "/sys/class/net/wlan0"
}
]
},
# Intended for use with desktop notifications
# Because it inherits all env vars from i3, it can intercat with dbus and other
# such user session services.
"cron" : {
"enabled" : 0,
"tasks" : [
{
"time" : "55 23 * * *",
"cmd" : [
"notify-send 'Sleeping Time!' 'Take a nap.'",
"mpg123 -q /usr/share/sounds/complete_quest_requirement.mp3"
]
},
{
# time in crontab format
"time" : "55 12 * * 2,4",
# commands as it should be invoked in bash script
"cmd" : [
"notify-send 'Pizza time!' 'M-m-m, tasty!'",
"mpg123 -q /usr/share/sounds/complete_quest_requirement.mp3"
]
}
]
},
# Weather to display Application Buttons
"app_buttons": 1,
# Buttons - application launch "buttons"
"apps": [
{
"full_text" : " X ",
"name" : "kill_fbpanel",
"cmd" : "killall -9 fbpanel",
"color": "#aa0000",
"border": "#adadad",
"border_active": "#3e78fd",
"separator": false,
"separator_block_width": 2
},
{
# text displayed on button
"full_text" : " Term ",
# internal application name, must be unique
"name" : "lilyterm",
# actual commad to be performed if button clicked
"cmd" : "lilyterm",
# WM_CLASS(STRING) = "irssi", "URxvt"
# ^-instance ^-class
# window instance as reported by xprop
"instance" : "lilyterm",
# window class as reported by xprop
"class" : "LilyTerm",
# text color
"color" : "#666666",
# normal border color
"border": "#adadad",
# border color if there is at lest one app window on desktop
"border_active": "#3e78fd",
# if true - '|' will be drawn on panel to the right side of the "button"
"separator": false,
# amount of space in pixels between buttons
"separator_block_width": 2
},
{
"full_text" : " Ff ",
"name" : "firefox",
"cmd" : "firefox",
"instance" : "Navigator",
"class" : "Firefox",
"color" : "#ffa500",
"border": "#adadad",
"border_active": "#3e78fd",
"separator": false,
"separator_block_width": 2
},
{
"full_text" : " V ",
"name" : "vivaldi",
"cmd" : "vivaldi",
"instance" : "vivaldi-stable",
"class" : "Vivaldi-stable",
"color" : "#ffa500",
"border": "#adadad",
"border_active": "#3e78fd",
"separator": false,
"separator_block_width": 2
},
{
"full_text" : " Chr ",
"name" : "chrome",
"cmd" : "chrome",
"instance" : "google-chrome",
"class" : "Google-chrome",
"color" : "#ffa500",
"border": "#adadad",
"border_active": "#3e78fd",
"separator": false,
"separator_block_width": 2
},
{
"full_text" : " O ",
"name" : "opera",
"cmd" : "opera",
"instance" : "Opera",
"class" : "Opera",
"color" : "#ffa500",
"border": "#adadad",
"border_active": "#3e78fd",
"separator": false,
"separator_block_width": 2
},
{
"full_text" : " @ ",
"name" : "thunderbird",
"cmd" : "thunderbird",
"instance" : "Mail",
"class" : "Thunderbird",
"color" : "#006994",
"border": "#adadad",
"border_active": "#3e78fd",
"separator": false,
"separator_block_width": 2
},
{
"full_text" : " Tg ",
"name" : "telegram",
"cmd" : "Telegram",
"instance" : "Telegram",
"class" : "TelegramDesktop",
"color" : "#5a8850",
"border": "#adadad",
"border_active": "#3e78fd",
"separator": false,
"separator_block_width": 2
},
{
"full_text" : " M ",
"name" : "myteam",
"cmd" : "myteam",
"instance" : "myteam",
"class" : "myteam",
"color" : "#5a8850",
"border": "#adadad",
"border_active": "#3e78fd",
"separator": false,
"separator_block_width": 2
},
{
"full_text" : " S ",
"name" : "slack",
"cmd" : "slack",
"instance" : "slack",
"class" : "Slack",
"color" : "#5a8850",
"border": "#adadad",
"border_active": "#3e78fd",
"separator": false,
"separator_block_width": 2
},
{
"full_text" : " Txt ",
"name" : "mousepad",
"cmd" : "mousepad",
"instance" : "mousepad",
"class" : "Mousepad",
"border": "#adadad",
"border_active": "#3e78fd",
"separator": false,
"separator_block_width": 2
},
{
"full_text" : " VSC ",
"name" : "code",
"cmd" : "code",
"instance" : "code",
"class" : "Code",
"border": "#adadad",
"border_active": "#3e78fd",
"separator": false,
"separator_block_width": 2
},
{
"full_text" : " K ",
"name" : "keepassxc",
"cmd" : "keepassxc",
"instance" : "keepassxc",
"class" : "KeePassXC",
"color" : "#7ec850",
"border" : "#adadad",
"border_active" : "#3e78fd"
}
]
}