diff options
| author | Quentin Aristote <quentin@aristote.fr> | 2023-02-22 22:39:56 +0100 |
|---|---|---|
| committer | Quentin Aristote <quentin@aristote.fr> | 2023-02-28 17:47:49 +0100 |
| commit | 70d60e5ee6d1092f765807b8483c9c16d2afa678 (patch) | |
| tree | edeb7a91de348d6fc5dd0e43b9ca7bef794b322c /modules/home-manager/personal/gui/x/i3 | |
| parent | bbb2f5e7cefb970b3e4994ee51bb2c3a18a073c2 (diff) | |
add home-manager modules
Diffstat (limited to 'modules/home-manager/personal/gui/x/i3')
5 files changed, 444 insertions, 0 deletions
diff --git a/modules/home-manager/personal/gui/x/i3/bar/default.nix b/modules/home-manager/personal/gui/x/i3/bar/default.nix new file mode 100644 index 0000000..58d4bce --- /dev/null +++ b/modules/home-manager/personal/gui/x/i3/bar/default.nix @@ -0,0 +1,27 @@ +{ config, lib, pkgs, ... }@extraArgs: + +let + statusPackage = + pkgs.personal.barista.override { i3statusGo = ./i3status.go; }; +in { + xsession.windowManager.i3.config.bars = [{ + statusCommand = "${statusPackage}/bin/i3status"; + fonts = { + names = [ "roboto" ]; + size = 11.0; + }; + colors.background = "#111111"; + }]; + + home.packages = with pkgs; + lib.optionals + (config.xsession.enable && config.xsession.windowManager.i3.enable) [ + material-design-icons + roboto + # source-code-pro + ]; + + # (Miscellaneous) Tray icons + services.blueman-applet.enable = + lib.mkDefault (extraArgs.osConfig.services.blueman.enable); +} diff --git a/modules/home-manager/personal/gui/x/i3/bar/i3status.go b/modules/home-manager/personal/gui/x/i3/bar/i3status.go new file mode 100644 index 0000000..196e6cc --- /dev/null +++ b/modules/home-manager/personal/gui/x/i3/bar/i3status.go @@ -0,0 +1,289 @@ +package main + +import ( + "io" + "net/http" + "os" + "regexp" + "strconv" + "time" + + "barista.run" + "barista.run/bar" + "barista.run/colors" + "barista.run/group" + "barista.run/modules/battery" + "barista.run/modules/clock" + "barista.run/modules/diskspace" + "barista.run/modules/funcs" + "barista.run/modules/netinfo" + "barista.run/modules/systemd" + "barista.run/modules/volume" + "barista.run/modules/volume/pulseaudio" + "barista.run/modules/wlan" + "barista.run/outputs" + "barista.run/pango" + "barista.run/pango/icons/mdi" +) + +func main() { + // Constants + colors.LoadFromMap(map[string]string{ + // Color palette of Cezanne's Vue de la Baie de Marseille + "good": "#C5D294", + "degraded": "#E9CC67", + "bad": "#FFBC88", + }) + mdi.Load() // repo path will be inserted at build time + + // Display status of several services + updateSuccessIcon := pango.Icon("mdi-reload") + updatingIcon := pango.Icon("mdi-update") + updateFailIcon := pango.Icon("mdi-reload-alert") + garbageFullIcon := pango.Icon("mdi-delete") + garbageEmptyingIcon := pango.Icon("mdi-delete-restore") + garbageEmptyIcon := pango.Icon("mdi-delete-outline") + barista.Add(group.Simple(systemd.Service("nixos-upgrade").Output(func(i systemd.ServiceInfo) bar.Output { + state := i.UnitInfo.State + var colorScheme string + var output *pango.Node + switch { + case state == systemd.StateInactive: + colorScheme = "good" + output = updateSuccessIcon + case state == systemd.StateActivating: + colorScheme = "degraded" + output = updatingIcon + default: + colorScheme = "bad" + output = updateFailIcon + } + return outputs.Pango(output).Color(colors.Scheme(colorScheme)) + }), + systemd.Service("nix-gc").Output(func(i systemd.ServiceInfo) bar.Output { + state := i.UnitInfo.State + var colorScheme string + var output *pango.Node + switch { + case state == systemd.StateInactive: + colorScheme = "good" + output = garbageEmptyIcon + case state == systemd.StateActivating: + colorScheme = "degraded" + output = garbageEmptyingIcon + default: + colorScheme = "bad" + output = garbageFullIcon + } + return outputs.Pango(output).Color(colors.Scheme(colorScheme)) + }))) + + // Display space left on / + storageIcon := pango.Icon("mdi-database") + barista.Add(diskspace.New("/").Output(func(i diskspace.Info) bar.Output { + used := i.UsedPct() + var colorScheme string + if used >= 90 { + colorScheme = "bad" + } else if used >= 50 { + colorScheme = "degraded" + } else { + colorScheme = "good" + } + return outputs.Pango(storageIcon, pango.Textf(" %d%%", used)).Color(colors.Scheme(colorScheme)) + })) + + // Check connection to the Mullvad VPN + mullvadIsUpRe := regexp.MustCompile(`^You are connected to Mullvad`) + mullvadServerRe := regexp.MustCompile(`\(server (.*)\)`) + mullvadIpRe := regexp.MustCompile(`Your IP address is (.*)`) + client := &http.Client{Timeout: 3 * time.Second} + incognitoIcon := pango.Icon("mdi-incognito") + incognitoOffIcon := pango.Icon("mdi-incognito-off") + barista.Add(funcs.Every(5*time.Second, func(s bar.Sink) { + icon := incognitoOffIcon + message := pango.Text("") + colorScheme := "bad" + res, err := client.Get("https://am.i.mullvad.net/connected") + if !s.Error(err) { + status, err := io.ReadAll(res.Body) + res.Body.Close() + if !s.Error(err) { + var re *regexp.Regexp + if mullvadIsUpRe.Match(status) { + re = mullvadServerRe + colorScheme = "good" + icon = incognitoIcon + } else { + re = mullvadIpRe + colorScheme = "degraded" + } + result := re.FindSubmatch(status) + if len(result) >= 2 { + message = pango.Textf(" %s", result[1]) + } + } + } + client.CloseIdleConnections() + s.Output(outputs.Pango(icon, message).Color(colors.Scheme(colorScheme))) + })) + + // Display the wifi status + wifiOffIcon := pango.Icon("mdi-wifi-off") + wifiRefreshIcon := pango.Icon("mdi-wifi-refresh") + wifiOnIcon := pango.Icon("mdi-wifi") + barista.Add(wlan.Named("wlp2s0").Output(func(w wlan.Info) bar.Output { + var output *pango.Node + var colorScheme string + switch { + case w.Connected(): + output = pango.New(wifiOnIcon, pango.Textf(" %s", w.SSID)) + colorScheme = "good" + case w.Connecting(): + output = wifiRefreshIcon + colorScheme = "degraded" + default: + output = wifiOffIcon + colorScheme = "bad" + } + return outputs.Pango(output).Color(colors.Scheme(colorScheme)) + })) + + // Display the ethernet status + ethernetCableOnIcon := pango.Icon("mdi-ethernet-cable") + ethernetCableOffIcon := pango.Icon("mdi-ethernet-cable-off") + barista.Add(netinfo.Prefix("e").Output(func(s netinfo.State) bar.Output { + var output *pango.Node + var colorScheme string + switch { + case s.Connected(): + ip := "<no ip>" + if len(s.IPs) > 0 { + ip = s.IPs[0].String() + } + output = pango.New(ethernetCableOnIcon, pango.Textf(" %s", ip)) + colorScheme = "good" + case s.Connecting(): + output = ethernetCableOnIcon + colorScheme = "degraded" + default: + output = ethernetCableOffIcon + colorScheme = "bad" + } + return outputs.Pango(output).Color(colors.Scheme(colorScheme)) + })) + + // Display the battery status + batteryIcons := [11]*pango.Node{pango.Icon("mdi-battery-outline"), + pango.Icon("mdi-battery-10"), + pango.Icon("mdi-battery-20"), + pango.Icon("mdi-battery-30"), + pango.Icon("mdi-battery-40"), + pango.Icon("mdi-battery-50"), + pango.Icon("mdi-battery-60"), + pango.Icon("mdi-battery-70"), + pango.Icon("mdi-battery-80"), + pango.Icon("mdi-battery-90"), + pango.Icon("mdi-battery")} + batteryChargingIcons := [11]*pango.Node{pango.Icon("mdi-battery-charging-outline"), + pango.Icon("mdi-battery-charging-10"), + pango.Icon("mdi-battery-charging-20"), + pango.Icon("mdi-battery-charging-30"), + pango.Icon("mdi-battery-charging-40"), + pango.Icon("mdi-battery-charging-50"), + pango.Icon("mdi-battery-charging-60"), + pango.Icon("mdi-battery-charging-70"), + pango.Icon("mdi-battery-charging-80"), + pango.Icon("mdi-battery-charging-90"), + pango.Icon("mdi-battery-charging-100")} + barista.Add(battery.All().Output(func(b battery.Info) bar.Output { + switch b.Status { + case battery.Disconnected, battery.Unknown: + return nil + default: + var icons [11]*pango.Node + var colorScheme string + if b.Status == battery.Charging { + icons = batteryChargingIcons + colorScheme = "good" + } else { + icons = batteryIcons + if b.RemainingPct() <= 10 { + colorScheme = "bad" + } else if b.RemainingPct() <= 20 { + colorScheme = "degraded" + } else { + colorScheme = "good" + } + } + icon := icons[b.RemainingPct()/10] + return outputs.Pango(icon, pango.Textf(" %d%%", b.RemainingPct())).Color(colors.Scheme(colorScheme)) + } + })) + + // Display brightness + brightnessHighIcon := pango.Icon("mdi-lightbulb-on") + brightnessMidIcon := pango.Icon("mdi-lightbulb-on-outline") + brightnessLowIcon := pango.Icon("mdi-lightbulb-outline") + ReadBrightness := func(name string) (int, error) { + valueStr, err := os.ReadFile("/sys/class/backlight/intel_backlight/" + name) + if err != nil { + return 0, err + } + return strconv.Atoi(string(valueStr[:len(valueStr)-1])) + } + brightnessMax, _ := ReadBrightness("max_brightness") // always non-zero, unless there's an error + barista.Add(funcs.Every(time.Second, func(s bar.Sink) { + brightness, err := ReadBrightness("brightness") + if !s.Error(err) { + value := (brightness * 100) / brightnessMax + var icon *pango.Node + if value <= 30 { + icon = brightnessLowIcon + } else if value < 70 { + icon = brightnessMidIcon + } else { + icon = brightnessHighIcon + } + s.Output(outputs.Pango(icon, pango.Textf(" %d%%", value))) + } + })) + + // Display output volume + volumeOffIcon := pango.Icon("mdi-volume-variant-off") + volumeLowIcon := pango.Icon("mdi-volume-low") + volumeMidIcon := pango.Icon("mdi-volume-medium") + volumeHighIcon := pango.Icon("mdi-volume-high") + barista.Add(volume.New(pulseaudio.DefaultSink()).Output(func(v volume.Volume) bar.Output { + volume := v.Pct() + var icon *pango.Node + if volume == 0 || v.Mute { + icon = volumeOffIcon + } else if volume <= 30 { + icon = volumeLowIcon + } else if volume <= 70 { + icon = volumeMidIcon + } else { + icon = volumeHighIcon + } + return outputs.Pango(icon, pango.Textf(" %d%%", volume)) + })) + + // Display microphone volume + microphoneOffIcon := pango.Icon("mdi-microphone-off") + microphoneIcon := pango.Icon("mdi-microphone") + barista.Add(volume.New(pulseaudio.DefaultSource()).Output(func(v volume.Volume) bar.Output { + volume := v.Pct() // the value returned by pulseaudio may be weird + var icon *pango.Node + if volume == 0 || v.Mute { + icon = microphoneOffIcon + } else { + icon = microphoneIcon + } + return outputs.Pango(icon, pango.Textf(" %d%%", volume)) + })) + + barista.Add(clock.Local().OutputFormat("2006-01-02 15:04:05")) + + panic(barista.Run()) +} diff --git a/modules/home-manager/personal/gui/x/i3/default.nix b/modules/home-manager/personal/gui/x/i3/default.nix new file mode 100644 index 0000000..beae770 --- /dev/null +++ b/modules/home-manager/personal/gui/x/i3/default.nix @@ -0,0 +1,56 @@ +{ config, lib, pkgs, ... }@extraArgs: + +let cfg = config.personal.x.i3; +in { + imports = [ ./bar ./keybindings.nix ./startup.nix ]; + + options.personal.x.i3 = { + enable = lib.mkEnableOption "i3" // { + default = + extraArgs.osConfig.services.xserver.windowManager.i3.enable or false; + }; + }; + + config = lib.mkIf cfg.enable { + xsession.windowManager.i3 = { + enable = cfg.enable; + package = lib.mkDefault pkgs.i3-gaps; + + config = { + assigns = lib.optionalAttrs (config.personal.profiles.multimedia + && (extraArgs.osConfig.programs.steam.enable or true)) { + "8: multimedia" = [ + { class = "^Steam$"; } + { class = "Netflix"; } + { class = "MUBI"; } + { class = "Deezer"; } + ]; + } // lib.optionalAttrs config.personal.profiles.social { + "9: social" = [ + { class = "^Mail$"; } + { class = "^thunderbird$"; } + { class = "^Signal$"; } + ]; + } // { + "10: passwords" = [{ + # matches <some db>.kbdx [Locked] - KeePassXC + title = ".*\\.kbdx \\[Locked\\] - KeePassXC$"; + }]; + }; + + workspaceAutoBackAndForth = lib.mkDefault true; + + window = { + titlebar = lib.mkDefault false; + border = lib.mkDefault 0; + }; + floating.titlebar = lib.mkDefault false; + gaps = { + inner = lib.mkDefault 15; + outer = lib.mkDefault 5; + }; + }; + }; + programs.rofi.enable = lib.mkDefault true; + }; +} diff --git a/modules/home-manager/personal/gui/x/i3/keybindings.nix b/modules/home-manager/personal/gui/x/i3/keybindings.nix new file mode 100644 index 0000000..3781867 --- /dev/null +++ b/modules/home-manager/personal/gui/x/i3/keybindings.nix @@ -0,0 +1,47 @@ +{ config, lib, pkgs, ... }: + +let + brightnessctl = "${pkgs.brightnessctl}/bin/brightnessctl"; + brightnessctlKbd = "${brightnessctl} --device dell:kbd_backlight"; + volumectl = "${pkgs.pulseaudio}/bin/pactl"; + screenshot = "${pkgs.shutter}/bin/shutter"; + + modifier = "Mod4"; +in { + xsession.windowManager.i3.config = { + inherit modifier; + + keybindings = lib.mkOptionDefault { + # launching apps + "${modifier}+Control+Return" = ''exec "$EDITOR"''; + "${modifier}+Shift+Return" = ''exec "$BROWSER"''; + "${modifier}+d" = lib.mkIf config.programs.rofi.enable + ''exec "rofi -modi drun,filebrowser,run,window -show drun"''; + + # exiting + "${modifier}+Shift+e" = "exec i3-msg exit"; + "${modifier}+l" = + "exec ${config.personal.home.lockscreen}/bin/lockscreen.sh"; + + # media keys + "XF86MonBrightnessUp" = "exec ${brightnessctl} set 5%+"; + "XF86MonBrightnessDown" = "exec ${brightnessctl} set 5%-"; + "XF86AudioRaiseVolume" = + "exec ${volumectl} set-sink-volume @DEFAULT_SINK@ +5%"; + "XF86AudioLowerVolume" = + "exec ${volumectl} set-sink-volume @DEFAULT_SINK@ -5%"; + "XF86AudioMute" = "exec ${volumectl} set-sink-mute @DEFAULT_SINK@ toggle"; + "Shift+XF86AudioRaiseVolume" = + "exec ${volumectl} set-source-volume @DEFAULT_SOURCE@ +5%"; + "Shift+XF86AudioLowerVolume" = + "exec ${volumectl} set-source-volume @DEFAULT_SOURCE@ -5%"; + "XF86AudioMicMute" = + "exec ${volumectl} set-source-mute @DEFAULT_SOURCE@ toggle"; + "XF86KbdBrightnessUp" = '' + exec ${brightnessctlKbd} set \ + $(( $(${brightnessctlKbd} max) - $(${brightnessctlKbd} get) )) + ''; + "Print" = "exec ${screenshot}"; + }; + }; +} diff --git a/modules/home-manager/personal/gui/x/i3/startup.nix b/modules/home-manager/personal/gui/x/i3/startup.nix new file mode 100644 index 0000000..9baf388 --- /dev/null +++ b/modules/home-manager/personal/gui/x/i3/startup.nix @@ -0,0 +1,25 @@ +{ config, lib, pkgs, ... }: + +{ + xsession.windowManager.i3.config.startup = let + autostart = { command, always ? false, notification ? false }: { + inherit command always notification; + }; + autostartIf = cond: args: lib.optional cond (autostart args); + in [ + (autostart { command = "rfkill block bluetooth"; }) + (autostart { command = "keepassxc"; }) + ] + ++ autostartIf config.programs.thunderbird.enable { command = "thunderbird"; } + ++ autostartIf config.personal.profiles.social { command = "signal-desktop"; } + # ++ autostartIf config.services.redshift.enable { + # command = "systemctl --user start redshift"; + # } + ++ autostartIf (config.personal.home.wallpaper != null) { + command = "${pkgs.feh}/bin/feh --bg-scale ${config.personal.home.wallpaper}"; + } + # ++ autostartIf config.services.xidlehook.enable { + # command = "systemctl --user start xidlehook.service"; + # } + ; +} |
