about summary refs log tree commit diff
path: root/modules/i3
diff options
context:
space:
mode:
Diffstat (limited to 'modules/i3')
-rw-r--r--modules/i3/conky.nix115
-rw-r--r--modules/i3/default.nix129
-rw-r--r--modules/i3/i3.conf131
-rw-r--r--modules/i3/workspace.nix107
4 files changed, 482 insertions, 0 deletions
diff --git a/modules/i3/conky.nix b/modules/i3/conky.nix
new file mode 100644
index 00000000..77a63ea5
--- /dev/null
+++ b/modules/i3/conky.nix
@@ -0,0 +1,115 @@
+{ pkgs ? import <nixpkgs> {}, timeout ? 300 }:
+
+with pkgs.lib;
+
+let
+  baseConfig = pkgs.writeText "conkyrc" ''
+    cpu_avg_samples 2
+    net_avg_samples 2
+    no_buffers yes
+    out_to_console yes
+    out_to_ncurses no
+    out_to_stderr no
+    out_to_x no
+    extra_newline no
+    update_interval 1.0
+    uppercase no
+    use_spacer none
+    pad_percents 3
+    use_spacer left
+    TEXT
+  '';
+
+  optexpr = name: expr: "\${${name}_disabled:-\\\${${name} ${expr}\\}}";
+  cexpr = name: args: "${optexpr name (concatStringsSep " " args)}";
+
+  mkNetInfo = iface: let
+    upspeed = cexpr "upspeed" [ iface ];
+    downspeed = cexpr "downspeed" [ iface ];
+  in "${upspeed} ${downspeed}";
+
+  mkDiskFree = path: let
+    used = cexpr "fs_used" [ path ];
+    size = cexpr "fs_size" [ path ];
+  in "${used}/${size}";
+
+  gpuTemp = "${cexpr "hwmon" [ "0" "temp" "1" ]}C";
+
+  weather = (cexpr "weather" [
+    "http://weather.noaa.gov/pub/data/observations/metar/stations/"
+    "EDMA"
+    "temperature"
+  ]) + "C";
+
+  mkConky = args: let
+    time = cexpr "time" [ "%a %b %d %T %Z %Y" ];
+    text = concatStringsSep " | " (args ++ singleton time);
+    conky = pkgs.conky.override {
+      weatherMetar = true;
+    };
+  in pkgs.writeScript "conky-run.sh" ''
+    #!${pkgs.stdenv.shell}
+    PATH="${pkgs.coreutils}/bin"
+
+    cpuload() {
+      for i in $(seq 1 $(nproc))
+      do
+        [ $i -eq 1 ] || echo -n ' '
+        echo -n "\''${cpu cpu$i}%"
+      done
+    }
+
+    cputemp_collect() {
+      for i in /sys/bus/platform/devices/coretemp.?/hwmon/hwmon?/temp?_input
+      do
+        [ -e "$i" ] || continue
+        echo "$i" | ${pkgs.gnused}/bin/sed -re \
+          's/^.*hwmon([0-9]+)[^0-9]*([0-9]+).*$/''${hwmon \1 temp \2}/'
+      done
+    }
+
+    cputemp() {
+      echo $(cputemp_collect)
+    }
+
+    tries=0
+    while ! raw_netinfo="$(${
+      "${pkgs.iproute}/sbin/ip route get 8.8.8.8 2> /dev/null"
+    })"; do
+      if [ $tries -ge ${toString timeout} ]; then
+        upspeed_disabled=N/A
+        downspeed_disabled=N/A
+        break
+      fi
+      echo "Waiting for primary network interface to become available..."
+      tries=$(($tries + 1))
+      sleep 1
+    done
+
+    primary_netdev="$(echo "$raw_netinfo" | \
+      ${pkgs.gnused}/bin/sed -nre 's/^.*dev *([^ ]+).*$/\1/p')"
+
+    ${conky}/bin/conky -c "${baseConfig}" -t "${text}"
+  '';
+
+in {
+  left = mkConky [
+    "CPU: $(cpuload) - ${cexpr "cpu" [ "cpu0" ]}%"
+    "MEM: \\$mem/\\$memmax - \\$memperc%"
+    "SWAP: \\$swap/\\$swapmax \\$swapperc%"
+  ];
+
+  right = mkConky [
+    "NET: ${mkNetInfo "$primary_netdev"}"
+    "DF: ${mkDiskFree "/"}"
+    "LAVG: \\$loadavg"
+    "TEMP - CPU: $(cputemp) - GPU: ${gpuTemp} - OUTSIDE: ${weather}"
+  ];
+
+  single = mkConky [
+    "CPU: $(cpuload) - ${cexpr "cpu" [ "cpu0" ]}%"
+    "MEM: \\$mem/\\$memmax - \\$memperc%"
+    "NET: ${mkNetInfo "$primary_netdev"}"
+    "TEMP - CPU: $(cputemp) - OUTSIDE: ${weather}"
+  ];
+}
diff --git a/modules/i3/default.nix b/modules/i3/default.nix
new file mode 100644
index 00000000..bca88cca
--- /dev/null
+++ b/modules/i3/default.nix
@@ -0,0 +1,129 @@
+{ pkgs, lib, config, ... }:
+
+with lib;
+
+let
+  inherit (config.services.xserver) xrandrHeads;
+
+  # The symbols if you press shift and a number key.
+  wsNumberSymbols = [
+    "exclam" "at" "numbersign" "dollar" "percent"
+    "asciicircum" "ampersand" "asterisk" "parenleft" "parenright"
+  ];
+
+  wsCount = length wsNumberSymbols;
+
+  headCount = length xrandrHeads;
+  wsPerHead = wsCount / headCount;
+  excessWs = wsCount - (headCount * wsPerHead);
+  headModifier = if config.vuizvui.i3.reverseHeads then reverseList else id;
+  getHeadAt = elemAt (headModifier xrandrHeads);
+
+  mkDefaultWorkspace = number: numberSymbol: {
+    name = toString number;
+    value = mkDefault {
+      label = null;
+      labelPrefix = "${toString number}: ";
+      keys.switchTo = "$mod+${if number == 10 then "0" else toString number}";
+      keys.moveTo = "$mod+Shift+${numberSymbol}";
+      head = if headCount == 0 then null
+             else getHeadAt ((number - (excessWs + 1)) / wsPerHead);
+    };
+  };
+
+  wsCfgList = mapAttrsToList (_: getAttr "config") config.vuizvui.i3.workspaces;
+  wsConfig = concatStrings wsCfgList;
+  defaultWorkspaces = listToAttrs (imap mkDefaultWorkspace wsNumberSymbols);
+
+  conky = import ./conky.nix {
+    inherit pkgs;
+    timeout = config.vuizvui.i3.networkTimeout;
+  };
+
+  mkBar = output: statusCmd: singleton ''
+    bar {
+      ${optionalString (output != null) "output ${output}"}
+      ${optionalString (statusCmd != null) "status_command ${statusCmd}"}
+      colors {
+        focused_workspace  #5c5cff #e5e5e5
+        active_workspace   #ffffff #0000ee
+        inactive_workspace #00cdcd #0000ee
+        urgent_workspace   #ffff00 #cd0000
+      }
+    }
+  '';
+
+  barConfig = let
+    barHeads = headModifier xrandrHeads;
+    bars = if headCount == 0 then mkBar null conky.single
+      else if headCount == 1 then mkBar (head barHeads) conky.single
+      else let inner = take (length barHeads - 2) (tail barHeads);
+           in mkBar (head barHeads) conky.left
+           ++ map (flip mkBar null) inner
+           ++ mkBar (last barHeads) conky.right;
+  in concatStrings (headModifier bars);
+
+in
+{
+  options.vuizvui.i3 = {
+    enable = mkEnableOption "i3";
+
+    workspaces = mkOption {
+      type = types.attrsOf (types.submodule (import ./workspace.nix));
+      default = listToAttrs (imap mkDefaultWorkspace wsNumberSymbols);
+      description = ''
+        Workspace to monitor assignment.
+
+        Workspaces are by default assigned starting from the leftmost monitor
+        being workspace 1 and the rightmost monitor being workspace 10. The
+        workspaces are divided by the number of available heads, so if you have
+        a dual head system, you'll end up having workspace 1 to 5 on the left
+        monitor and 6 to 10 on the right.
+      '';
+    };
+
+    reverseHeads = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Reverse the order of the heads, so if enabled and you have two heads,
+        you'll end up having workspaces 1 to 5 on the right head and 6 to 10 on
+        the left head.
+      '';
+    };
+
+    networkTimeout = mkOption {
+      type = types.int;
+      default = 300;
+      description = ''
+        Maximum number of seconds to wait for network device detection.
+      '';
+    };
+  };
+
+  config.vuizvui.i3.workspaces = defaultWorkspaces;
+
+  config.services.xserver.windowManager = mkIf config.vuizvui.i3.enable {
+    default = "i3";
+
+    i3.enable = true;
+    i3.configFile = pkgs.substituteAll {
+      name = "i3.conf";
+      src = ./i3.conf;
+
+      inherit (pkgs) dmenu xterm pvolctrl;
+      inherit (pkgs.xorg) xsetroot;
+      inherit wsConfig barConfig;
+
+      lockall = pkgs.writeScript "lockvt.sh" ''
+        #!${pkgs.stdenv.shell}
+        "${pkgs.socat}/bin/socat" - UNIX-CONNECT:/run/console-lock.sock \
+          < /dev/null
+      '';
+
+      postInstall = ''
+        ${pkgs.i3}/bin/i3 -c "$target" -C
+      '';
+    };
+  };
+}
diff --git a/modules/i3/i3.conf b/modules/i3/i3.conf
new file mode 100644
index 00000000..cd14c425
--- /dev/null
+++ b/modules/i3/i3.conf
@@ -0,0 +1,131 @@
+# default modifier key
+set $mod Mod4
+
+# we want to have a VT-style font :-)
+font -dosemu-vga-medium-r-normal--17-160-75-75-c-80-ibm-cp866
+
+# Use Mouse+$mod to drag floating windows to their wanted position
+floating_modifier $mod
+
+# reasonable defaults!
+default_orientation horizontal
+workspace_layout tabbed
+popup_during_fullscreen ignore
+
+# start a terminal
+bindsym $mod+Shift+Return exec --no-startup-id @xterm@/bin/xterm
+
+# kill focused window
+bindsym $mod+Shift+C kill
+
+# start dmenu (a program launcher)
+bindsym $mod+p exec --no-startup-id @dmenu@/bin/dmenu_run
+
+# start lock screen
+bindsym $mod+Shift+Escape exec --no-startup-id @lockall@
+
+# set background
+exec @xsetroot@/bin/xsetroot -solid black
+
+# audio controls
+bindsym XF86AudioLowerVolume exec @pvolctrl@/bin/pvolctrl -10
+bindsym XF86AudioRaiseVolume exec @pvolctrl@/bin/pvolctrl 10
+bindsym XF86AudioMute exec @pvolctrl@/bin/pvolctrl 0
+
+# change/move focus
+bindsym $mod+Shift+Left move left
+bindsym $mod+Shift+H move left
+bindsym $mod+Shift+Down move down
+bindsym $mod+Shift+T move down
+bindsym $mod+Shift+Up move up
+bindsym $mod+Shift+N move up
+bindsym $mod+Shift+Right move right
+bindsym $mod+Shift+S move right
+
+bindsym $mod+Left focus left
+bindsym $mod+h focus left
+bindsym $mod+Down focus down
+bindsym $mod+t focus down
+bindsym $mod+Up focus up
+bindsym $mod+n focus up
+bindsym $mod+Right focus right
+bindsym $mod+s focus right
+
+# split in horizontal orientation
+bindsym $mod+i split h
+
+# split in vertical orientation
+bindsym $mod+d split v
+
+# enter fullscreen mode for the focused container
+bindsym $mod+f fullscreen
+
+# change container layout (stacked, tabbed, default)
+bindsym $mod+apostrophe layout stacking
+bindsym $mod+comma layout tabbed
+bindsym $mod+period layout default
+
+# toggle tiling / floating
+bindsym $mod+Shift+space floating toggle
+
+# change focus between tiling / floating windows
+bindsym $mod+space focus mode_toggle
+
+# focus the parent container
+bindsym $mod+a focus parent
+
+# focus the child container
+bindsym $mod+semicolon focus child
+
+# reload the configuration file
+bindsym $mod+Shift+L reload
+# restart i3 inplace (preserves your layout/session, can be used to upgrade i3)
+bindsym $mod+Shift+R restart
+# exit i3 (logs you out of your X session)
+bindsym $mod+Shift+Q exit
+
+# resize window (you can also use the mouse for that)
+mode "resize" {
+    # These bindings trigger as soon as you enter the resize mode
+
+    # They resize the border in the direction you pressed, e.g.
+    # when pressing left, the window is resized so that it has
+    # more space on its left
+
+    bindsym Left resize shrink left 10 px or 10 ppt
+    bindsym h resize shrink left 10 px or 10 ppt
+    bindsym Down resize shrink down 10 px or 10 ppt
+    bindsym t resize shrink down 10 px or 10 ppt
+    bindsym Up resize shrink up 10 px or 10 ppt
+    bindsym n resize shrink up 10 px or 10 ppt
+    bindsym Right resize shrink right 10 px or 10 ppt
+    bindsym s resize shrink right 10 px or 10 ppt
+
+    bindsym Shift+Left resize grow left 10 px or 10 ppt
+    bindsym Shift+H resize grow left 10 px or 10 ppt
+    bindsym Shift+Down resize grow down 10 px or 10 ppt
+    bindsym Shift+T resize grow down 10 px or 10 ppt
+    bindsym Shift+Up resize grow up 10 px or 10 ppt
+    bindsym Shift+N resize grow up 10 px or 10 ppt
+    bindsym Shift+Right resize grow right 10 px or 10 ppt
+    bindsym Shift+S resize grow right 10 px or 10 ppt
+
+    # back to normal: Enter or Escape
+    bindsym Return mode "default"
+    bindsym Escape mode "default"
+}
+
+bindsym $mod+r mode "resize"
+
+# workspace configuration
+@wsConfig@
+
+# ratmenu should be as unintrusive as possible
+for_window [class="^ratmenu$"] floating enable
+for_window [class="^ratmenu$"] border none
+
+# various app cruft
+for_window [class="^Dia$"] floating enable
+
+# bar configuration
+@barConfig@
diff --git a/modules/i3/workspace.nix b/modules/i3/workspace.nix
new file mode 100644
index 00000000..403ba57d
--- /dev/null
+++ b/modules/i3/workspace.nix
@@ -0,0 +1,107 @@
+{ name, lib, config, ... }:
+
+with lib;
+
+let
+  finalLabel =
+    if config.label == null then name
+    else config.labelPrefix + config.label;
+
+  mkDoc = anchor: "http://i3wm.org/docs/userguide.html#${anchor}";
+in
+{
+  options = {
+    labelPrefix = mkOption {
+      type = types.str;
+      default = "";
+      example = "666: ";
+      description = ''
+        The value that will be put in front of the <option>label</option>.
+        So if you have a label called <replaceable>bar</replaceable> and a
+        <option>labelPrefix</option> called <replaceable>foo</replaceable> the
+        label for the workspace will be <replaceable>foobar</replaceable>.
+      '';
+    };
+
+    label = mkOption {
+      type = types.nullOr types.str;
+      default = name;
+      description = ''
+        The label of this workspace, which is its name by default. If the value
+        is <replaceable>null</replaceable>, the resulting label of the workspace
+        is just its name and no <option>labelPrefix</option> is applied.
+      '';
+    };
+
+    assign = mkOption {
+      type = types.listOf types.attrs;
+      default = [];
+      example = [
+        { class = "^Chromium(?:-browser)?\$"; }
+        { instance = "^gajim\$"; }
+      ];
+      description = let
+        anchor = "_automatically_putting_clients_on_specific_workspaces";
+      in ''
+        Assign windows to this specific workspace using the attribute names
+        described by <link xlink:href="${mkDoc anchor}"/>.
+      '';
+    };
+
+    head = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        The XRandR head this workspace will be assigned to.
+      '';
+    };
+
+    keys = let
+      commonDesc = ''
+        The <replaceable>$mod</replaceable> placeholder represents the default
+        modifier key. Details about the syntax of key combinations can be found
+        at <link xlink:href="${mkDoc "keybindings"}"/>.
+      '';
+    in {
+      switchTo = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "$mod+1";
+        description = ''
+          Key combination to switch to this workspace.
+        '' + commonDesc;
+      };
+
+      moveTo = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "$mod+Shift+exclam";
+        description = ''
+          Key combination to move a container to this workspace.
+        '' + commonDesc;
+      };
+    };
+
+    config = mkOption {
+      type = types.lines;
+      default = "";
+      description = ''
+        Raw configuration options for this workspace.
+      '';
+    };
+  };
+
+  config.config = let
+    mkAssign = mapAttrsToList (criteria: value: "${criteria}=\"${value}\"");
+    mkSym = sym: rest: optionalString (sym != null) "bindsym ${sym} ${rest}";
+  in ''
+    ${optionalString (config.head != null) ''
+    workspace "${finalLabel}" output ${config.head}
+    ''}
+    ${mkSym config.keys.switchTo "workspace \"${finalLabel}\""}
+    ${mkSym config.keys.moveTo "move workspace \"${finalLabel}\""}
+    ${concatMapStrings (assign: ''
+    assign [${concatStringsSep " " (mkAssign assign)}] ${finalLabel}
+    '') config.assign}
+  '';
+}