You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
536 lines
17 KiB
536 lines
17 KiB
#!/usr/bin/perl |
|
|
|
######################################################################## |
|
# Usage |
|
######################################################################## |
|
# |
|
# $ displays.pl [layout] [-w] |
|
# |
|
# layout Name of desired layout, as configured starting at line 100 |
|
# If missing, last known layout is used (logged to file $last) |
|
# -w (Re)load waybar only. Skip display configuration. Only kill |
|
# existing waybars and start new ones for desired layout |
|
|
|
######################################################################## |
|
# Dependencies |
|
######################################################################## |
|
# |
|
# Depends on JSON::XS and Proc::ProcessTable |
|
# |
|
# Debian: |
|
# apt install libjson-xs-perl libproc-processtable-perl |
|
|
|
######################################################################## |
|
# Directories and Files |
|
######################################################################## |
|
|
|
# Template is a normal config with the following in place of values: |
|
# "output": __OUTPUT__ |
|
# "position": __POSITION__ |
|
# "width": __WIDTH__ (optional) |
|
my $waybar_template = "$ENV{'HOME'}/.dotfiles/waybar/config.template"; |
|
|
|
my $swaysock = $ENV{'SWAYSOCK'} || $ENV{'HOME'} . "/.spool/sway-ipc.sock"; |
|
|
|
# Path to actual config file generated from template |
|
my $waybar_config = "$ENV{'HOME'}/.config/waybar/config"; |
|
|
|
# File to log and recover last used layout name |
|
my $last = "$ENV{'HOME'}/.spool/last_display"; |
|
|
|
# File to log active outputs (used by swayidle, and to attempt to restore |
|
my $active_outputs = "$ENV{'HOME'}/.spool/active_outputs"; |
|
|
|
######################################################################## |
|
# Display Serials and Names |
|
######################################################################## |
|
|
|
# Hash to match display Serial with a friendly name. |
|
# You can get the serial from (quoted with *): |
|
# $ swaymsg -t get_outputs |
|
# Output eDP-1 'Unknown 0x057D *0x00000000*' |
|
my %outputs = ( |
|
#'0x00000101' => 'Sam', |
|
'HTNCB00059' => 'Sam', |
|
#'3CQ4342S6W' => 'HP-1', |
|
'0x00000101' => 'HP-1', |
|
'3CQ3310Q1Q' => 'HP-2', |
|
'0x00000000' => 'LVDS', |
|
'0x00000101' => 'TV', |
|
); |
|
|
|
######################################################################## |
|
# Display Configurations |
|
######################################################################## |
|
|
|
# First-level key of hash is the name of each configuration |
|
# Second-level keys are the display friendly-names, above |
|
# Third-level are the actual settings for that display |
|
my %configs = ( |
|
'TV' => { |
|
'HP-1' => { |
|
'on' => 0 |
|
}, |
|
'HP-2' => { |
|
'on' => 0 |
|
}, |
|
'Sam' => { |
|
'on' => 0 |
|
}, |
|
'TV' => { |
|
'on' => 1, |
|
'width' => 3840, |
|
'height' => 2160, |
|
'x' => 0, |
|
'y' => 0, |
|
'rotate' => 0, |
|
'scale' => 1.333333333, |
|
'waybar' => 'top', |
|
'fallback' => '#282828' |
|
}, |
|
'eDP-1' => { |
|
'on' => 1, |
|
'width' => 1920, |
|
'height' => 1080, |
|
'x' => 3079, |
|
'y' => 0, |
|
'rotate' => 0, |
|
'waybar' => 'bottom', |
|
'fallback' => '#282828' |
|
} |
|
}, |
|
'detached' => { |
|
'HP-1' => { |
|
'on' => 0 |
|
}, |
|
'HP-2' => { |
|
'on' => 0 |
|
}, |
|
'Sam' => { |
|
'on' => 0 |
|
}, |
|
'TV' => { |
|
'on' => 0 |
|
}, |
|
'eDP-1' => { |
|
'on' => 1, |
|
'width' => 1920, |
|
'height' => 1080, |
|
'x' => 0, |
|
'y' => 0, |
|
'rotate' => 0, |
|
'waybar' => 'bottom', |
|
'fallback' => '#282828' |
|
} |
|
}, |
|
'stacked-laptop' => { |
|
'Sam' => { |
|
'on' => 1, |
|
'width' => 1920, |
|
'height' => 1200, |
|
'x' => 960, |
|
'y' => 0, |
|
'rotate' => 0, |
|
'waybar' => 'bottom', |
|
'fallback' => '#282828' |
|
}, |
|
'HP-1' => { |
|
'on' => 1, |
|
'width' => 1920, |
|
'height' => 1080, |
|
'x' => 1920, |
|
'y' => 1200, |
|
'rotate' => 0, |
|
'waybar' => 'top', |
|
'fallback' => '#282828' |
|
}, |
|
'HP-2' => { |
|
'on' => 0 |
|
}, |
|
'TV' => { |
|
'on' => 0 |
|
}, |
|
'eDP-1' => { |
|
'on' => 1, |
|
'width' => 1920, |
|
'height' => 1080, |
|
'x' => 0, |
|
'y' => 1200, |
|
'rotate' => 0, |
|
'waybar' => 'bottom', |
|
'fallback' => '#282828' |
|
} |
|
}, |
|
'stacked' => { |
|
'Sam' => { |
|
'on' => 1, |
|
'width' => 1920, |
|
'height' => 1200, |
|
'x' => 960, |
|
'y' => 0, |
|
'rotate' => 0, |
|
'waybar' => 'bottom', |
|
'fallback' => '#282828' |
|
}, |
|
'HP-1' => { |
|
'on' => 1, |
|
'width' => 1920, |
|
'height' => 1080, |
|
'x' => 1920, |
|
'y' => 1200, |
|
'rotate' => 0, |
|
'waybar' => 'top', |
|
'fallback' => '#282828' |
|
}, |
|
'HP-2' => { |
|
'on' => 1, |
|
'width' => 1920, |
|
'height' => 1080, |
|
'x' => 0, |
|
'y' => 1200, |
|
'rotate' => 0, |
|
'waybar' => 'top', |
|
'fallback' => '#282828' |
|
}, |
|
'TV' => { |
|
'on' => 0 |
|
}, |
|
'eDP-1' => { |
|
'on' => 0 |
|
} |
|
}, |
|
'sidebyside' => { |
|
'HP-1' => { |
|
'on' => 1, |
|
'width' => 1080, |
|
'height' => 1920, |
|
'x' => 0, |
|
'y' => 225, |
|
'rotate' => 270, |
|
'waybar' => 'top', |
|
'fallback' => '#282828' |
|
}, |
|
'Sam' => { |
|
'on' => 1, |
|
'width' => 1200, |
|
'height' => 1920, |
|
'x' => 1080, |
|
'y' => 0, |
|
'rotate' => 90, |
|
'waybar' => 'top', |
|
'fallback' => '#282828' |
|
}, |
|
'HP-2' => { |
|
'on' => 1, |
|
'width' => 1080, |
|
'height' => 1920, |
|
'x' => 2280, |
|
'y' => 225, |
|
'rotate' => 90, |
|
'waybar' => 'top', |
|
'fallback' => '#282828' |
|
}, |
|
'TV' => { |
|
'on' => 0 |
|
}, |
|
'eDP-1' => { |
|
'on' => 0, |
|
} |
|
} |
|
); |
|
|
|
my $hostname = `hostname`; |
|
chomp($hostname); |
|
if ($hostname eq "yoga.lan.john.me.tz") { |
|
$configs{'detached'}->{'eDP-1'}->{'width'} = 2560; |
|
$configs{'detached'}->{'eDP-1'}->{'height'} = 1440; |
|
$configs{'detached'}->{'eDP-1'}->{'scale'} = 1.333333; |
|
} |
|
|
|
|
|
######################################################################## |
|
# Disable display from other laptop |
|
######################################################################## |
|
|
|
if ($ENV{'SSH_AUTH_SOCK'} eq '/home/jpm/.ssh/ssh-agent.sock') { |
|
foreach my $layout (keys %configs) { |
|
delete($configs{$layout}{'LVDS'}); |
|
} |
|
$outputs{'0x00000000'} = 'eDP-1'; |
|
} else { |
|
foreach my $layout (keys %configs) { |
|
delete($configs{$layout}{'eDP-1'}); |
|
} |
|
} |
|
|
|
######################################################################## |
|
# Program (do not edit below) |
|
######################################################################## |
|
|
|
use strict; |
|
use warnings; |
|
|
|
my $waybar_only = 0; |
|
my $restore = 0; # Set if no config is provided. Requires validation. |
|
my $config; |
|
|
|
if (scalar(@ARGV)) { |
|
while (@ARGV) { |
|
my $arg = shift; |
|
if ($arg eq "-w") { |
|
$waybar_only = 1; |
|
} else { |
|
if (defined $config) { |
|
die "Too many arguments.\n"; |
|
} |
|
$config = $arg; |
|
} |
|
} |
|
} |
|
|
|
# Get previous config if one is not provided |
|
unless (defined $config) { |
|
open(my $fh, '<', $last) || |
|
die "Config name not provided and failed to open $last\n"; |
|
$config = <$fh>; |
|
close($fh); |
|
chomp $config; |
|
$restore = 1; |
|
} |
|
|
|
# Bail if requested config doesn't exist |
|
if (!defined($configs{$config})) { |
|
if ($restore) { |
|
# If restoration is attempted with invalid config, just enable eDP-1 |
|
$config = 'detached'; |
|
} else { |
|
die "$config is not a defined configuration: " |
|
. join(', ', keys %configs) . "\n"; |
|
} |
|
} |
|
|
|
# Write config that is to be used so that it can be recovered as default |
|
if (open(my $fh, '>', $last)) { |
|
print $fh $config; |
|
close($fh); |
|
} else { |
|
print STDERR "Config name cannot be logged to: $last\n"; |
|
} |
|
|
|
# Fetch connected displays |
|
use JSON::XS; |
|
my $json = JSON::XS->new(); |
|
my $displays_raw = `swaymsg -s $swaysock -t get_outputs --raw`; |
|
my $displays = $json->decode($displays_raw); |
|
|
|
# For each connected display, collect the desired settings for enabled |
|
# displays and a list of displays to disable |
|
my $on; |
|
my @off; |
|
for (my $i = 0; $i < scalar(@$displays); $i++) { |
|
|
|
# If a display is found without any settings print error and turn it off |
|
unless (defined $configs{$config}{$outputs{$displays->[$i]->{serial}}}){ |
|
print STDERR "Output $displays->[$i]->{name} (" |
|
. $displays->[$i]->{serial} |
|
. ") found without defined function. Disabling.\n"; |
|
push @off, $displays->[$i]->{name}; |
|
next; |
|
} |
|
|
|
# If display is enabled, copy all of the desired settings |
|
if ($configs{$config}{$outputs{$displays->[$i]->{serial}}}{on}) { |
|
$on->{$outputs{$displays->[$i]->{serial}}} = |
|
$configs{$config}{$outputs{$displays->[$i]->{serial}}}; |
|
$on->{$outputs{$displays->[$i]->{serial}}}{output} = |
|
$displays->[$i]->{name}; |
|
# Otherwise simply list it for disabling |
|
} else { |
|
push @off, $displays->[$i]->{name}; |
|
} |
|
|
|
} |
|
|
|
# Verify that all requested displays are actually available |
|
my @unavailable; |
|
foreach my $output (keys %$on) { |
|
my $found = 0; |
|
foreach my $o (keys %outputs) { |
|
if ($outputs{$o} eq $output) { |
|
$found = 1; |
|
last; |
|
} |
|
} |
|
unless ($found) { |
|
push @unavailable, $output; |
|
} |
|
} |
|
if (scalar(@unavailable)) { |
|
die "Config requires unavailable output(s) " . join(', ', @unavailable) |
|
. "\n"; |
|
} |
|
|
|
# Skip enabling/disabling displays if only running waybar (re)start |
|
unless ($waybar_only) { |
|
# Number of simultaneous outputs is limited by gpu, so disabled displays |
|
# first |
|
foreach (@off) { |
|
|
|
# Sway returns status as JSON |
|
my $res_raw = `swaymsg -s $swaysock output $_ disable`; |
|
my $res = $json->decode($res_raw)->[0]; |
|
|
|
# If failed, print to STDERR |
|
unless ($res->{success}) { |
|
print STDERR "Error ($res->{error}) in command 'swaymsg" |
|
. " -s $swaysock output $_ disable'\n"; |
|
} |
|
|
|
} |
|
} |
|
|
|
# Kill existing Waybars |
|
require Proc::ProcessTable; |
|
my $t = new Proc::ProcessTable; |
|
foreach my $p ( @{ $t->table } ) { |
|
my $cmndline = $p->{'cmndline'}; |
|
$cmndline =~ s/\s*$//g; |
|
if ($cmndline =~ /^waybar.*/) { |
|
# Never kill this process |
|
if ($p->{'pid'} == $$) { |
|
next; |
|
} else { |
|
$p->kill(9); |
|
} |
|
} |
|
} |
|
|
|
# Load in config template |
|
my $template; |
|
if (open (my $fh, '<', $waybar_template)) { |
|
while (<$fh>) { |
|
$template .= $_; |
|
} |
|
close $fh; |
|
chomp $template; |
|
} else { |
|
print STDERR "Failed to load template $waybar_template\n"; |
|
} |
|
|
|
# If template is already set up as an array, remove the square brackets so that |
|
# we can concatenate multiple displays |
|
$template =~ s/^[^\[\{]*\[(.*)\]$/$1/s; |
|
|
|
# Setup waybar config file |
|
my $waybar = ''; |
|
|
|
# Configure each enabled display |
|
my @active; |
|
foreach my $out (keys %$on) { |
|
|
|
push @active, $on->{$out}->{output}; |
|
unless ($waybar_only) { |
|
# Build command, starting by enabling and powering on |
|
my $cmd = "swaymsg -s $swaysock output $on->{$out}->{output}" . |
|
" enable dpms on"; |
|
|
|
# If additional options are provided, add them to command |
|
if (defined $on->{$out}->{scale}) { |
|
$cmd .= " scale $on->{$out}->{scale}"; |
|
} else { |
|
$cmd .= " scale 1"; |
|
} |
|
if (defined $on->{$out}->{rotate}) { |
|
$cmd .= " transform $on->{$out}->{rotate}"; |
|
} else { |
|
$cmd .= " transform 0"; |
|
} |
|
if (defined $on->{$out}->{x} && defined $on->{$out}->{y}) { |
|
$cmd .= " position $on->{$out}->{x} $on->{$out}->{y}"; |
|
} |
|
if (defined $on->{$out}->{width} && |
|
defined $on->{$out}->{height} ) |
|
{ |
|
$cmd .= " mode $on->{$out}->{width}x" . |
|
"$on->{$out}->{height}"; |
|
} |
|
if (defined $on->{$out}->{bg} && |
|
-f $on->{$out}->{bg}) |
|
{ |
|
$cmd .= " bg $on->{$out}->{bg} fit"; |
|
if (defined $on->{$out}->{fallback}) { |
|
$cmd .= " $on->{$out}->{fallback}"; |
|
} |
|
} elsif (defined $on->{$out}->{fallback}) { |
|
$cmd .= " bg $on->{$out}->{fallback} solid_color"; |
|
} |
|
|
|
# Sway returns status as JSON |
|
my $res_raw = `$cmd`; |
|
#print $res_raw . "\n"; |
|
my $res = $json->decode($res_raw)->[0]; |
|
|
|
# If failed, print to STDERR |
|
unless ($res->{success}) { |
|
print STDERR "Error ($res->{error}) in command " . |
|
"'$cmd'\n"; |
|
} |
|
} |
|
|
|
# Skip waybar setup if template failed to be loaded |
|
if ( (defined $template) && |
|
(defined $on->{$out}->{waybar}) && |
|
($on->{$out}->{waybar} =~ m/(top|bottom|left|right)/) ) |
|
{ |
|
|
|
# If there's already a display set up, add a comma |
|
unless ($waybar eq '') { |
|
$waybar .= ','; |
|
} |
|
|
|
$waybar .= $template; |
|
|
|
# Replace basic preferences |
|
$waybar =~ s/__OUTPUT__/"$on->{$out}->{output}"/gg; |
|
$waybar =~ s/__POSITION__/"$on->{$out}->{waybar}"/gg; |
|
if (defined $on->{$out}->{width}) { |
|
my $x = $on->{$out}->{width}; |
|
if (defined $on->{$out}->{scale}) { |
|
$x = sprintf("%.0d", $x / $on->{$out}->{scale}); |
|
} |
|
$waybar =~ s/__WIDTH__/$x/gg; |
|
# If width is not set, comment that line out to use default |
|
} else { |
|
$waybar =~ s/([^\s]*\s*)__WIDTH__/\/\/ $1__WIDTH__/gg; |
|
} |
|
|
|
} |
|
} |
|
|
|
# Log active outputs for recovery after crash/reboot |
|
if (open(my $fh, '>', $active_outputs)) { |
|
print $fh join(' ', @active); |
|
close($fh); |
|
} else { |
|
print STDERR "Cannot write active outputs to: $active_outputs\n"; |
|
} |
|
|
|
# Restore array formatting |
|
$waybar = '[' . $waybar . ']'; |
|
|
|
# Start Waybar as fork |
|
my $pid = fork; |
|
unless ($pid) { |
|
open STDIN, '/dev/null'; |
|
open STDOUT, '>>/dev/null'; |
|
open STDERR, '>>/dev/null'; |
|
# Write config to a temporary file |
|
if (open (my $fh, '>', $waybar_config)) { |
|
print $fh $waybar; |
|
close $fh; |
|
} else { |
|
die "Failed to write configuration file: $waybar_config\n"; |
|
} |
|
my $waydisplay = $ENV{'WAYLAND_DISPLAY'} || 'wayland-0'; |
|
`WAYLAND_DISPLAY=$waydisplay nohup waybar -b waybar0 --config=$waybar_config >> $ENV{'HOME'}/.waybar.log`; |
|
}
|
|
|