about summary refs log tree commit diff
path: root/pkgs/profpatsch
diff options
context:
space:
mode:
authorProfpatsch <mail@profpatsch.de>2021-04-23 16:02:00 +0200
committerProfpatsch <mail@profpatsch.de>2021-04-23 16:02:36 +0200
commit84d6379392b9f580424ef1ef25c499e698a7d69f (patch)
treed1ae2e661b3cb78c30fbf6c1b1eefd3754854ba8 /pkgs/profpatsch
parent594591447c0e9668ac7355b47af961fc181e230e (diff)
pkgs/profpatsch: add xrandr two monitor setup
Just to prove I can.
Diffstat (limited to 'pkgs/profpatsch')
-rw-r--r--pkgs/profpatsch/default.nix11
-rw-r--r--pkgs/profpatsch/xrandr.nix194
2 files changed, 205 insertions, 0 deletions
diff --git a/pkgs/profpatsch/default.nix b/pkgs/profpatsch/default.nix
index c4daf05b..c5f8ed94 100644
--- a/pkgs/profpatsch/default.nix
+++ b/pkgs/profpatsch/default.nix
@@ -140,6 +140,8 @@ in rec {
     inherit writeExecline writeHaskellInterpret getBins runInEmptyEnv sandbox;
   };
 
+  xrandr = import ./xrandr.nix { inherit pkgs getBins runExeclineLocal writeExecline toNetstringKeyVal dhall-json; };
+
   inherit (callPackage ./utils-hs {})
     until watch-server
     haskellPackages;
@@ -186,6 +188,15 @@ in rec {
   toNetstring = s:
     "${toString (builtins.stringLength s)}:${s},";
 
+  toNetstringList = xs:
+    lib.concatStrings (map toNetstring xs);
+
+  toNetstringKeyVal = attrs:
+    lib.concatStrings
+      (lib.mapAttrsToList
+        (k: v: toNetstring (toNetstring k + toNetstring v))
+        attrs);
+
   inherit getBins binify;
 
   inherit (import ./sandbox.nix {inherit pkgs writeExecline; })
diff --git a/pkgs/profpatsch/xrandr.nix b/pkgs/profpatsch/xrandr.nix
new file mode 100644
index 00000000..fd8c66d0
--- /dev/null
+++ b/pkgs/profpatsch/xrandr.nix
@@ -0,0 +1,194 @@
+{ pkgs, getBins, writeExecline, runExeclineLocal, toNetstringKeyVal, dhall-json, ... }:
+
+let
+  inherit (pkgs) lib;
+  bins = getBins pkgs.nodejs [ "node" ]
+      // getBins pkgs.coreutils [ "echo" "ln" "mkdir" ]
+      // getBins dhall-json [ "json-to-dhall" ]
+      // getBins pkgs.xorg.xrandr [ "xrandr" ]
+      ;
+
+  writeNodejs = {
+    name,
+    # an attrset of node dependency name to source directory;
+    # will be made into a node_modules directory and set as `NODE_PATH`.
+    dependencies
+  }:
+    let
+      node_modules = runExeclineLocal "${name}-node_modules" {
+        stdin = toNetstringKeyVal dependencies;
+      } [
+        "importas" "out" "out"
+        "if" [ bins.mkdir "$out" ]
+        "forstdin" "-o" "0" "-Ed" "" "dep"
+        "multidefine" "-d" "" "$dep" [ "name" "source" ]
+        "if" [ bins.echo "\${name} - \${source}" ]
+        bins.ln "-sT" "\${source}" "\${out}/\${name}"
+      ];
+
+    in pkgs.writers.makeScriptWriter {
+      interpreter = writeExecline "nodejs-with-modules" {} [
+        "export" "NODE_PATH" node_modules
+        bins.node "$@"
+      ];
+    } name;
+
+  dhall-typecheck = {
+    name,
+    dhallType,
+    recordsLoose ? false
+  }:
+    writeExecline name {} ([
+      bins.json-to-dhall
+    ]
+    ++ lib.optional recordsLoose "--records-loose"
+    ++ [
+      dhallType
+    ]);
+
+  parse = writeNodejs {
+    name = "xrandr-parse";
+    dependencies = {
+      xrandr-parse =
+        (pkgs.fetchFromGitHub {
+          owner = "lionep";
+          repo = "xrandr-parse";
+          rev = "a35bfd625c1b0834aa94f136cf8282b25624b3e6";
+          sha256 = "0c8mfsvgg76ia2i9gsgdwy567xzba5274xpj8si37yah5qpp8dkm";
+        });
+    };
+  } ''
+    var parse = require('xrandr-parse');
+    var exec = require('child_process').exec;
+
+    exec('xrandr', function (err, stdout) {
+        var query = parse(stdout);
+        console.log(JSON.stringify(query, null, 2));
+    });
+  '';
+
+  type = pkgs.writeText "type.dhall" ''
+    let Resolution = {
+      height: Text,
+      width: Text,
+      rate: Double
+    }
+    let Monitor =
+      < NotConnected : {}
+      | Connected : {
+          connected: Bool,
+          modes: List Resolution,
+          index: Natural,
+          native: Resolution
+      }
+      | Active : {
+          modes: List Resolution,
+          index: Natural,
+          native: Resolution,
+          current: Resolution
+      } >
+
+    in List {
+      mapKey: Text,
+      mapValue: Monitor
+    }
+  '';
+
+
+  two-monitor-setup = writeExecline "test" {} [
+    "backtick" "-Ei" "json" [ parse ]
+    "if" [
+      "pipeline" [ bins.echo "$json" ]
+      "redirfd" "-w" "1" "/dev/null"
+      (dhall-typecheck {
+        name = "typecheck-xrandr";
+        dhallType = type;
+        recordsLoose = true;
+      })
+    ]
+    "pipeline" [ bins.echo "$json" ]
+    two-monitor-setup-script
+  ];
+
+
+  two-monitor-setup-script = pkgs.writers.writePython3 "xrandr-two-monitor-setup" {} ''
+    import json
+    import sys
+    import os
+
+    # TODO: use netencode for input
+    monitors = json.load(sys.stdin)
+
+    connected = {
+      k: v for k, v in monitors.items()
+      if 'connected' in v and v['connected']
+    }
+
+    if 'eDP1' not in connected:
+        print("could not find eDP1 (laptop screen)", file=sys.stderr)
+        sys.exit(1)
+
+    if len(connected) != 2:
+        print("only know how to configure two monitors")
+
+    eDP1 = connected['eDP1']
+
+    # laptop screen is active
+    assert('current' in eDP1)
+
+    # how far the laptop screen should be offset to end
+    # at the bottom of the monitor
+    h_offset = 0
+    for k, v in connected.items():
+        if k == 'eDP1':
+            assert('current' in v)
+
+        else:
+            h = int(v['native']['height'])
+            h_offset = h - int(eDP1['native']['height'])
+            external_monitor = (k, v)
+            # can’t handle bigger laptop screens atm
+            assert(h_offset >= 0)
+
+    xrandr_command = [
+        "--verbose",
+
+        "--output", external_monitor[0],
+        "--primary",
+        "--mode", "{}x{}".format(
+            external_monitor[1]['native']['width'],
+            external_monitor[1]['native']['height']
+        ),
+        "--pos", "{}x{}".format(
+            # offset by the laptop size to the right
+            eDP1['native']['width'],
+            # monitor starts at 0
+            0
+        ),
+
+        "--output", "eDP1",
+        "--mode", "{}x{}".format(
+            eDP1['native']['width'],
+            eDP1['native']['height'],
+        ),
+        "--pos", "{}x{}".format(
+            # laptop is the leftmost screen
+            0,
+            # but offset to the bottom so that it always aligns on the left bottom
+            h_offset
+        ),
+    ]
+
+    os.execvp(
+        "${bins.xrandr}",
+        ["${bins.xrandr}"]
+        + xrandr_command
+    )
+  '';
+
+in {
+  inherit
+    parse
+    two-monitor-setup
+    ;
+}