diff --git a/Makefile b/Makefile index 03886c53..f2ff05f0 100644 --- a/Makefile +++ b/Makefile @@ -36,9 +36,6 @@ history: repl: nix repl -f flake:nixpkgs -eye: - systemctl --user start gammastep.service - gc: # remove all generations older than 7 days sudo nix profile wipe-history --profile /nix/var/nix/profiles/system --older-than 7d @@ -156,6 +153,3 @@ fmt: # format the nix files in this repo nix fmt -.PHONY: clean -clean: - rm -rf result diff --git a/README.md b/README.md index 06be4f2e..ce17748e 100644 --- a/README.md +++ b/README.md @@ -161,6 +161,7 @@ Other dotfiles that inspired me: - [fufexan/dotfiles](https://github.com/fufexan/dotfiles): gtk theme, xdg, git, media, anyrun, etc. - Modularized NixOS Configuration - [hlissner/dotfiles](https://github.com/hlissner/dotfiles) + - [viperML/dotfiles](https://github.com/viperML/dotfiles) - Hyprland(wayland) - [notwidow/hyprland](https://github.com/notwidow/hyprland): This is where I start my hyprland journey. - [HeinzDev/Hyprland-dotfiles](https://github.com/HeinzDev/Hyprland-dotfiles): Refer to the waybar configuration here. diff --git a/flake.lock b/flake.lock index f724fae8..c8f4a385 100644 --- a/flake.lock +++ b/flake.lock @@ -952,11 +952,11 @@ "wallpapers": { "flake": false, "locked": { - "lastModified": 1694759298, - "narHash": "sha256-eSDBX6aA7zxodUq3MmV99JS0vr+aUwbEuLPoKnU5kHg=", + "lastModified": 1703310017, + "narHash": "sha256-SL3PIDa4BNz2rDZ/9KwnJCAfhelIoqpLtQ/FSYgfusI=", "owner": "ryan4yin", "repo": "wallpapers", - "rev": "f59f49a44042d2c669e18da07e652e00c431e9e9", + "rev": "20dea02ee2c0bb74dadcc3d4ec5efe78d00520dc", "type": "github" }, "original": { diff --git a/home/linux/desktop/default.nix b/home/linux/desktop/default.nix index be6b8a02..c956e98d 100644 --- a/home/linux/desktop/default.nix +++ b/home/linux/desktop/default.nix @@ -1,11 +1,12 @@ {pkgs, ...}: { imports = [ + ./wallpaper + ./creative.nix ./gtk.nix ./immutable-file.nix ./media.nix ./ssh.nix - ./wallpaper.nix ./xdg.nix ./eye-protection.nix ]; diff --git a/home/linux/desktop/wallpaper.nix b/home/linux/desktop/wallpaper.nix deleted file mode 100644 index 523e6578..00000000 --- a/home/linux/desktop/wallpaper.nix +++ /dev/null @@ -1,8 +0,0 @@ -{wallpapers, ...}: { - # https://github.com/ryan4yin/wallpapers - xdg.configFile."wallpapers".source = wallpapers; - home.file.".local/bin/wallpaper_random" = { - source = "${wallpapers}/wallpaper_random.py"; - executable = true; - }; -} diff --git a/home/linux/desktop/wallpaper/default.nix b/home/linux/desktop/wallpaper/default.nix new file mode 100644 index 00000000..8380eaa3 --- /dev/null +++ b/home/linux/desktop/wallpaper/default.nix @@ -0,0 +1,31 @@ +{ + pkgs, + config, + lib, + wallpapers, + ... +}: { + systemd.user.services.wallpaper = { + Unit = { + Description = "Wallpaper Switcher daemon"; + After = ["graphical-session-pre.target" "xdg-desktop-autostart.target"]; + Wants = ["graphical-session-pre.target"]; + }; + Install.WantedBy = ["graphical-session.target"]; + Service = { + ExecStart = lib.getExe (pkgs.writeShellApplication { + name = "wallpaper"; + runtimeInputs = with pkgs; [procps feh swaybg python3]; + text = '' + export WALLPAPERS_DIR="${wallpapers}" + export WALLPAPERS_STATE_FILEPATH="${config.xdg.stateHome}/wallpaper-switcher/switcher_state" + export WALLPAPER_WAIT_MIN=10 + export WALLPAPER_WAIT_MAX=20 + exec ${./wallpaper-switcher.py} + ''; + }); + RestartSec = 3; + Restart = "on-failure"; + }; + }; +} diff --git a/home/linux/desktop/wallpaper/wallpaper-switcher.py b/home/linux/desktop/wallpaper/wallpaper-switcher.py new file mode 100755 index 00000000..fbb46f5a --- /dev/null +++ b/home/linux/desktop/wallpaper/wallpaper-switcher.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python3 + +""" +This script will randomly select a wallpaper from the wallpapers directory. +It will skip the last wallpaper used, so that you don't get the same wallpaper. + +It will also set the wallpaper using `feh` for X11, or `swaybg` for Wayland. + +Maintainer: ryan4yin [xiaoyin_c@qq.com] +""" + +import os +import time +import random +import json +from pathlib import Path +from typing import Union +import subprocess +import logging + +logging.basicConfig(level=logging.INFO) + +logger = logging.getLogger(__name__) + + +class WallpaperSwitcher: + def __init__( + self, + wait_min, + wait_max, + wallpapers_dir: Path, + state_filepath: Path, + image_extensions: Union[tuple, list], + ) -> None: + self.wallpapers_dir = wallpapers_dir + self.image_extensions = image_extensions + self.state_filepath = state_filepath + self.wait_min = wait_min + self.wait_max = wait_max + + # initialize the state file + self.state_filepath.parent.mkdir(parents=True, exist_ok=True) + self.current_state = self.state_filepath.open("a+", encoding="utf-8") + self.current_wallpaper_list = list() + + def run(self): + """ + Iterate on all wallpapers in the wallpapers directory, cycling through them in a random order. + """ + self.initialize_state() + while True: + for i, w in enumerate(self.current_wallpaper_list): + if i < self.current_wallpaper_index: + continue + + logger.info( + f"Setting wallpaper {i+1}/{len(self.current_wallpaper_list)}: {w}" + ) + self.set_wallpaper(w) + + # update the state + self.current_wallpaper_index = i + self.save_state() + + wait_time = random.randint(self.wait_min, self.wait_max) + logger.info(f"Waiting {wait_time} seconds...") + time.sleep(wait_time) + + # reset the state + self.reset_state() + + def save_state(self): + wallpaper_list = [w.as_posix() for w in self.current_wallpaper_list] + state = { + "current_wallpaper_list": wallpaper_list, + "current_wallpaper_index": self.current_wallpaper_index, + } + self.current_state.truncate(0) + self.current_state.write(json.dumps(state, indent=4)) + self.current_state.flush() + + def initialize_state(self): + self.current_state.seek(0) + data = self.current_state.read() + if not data: + logger.info("No state found, resetting...") + self.reset_state() + else: + logger.info("State found, reloading...") + state = json.loads(data) + wallpapers = [Path(w) for w in state["current_wallpaper_list"]] + self.current_wallpaper_list = wallpapers + self.current_wallpaper_index = state["current_wallpaper_index"] + + def reset_state(self): + logger.info(f"Rescanning & shuffle wallpapers in {self.wallpapers_dir} ...") + wallpapers = list( + filter( + lambda x: x.suffix in self.image_extensions, + self.wallpapers_dir.iterdir(), + ) + ) + random.shuffle(wallpapers) + self.current_wallpaper_list = wallpapers + self.current_wallpaper_index = 0 + + def set_wallpaper(self, path: Path): + # check if we are running under x11 or wayland + if ( + "WAYLAND_DISPLAY" in os.environ + or os.environ.get("XDG_SESSION_TYPE") == "wayland" + ): + self.set_wallpaper_wayland(path) + else: + self.set_wallpaper_x11(path) + + def set_wallpaper_x11(self, path: Path): + subprocess.run(["feh", "--bg-fill", path]) + + def set_wallpaper_wayland(self, path: Path): + # find all swaybg processes + swaybg_pids = subprocess.run( + ["pgrep", "-f", "swaybg"], stdout=subprocess.PIPE + ).stdout.decode("utf-8") + + # run swaybg in the background, and make it running even after the parent process exits + subprocess.Popen( + ["swaybg", "--output", "*", "--mode", "fill", "--image", path], + start_new_session=True, + ) + time.sleep(1) + + # kill all old swaybg processes + for pid in swaybg_pids.splitlines(): + try: + os.kill(int(pid), 9) + except ProcessLookupError: + pass + + +def main(): + wallpapers_dir = os.getenv("WALLPAPERS_DIR") + state_filepath = os.getenv("WALLPAPERS_STATE_FILEPATH") + if not wallpapers_dir: + raise Exception("WALLPAPERS_DIR not set") + if not state_filepath: + raise Exception("WALLPAPERS_STATE_FILEPATH not set") + + image_postfix = ( + ".jpg", + ".jpeg", + ".png", + # ".gif", + # ".webp" + ) + wait_min = int(os.getenv("WALLPAPER_WAIT_MIN", 60)) + wait_max = int(os.getenv("WALLPAPER_WAIT_MAX", 300)) + wallpaper_switcher = WallpaperSwitcher( + wait_min, + wait_max, + Path(wallpapers_dir).expanduser(), + Path(state_filepath).expanduser(), + image_postfix, + ) + wallpaper_switcher.run() + + +if __name__ == "__main__": + main() diff --git a/home/linux/desktop/xdg.nix b/home/linux/desktop/xdg.nix index d7b7b255..4f305e8a 100644 --- a/home/linux/desktop/xdg.nix +++ b/home/linux/desktop/xdg.nix @@ -12,9 +12,14 @@ xdg-user-dirs ]; + xdg.configFile."mimeapps.list".force = true; xdg = { enable = true; - cacheHome = config.home.homeDirectory + "/.local/cache"; + + cacheHome = "${config.home.homeDirectory}/.cache"; + configHome = "${config.home.homeDirectory}/.config"; + dataHome = "${config.home.homeDirectory}/.local/share"; + stateHome = "${config.home.homeDirectory}/.local/state"; # manage $XDG_CONFIG_HOME/mimeapps.list # xdg search all desktop entries from $XDG_DATA_DIRS, check it by command: @@ -51,7 +56,7 @@ "x-scheme-handler/unknown" = browser; "x-scheme-handler/discord" = ["discord.desktop"]; - "x-scheme-handler/tg" = ["telegramdesktop.desktop"]; + "x-scheme-handler/tg" = ["org.telegram.desktop.desktop "]; "audio/*" = ["mpv.desktop"]; "video/*" = ["mpv.dekstop"]; diff --git a/home/linux/hyprland/default.nix b/home/linux/hyprland/default.nix index 2d0e5efe..b5561711 100644 --- a/home/linux/hyprland/default.nix +++ b/home/linux/hyprland/default.nix @@ -11,17 +11,25 @@ ]; # NOTE: - # (Required) NixOS Module: enables critical components needed to run Hyprland properly - # (Optional) Home-manager module: lets you declaratively configure Hyprland + # We have to enable hyprland/i3's systemd user service in home-manager, + # so that gammastep/wallpaper-switcher's user service can be start correctly! + # they are all depending on hyprland/i3's user graphical-session wayland.windowManager.hyprland = { enable = true; package = hyprland.packages.${pkgs.system}.hyprland; settings = lib.mkForce {}; extraConfig = builtins.readFile ./hypr-conf/hyprland.conf; - # programs.grammastep need this to be enabled. + # gammastep/wallpaper-switcher need this to be enabled. systemd.enable = true; }; + # NOTE: this executable is used by greetd to start a wayland session when system boot up + # with such a vendor-no-locking script, we can switch to another wayland compositor without modifying greetd's config in NixOS module + home.file.".wayland-session" = { + source = "${pkgs.hyprland}/bin/Hyprland"; + executable = true; + }; + # hyprland configs, based on https://github.com/notwidow/hyprland xdg.configFile = { "hypr/mako" = { diff --git a/home/linux/hyprland/hypr-conf/scripts/startup b/home/linux/hyprland/hypr-conf/scripts/startup index 6888d599..bf23a223 100755 --- a/home/linux/hyprland/hypr-conf/scripts/startup +++ b/home/linux/hyprland/hypr-conf/scripts/startup @@ -10,8 +10,9 @@ for _prs in "${_ps[@]}"; do fi done -# Set wallpaper -swaybg --output '*' --mode fill --image ~/.config/wallpapers/default_wallpaper & +# Set wallpaper via a wallpaper.service +# it will by start by home-manager automatically, do not need to restart it here. +# systemctl --user restart wallpaper.service # Lauch notification daemon (mako) ~/.config/hypr/scripts/notifications & diff --git a/home/linux/hyprland/packages.nix b/home/linux/hyprland/packages.nix new file mode 100644 index 00000000..b8d7970e --- /dev/null +++ b/home/linux/hyprland/packages.nix @@ -0,0 +1,27 @@ +{pkgs, ...}: { + home.packages = with pkgs; [ + waybar # the status bar + swaybg # the wallpaper + swayidle # the idle timeout + swaylock # locking the screen + wlogout # logout menu + wl-clipboard # copying and pasting + hyprpicker # color picker + + wf-recorder # creen recording + grim # taking screenshots + slurp # selecting a region to screenshot + # TODO replace by `flameshot gui --raw | wl-copy` + + mako # the notification daemon, the same as dunst + + yad # a fork of zenity, for creating dialogs + + # audio + alsa-utils # provides amixer/alsamixer/... + mpd # for playing system sounds + mpc-cli # command-line mpd client + ncmpcpp # a mpd client with a UI + networkmanagerapplet # provide GUI app: nm-connection-editor + ]; +} diff --git a/home/linux/i3/bin/bright b/home/linux/i3/bin/bright old mode 100644 new mode 100755 diff --git a/home/linux/i3/bin/logout b/home/linux/i3/bin/logout new file mode 100755 index 00000000..c17596cb --- /dev/null +++ b/home/linux/i3/bin/logout @@ -0,0 +1 @@ +i3-msg exit diff --git a/home/linux/i3/config b/home/linux/i3/config index 6598c19c..8f2f64e6 100644 --- a/home/linux/i3/config +++ b/home/linux/i3/config @@ -261,8 +261,7 @@ exec --no-startup-id dex --autostart --environment i3 exec --no-startup-id ~/.screenlayout/monitor.sh # set wallpaper -# exec --no-startup-id sleep 2 && nitrogen --restore -exec --no-startup-id sleep 1 && feh --bg-fill ~/.config/wallpapers/default_wallpaper +exec --no-startup-id sleep 1 && systemctl --user restart wallpaper.service # set powersavings for display: exec --no-startup-id xset s 480 dpms 600 600 600 diff --git a/home/linux/i3/default.nix b/home/linux/i3/default.nix index b8383a15..c8c70cbc 100644 --- a/home/linux/i3/default.nix +++ b/home/linux/i3/default.nix @@ -1,35 +1,55 @@ -{pkgs, ...}: { +_: { # i3 window manager's config, based on https://github.com/endeavouros-team/endeavouros-i3wm-setup imports = [ + ./packages.nix ./x11-apps.nix ]; - home.file = { - ".config/i3/config".source = ./config; - ".config/i3/i3blocks.conf".source = ./i3blocks.conf; - ".config/i3/scripts" = { + # NOTE: + # We have to enable hyprland/i3's systemd user service in home-manager, + # so that gammastep/wallpaper-switcher's user service can be start correctly! + # they are all depending on hyprland/i3's user graphical-session + xsession = { + enable = true; + windowManager.i3 = { + enable = true; + extraConfig = builtins.readFile ./config; + }; + # Path, relative to HOME, where Home Manager should write the X session script. + # and NixOS will use it to start xorg session when system boot up + scriptPath = ".xsession"; + }; + + xdg.configFile = { + "i3/i3blocks.conf".source = ./i3blocks.conf; + "i3/scripts" = { source = ./scripts; # copy the scripts directory recursively recursive = true; executable = true; # make all scripts executable }; - ".config/i3/layouts" = { + "i3/layouts" = { source = ./layouts; recursive = true; }; - # rofi is a application launcher and dmenu replacement - ".config/rofi" = { + "rofi" = { source = ./rofi-conf; # copy the scripts directory recursively recursive = true; }; + }; + home.file = { ".local/bin/bright" = { source = ./bin/bright; executable = true; }; + ".local/bin/logout" = { + source = ./bin/logout; + executable = true; + }; # xrandr - set primary screen ".screenlayout/monitor.sh".source = ./dual-monitor-4k-1080p.sh; diff --git a/home/linux/i3/packages.nix b/home/linux/i3/packages.nix new file mode 100644 index 00000000..60b95b5e --- /dev/null +++ b/home/linux/i3/packages.nix @@ -0,0 +1,24 @@ +{pkgs, ...}: { + home.packages = with pkgs; [ + rofi # application launcher, the same as dmenu + dunst # notification daemon + i3blocks # status bar + i3lock # default i3 screen locker + xautolock # lock screen after some time + i3status # provide information to i3bar + i3-gaps # i3 with gaps + picom # transparency and shadows + feh # set wallpaper + xcolor # color picker + + acpi # battery information + arandr # screen layout manager + dex # autostart applications + xbindkeys # bind keys to commands + xorg.xbacklight # control screen brightness, the same as light + xorg.xdpyinfo # get screen information + scrot # minimal screen capture tool, used by i3 blur lock to take a screenshot + sysstat # get system information + alsa-utils # provides amixer/alsamixer/... + ]; +} diff --git a/modules/nixos/i3.nix b/modules/nixos/i3.nix deleted file mode 100644 index 865f7c9b..00000000 --- a/modules/nixos/i3.nix +++ /dev/null @@ -1,65 +0,0 @@ -{pkgs, ...}: { - #################################################################### - # - # NixOS's Configuration for I3 Window Manager - # - #################################################################### - - imports = [ - ./base/i18n.nix - ./base/misc.nix - ./base/networking.nix - ./base/remote-building.nix - ./base/user-group.nix - ./base/visualisation.nix - - ./desktop - ../base.nix - ]; - - # i3 related options - services = { - gvfs.enable = true; # Mount, trash, and other functionalities - tumbler.enable = true; # Thumbnail support for images - - xserver = { - enable = true; - displayManager = { - lightdm.enable = true; - autoLogin = { - enable = true; - user = "ryan"; - }; - defaultSession = "none+i3"; - }; - # Configure keymap in X11 - xkb.layout = "us"; - - windowManager.i3 = { - enable = true; - extraPackages = with pkgs; [ - rofi # application launcher, the same as dmenu - dunst # notification daemon - i3blocks # status bar - i3lock # default i3 screen locker - xautolock # lock screen after some time - i3status # provide information to i3bar - i3-gaps # i3 with gaps - picom # transparency and shadows - feh # set wallpaper - xcolor # color picker - - acpi # battery information - arandr # screen layout manager - dex # autostart applications - xbindkeys # bind keys to commands - xorg.xbacklight # control screen brightness, the same as light - xorg.xdpyinfo # get screen information - scrot # minimal screen capture tool, used by i3 blur lock to take a screenshot - sysstat # get system information - alsa-utils # provides amixer/alsamixer/... - ]; - }; - }; - }; -} diff --git a/modules/nixos/hyprland.nix b/modules/nixos/wayland.nix similarity index 50% rename from modules/nixos/hyprland.nix rename to modules/nixos/wayland.nix index 7a446797..06507635 100644 --- a/modules/nixos/hyprland.nix +++ b/modules/nixos/wayland.nix @@ -28,48 +28,23 @@ }; services = { - xserver.enable = false; + xserver.enable = false; # disable xorg server # https://wiki.archlinux.org/title/Greetd greetd = { enable = true; settings = { default_session = { - user = "ryan"; # Hyprland is installed only for user ryan via home-manager! - command = "Hyprland"; # start Hyprland directly without a login manager - # command = "${pkgs.greetd.tuigreet}/bin/tuigreet --time --cmd Hyprland"; # start Hyprland with a TUI login manager + # Wayland Desktop Manager is installed only for user ryan via home-manager! + user = "ryan"; + # .wayland-session is a script generated by home-manager, which links to the current wayland compositor(sway/hyprland or others). + # with such a vendor-no-locking script, we can switch to another wayland compositor without modifying greetd's config here. + command = "$HOME/.wayland-session"; # start a wayland session directly without a login manager + # command = "${pkgs.greetd.tuigreet}/bin/tuigreet --time --cmd $HOME/.wayland-session"; # start wayland session with a TUI login manager }; }; }; }; - # List packages installed in system profile. To search, run: - # $ nix search wget - environment.systemPackages = with pkgs; [ - waybar # the status bar - swaybg # the wallpaper - swayidle # the idle timeout - swaylock # locking the screen - wlogout # logout menu - wl-clipboard # copying and pasting - hyprpicker # color picker - - wf-recorder # creen recording - grim # taking screenshots - slurp # selecting a region to screenshot - # TODO replace by `flameshot gui --raw | wl-copy` - - mako # the notification daemon, the same as dunst - - yad # a fork of zenity, for creating dialogs - - # audio - alsa-utils # provides amixer/alsamixer/... - mpd # for playing system sounds - mpc-cli # command-line mpd client - ncmpcpp # a mpd client with a UI - networkmanagerapplet # provide GUI app: nm-connection-editor - ]; - # fix https://github.com/ryan4yin/nix-config/issues/10 security.pam.services.swaylock = {}; } diff --git a/modules/nixos/xorg.nix b/modules/nixos/xorg.nix new file mode 100644 index 00000000..b0e3f7ad --- /dev/null +++ b/modules/nixos/xorg.nix @@ -0,0 +1,53 @@ +{pkgs, ...}: { + #################################################################### + # + # NixOS's Configuration for Xorg Server + # + #################################################################### + + imports = [ + ./base/i18n.nix + ./base/misc.nix + ./base/networking.nix + ./base/remote-building.nix + ./base/user-group.nix + ./base/visualisation.nix + + ./desktop + ../base.nix + ]; + + services = { + gvfs.enable = true; # Mount, trash, and other functionalities + tumbler.enable = true; # Thumbnail support for images + + xserver = { + enable = true; + displayManager = { + lightdm.enable = true; + autoLogin = { + enable = true; + user = "ryan"; + }; + # use a fake session to skip desktop manager + # and let Home Manager take care of the X session + defaultSession = "hm-session"; + }; + desktopManager = { + runXdgAutostartIfNone = true; + session = [ + { + name = "hm-session"; + manage = "window"; + start = '' + ${pkgs.runtimeShell} $HOME/.xsession & + waitPID=$! + ''; + } + ]; + }; + # Configure keymap in X11 + xkb.layout = "us"; + }; + }; +} diff --git a/systems/vars.nix b/systems/vars.nix index fcb111fa..67a79b0a 100644 --- a/systems/vars.nix +++ b/systems/vars.nix @@ -4,7 +4,7 @@ nixos-modules = [ ../hosts/idols/ai ../secrets/nixos.nix - ../modules/nixos/i3.nix + ../modules/nixos/xorg.nix ]; home-module = import ../home/linux/desktop-i3.nix; }; @@ -12,7 +12,7 @@ nixos-modules = [ ../hosts/idols/ai ../secrets/nixos.nix - ../modules/nixos/hyprland.nix + ../modules/nixos/wayland.nix ]; home-module = import ../home/linux/desktop-hyprland.nix; };