A variety of simple automation scripts and enhancements to Sway and i3.
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

343 lines
9.7KB

  1. #!/usr/bin/perl
  2. ########################################################################
  3. # Usage
  4. ########################################################################
  5. #
  6. # $ displays.pl [layout] [-w]
  7. #
  8. # layout Name of desired layout, as configured starting at line 100
  9. # If missing, last known layout is used (logged to file $last)
  10. # -w (Re)load waybar only. Skip display configuration. Only kill
  11. # existing waybars and start new ones for desired layout
  12. ########################################################################
  13. # Dependencies
  14. ########################################################################
  15. #
  16. # Depends on JSON::XS and Proc::ProcessTable
  17. #
  18. # Debian:
  19. # apt install libjson-xs-perl libproc-processtable-perl
  20. ########################################################################
  21. # Directories and Files
  22. ########################################################################
  23. # Template is a normal config with the following in place of values:
  24. # "output": __OUTPUT__
  25. # "position": __POSITION__
  26. # "width": __WIDTH__ (optional)
  27. my $waybar_template = "$ENV{'HOME'}/.config/waybar/config.template";
  28. # Temporary directory to save generated waybar config(s)
  29. my $waybar_temporary = '/tmp';
  30. # File to log and recover last used layout name
  31. my $last = "$ENV{'HOME'}/.config/last_display";
  32. ########################################################################
  33. # Display Serials and Names
  34. ########################################################################
  35. # Hash to match display Serial with a friendly name.
  36. # You can get the serial from (quoted with *):
  37. # $ swaymsg -t get_outputs
  38. # Output eDP-1 'Unknown 0x057D *0x00000000*'
  39. my %outputs = (
  40. '0x00000101' => 'Sam',
  41. '3CQ4342S6W' => 'HP-1',
  42. '3CQ3310Q1Q' => 'HP-2',
  43. '0x00000000' => 'LVDS'
  44. );
  45. ########################################################################
  46. # Display Configurations
  47. ########################################################################
  48. # First-level key of hash is the name of each configuration
  49. # Second-level keys are the display friendly-names, above
  50. # Third-level are the actual settings for that display
  51. my %configs = (
  52. 'detached' => {
  53. 'HP-1' => {
  54. 'on' => 0
  55. },
  56. 'HP-2' => {
  57. 'on' => 0
  58. },
  59. 'Sam' => {
  60. 'on' => 0
  61. },
  62. 'LVDS' => {
  63. 'on' => 1,
  64. 'width' => 1920,
  65. 'height' => 1080,
  66. 'x' => 0,
  67. 'y' => 0,
  68. 'rotate' => 0,
  69. 'waybar' => 'bottom'
  70. }
  71. },
  72. 'stacked' => {
  73. 'Sam' => {
  74. 'on' => 1,
  75. 'width' => 1920,
  76. 'height' => 1080,
  77. 'x' => 960,
  78. 'y' => 0,
  79. 'rotate' => 0,
  80. 'waybar' => 'bottom'
  81. },
  82. 'HP-1' => {
  83. 'on' => 1,
  84. 'width' => 1920,
  85. 'height' => 1080,
  86. 'x' => 0,
  87. 'y' => 1200,
  88. 'rotate' => 0,
  89. 'waybar' => 'top'
  90. },
  91. 'HP-2' => {
  92. 'on' => 1,
  93. 'width' => 1920,
  94. 'height' => 1080,
  95. 'x' => 1920,
  96. 'y' => 1200,
  97. 'rotate' => 0,
  98. 'waybar' => 'top'
  99. },
  100. 'LVDS' => {
  101. 'on' => 0
  102. }
  103. },
  104. 'sidebyside' => {
  105. 'HP-1' => {
  106. 'on' => 1,
  107. 'width' => 1080,
  108. 'height' => 1920,
  109. 'x' => 0,
  110. 'y' => 225,
  111. 'rotate' => 270,
  112. 'waybar' => 'top'
  113. },
  114. 'Sam' => {
  115. 'on' => 1,
  116. 'width' => 1200,
  117. 'height' => 1920,
  118. 'x' => 1080,
  119. 'y' => 0,
  120. 'rotate' => 90,
  121. 'waybar' => 'top'
  122. },
  123. 'HP-2' => {
  124. 'on' => 1,
  125. 'width' => 1080,
  126. 'height' => 1920,
  127. 'x' => 2280,
  128. 'y' => 225,
  129. 'rotate' => 90,
  130. 'waybar' => 'top'
  131. },
  132. 'LVDS' => {
  133. 'on' => 0,
  134. }
  135. }
  136. );
  137. ########################################################################
  138. # Program (do not edit below)
  139. ########################################################################
  140. use strict;
  141. use warnings;
  142. my $waybar_only = 0;
  143. my $config;
  144. if (scalar(@ARGV)) {
  145. while (@ARGV) {
  146. my $arg = shift;
  147. if ($arg eq "-w") {
  148. $waybar_only = 1;
  149. } else {
  150. if (defined $config) {
  151. die "Too many arguments.\n";
  152. }
  153. $config = $arg;
  154. }
  155. }
  156. }
  157. # Get previous config if one is not provided
  158. unless (defined $config) {
  159. open(my $fh, '<', $last) || die "Config name not provided and failed to open $last\n";
  160. $config = <$fh>;
  161. close($fh);
  162. chomp $config;
  163. }
  164. # Bail if requested config doesn't exist
  165. unless (defined $configs{$config}) {
  166. die "$config is not a defined configuration: " . join(', ', keys %configs) . "\n";
  167. }
  168. # Write config that is to be used so that it can be recovered as default
  169. open(my $fh, '>', $last) || print STDERR "Config name cannot be written to $last\n";
  170. print $fh $config;
  171. close($fh);
  172. # Fetch connected displays
  173. use JSON::XS;
  174. my $json = JSON::XS->new();
  175. my $displays_raw = `swaymsg -t get_outputs --raw`;
  176. my $displays = $json->decode($displays_raw);
  177. # For each connected display, collect the desired settings for enabled
  178. # displays and a list of displays to disable
  179. my $on;
  180. my @off;
  181. for (my $i = 0; $i < scalar(@$displays); $i++) {
  182. # If a display is found without any settings, print error and turn it off
  183. unless (defined $configs{$config}{$outputs{$displays->[$i]->{serial}}}) {
  184. print STDERR "Output $displays->[$i]->{name} ($displays->[$i]->{serial}) found without defined function. Disabling.\n";
  185. push @off, $displays->[$i]->{name};
  186. next;
  187. }
  188. # If display is enabled, copy all of the desired settings
  189. if ($configs{$config}{$outputs{$displays->[$i]->{serial}}}{on}) {
  190. $on->{$outputs{$displays->[$i]->{serial}}} = $configs{$config}{$outputs{$displays->[$i]->{serial}}};
  191. $on->{$outputs{$displays->[$i]->{serial}}}{output} = $displays->[$i]->{name};
  192. # Otherwise simply list it for disabling
  193. } else {
  194. push @off, $displays->[$i]->{name};
  195. }
  196. }
  197. # Skip enabling/disabling displays if only running waybar (re)start
  198. unless ($waybar_only) {
  199. # Number of simultaneous outputs is limited by gpu, so disabled displays first
  200. foreach (@off) {
  201. # Sway returns status as JSON
  202. my $res_raw = `sway output $_ disable`;
  203. my $res = $json->decode($res_raw)->[0];
  204. # If failed, print to STDERR
  205. unless ($res->{success}) {
  206. print STDERR "Error ($res->{error}) in command 'sway output $_ disable'\n";
  207. }
  208. }
  209. }
  210. # Kill existing Waybars
  211. require Proc::ProcessTable;
  212. my $t = new Proc::ProcessTable;
  213. foreach my $p ( @{ $t->table } ) {
  214. my $cmndline = $p->{'cmndline'};
  215. $cmndline =~ s/\s*$//g;
  216. if ($cmndline =~ /^waybar.*/) {
  217. # Never kill this process
  218. if ($p->{'pid'} == $$) {
  219. next;
  220. # SIGKILL match
  221. # TODO BUG: when multiple processes are running, some respawn with new PIDs. IDK why?
  222. } else {
  223. $p->kill(9);
  224. }
  225. }
  226. }
  227. # Load in config template
  228. my $template;
  229. if (open (my $fh, '<', $waybar_template)) {
  230. while (<$fh>) {
  231. $template .= $_;
  232. }
  233. close $fh;
  234. chomp $template;
  235. } else {
  236. print STDERR "Failed to load template $waybar_template\n";
  237. }
  238. # If template is already set up as an array, remove the square brackets so that we can concatenate multiple displays
  239. $template =~ s/^[^\[\{]*\[(.*)\]$/$1/s;
  240. # Setup waybar config file
  241. my $waybar = '';
  242. # Configure each enabled display
  243. foreach my $out (keys %$on) {
  244. unless ($waybar_only) {
  245. # Build command, starting by enabling and powering on
  246. my $cmd = "sway output $on->{$out}->{output} enable dpms on";
  247. # If additional options are provided, add them to command
  248. if (defined $on->{$out}->{rotate}) {
  249. $cmd .= " transform $on->{$out}->{rotate}";
  250. }
  251. if (defined $on->{$out}->{x} && defined $on->{$out}->{y}) {
  252. $cmd .= " position $on->{$out}->{x} $on->{$out}->{y}";
  253. }
  254. if (defined $on->{$out}->{width} && defined $on->{$out}->{height}) {
  255. $cmd .= " mode $on->{$out}->{width}x$on->{$out}->{height}";
  256. }
  257. # Sway returns status as JSON
  258. my $res_raw = `$cmd`;
  259. my $res = $json->decode($res_raw)->[0];
  260. # If failed, print to STDERR
  261. unless ($res->{success}) {
  262. print STDERR "Error ($res->{error}) in command '$cmd'\n";
  263. }
  264. }
  265. # Skip waybar setup if template failed to be loaded
  266. if ( (defined $template) && (defined $on->{$out}->{waybar}) && ($on->{$out}->{waybar} =~ m/(top|bottom|left|right)/) ) {
  267. # If there's already a display set up, add a comma
  268. unless ($waybar eq '') {
  269. $waybar .= ',';
  270. }
  271. $waybar .= $template;
  272. # Replace basic preferences
  273. $waybar =~ s/__OUTPUT__/"$on->{$out}->{output}"/;
  274. $waybar =~ s/__POSITION__/"$on->{$out}->{waybar}"/;
  275. if (defined $on->{$out}->{width}) {
  276. $waybar =~ s/__WIDTH__/$on->{$out}->{width}/;
  277. # If width is not set, comment that line out to use default
  278. } else {
  279. $waybar =~ s/([^\s]*\s*)__WIDTH__/\/\/ $1__WIDTH__/gg;
  280. }
  281. }
  282. }
  283. # Restore array formatting
  284. $waybar = '[' . $waybar . ']';
  285. # Start Waybar as fork
  286. my $pid = fork;
  287. unless ($pid) {
  288. open STDIN, '/dev/null';
  289. open STDOUT, '>>/dev/null';
  290. open STDERR, '>>/dev/null';
  291. # Write config to a temporary file
  292. my $tmp = $waybar_temporary . "/waybar-" . time() .".config";
  293. open ($fh, '>', $tmp);
  294. print $fh $waybar;
  295. close $fh;
  296. `nohup waybar --config=$tmp`;
  297. # Remove config
  298. unlink $tmp;
  299. }