#!/usr/bin/perl ######################################################################## # 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 = '/home/jpm/.config/waybar/config.template'; # Temporary directory to save generated waybar config(s) my $waybar_temporary = '/tmp'; ######################################################################## # 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; # Bail if zero or >1 args my $last = "/home/jpm/.config/last_display"; 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; } } } unless (defined $config) { open(my $fh, '<', $last); $config = <$fh>; close($fh); chomp $config; print "$config\n"; } # Bail if requested config doesn't exist unless (defined $configs{$config}) { die "$config is not a defined configuration: " . join(', ', keys %configs) . "\n"; } # Import and setup JSON object use JSON::XS; my $json = JSON::XS->new(); # Fetch connected displays 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}; } } unless ($waybar_only) { # Number of simultaneous outputs is limited by possible, 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.*/) { if ($p->{'pid'} == $$) { next; } else { $p->kill(9); } } } # 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"; } } # If waybar position is set, fork, generate config and execute it if (defined $on->{$out}->{waybar}) { my $pid = fork; unless ($pid) { open STDIN, '/dev/null'; open STDOUT, '>>/dev/null'; open STDERR, '>>/dev/null'; # Load in config template my $waybar; open (my $fh, '<', $waybar_template); while (<$fh>) { $waybar .= $_; } close $fh; chomp $waybar; # 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; } # Write config to a temporary file my $tmp = $waybar_temporary . "/" . $on->{$out}->{output} . ".tmp"; open ($fh, '>', $tmp); print $fh $waybar; close $fh; `nohup waybar --config=$tmp`; print $tmp . "\n"; # Remove config unlink $tmp; } } } open(my $fh, '>', $last); print $fh $config; close($fh);