scripts/sway/displays.pl

356 lines
11 KiB
Perl
Executable File

#!/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'}/.config/waybar/config.template";
# Temporary directory to save generated waybar config(s)
my $waybar_temporary = '/tmp';
# File to log and recover last used layout name
my $last = "$ENV{'HOME'}/.config/last_display";
########################################################################
# 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',
'3CQ4342S6W' => 'HP-1',
'3CQ3310Q1Q' => 'HP-2',
'0x00000000' => 'LVDS'
);
########################################################################
# 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 = (
'detached' => {
'HP-1' => {
'on' => 0
},
'HP-2' => {
'on' => 0
},
'Sam' => {
'on' => 0
},
'LVDS' => {
'on' => 1,
'width' => 1920,
'height' => 1080,
'x' => 0,
'y' => 0,
'rotate' => 0,
'waybar' => 'bottom'
}
},
'stacked' => {
'Sam' => {
'on' => 1,
'width' => 1920,
'height' => 1080,
'x' => 960,
'y' => 0,
'rotate' => 0,
'waybar' => 'bottom'
},
'HP-1' => {
'on' => 1,
'width' => 1920,
'height' => 1080,
'x' => 0,
'y' => 1200,
'rotate' => 0,
'waybar' => 'top'
},
'HP-2' => {
'on' => 1,
'width' => 1920,
'height' => 1080,
'x' => 1920,
'y' => 1200,
'rotate' => 0,
'waybar' => 'top'
},
'LVDS' => {
'on' => 0
}
},
'sidebyside' => {
'HP-1' => {
'on' => 1,
'width' => 1080,
'height' => 1920,
'x' => 0,
'y' => 225,
'rotate' => 270,
'waybar' => 'top'
},
'Sam' => {
'on' => 1,
'width' => 1200,
'height' => 1920,
'x' => 1080,
'y' => 0,
'rotate' => 90,
'waybar' => 'top'
},
'HP-2' => {
'on' => 1,
'width' => 1080,
'height' => 1920,
'x' => 2280,
'y' => 225,
'rotate' => 90,
'waybar' => 'top'
},
'LVDS' => {
'on' => 0,
}
}
);
########################################################################
# Program (do not edit below)
########################################################################
use strict;
use warnings;
my $waybar_only = 0;
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;
}
# Bail if requested config doesn't exist
unless (defined $configs{$config}) {
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
open(my $fh, '>', $last) ||
print STDERR "Config name cannot be written to $last\n";
print $fh $config;
close($fh);
# Fetch connected displays
use JSON::XS;
my $json = JSON::XS->new();
my $displays_raw = `swaymsg -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};
}
}
# 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 = `sway output $_ disable`;
my $res = $json->decode($res_raw)->[0];
# If failed, print to STDERR
unless ($res->{success}) {
print STDERR "Error ($res->{error}) in command 'sway"
. "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
foreach my $out (keys %$on) {
unless ($waybar_only) {
# Build command, starting by enabling and powering on
my $cmd = "sway output $on->{$out}->{output} enable dpms on";
# If additional options are provided, add them to command
if (defined $on->{$out}->{rotate}) {
$cmd .= " transform $on->{$out}->{rotate}";
}
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}";
}
# Sway returns status as JSON
my $res_raw = `$cmd`;
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}"/;
$waybar =~ s/__POSITION__/"$on->{$out}->{waybar}"/;
if (defined $on->{$out}->{width}) {
$waybar =~ s/__WIDTH__/$on->{$out}->{width}/;
# If width is not set, comment that line out to use default
} else {
$waybar =~ s/([^\s]*\s*)__WIDTH__/\/\/ $1__WIDTH__/gg;
}
}
}
# 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
my $tmp = $waybar_temporary . "/waybar-" . time() .".config";
open ($fh, '>', $tmp);
print $fh $waybar;
close $fh;
`nohup waybar --config=$tmp`;
# Remove config
unlink $tmp;
}