diff --git a/system-config/.gitignore b/system-config/.gitignore new file mode 100644 index 0000000..a7de16b --- /dev/null +++ b/system-config/.gitignore @@ -0,0 +1,3 @@ +__pycache__/ +*.py[cod] +.ruff_cache/ diff --git a/system-config/README.md b/system-config/README.md new file mode 100644 index 0000000..b1d455d --- /dev/null +++ b/system-config/README.md @@ -0,0 +1,79 @@ +# system-config + +A small, declarative Arch configuration managed by Decman. + +## Design + +- **Concerns live in `packages/`**: each concern has a native (`*-repo.txt`) + and AUR (`*-aur.txt`) list. +- **Hosts live in `hosts.py`**: a host composes concerns. `laptop()` contains + every explicit package. `desktop()` begins with shared concerns only. +- **System files live in `config/system/`**: shared files go in `common/`; + hardware, sleep/logind, and boot policy go in the relevant host directory. +- **State lives in `manifests/`**: units follow the same concern and host + scopes as packages. +- **Python glue lives in `modules/`**: package and system-state adapters only. + +No yadm migration, user-home dotfiles, or application state is managed here. +Those can be added later as small, explicit modules. + +## System policy + +- `config/system/common/pacman/pacman.conf` enables only the official core, + extra, and multilib repositories. No debug repository is enabled. +- `config/system/common/makepkg/makepkg.conf` sets `OPTIONS` with `!debug`, so + newly built packages do not produce debug packages. +- `config/system/common/locale/` owns locale generation and system locale. +- `config/system/common/keyboard/` owns console and X11 keyboard policy. +- `config/system/laptop/sleep.conf.d/` owns sleep policy; the logind drop-in + remains in `config/system/laptop/systemd/`. +- `config/system/laptop/power/tlp.conf` owns laptop power policy. TLP reads + `/etc/tlp.conf` after drop-ins, so a whole-file policy is required here. +- `config/system/laptop/boot/mkinitcpio.conf` owns encrypted-root and resume + initramfs hooks; its module rebuilds the initramfs after a change. +- `config/system/laptop/refind/` owns the laptop ESP rEFInd configuration and + encrypted-root kernel options. It is intentionally laptop-only. +- `config/system/laptop/iwlwifi.conf` retains the Wi-Fi 6 stability workaround; + NetworkManager owns Wi-Fi power saving for battery use. +- `config/user/{common,laptop,desktop}/` owns selected active user dotfiles. + Host-specific Hyprland and Noctalia variants are selected by host composition. +- EnvyControl's generated NVIDIA blacklist and udev files are intentionally + unmanaged so GPU modes can be switched outside of Decman. + +Changing the debug policy does not uninstall debug packages already installed; +remove those explicitly in a separate reviewed package change. + +## Package concerns + +| Scope | Concerns | +| --- | --- | +| Shared | core, wayland, fonts, development, productivity, media, research, network, services | +| Laptop | hardware, legacy; hardware units | +| Desktop | shared concerns now; add `packages/desktop/-{repo,aur}.txt` when hardware/software differs | + +Every package must have a named purpose. Add a new concern when an existing one +does not explain a package. System and user units use the same naming: +`manifests/common/-{system,user}.txt` or +`manifests/laptop/-system.txt`. + +## Commands + +```bash +cd ~/system-config +./scripts/verify.py +sudo decman --source source.py --dry-run --no-hooks +sudo decman --source source.py --no-hooks +``` + +`verify.py` proves that every currently explicit package appears exactly once +in the active laptop profile. The ignored dependency lists prevent Decman's +orphan cleanup from removing dependencies during adoption. + +After intentional package changes, regenerate and review concern lists: + +```bash +./scripts/split_packages.py +./scripts/refresh_orphan_guards.py +./scripts/verify.py +git diff +``` diff --git a/system-config/config/system/common/keyboard/vconsole.conf b/system-config/config/system/common/keyboard/vconsole.conf new file mode 100644 index 0000000..28aab70 --- /dev/null +++ b/system-config/config/system/common/keyboard/vconsole.conf @@ -0,0 +1,6 @@ +# Written by systemd-localed(8) or systemd-firstboot(1), read by systemd-localed +# and systemd-vconsole-setup(8). Use localectl(1) to update this file. +KEYMAP=us +XKBLAYOUT=us +XKBMODEL=variant +XKBVARIANT=altgr-intl diff --git a/system-config/config/system/common/keyboard/x11.conf b/system-config/config/system/common/keyboard/x11.conf new file mode 100644 index 0000000..f31329b --- /dev/null +++ b/system-config/config/system/common/keyboard/x11.conf @@ -0,0 +1,9 @@ +# Written by systemd-localed(8), read by systemd-localed and Xorg. It's +# probably wise not to edit this file manually. Use localectl(1) to +# update this file. +Section "InputClass" + Identifier "system-keyboard" + MatchIsKeyboard "on" + Option "XkbLayout" "us" + Option "XkbVariant" "intl" +EndSection diff --git a/system-config/config/system/common/keyd.conf b/system-config/config/system/common/keyd.conf new file mode 100644 index 0000000..d105c24 --- /dev/null +++ b/system-config/config/system/common/keyd.conf @@ -0,0 +1,20 @@ +[ids] + +* + +[main] +capslock = layer(capslayer) +' = G-' + + +[capslayer] +j = down +h = left +k = up +l = right + +a = G-q +o = G-p +u = G-y +s = G-s +e = G-5 diff --git a/system-config/config/system/common/locale/locale.conf b/system-config/config/system/common/locale/locale.conf new file mode 100644 index 0000000..1bf94dd --- /dev/null +++ b/system-config/config/system/common/locale/locale.conf @@ -0,0 +1,3 @@ +# This is the fallback locale configuration provided by systemd. + +LANG="en_US.UTF-8" diff --git a/system-config/config/system/common/locale/locale.gen b/system-config/config/system/common/locale/locale.gen new file mode 100644 index 0000000..a66d814 --- /dev/null +++ b/system-config/config/system/common/locale/locale.gen @@ -0,0 +1 @@ +en_US.UTF-8 UTF-8 diff --git a/system-config/config/system/common/makepkg/makepkg.conf b/system-config/config/system/common/makepkg/makepkg.conf new file mode 100644 index 0000000..77b6a41 --- /dev/null +++ b/system-config/config/system/common/makepkg/makepkg.conf @@ -0,0 +1,168 @@ +#!/hint/bash +# shellcheck disable=2034 + +# +# /etc/makepkg.conf +# + +######################################################################### +# SOURCE ACQUISITION +######################################################################### +# +#-- The download utilities that makepkg should use to acquire sources +# Format: 'protocol::agent' +DLAGENTS=('file::/usr/bin/curl -qgC - -o %o %u' + 'ftp::/usr/bin/curl -qgfC - --ftp-pasv --retry 3 --retry-delay 3 -o %o %u' + 'http::/usr/bin/curl -qgb "" -fLC - --retry 3 --retry-delay 3 -o %o %u' + 'https::/usr/bin/curl -qgb "" -fLC - --retry 3 --retry-delay 3 -o %o %u' + 'rsync::/usr/bin/rsync --no-motd -z %u %o' + 'scp::/usr/bin/scp -C %u %o') + +# Other common tools: +# /usr/bin/snarf +# /usr/bin/lftpget -c +# /usr/bin/wget + +#-- The package required by makepkg to download VCS sources +# Format: 'protocol::package' +VCSCLIENTS=('bzr::breezy' + 'fossil::fossil' + 'git::git' + 'hg::mercurial' + 'svn::subversion') + +######################################################################### +# ARCHITECTURE, COMPILE FLAGS +######################################################################### +# +CARCH="x86_64" +CHOST="x86_64-pc-linux-gnu" + +#NPROC=2 + +#-- Compiler and Linker Flags +#CPPFLAGS="" +CFLAGS="-march=x86-64 -mtune=generic -O2 -pipe -fno-plt -fexceptions \ + -Wp,-D_FORTIFY_SOURCE=3 -Wformat -Werror=format-security \ + -fstack-clash-protection -fcf-protection \ + -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer" +CXXFLAGS="$CFLAGS -Wp,-D_GLIBCXX_ASSERTIONS" +LDFLAGS="-Wl,-O1 -Wl,--sort-common -Wl,--as-needed -Wl,-z,relro -Wl,-z,now \ + -Wl,-z,pack-relative-relocs" +LTOFLAGS="-flto=auto" +#-- Make Flags: change this for DistCC/SMP systems +#MAKEFLAGS="-j2" +#-- Debugging flags +DEBUG_CFLAGS="-g" +DEBUG_CXXFLAGS="$DEBUG_CFLAGS" + +######################################################################### +# BUILD ENVIRONMENT +######################################################################### +# +# Makepkg defaults: BUILDENV=(!distcc !color !ccache check !sign) +# A negated environment option will do the opposite of the comments below. +# +#-- distcc: Use the Distributed C/C++/ObjC compiler +#-- color: Colorize output messages +#-- ccache: Use ccache to cache compilation +#-- check: Run the check() function if present in the PKGBUILD +#-- sign: Generate PGP signature file +# +BUILDENV=(!distcc color !ccache check !sign) +# +#-- If using DistCC, your MAKEFLAGS will also need modification. In addition, +#-- specify a space-delimited list of hosts running in the DistCC cluster. +#DISTCC_HOSTS="" +# +#-- Specify a directory for package building. +#BUILDDIR=/tmp/makepkg + +######################################################################### +# GLOBAL PACKAGE OPTIONS +# These are default values for the options=() settings +######################################################################### +# +# Makepkg defaults: +# OPTIONS=(!strip docs libtool staticlibs emptydirs !zipman !purge !debug !lto !autodeps) +# A negated option will do the opposite of the comments below. +# +#-- strip: Strip symbols from binaries/libraries +#-- docs: Save doc directories specified by DOC_DIRS +#-- libtool: Leave libtool (.la) files in packages +#-- staticlibs: Leave static library (.a) files in packages +#-- emptydirs: Leave empty directories in packages +#-- zipman: Compress manual (man and info) pages in MAN_DIRS with gzip +#-- purge: Remove files specified by PURGE_TARGETS +#-- debug: Add debugging flags as specified in DEBUG_* variables +#-- lto: Add compile flags for building with link time optimization +#-- autodeps: Automatically add depends/provides +# +OPTIONS=(strip docs !libtool !staticlibs emptydirs zipman purge !debug lto) + +#-- File integrity checks to use. Valid: md5, sha1, sha224, sha256, sha384, sha512, b2 +INTEGRITY_CHECK=(sha256) +#-- Options to be used when stripping binaries. See `man strip' for details. +STRIP_BINARIES="--strip-all" +#-- Options to be used when stripping shared libraries. See `man strip' for details. +STRIP_SHARED="--strip-unneeded" +#-- Options to be used when stripping static libraries. See `man strip' for details. +STRIP_STATIC="--strip-debug" +#-- Manual (man and info) directories to compress (if zipman is specified) +MAN_DIRS=(usr{,/local}{,/share}/{man,info}) +#-- Doc directories to remove (if !docs is specified) +DOC_DIRS=(usr/{,local/}{,share/}{doc,gtk-doc}) +#-- Files to be removed from all packages (if purge is specified) +PURGE_TARGETS=(usr/{,share}/info/dir .packlist *.pod) +#-- Directory to store source code in for debug packages +DBGSRCDIR="/usr/src/debug" +#-- Prefix and directories for library autodeps +LIB_DIRS=('lib:usr/lib' 'lib32:usr/lib32') + +######################################################################### +# PACKAGE OUTPUT +######################################################################### +# +# Default: put built package and cached source in build directory +# +#-- Destination: specify a fixed directory where all packages will be placed +#PKGDEST=/home/packages +#-- Source cache: specify a fixed directory where source files will be cached +#SRCDEST=/home/sources +#-- Source packages: specify a fixed directory where all src packages will be placed +#SRCPKGDEST=/home/srcpackages +#-- Log files: specify a fixed directory where all log files will be placed +#LOGDEST=/home/makepkglogs +#-- Packager: name/email of the person or organization building packages +#PACKAGER="John Doe " +#-- Specify a key to use for package signing +#GPGKEY="" + +######################################################################### +# COMPRESSION DEFAULTS +######################################################################### +# +COMPRESSGZ=(gzip -c -f -n) +COMPRESSBZ2=(bzip2 -c -f) +COMPRESSXZ=(xz -c -z -) +COMPRESSZST=(zstd -c -T0 -) +COMPRESSLRZ=(lrzip -q) +COMPRESSLZO=(lzop -q) +COMPRESSZ=(compress -c -f) +COMPRESSLZ4=(lz4 -q) +COMPRESSLZ=(lzip -c -f) + +######################################################################### +# EXTENSION DEFAULTS +######################################################################### +# +PKGEXT='.pkg.tar.zst' +SRCEXT='.src.tar.gz' + +######################################################################### +# OTHER +######################################################################### +# +#-- Command used to run pacman as root, instead of trying sudo and su +#PACMAN_AUTH=() +# vim: set ft=sh ts=2 sw=2 et: diff --git a/system-config/config/system/common/pacman/pacman.conf b/system-config/config/system/common/pacman/pacman.conf new file mode 100644 index 0000000..04738b0 --- /dev/null +++ b/system-config/config/system/common/pacman/pacman.conf @@ -0,0 +1,99 @@ +# +# /etc/pacman.conf +# +# See the pacman.conf(5) manpage for option and repository directives + +# +# GENERAL OPTIONS +# +[options] +# The following paths are commented out with their default values listed. +# If you wish to use different paths, uncomment and update the paths. +#RootDir = / +#DBPath = /var/lib/pacman/ +#CacheDir = /var/cache/pacman/pkg/ +#LogFile = /var/log/pacman.log +#GPGDir = /etc/pacman.d/gnupg/ +#HookDir = /etc/pacman.d/hooks/ +HoldPkg = pacman glibc +#XferCommand = /usr/bin/curl -L -C - -f -o %o %u +#XferCommand = /usr/bin/wget --passive-ftp -c -O %o %u +#CleanMethod = KeepInstalled +Architecture = auto + +# Pacman won't upgrade packages listed in IgnorePkg and members of IgnoreGroup +#IgnorePkg = +#IgnoreGroup = + +#NoUpgrade = +#NoExtract = + +# Misc options +#UseSyslog +#Color +#NoProgressBar +CheckSpace +#VerbosePkgLists +#ParallelDownloads = 5 +DownloadUser = alpm +#DisableSandbox + +# By default, pacman accepts packages signed by keys that its local keyring +# trusts (see pacman-key and its man page), as well as unsigned packages. +SigLevel = Required DatabaseOptional +LocalFileSigLevel = Optional +#RemoteFileSigLevel = Required + +# NOTE: You must run `pacman-key --init` before first using pacman; the local +# keyring can then be populated with the keys of all official Arch Linux +# packagers with `pacman-key --populate archlinux`. + +# +# REPOSITORIES +# - can be defined here or included from another file +# - pacman will search repositories in the order defined here +# - local/custom mirrors can be added here or in separate files +# - repositories listed first will take precedence when packages +# have identical names, regardless of version number +# - URLs will have $repo replaced by the name of the current repo +# - URLs will have $arch replaced by the name of the architecture +# +# Repository entries are of the format: +# [repo-name] +# Server = ServerName +# Include = IncludePath +# +# The header [repo-name] is crucial - it must be present and +# uncommented to enable the repo. +# + +# The testing repositories are disabled by default. To enable, uncomment the +# repo name header and Include lines. You can add preferred servers immediately +# after the header, and they will be used before the default mirrors. + +#[core-testing] +#Include = /etc/pacman.d/mirrorlist + +[core] +Include = /etc/pacman.d/mirrorlist + +#[extra-testing] +#Include = /etc/pacman.d/mirrorlist + +[extra] +Include = /etc/pacman.d/mirrorlist + +# If you want to run 32 bit applications on your x86_64 system, +# enable the multilib repositories as required here. + +#[multilib-testing] +#Include = /etc/pacman.d/mirrorlist + +[multilib] +Include = /etc/pacman.d/mirrorlist + +# An example of a custom package repository. See the pacman manpage for +# tips on creating your own repositories. +#[custom] +#SigLevel = Optional TrustAll +#Server = file:///home/custompkgs diff --git a/system-config/config/system/laptop/boot/mkinitcpio.conf b/system-config/config/system/laptop/boot/mkinitcpio.conf new file mode 100644 index 0000000..46ee3ec --- /dev/null +++ b/system-config/config/system/laptop/boot/mkinitcpio.conf @@ -0,0 +1,81 @@ +# vim:set ft=sh +# MODULES +# The following modules are loaded before any boot hooks are +# run. Advanced users may wish to specify all system modules +# in this array. For instance: +# MODULES=(usbhid xhci_hcd) +MODULES=() + +# BINARIES +# This setting includes any additional binaries a given user may +# wish into the CPIO image. This is run last, so it may be used to +# override the actual binaries included by a given hook +# BINARIES are dependency parsed, so you may safely ignore libraries +BINARIES=() + +# FILES +# This setting is similar to BINARIES above, however, files are added +# as-is and are not parsed in any way. This is useful for config files. +FILES=() + +# HOOKS +# This is the most important setting in this file. The HOOKS control the +# modules and scripts added to the image, and what happens at boot time. +# Order is important, and it is recommended that you do not change the +# order in which HOOKS are added. Run 'mkinitcpio -H ' for +# help on a given hook. +# 'base' is _required_ unless you know precisely what you are doing. +# 'udev' is _required_ in order to automatically load modules +# 'filesystems' is _required_ unless you specify your fs modules in MODULES +# Examples: +## This setup specifies all modules in the MODULES setting above. +## No RAID, lvm2, or encrypted root is needed. +# HOOKS=(base) +# +## This setup will autodetect all modules for your system and should +## work as a sane default +# HOOKS=(base udev autodetect modconf block filesystems fsck) +# +## This setup will generate a 'full' image which supports most systems. +## No autodetection is done. +# HOOKS=(base udev modconf block filesystems fsck) +# +## This setup assembles a mdadm array with an encrypted root file system. +## Note: See 'mkinitcpio -H mdadm_udev' for more information on RAID devices. +# HOOKS=(base udev modconf keyboard keymap consolefont block mdadm_udev encrypt filesystems fsck) +# +## This setup loads an lvm2 volume group. +# HOOKS=(base udev modconf block lvm2 filesystems fsck) +# +## This will create a systemd based initramfs which loads an encrypted root filesystem. +# HOOKS=(base systemd autodetect modconf kms keyboard sd-vconsole sd-encrypt block filesystems fsck) +# +## NOTE: If you have /usr on a separate partition, you MUST include the +# usr and fsck hooks. +HOOKS=(base udev autodetect microcode modconf kms keyboard keymap consolefont block encrypt filesystems resume fsck) + +# COMPRESSION +# Use this to compress the initramfs image. By default, zstd compression +# is used for Linux ≥ 5.9 and gzip compression is used for Linux < 5.9. +# Use 'cat' to create an uncompressed image. +#COMPRESSION="zstd" +#COMPRESSION="gzip" +#COMPRESSION="bzip2" +#COMPRESSION="lzma" +#COMPRESSION="xz" +#COMPRESSION="lzop" +#COMPRESSION="lz4" + +# COMPRESSION_OPTIONS +# Additional options for the compressor +#COMPRESSION_OPTIONS=() + +# MODULES_DECOMPRESS +# Decompress loadable kernel modules and their firmware during initramfs +# creation. Switch (yes/no). +# Enable to allow further decreasing image size when using high compression +# (e.g. xz -9e or zstd --long --ultra -22) at the expense of increased RAM usage +# at early boot. +# Note that any compressed files will be placed in the uncompressed early CPIO +# to avoid double compression. +#MODULES_DECOMPRESS="no" diff --git a/system-config/config/system/laptop/iwlwifi.conf b/system-config/config/system/laptop/iwlwifi.conf new file mode 100644 index 0000000..1b4722b --- /dev/null +++ b/system-config/config/system/laptop/iwlwifi.conf @@ -0,0 +1,3 @@ +# Keep Wi-Fi 6 disabled for this laptop's known stability workaround. +# NetworkManager owns association-level Wi-Fi power saving. +options iwlwifi disable_11ax=1 diff --git a/system-config/config/system/laptop/power/tlp.conf b/system-config/config/system/laptop/power/tlp.conf new file mode 100644 index 0000000..6dff92e --- /dev/null +++ b/system-config/config/system/laptop/power/tlp.conf @@ -0,0 +1,11 @@ +DEVICES_TO_DISABLE_ON_STARTUP="bluetooth" +DEVICES_TO_DISABLE_ON_BAT_NOT_IN_USE="bluetooth" + +CPU_ENERGY_PERF_POLICY_ON_AC=balance +CPU_ENERGY_PERF_POLICY_ON_BAT=power + +CPU_BOOST_ON_AC=1 +CPU_BOOST_ON_BAT=0 + +CPU_MAX_PERF_ON_AC=100 +CPU_MAX_PERF_ON_BAT=70 diff --git a/system-config/config/system/laptop/refind/refind.conf b/system-config/config/system/laptop/refind/refind.conf new file mode 100644 index 0000000..2023cc5 --- /dev/null +++ b/system-config/config/system/laptop/refind/refind.conf @@ -0,0 +1,732 @@ +# +# refind.conf +# Configuration file for the rEFInd boot menu +# + +# Timeout in seconds for the main menu screen. Setting the timeout to 0 +# disables automatic booting (i.e., no timeout). Setting it to -1 causes +# an immediate boot to the default OS *UNLESS* a keypress is in the buffer +# when rEFInd launches, in which case that keypress is interpreted as a +# shortcut key. If no matching shortcut is found, rEFInd displays its +# menu with no timeout. +# +timeout 20 + +# Set the logging level. When set to 0, rEFInd does not log its actions. +# When set to 1 or above, rEFInd creates a file called refind.log in +# its home directory on the ESP and records information about what it's +# doing. Higher values record more information, up to a maximum of 4. +# This token should be left at the default of 0 except when debugging +# problems. +# Default value is 0 +# +#log_level 1 + +# Normally, when the timeout period has passed, rEFInd boots the +# default_selection. If the following option is uncommented, though, +# rEFInd will instead attempt to shut down the computer. +# CAUTION: MANY COMPUTERS WILL INSTEAD HANG OR REBOOT! Macs and more +# recent UEFI-based PCs are most likely to work with this feature. +# Default value is true +# +#shutdown_after_timeout + +# Whether to store rEFInd's rEFInd-specific variables in NVRAM (1, true, +# or on) or in files in the "vars" subdirectory of rEFInd's directory on +# disk (0, false, or off). Using NVRAM works well with most computers; +# however, it increases wear on the motherboard's NVRAM, and if the EFI +# is buggy or the NVRAM is old and worn out, it may not work at all. +# Storing variables on disk is a viable alternative in such cases, or +# if you want to minimize wear and tear on the NVRAM; however, it won't +# work if rEFInd is stored on a filesystem that's read-only to the EFI +# (such as an HFS+ volume), and it increases the risk of filesystem +# damage. Note that this option affects ONLY rEFInd's own variables, +# such as the PreviousBoot, HiddenTags, HiddenTools, and HiddenLegacy +# variables. It does NOT affect Secure Boot or other non-rEFInd +# variables. +# Default is true +# +use_nvram false + +# Screen saver timeout; the screen blanks after the specified number of +# seconds with no keyboard input. The screen returns after most keypresses +# (unfortunately, not including modifier keys such as Shift, Control, Alt, +# or Option). Setting a value of "-1" causes rEFInd to start up with its +# screen saver active. The default is 0, which disables the screen saver. +# +#screensaver 300 + +# Hide user interface elements for personal preference or to increase +# security: +# banner - the rEFInd title banner (built-in or loaded via "banner") +# label - boot option text label in the menu +# singleuser - remove the submenu options to boot macOS in single-user +# or verbose modes; affects ONLY macOS +# safemode - remove the submenu option to boot macOS in "safe mode" +# hwtest - the submenu option to run Apple's hardware test +# arrows - scroll arrows on the OS selection tag line +# hints - brief command summary in the menu +# editor - the options editor (+, F2, or Insert on boot options menu) +# badges - device-type badges for boot options +# all - all of the above +# Default is none of these (all elements active) +# +#hideui singleuser +#hideui all + +# Set the name of a subdirectory in which icons are stored. Icons must +# have the same names they have in the standard directory. The directory +# name is specified relative to the main rEFInd binary's directory. If +# an icon can't be found in the specified directory, an attempt is made +# to load it from the default directory; thus, you can replace just some +# icons in your own directory and rely on the default for others. +# Icon files may be in any supported format -- ICNS (*.icns), BMP (*.bmp), +# PNG (*.png), or JPEG (*.jpg or *.jpeg); however, rEFInd's BMP and JPEG +# implementations do not support transparency, which is highly desirable +# in icons. +# Default is "icons". +# +#icons_dir myicons +#icons_dir icons/snowy + +# Use a custom title banner instead of the rEFInd icon and name. The file +# path is relative to the directory where refind.efi is located. The color +# in the top left corner of the image is used as the background color +# for the menu screens. Currently uncompressed BMP images with color +# depths of 24, 8, 4 or 1 bits are supported, as well as PNG and JPEG +# images. (ICNS images can also be used, but ICNS has limitations that +# make it a poor choice for this purpose.) PNG and JPEG support is +# limited by the underlying libraries; some files, like progressive JPEGs, +# will not work. +# +#banner hostname.bmp +#banner mybanner.jpg +#banner icons/snowy/banner-snowy.png + +# Specify how to handle banners that aren't exactly the same as the screen +# size: +# noscale - Crop if too big, show with border if too small +# fillscreen - Fill the screen +# Default is noscale +# +#banner_scale fillscreen + +# Icon sizes. All icons are square, so just one value is specified. The +# big icons are used for OS selectors in the first row and the small +# icons are used for tools on the second row. Drive-type badges are 1/4 +# the size of the big icons. Legal values are 32 and above. If the icon +# files do not hold icons of the proper size, the icons are scaled to +# the specified size. The default values are 48 and 128 for small and +# big icons, respectively. +# +#small_icon_size 96 +#big_icon_size 256 + +# Custom images for the selection background. There is a big one (144 x 144) +# for the OS icons, and a small one (64 x 64) for the function icons in the +# second row. If only a small image is given, that one is also used for +# the big icons by stretching it in the middle. If only a big one is given, +# the built-in default will be used for the small icons. If an image other +# than the optimal size is specified, it will be scaled in a way that may +# be ugly. +# +# Like the banner option above, these options take a filename of an +# uncompressed BMP, PNG, JPEG, or ICNS image file with a color depth of +# 24, 8, 4, or 1 bits. The PNG or ICNS format is required if you need +# transparency support (to let you "see through" to a full-screen banner). +# +#selection_big selection-big.bmp +#selection_small selection-small.bmp + +# Set the font to be used for all textual displays in graphics mode. +# For best results, the font must be a PNG file with alpha channel +# transparency. It must contain ASCII characters 32-126 (space through +# tilde), inclusive, plus a glyph to be displayed in place of characters +# outside of this range, for a total of 96 glyphs. Only monospaced fonts +# are supported. Fonts may be of any size, although large fonts can +# produce display irregularities. +# The default is rEFInd's built-in font, Luxi Mono Regular 12 point. +# +#font myfont.png + +# Use text mode only. When enabled, this option forces rEFInd into text mode. +# Passing this option a "0" value causes graphics mode to be used. Pasing +# it no value or any non-0 value causes text mode to be used. +# Default is to use graphics mode. +# +#textonly + +# Set the EFI text mode to be used for textual displays. This option +# takes a single digit that refers to a mode number. Mode 0 is normally +# 80x25, 1 is sometimes 80x50, and higher numbers are system-specific +# modes. Mode 1024 is a special code that tells rEFInd to not set the +# text mode; it uses whatever was in use when the program was launched. +# If you specify an invalid mode, rEFInd pauses during boot to inform +# you of valid modes. +# CAUTION: On VirtualBox, and perhaps on some real computers, specifying +# a text mode and uncommenting the "textonly" option while NOT specifying +# a resolution can result in an unusable display in the booted OS. +# Default is 1024 (no change) +# +#textmode 2 + +# Set the screen's video resolution. Pass this option one of the following: +# * two integer values, corresponding to the X and Y resolutions +# * one integer value, corresponding to a GOP (UEFI) video mode +# * the string "max", which sets the maximum available resolution +# Note that not all resolutions are supported. On UEFI systems, passing +# an incorrect value results in a message being shown on the screen to +# that effect, along with a list of supported modes. On EFI 1.x systems +# (e.g., Macintoshes), setting an incorrect mode silently fails. On both +# types of systems, setting an incorrect resolution results in the default +# resolution being used. A resolution of 1024x768 usually works, but higher +# values often don't. +# Default is "0 0" (use the system default resolution, usually 800x600). +# +#resolution 1024 768 +#resolution 1440 900 +#resolution 3 +#resolution max + +# Enable touch screen support. If active, this feature enables use of +# touch screen controls (as on tablets). Note, however, that not all +# tablets' EFIs provide the necessary underlying support, so this +# feature may not work for you. If it does work, you should be able +# to launch an OS or tool by touching it. In a submenu, touching +# anywhere launches the currently-selection item; there is, at present, +# no way to select a specific submenu item. This feature is mutually +# exclusive with the enable_mouse feature. If both are uncommented, +# the one read most recently takes precedence. +# +#enable_touch + +# Enable mouse support. If active, this feature enables use of the +# computer's mouse. Note, however, that not all computers' EFIs +# provide the necessary underlying support, so this feature may not +# work for you. If it does work, you should be able to launch an +# OS or tool by clicking it with the mouse pointer. This feature +# is mutually exclusive with the enable_touch feature. If both +# are uncommented, the one read most recently takes precedence. +# +#enable_mouse + +# Size of the mouse pointer, in pixels, per side. +# Default is 16 +# +#mouse_size 16 + +# Speed of mouse tracking. Higher numbers equate to faster +# mouse movement. This option requires that enable_mouse be +# uncommented. +# Legal values are between 1 and 32. Default is 4. +# +#mouse_speed 4 + +# Launch specified OSes in graphics mode. By default, rEFInd switches +# to text mode and displays basic pre-launch information when launching +# all OSes except macOS. Using graphics mode can produce a more seamless +# transition, but displays no information, which can make matters +# difficult if you must debug a problem. Also, on at least one known +# computer, using graphics mode prevents a crash when using the Linux +# kernel's EFI stub loader. You can specify an empty list to boot all +# OSes in text mode. +# Valid options: +# osx - macOS +# linux - A Linux kernel with EFI stub loader +# elilo - The ELILO boot loader +# grub - The GRUB (Legacy or 2) boot loader +# windows - Microsoft Windows +# Default value: osx +# +#use_graphics_for osx,linux + +# Which non-bootloader tools to show on the tools line, and in what +# order to display them: +# shell - the EFI shell (requires external program; see rEFInd +# documentation for details) +# memtest - the memtest86 program, in EFI/tools, EFI/memtest86, +# EFI/memtest, EFI/tools/memtest86, or EFI/tools/memtest +# gptsync - the (dangerous) gptsync.efi utility (requires external +# program; see rEFInd documentation for details) +# gdisk - the gdisk partitioning program +# apple_recovery - boots the Apple Recovery HD partition, if present +# windows_recovery - boots an OEM Windows recovery tool, if present +# (see also the windows_recovery_files option) +# mok_tool - makes available the Machine Owner Key (MOK) maintenance +# tool, MokManager.efi, used on Secure Boot systems +# csr_rotate - adjusts Apple System Integrity Protection (SIP) +# policy. Requires "csr_values" to be set. +# install - an option to install rEFInd from the current location +# to another ESP +# bootorder - adjust the EFI's (NOT rEFInd's) boot order +# about - an "about this program" option +# hidden_tags - manage hidden tags +# exit - a tag to exit from rEFInd +# shutdown - shuts down the computer (a bug causes this to reboot +# many UEFI systems) +# reboot - a tag to reboot the computer +# firmware - a tag to reboot the computer into the firmware's +# user interface (ignored on older computers) +# fwupdate - a tag to update the firmware; launches the fwupx64.efi +# (or similar) program +# netboot - launch the ipxe.efi tool for network (PXE) booting +# Default is shell,memtest,gdisk,apple_recovery,windows_recovery,mok_tool,about,hidden_tags,shutdown,reboot,firmware,fwupdate +# To completely disable scanning for all tools, provide a showtools line +# with no options. +# +#showtools shell, bootorder, gdisk, memtest, mok_tool, apple_recovery, windows_recovery, about, hidden_tags, reboot, exit, firmware, fwupdate + +# Tool binaries to be excluded from the tools line, even if the +# general class is specified in showtools. This enables trimming an +# overabundance of tools, as when you see multiple mok_tool entries +# after installing multiple Linux distributions. +# Just as with dont_scan_files, you can specify a filename alone, a +# full pathname, or a volume identifier (filesystem label, partition +# name, or partition GUID) and a full pathname. +# Default is an empty list (nothing is excluded) +# +#dont_scan_tools ESP2:/EFI/ubuntu/mmx64.efi,gptsync_x64.efi + +# Boot loaders that can launch a Windows restore or emergency system. +# These tend to be OEM-specific. +# Default is LRS_ESP:/EFI/Microsoft/Boot/LrsBootmgr.efi +# +#windows_recovery_files LRS_ESP:/EFI/Microsoft/Boot/LrsBootmgr.efi + +# Directories in which to search for EFI drivers. These drivers can +# provide filesystem support, give access to hard disks on plug-in +# controllers, etc. In most cases none are needed, but if you add +# EFI drivers and you want rEFInd to automatically load them, you +# should specify one or more paths here. rEFInd always scans the +# "drivers" and "drivers_{arch}" subdirectories of its own installation +# directory (where "{arch}" is your architecture code); this option +# specifies ADDITIONAL directories to scan. +# Default is to scan no additional directories for EFI drivers +# +#scan_driver_dirs EFI/tools/drivers,drivers + +# Which types of boot loaders to search, and in what order to display them: +# internal - internal EFI disk-based boot loaders +# external - external EFI disk-based boot loaders +# optical - EFI optical discs (CD, DVD, etc.) +# netboot - EFI network (PXE) boot options +# hdbios - BIOS disk-based boot loaders +# biosexternal - BIOS external boot loaders (USB, eSATA, etc.) +# cd - BIOS optical-disc boot loaders +# manual - use stanzas later in this configuration file +# firmware - boot EFI programs set in the firmware's NVRAM +# Note that the legacy BIOS options require firmware support, which is +# not present on all computers. +# The netboot option is experimental and relies on the ipxe.efi and +# ipxe_discover.efi program files. +# On UEFI PCs, default is internal,external,optical,manual +# On Macs, default is internal,hdbios,external,biosexternal,optical,cd,manual +# +#scanfor internal,external,optical,manual,firmware + +# By default, rEFInd relies on the UEFI firmware to detect BIOS-mode boot +# devices. This sometimes doesn't detect all the available devices, though. +# For these cases, uefi_deep_legacy_scan results in a forced scan and +# modification of NVRAM variables on each boot. Adding "0", "off", or +# "false" resets to the default value. This token has no effect on Macs or +# when no BIOS-mode options are set via scanfor. +# Default is unset (or "uefi_deep_legacy_scan false") +# +#uefi_deep_legacy_scan + +# Delay for the specified number of seconds before scanning disks. +# This can help some users who find that some of their disks +# (usually external or optical discs) aren't detected initially, +# but are detected after pressing Esc. +# The default is 0. +# +#scan_delay 5 + +# When scanning volumes for EFI boot loaders, rEFInd always looks for +# macOS's and Microsoft Windows' boot loaders in their normal locations, +# and scans the root directory and every subdirectory of the /EFI directory +# for additional boot loaders, but it doesn't recurse into these directories. +# The also_scan_dirs token adds more directories to the scan list. +# Directories are specified relative to the volume's root directory. This +# option applies to ALL the volumes that rEFInd scans UNLESS you include +# a volume name and colon before the directory name, as in "myvol:/somedir" +# to scan the somedir directory only on the filesystem named myvol. If a +# specified directory doesn't exist, it's ignored (no error condition +# results). The "+" symbol denotes appending to the list of scanned +# directories rather than overwriting that list. +# The default is to scan the "boot" and "@/boot" directories in addition +# to various hard-coded directories. +# +#also_scan_dirs boot,ESP2:EFI/linux/kernels +#also_scan_dirs boot,@/boot +#also_scan_dirs +,@/kernels + +# Partitions (or whole disks, for legacy-mode boots) to omit from scans. +# For EFI-mode scans, you normally specify a volume by its label, which you +# can obtain in an EFI shell by typing "vol", from Linux by typing +# "blkid /dev/{devicename}", or by examining the disk's label in various +# OSes' file browsers. It's also possible to identify a partition by its +# unique GUID (aka its "PARTUUID" in Linux parlance). (Note that this is +# NOT the partition TYPE CODE GUID.) This identifier can be obtained via +# "blkid" in Linux or "diskutil info {partition-id}" in macOS. +# For legacy-mode scans, you can specify any subset of the boot loader +# description shown when you highlight the option in rEFInd. +# The default is "LRS_ESP". +# +#dont_scan_volumes "Recovery HD" + +# Directories that should NOT be scanned for boot loaders. By default, +# rEFInd doesn't scan its own directory, the EFI/tools directory, the +# EFI/memtest directory, the EFI/memtest86 directory, or the +# com.apple.recovery.boot directory. Using the dont_scan_dirs option +# enables you to "blacklist" other directories; but be sure to use "+" +# as the first element if you want to continue blacklisting existing +# directories. You might use this token to keep EFI/boot/bootx64.efi out +# of the menu if that's a duplicate of another boot loader or to exclude +# a directory that holds drivers or non-bootloader utilities provided by +# a hardware manufacturer. If a directory is listed both here and in +# also_scan_dirs, dont_scan_dirs takes precedence. Note that this +# blacklist applies to ALL the filesystems that rEFInd scans, not just +# the ESP, unless you precede the directory name by a filesystem name or +# partition unique GUID, as in "myvol:EFI/somedir" to exclude EFI/somedir +# from the scan on the myvol volume but not on other volumes. +# +#dont_scan_dirs ESP:/EFI/boot,EFI/Dell,EFI/memtest86 + +# Files that should NOT be included as EFI boot loaders (on the +# first line of the display). If you're using a boot loader that +# relies on support programs or drivers that are installed alongside +# the main binary or if you want to "blacklist" certain loaders by +# name rather than location, use this option. Note that this will +# NOT prevent certain binaries from showing up in the second-row +# set of tools. Most notably, various Secure Boot and recovery +# tools are present in this list, but may appear as second-row +# items. +# The file may be specified as a bare name (e.g., "notme.efi"), as +# a complete pathname (e.g., "/EFI/somedir/notme.efi"), or as a +# complete pathname with volume (e.g., "SOMEDISK:/EFI/somedir/notme.efi" +# or 2C17D5ED-850D-4F76-BA31-47A561740082:/EFI/somedir/notme.efi"). +# OS tags hidden via the Delete or '-' key in the rEFInd menu are +# added to this list, but stored in NVRAM. +# The default is shim.efi,shim-fedora.efi,shimx64.efi,PreLoader.efi, +# TextMode.efi,ebounce.efi,GraphicsConsole.efi,MokManager.efi,HashTool.efi, +# HashTool-signed.efi,bootmgr.efi,fb{arch}.efi +# (where "{arch}" is the architecture code, like "x64"). +# If you want to keep these defaults but add to them, be sure to +# specify "+" as the first item in the new list; if you don't, then +# items from the default list are likely to appear. +# +#dont_scan_files shim.efi,MokManager.efi + +# EFI NVRAM Boot#### variables that should NOT be presented as loaders +# when "firmware" is an option to "scanfor". The comma-separated list +# presented here contains strings that are matched against the +# description field -- if a value here is a case-insensitive substring +# of the boot option description, then it will be excluded from the +# boot list. To specify a string that includes a space, enclose it +# in quotes. Specifying "shell" will counteract the automatic +# inclusion of built-in EFI shells. +# +#dont_scan_firmware HARDDISK,shell,"Removable Device" + +# Scan for Linux kernels that lack a ".efi" filename extension. This is +# useful for better integration with Linux distributions that provide +# kernels with EFI stub loaders but that don't give those kernels filenames +# that end in ".efi", particularly if the kernels are stored on a +# filesystem that the EFI can read. When set to "1", "true", or "on", this +# option causes all files in scanned directories with names that begin with +# "vmlinuz", "bzImage", or "kernel" to be included as loaders, even if they +# lack ".efi" extensions. Passing this option a "0", "false", or "off" value +# causes kernels without ".efi" extensions to NOT be scanned. +# Default is "true" -- to scan for kernels without ".efi" extensions. +# +#scan_all_linux_kernels false + +# Support loaders that have been compressed with gzip. +# On x86 and x86-64 platforms, Linux kernels are self-decompressing. +# On ARM64, Linux kernel files are typically compressed with gzip, +# including the EFI stub loader. This makes them unloadable in rEFInd +# unless rEFInd itself uncompresses them. This option enables rEFInd +# to do this. This feature is unnecessary on x86 and x86-64 systems. +# Default is "false" on x86 and x86-64; "true" on ARM64. +# +#support_gzipped_loaders true + +# Combine all Linux kernels in a given directory into a single entry. +# When so set, the kernel with the most recent time stamp will be launched +# by default, and its filename will appear in the entry's description. +# To launch other kernels, the user must press F2 or Insert; alternate +# kernels then appear as options on the sub-menu. +# Default is "true" -- kernels are "folded" into a single menu entry. +# +#fold_linux_kernels false + +# Comma-delimited list of strings to treat as if they were numbers for the +# purpose of kernel version number detection. These strings are matched on a +# first-found basis; that is, if you want to treat both "linux-lts" and +# "linux" as version strings, they MUST be specified as "linux-lts,linux", +# since if you specify it the other way, both vmlinuz-linux and +# vmlinuz-linux-lts will return with "linux" as the "version string," which +# is not what you'd want. Also, if the kernel or initrd file includes both a +# specified string and digits, the "version string" includes both. For +# instance, "vmlinuz-linux-4.8" would yield a version string of "linux-4.8". +# This option is intended for Arch and other distributions that don't include +# version numbers in their kernel filenames, but may provide other uniquely +# identifying strings for multiple kernels. If this feature causes problems +# (say, if your kernel filename includes "linux" but the initrd filename +# doesn't), be sure this is set to an empty string +# (extra_kernel_version_strings "") or comment out the option to disable it. +# Default is no extra version strings +# +#extra_kernel_version_strings linux-lts,linux + +# Write to systemd EFI variables (currently only LoaderDevicePartUUID) when +# launching Linux via an EFI stub loader, ELILO, or GRUB. This variable, +# when present, causes systemd to mount the ESP at /boot or /efi *IF* either +# directory is empty and nothing else is mounted there. +# Default is "false" +# +#write_systemd_vars true + +# Symlinked loaders will be processed when this setting is set to true. +# These are ignored by default as they may result in undesirable outcomes. +# This token may, however, be useful on Linux setups that provide symbolic +# links in scanned locations that point to kernels in unscanned locations, +# such as some openSUSE installations. +# +#follow_symlinks true + +# Set the maximum number of tags that can be displayed on the screen at +# any time. If more loaders are discovered than this value, rEFInd shows +# a subset in a scrolling list. If this value is set too high for the +# screen to handle, it's reduced to the value that the screen can manage. +# If this value is set to 0 (the default), it's adjusted to the number +# that the screen can handle. +# +#max_tags 0 + +# Set the default menu selection. The available arguments match the +# keyboard accelerators available within rEFInd. You may select the +# default loader using: +# - A digit between 1 and 9, in which case the Nth loader in the menu +# will be the default. +# - A "+" symbol at the start of the string, which refers to the most +# recently booted loader. +# - Any substring that corresponds to a portion of the loader's title +# (usually the OS's name, boot loader's path, or a volume or +# filesystem title). +# You may also specify multiple selectors by separating them with commas +# and enclosing the list in quotes. (The "+" option is only meaningful in +# this context.) +# If you follow the selector(s) with two times, in 24-hour format, the +# default will apply only between those times. The times are in the +# motherboard's time standard, whether that's UTC or local time, so if +# you use UTC, you'll need to adjust this from local time manually. +# Times may span midnight as in "23:30 00:30", which applies to 11:30 PM +# to 12:30 AM. You may specify multiple default_selection lines, in which +# case the last one to match takes precedence. Thus, you can set a main +# option without a time followed by one or more that include times to +# set different defaults for different times of day. +# The default behavior is to boot the previously-booted OS. +# +#default_selection 1 +#default_selection Microsoft +#default_selection "+,bzImage,vmlinuz" +#default_selection Maintenance 23:30 2:00 +#default_selection "Maintenance,macOS" 1:00 2:30 + +# Enable VMX bit and lock the CPU MSR if unlocked. +# On some Intel Apple computers, the firmware does not lock the MSR 0x3A. +# The symptom on Windows is Hyper-V not working even if the CPU +# meets the minimum requirements (HW assisted virtualization and SLAT) +# DO NOT SET THIS EXCEPT ON INTEL CPUs THAT SUPPORT VMX! See +# http://www.thomas-krenn.com/en/wiki/Activating_the_Intel_VT_Virtualization_Feature +# for more on this subject. +# The default is false: Don't try to enable and lock the MSR. +# +#enable_and_lock_vmx false + +# Tell a Mac's EFI that macOS is about to be launched, even when it's not. +# This option causes some Macs to initialize their hardware differently than +# when a third-party OS is launched normally. In some cases (particularly on +# Macs with multiple video cards), using this option can cause hardware to +# work that would not otherwise work. On the other hand, using this option +# when it is not necessary can cause hardware (such as keyboards and mice) to +# become inaccessible. Therefore, you should not enable this option if your +# non-Apple OSes work correctly; enable it only if you have problems with +# some hardware devices. When needed, a value of "10.9" usually works, but +# you can experiment with other values. This feature has no effect on +# non-Apple computers. +# The default is inactive (no macOS spoofing is done). +# +#spoof_osx_version 10.9 + +# Set the CSR values for Apple's System Integrity Protection (SIP) feature. +# Values are two-byte (four-character) hexadecimal numbers. These values +# define which specific security features are enabled. Below are the codes +# for what the values mean. Add them up (in hexadecimal!) to set new values. +# Apple's "csrutil enable" and "csrutil disable" commands set values of 10 +# and 877, respectively. (Prior to OS 11, 77 was used rather than 877; 877 +# is required for OS 11, and should work for OS X 10.x, too.) +# CSR_ALLOW_UNTRUSTED_KEXTS 0x0001 +# CSR_ALLOW_UNRESTRICTED_FS 0x0002 +# CSR_ALLOW_TASK_FOR_PID 0x0004 +# CSR_ALLOW_KERNEL_DEBUGGER 0x0008 +# CSR_ALLOW_APPLE_INTERNAL 0x0010 +# CSR_ALLOW_UNRESTRICTED_DTRACE 0x0020 +# CSR_ALLOW_UNRESTRICTED_NVRAM 0x0040 +# CSR_ALLOW_DEVICE_CONFIGURATION 0x0080 +# CSR_ALLOW_ANY_RECOVERY_OS 0x0100 +# CSR_ALLOW_UNAPPROVED_KEXTS 0x0200 +# CSR_ALLOW_EXECUTABLE_POLICY_OVERRIDE 0x0400 +# CSR_ALLOW_UNAUTHENTICATED_ROOT 0x0800 +#csr_values 10,877 + +# Include a secondary configuration file within this one. This secondary +# file is loaded as if its options appeared at the point of the "include" +# token itself, so if you want to override a setting in the main file, +# the secondary file must be referenced AFTER the setting you want to +# override. Note that the secondary file may NOT load a tertiary file. +# +#include manual.conf + +# Sample manual configuration stanzas. Each begins with the "menuentry" +# keyword followed by a name that's to appear in the menu (use quotes +# if you want the name to contain a space) and an open curly brace +# ("{"). Each entry ends with a close curly brace ("}"). Common +# keywords within each stanza include: +# +# volume - identifies the filesystem from which subsequent files +# are loaded. You can specify the volume by filesystem +# label, by partition label, or by partition GUID number +# (but NOT yet by filesystem UUID number). +# loader - identifies the boot loader file +# initrd - Specifies an initial RAM disk file +# icon - specifies a custom boot loader icon +# ostype - OS type code to determine boot options available by +# pressing Insert. Valid values are "MacOS", "Linux", +# "Windows", and "XOM". Case-sensitive. +# graphics - set to "on" to enable graphics-mode boot (useful +# mainly for MacOS) or "off" for text-mode boot. +# Default is auto-detected from loader filename. +# options - sets options to be passed to the boot loader; use +# quotes if more than one option should be passed or +# if any options use characters that might be changed +# by rEFInd parsing procedures (=, /, #, or tab). +# disabled - use alone or set to "yes" to disable this entry. +# +# Note that you can use either DOS/Windows/EFI-style backslashes (\) +# or Unix-style forward slashes (/) as directory separators. Either +# way, all file references are on the ESP from which rEFInd was +# launched. +# Use of quotes around parameters causes them to be interpreted as +# one keyword, and for parsing of special characters (spaces, =, /, +# and #) to be disabled. This is useful mainly with the "options" +# keyword. Use of quotes around parameters that specify filenames is +# permissible, but you must then use backslashes instead of slashes, +# except when you must pass a forward slash to the loader, as when +# passing a root= option to a Linux kernel. + +# Below are several sample boot stanzas. All are disabled by default. +# Find one similar to what you need, copy it, remove the "disabled" line, +# and adjust the entries to suit your needs. + +# A sample entry for a Linux 3.13 kernel with EFI boot stub support +# on a partition with a GUID of 904404F8-B481-440C-A1E3-11A5A954E601. +# This entry includes Linux-specific boot options and specification +# of an initial RAM disk. Note uses of Linux-style forward slashes. +# Also note that a leading slash is optional in file specifications. +menuentry Linux { + icon EFI/refind/icons/os_linux.png + volume 904404F8-B481-440C-A1E3-11A5A954E601 + loader bzImage-3.3.0-rc7 + initrd initrd-3.3.0.img + options "ro root=UUID=5f96cafa-e0a7-4057-b18f-fa709db5b837" + disabled +} + +# Below is a more complex Linux example, specifically for Arch Linux. +# This example MUST be modified for your specific installation; if nothing +# else, the PARTUUID code must be changed for your disk. Because Arch Linux +# does not include version numbers in its kernel and initrd filenames, you +# may need to use manual boot stanzas when using fallback initrds or +# multiple kernels with Arch. This example is modified from one in the Arch +# wiki page on rEFInd (https://wiki.archlinux.org/index.php/rEFInd). +menuentry "Arch Linux" { + icon /EFI/refind/icons/os_arch.png + volume "Arch Linux" + loader /boot/vmlinuz-linux + initrd /boot/initramfs-linux.img + options "root=PARTUUID=5028fa50-0079-4c40-b240-abfaf28693ea rw add_efi_memmap" + submenuentry "Boot using fallback initramfs" { + initrd /boot/initramfs-linux-fallback.img + } + submenuentry "Boot to terminal" { + add_options "systemd.unit=multi-user.target" + } + disabled +} + +# A sample entry for loading Ubuntu using its standard name for +# its GRUB 2 boot loader. Note uses of Linux-style forward slashes +menuentry Ubuntu { + loader /EFI/ubuntu/grubx64.efi + icon /EFI/refind/icons/os_linux.png + disabled +} + +# A minimal ELILO entry, which probably offers nothing that +# auto-detection can't accomplish. +menuentry "ELILO" { + loader \EFI\elilo\elilo.efi + disabled +} + +# Like the ELILO entry, this one offers nothing that auto-detection +# can't do; but you might use it if you want to disable auto-detection +# but still boot Windows.... +menuentry "Windows 7" { + loader \EFI\Microsoft\Boot\bootmgfw.efi + disabled +} + +# EFI shells are programs just like boot loaders, and can be +# launched in the same way. You can pass a shell the name of a +# script that it's to run on the "options" line. The script +# could initialize hardware and then launch an OS, or it could +# do something entirely different. +menuentry "Windows via shell script" { + icon \EFI\refind\icons\os_win.png + loader \EFI\tools\shell.efi + options "fs0:\EFI\tools\launch_windows.nsh" + disabled +} + +# MacOS is normally detected and run automatically; however, +# if you want to do something unusual, a manual boot stanza may +# be the way to do it. This one does nothing very unusual, but +# it may serve as a starting point. Note that you'll almost +# certainly need to change the "volume" line for this example +# to work. +menuentry "My macOS" { + icon \EFI\refind\icons\os_mac.png + volume "macOS boot" + loader \System\Library\CoreServices\boot.efi + disabled +} + +# The firmware_bootnum token takes a HEXADECIMAL value as an option +# and sets that value using the EFI's BootNext variable and then +# reboots the computer. This then causes a one-time boot of the +# computer using this EFI boot option. It can be used for various +# purposes, but one that's likely to interest some rEFInd users is +# that some Macs with HiDPI displays produce lower-resolution +# desktops when booted through rEFInd than when booted via Apple's +# own boot manager. Booting using the firmware_bootnum option +# produces the better resolution. Note that no loader option is +# used in this type of configuration. +menuentry "macOS via BootNext" { + icon /EFI/refind/icons/os_mac.png + firmware_bootnum 80 + disabled +} diff --git a/system-config/config/system/laptop/refind/refind_linux.conf b/system-config/config/system/laptop/refind/refind_linux.conf new file mode 100644 index 0000000..e2f4806 --- /dev/null +++ b/system-config/config/system/laptop/refind/refind_linux.conf @@ -0,0 +1,3 @@ +"Boot with standard options" "rw root=/dev/mapper/cryptroot cryptdevice=UUID=c452045f-1bad-48c0-9816-f53c67a416dc:cryptroot resume=UUID=11c0ac93-cd2a-451d-9a6e-cf7f855fb26a quiet" +"Boot to single-user mode" "rw root=/dev/mapper/cryptroot cryptdevice=UUID=c452045f-1bad-48c0-9816-f53c67a416dc:cryptroot resume=UUID=11c0ac93-cd2a-451d-9a6e-cf7f855fb26a quiet single" +"Boot with minimal options" "rw root=/dev/mapper/cryptroot cryptdevice=UUID=c452045f-1bad-48c0-9816-f53c67a416dc:cryptroot resume=UUID=11c0ac93-cd2a-451d-9a6e-cf7f855fb26a quiet" diff --git a/system-config/config/system/laptop/sleep.conf.d/90-laptop.conf b/system-config/config/system/laptop/sleep.conf.d/90-laptop.conf new file mode 100644 index 0000000..32370a4 --- /dev/null +++ b/system-config/config/system/laptop/sleep.conf.d/90-laptop.conf @@ -0,0 +1,6 @@ +# Laptop suspend and hibernation policy. +[Sleep] +AllowSuspend=yes +AllowHibernation=yes +AllowSuspendThenHibernate=yes +HibernateDelaySec=15min diff --git a/system-config/config/system/laptop/systemd/90-noctalia-battery-idle.conf b/system-config/config/system/laptop/systemd/90-noctalia-battery-idle.conf new file mode 100644 index 0000000..548dadb --- /dev/null +++ b/system-config/config/system/laptop/systemd/90-noctalia-battery-idle.conf @@ -0,0 +1,2 @@ +[Login] +IdleAction=ignore diff --git a/system-config/config/system/laptop/touchpad.conf b/system-config/config/system/laptop/touchpad.conf new file mode 100644 index 0000000..b3aea8d --- /dev/null +++ b/system-config/config/system/laptop/touchpad.conf @@ -0,0 +1,9 @@ +Section "InputClass" + Identifier "touchpad" + Driver "libinput" + MatchIsTouchpad "on" + Option "Tapping" "on" + Option "TappingButtonMap" "lrm" + Option "NaturalScrolling" "true" + Option "ClickMethod" "clickfinger" +EndSection diff --git a/system-config/config/system/laptop/wifi-powersave.conf b/system-config/config/system/laptop/wifi-powersave.conf new file mode 100644 index 0000000..528433a --- /dev/null +++ b/system-config/config/system/laptop/wifi-powersave.conf @@ -0,0 +1,4 @@ +# File to be place under /etc/NetworkManager/conf.d +[connection] +# Values are 0 (use default), 1 (ignore/don't touch), 2 (disable) or 3 (enable). +wifi.powersave = 3 diff --git a/system-config/config/user/common/environment.d/pi-status-bridge.conf b/system-config/config/user/common/environment.d/pi-status-bridge.conf new file mode 100644 index 0000000..f35a284 --- /dev/null +++ b/system-config/config/user/common/environment.d/pi-status-bridge.conf @@ -0,0 +1,3 @@ +# Used by the Noctalia Pi Status Bridge adapter and the Hyprland launcher. +PI_STATUS_BRIDGE_CLIENT=/home/alex/.npm-global/bin/pi-status-bridge-client +PI_STATUS_UI_BINARY=/home/alex/.local/bin/pi-status-ui diff --git a/system-config/config/user/common/fish/config.fish b/system-config/config/user/common/fish/config.fish new file mode 100644 index 0000000..c392077 --- /dev/null +++ b/system-config/config/user/common/fish/config.fish @@ -0,0 +1,44 @@ +starship init fish | source +set fish_greeting "" + +# Prefer Neovim for both explicit and editor-variable invocations. +alias vim nvim +set -gx EDITOR nvim +set -gx VISUAL nvim + +# pyenv setup +while set pyenv_index (contains -i -- "/home/alex/.pyenv/shims" $PATH) + set -eg PATH[$pyenv_index] +end +set -e pyenv_index +set -gx PATH '/home/alex/.pyenv/shims' $PATH +set -gx PYENV_SHELL fish +command pyenv rehash 2>/dev/null +function pyenv + set command $argv[1] + set -e argv[1] + + switch "$command" + case rehash shell + source (pyenv "sh-$command" $argv|psub) + case "*" + command pyenv "$command" $argv + end +end + +# bun +set --export BUN_INSTALL "$HOME/.bun" +set --export PATH $BUN_INSTALL/bin $PATH + +# opencode +fish_add_path /home/alex/.opencode/bin + +# Yazi wrapper that keeps the shell in its selected directory. +function y + set tmp (mktemp -t "yazi-cwd.XXXXXX") + command yazi $argv --cwd-file="$tmp" + if read -z cwd <"$tmp"; and [ "$cwd" != "$PWD" ]; and test -d "$cwd" + builtin cd -- "$cwd" + end + command rm -f -- "$tmp" +end diff --git a/system-config/config/user/common/hypr/hypridle.conf b/system-config/config/user/common/hypr/hypridle.conf new file mode 100644 index 0000000..484eda7 --- /dev/null +++ b/system-config/config/user/common/hypr/hypridle.conf @@ -0,0 +1,6 @@ +# Non-locking Wayland screen blanker. It covers every monitor in black and +# dismisses itself on any key press or mouse click; it never changes DPMS. +listener { + timeout = 300 + on-timeout = /home/alex/.local/bin/wayshell-blanker +} diff --git a/system-config/config/user/common/hypr/hyprland.lua b/system-config/config/user/common/hypr/hyprland.lua new file mode 100644 index 0000000..2505a8b --- /dev/null +++ b/system-config/config/user/common/hypr/hyprland.lua @@ -0,0 +1,165 @@ +-- Shared Hyprland profile. yadm deploys machine.lua for the active class. +-- +-- Scope: preserve launch, media, directional focus/move, workspace, scratchpad, +-- floating, fullscreen, and window-placement muscle memory. i3's split-tree, +-- stacking/tabbed, and resize-mode behavior is intentionally deferred. +local machine = dofile(os.getenv("HOME") .. "/.config/hypr/machine.lua") + +for _, monitor in ipairs(machine.monitors or {}) do + hl.monitor(monitor) +end + +local main_mod = "SUPER" +local terminal = "kitty" + +local input = { + kb_layout = "us", + kb_variant = "intl", + numlock_by_default = true, +} +for key, value in pairs(machine.input or {}) do + input[key] = value +end + +hl.config({ + input = input, + general = { + gaps_in = 4, + -- Keep a thin black perimeter so the bottom border remains on-screen. + gaps_out = 2, + border_size = 2, + col = { + -- Muted Ember tones: visible focus without a high-brightness OLED edge. + active_border = "rgba(c9782ed9)", + inactive_border = "rgba(2a190fa6)", + }, + }, + decoration = { + rounding = 0, + }, + animations = { + enabled = false, + }, + binds = { + -- Selecting the current workspace again returns to the last workspace. + workspace_back_and_forth = true, + }, +}) + +-- Three-finger horizontal swipes change workspaces; touchscreens remain excluded +-- by Hyprland's default workspace_swipe_touch = false behavior. +hl.gesture({ + fingers = 3, + direction = "horizontal", + action = "workspace", +}) + +-- Dynamically hide the border when a workspace has exactly one tiled app. +hl.workspace_rule({ + workspace = "w[t1]", + no_border = true, +}) + +-- Application and session bindings. +local noctalia_ipc = "noctalia msg " +local pi_status_ui_toggle = 'ui="${PI_STATUS_UI_BINARY:-$HOME/.local/bin/pi-status-ui}"; if test -x "$ui"; then setsid -f env TMPDIR=/tmp "$ui" --toggle >/tmp/pi-status-ui.log 2>&1; fi' +-- Tap and release Super on its own to open Noctalia's control-center home. +hl.bind(main_mod .. " + SUPER_L", hl.dsp.exec_cmd(noctalia_ipc .. "panel-toggle control-center"), { release = true }) +hl.bind(main_mod .. " + RETURN", hl.dsp.exec_cmd(terminal)) +hl.bind(main_mod .. " + CTRL + RETURN", hl.dsp.exec_cmd(terminal .. " -e zellij")) +hl.bind(main_mod .. " + SHIFT + RETURN", hl.dsp.exec_cmd("rofi -show combi")) +hl.bind(main_mod .. " + SPACE", hl.dsp.exec_cmd(pi_status_ui_toggle)) +hl.bind(main_mod .. " + N", hl.dsp.exec_cmd(noctalia_ipc .. "panel-toggle alex/knowledge-lookup:panel")) +hl.bind(main_mod .. " + S", hl.dsp.exec_cmd(noctalia_ipc .. "panel-toggle control-center")) +hl.bind(main_mod .. " + 0", hl.dsp.exec_cmd(noctalia_ipc .. "panel-toggle session")) +hl.bind(main_mod .. " + SHIFT + F", hl.dsp.exec_cmd(noctalia_ipc .. "bar-toggle main")) +hl.bind(main_mod .. " + SHIFT + R", hl.dsp.exec_cmd("hyprctl reload")) +hl.bind(main_mod .. " + CTRL + L", hl.dsp.exec_cmd("command -v hyprlock >/dev/null && hyprlock || loginctl lock-session")) +hl.bind(main_mod .. " + CTRL + SHIFT + ALT + S", hl.dsp.exec_cmd("systemctl suspend")) +hl.bind(main_mod .. " + CTRL + SHIFT + ALT + H", hl.dsp.exec_cmd("systemctl hibernate")) +hl.bind(main_mod .. " + SHIFT + C", hl.dsp.window.close()) +hl.bind(main_mod .. " + CTRL + SHIFT + F", hl.dsp.exec_cmd("/home/alex/.local/bin/screenshot-select")) + +-- Audio, media, brightness, and calculator keys. +hl.bind("XF86AudioLowerVolume", hl.dsp.exec_cmd("wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-"), { locked = true }) +hl.bind("XF86AudioRaiseVolume", hl.dsp.exec_cmd("wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+"), { locked = true }) +hl.bind("XF86AudioMute", hl.dsp.exec_cmd("wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle"), { locked = true }) +hl.bind(main_mod .. " + XF86AudioMute", hl.dsp.exec_cmd("wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle"), { locked = true }) +hl.bind("XF86AudioPlay", hl.dsp.exec_cmd("playerctl play-pause"), { locked = true }) +hl.bind("XF86AudioNext", hl.dsp.exec_cmd("playerctl next"), { locked = true }) +hl.bind("XF86AudioPrev", hl.dsp.exec_cmd("playerctl previous"), { locked = true }) +hl.bind(main_mod .. " + XF86AudioRaiseVolume", hl.dsp.exec_cmd("playerctl next"), { locked = true }) +hl.bind(main_mod .. " + XF86AudioLowerVolume", hl.dsp.exec_cmd("playerctl previous"), { locked = true }) +hl.bind("XF86MonBrightnessUp", hl.dsp.exec_cmd('light -A 5'), { locked = true }) +hl.bind("XF86MonBrightnessDown", hl.dsp.exec_cmd('light -U 5'), { locked = true }) +hl.bind(main_mod .. " + XF86MonBrightnessDown", hl.dsp.exec_cmd('light -S 0.0'), { locked = true }) +hl.bind(main_mod .. " + XF86MonBrightnessUp", hl.dsp.exec_cmd('light -S 100'), { locked = true }) +hl.bind("XF86Calculator", hl.dsp.exec_cmd("qalculate-qt"), { locked = true }) + +-- Retain i3's hjkl and arrow-key directional workflow. +for _, direction in ipairs({ "left", "down", "up", "right" }) do + local key = ({ left = "H", down = "J", up = "K", right = "L" })[direction] + hl.bind(main_mod .. " + " .. key, hl.dsp.focus({ direction = direction })) + hl.bind(main_mod .. " + " .. direction, hl.dsp.focus({ direction = direction })) +end + +for key, direction in pairs({ H = "l", J = "d", K = "u", L = "r", LEFT = "l", DOWN = "d", UP = "u", RIGHT = "r" }) do + hl.bind(main_mod .. " + SHIFT + " .. key, hl.dsp.window.move({ direction = direction })) +end + +-- Numbered workspaces match the i3 profile. Shift moves; Ctrl moves and follows. +for workspace = 1, 9 do + local key = tostring(workspace) + hl.bind(main_mod .. " + " .. key, hl.dsp.focus({ workspace = workspace })) + hl.bind(main_mod .. " + SHIFT + " .. key, hl.dsp.window.move({ workspace = workspace })) + hl.bind(main_mod .. " + CTRL + " .. key, hl.dsp.window.move({ workspace = workspace, follow = true })) +end +-- Match i3's back-and-forth workflow: Super+Tab switches to the last workspace; +-- selecting the current numbered workspace also returns to it. +hl.bind(main_mod .. " + TAB", hl.dsp.focus({ workspace = "previous" })) +hl.bind(main_mod .. " + CTRL + RIGHT", hl.dsp.focus({ workspace = "+1" })) +hl.bind(main_mod .. " + CTRL + LEFT", hl.dsp.focus({ workspace = "-1" })) + +-- Hyprland special workspaces are the Wayland analogue of the i3 scratchpad. +hl.bind(main_mod .. " + MINUS", hl.dsp.workspace.toggle_special("scratchpad")) +hl.bind(main_mod .. " + SHIFT + MINUS", hl.dsp.window.move({ workspace = "special:scratchpad" })) + +-- Keep the i3 floating and fullscreen shortcuts. Layout-specific binds are deferred. +hl.bind(main_mod .. " + F", hl.dsp.window.fullscreen({ action = "toggle" })) +hl.bind(main_mod .. " + SHIFT + SPACE", hl.dsp.window.float({ action = "toggle" })) +hl.bind(main_mod .. " + mouse:272", hl.dsp.window.drag(), { mouse = true }) +hl.bind(main_mod .. " + mouse:273", hl.dsp.window.resize(), { mouse = true }) + +-- i3 for_window/assign translations. Verify Wayland class/title values with `hyprctl clients`. +local function float_rule(name, match, border_size) + hl.window_rule({ + name = name, + match = match, + float = true, + border_size = border_size, + }) +end + +hl.window_rule({ name = "obsidian-workspace", match = { class = "^obsidian$" }, workspace = "8" }) +hl.window_rule({ name = "thunderbird-workspace", match = { class = "^Thunderbird$" }, workspace = "9" }) +float_rule("pi-status-ui", { class = "^(pi-status-ui|Pi-status-ui)$" }, 1) +float_rule("alsamixer", { title = "^alsamixer$" }, 1) +float_rule("qalculate", { title = "^Qalculate!$" }) +float_rule("psensor", { class = "^Psensor$" }) +float_rule("lxrandr", { title = "^lxrandr$" }) +float_rule("blueman", { class = "^Blueman-manager$" }) +float_rule("gparted", { class = "^GParted$" }) +float_rule("pavucontrol", { class = "^pavucontrol$" }) +float_rule("volume-control", { title = "^Volume Control$" }) +float_rule("virtualbox", { class = ".*VirtualBox.*" }) +hl.window_rule({ name = "virtualbox-manager-tiles", match = { class = ".*VirtualBox Manager.*" }, tile = true }) +hl.window_rule({ name = "virtualbox-machine-tiles", match = { class = ".*VirtualBox Machine.*" }, tile = true }) +-- Send music clients to the existing scratchpad without stealing startup focus. +hl.window_rule({ name = "spotify-music", match = { class = "^Spotify$" }, workspace = "special:scratchpad silent", no_initial_focus = true }) +hl.window_rule({ name = "deezer-music", match = { class = "^Deezer$" }, workspace = "special:scratchpad silent", no_initial_focus = true }) + +hl.on("hyprland.start", function() + for _, command in ipairs(machine.startup or {}) do + hl.exec_cmd(command) + end +end) diff --git a/system-config/config/user/common/hypr/hyprlock.conf b/system-config/config/user/common/hypr/hyprlock.conf new file mode 100644 index 0000000..0ce112a --- /dev/null +++ b/system-config/config/user/common/hypr/hyprlock.conf @@ -0,0 +1,45 @@ +# Minimal Hyprlock screen using the current desktop as a blurred backdrop. + +$font = Sans + +general { + hide_cursor = true +} + +background { + monitor = + path = screenshot + blur_passes = 3 +} + +input-field { + monitor = + size = 20%, 5% + outline_thickness = 2 + inner_color = rgba(0, 0, 0, 0.25) + outer_color = rgba(eeeeeecc) + check_color = rgba(8ec07ccc) + fail_color = rgba(fb4934cc) + + font_color = rgb(eeeeee) + fade_on_empty = false + rounding = 12 + font_family = $font + placeholder_text = Input password... + fail_text = $PAMFAIL + + position = 0, -20 + halign = center + valign = center +} + +label { + monitor = + text = $TIME + font_size = 72 + font_family = $font + + position = 0, 120 + halign = center + valign = center +} diff --git a/system-config/config/user/common/kitty/kitty.conf b/system-config/config/user/common/kitty/kitty.conf new file mode 100644 index 0000000..fba2be2 --- /dev/null +++ b/system-config/config/user/common/kitty/kitty.conf @@ -0,0 +1,35 @@ +# Match the active Alacritty look while retaining Kitty's native graphics protocol. +font_family MesloLGS NF +font_size 15.0 + +# Palette: active ~/.config/alacritty/alacritty.toml plus Noctalia cursor/selection. +foreground #FCFCFA +background #2D2A2E +cursor #fff1e6 +cursor_text_color #000000 +selection_foreground #fff1e6 +selection_background #3b220f + +color0 #403E41 +color1 #FF6188 +color2 #A9DC76 +color3 #FFD866 +color4 #FC9867 +color5 #AB9DF2 +color6 #78DCE8 +color7 #FCFCFA +color8 #727072 +color9 #FF6188 +color10 #A9DC76 +color11 #FFD866 +color12 #FC9867 +color13 #AB9DF2 +color14 #78DCE8 +color15 #FCFCFA + +# Preserve Alacritty-like minimal window behavior. +window_padding_width 0 +copy_on_select no + +# Keep the shell integration and graphics protocol available to Ranger. +shell_integration enabled diff --git a/system-config/config/user/common/noctalia/10-base.toml b/system-config/config/user/common/noctalia/10-base.toml new file mode 100644 index 0000000..f80a6cd --- /dev/null +++ b/system-config/config/user/common/noctalia/10-base.toml @@ -0,0 +1,84 @@ +# Shared Noctalia defaults. Keep GUI experiments in state; promote durable choices here. +[shell] +setup_wizard_enabled = false +offline_mode = true +external_ip_enabled = false +telemetry_enabled = false + +# Noctalia owns idle handling so ScreenSaver/Wayland idle inhibitors (such as +# browser video playback) prevent these actions. Power displays down after five +# minutes, lock after ten minutes, then suspend-then-hibernate after thirty. +[idle] +behavior_order = ["lock", "screen-off", "suspend-then-hibernate"] +pre_action_fade_seconds = 2.0 + +[idle.behavior.lock] +action = "lock" +enabled = true +timeout = 600 + +[idle.behavior.screen-off] +action = "screen_off" +enabled = true +timeout = 300 + +[idle.behavior.suspend-then-hibernate] +action = "command" +command = "loginctl lock-session && systemctl suspend-then-hibernate" +enabled = true +timeout = 1800 + +[theme] +mode = "dark" +source = "custom" +custom_palette = "Ember OLED" +pure_black_dark = true + +# Noctalia merges files alphabetically. Keep reusable modules here; machine-specific +# layout and hardware settings live in 90-machine.toml, while 99-extend.toml is local. +[plugins] +enabled = ["alex/system-status", "alex/knowledge-lookup"] + +[widget.cpu] +type = "alex/system-status:cpu" + +[widget.ram] +type = "alex/system-status:memory" + +[widget.network-rate] +type = "alex/system-status:network-rate" + +[widget.idle-inhibitors] +type = "alex/system-status:inhibitors" + +[widget.knowledge-lookup] +type = "alex/knowledge-lookup:bar" + +[widget.temp] +type = "sysmon" +stat = "cpu_temp" +visualization = "none" +show_value = true + +[widget.disk] +type = "sysmon" +stat = "disk_used_pct" +visualization = "none" +show_value = true +path = "/" + +[desktop_widgets] +enabled = false + +[wallpaper] +enabled = true +fill_color = "#000000" + +[wallpaper.default] +path = "color:#000000" + +[nightlight] +enabled = true +force = false +temperature_day = 6500 +temperature_night = 2500 diff --git a/system-config/config/user/common/noctalia/palettes/Ember OLED.json b/system-config/config/user/common/noctalia/palettes/Ember OLED.json new file mode 100644 index 0000000..1ac8dbc --- /dev/null +++ b/system-config/config/user/common/noctalia/palettes/Ember OLED.json @@ -0,0 +1,48 @@ +{ + "dark": { + "mPrimary": "#ff8800", + "mOnPrimary": "#1a0d00", + "mSecondary": "#ffb05c", + "mOnSecondary": "#2b1600", + "mTertiary": "#ffcc99", + "mOnTertiary": "#2b1600", + "mError": "#ff6b4a", + "mOnError": "#2b0700", + "mSurface": "#050403", + "mOnSurface": "#fff1e6", + "mSurfaceVariant": "#1a110c", + "mOnSurfaceVariant": "#f4c7a1", + "mOutline": "#6e4a31", + "mShadow": "#000000", + "mHover": "#2a190f", + "mOnHover": "#fff1e6", + "terminal": { + "background": "#050403", + "foreground": "#fff1e6", + "cursor": "#fff1e6", + "cursorText": "#050403", + "selectionBg": "#3b220f", + "selectionFg": "#fff1e6", + "normal": { + "black": "#050403", + "red": "#ff6b4a", + "green": "#c98a45", + "yellow": "#ffb05c", + "blue": "#c97c45", + "magenta": "#d98a5c", + "cyan": "#e8a76a", + "white": "#fff1e6" + }, + "bright": { + "black": "#6e4a31", + "red": "#ff8f72", + "green": "#e8ad68", + "yellow": "#ffca80", + "blue": "#e8a06a", + "magenta": "#f0ad82", + "cyan": "#ffd0a0", + "white": "#ffffff" + } + } + } +} diff --git a/system-config/config/user/common/systemd/user/idle-inhibitor-monitor.service b/system-config/config/user/common/systemd/user/idle-inhibitor-monitor.service new file mode 100644 index 0000000..31c1b86 --- /dev/null +++ b/system-config/config/user/common/systemd/user/idle-inhibitor-monitor.service @@ -0,0 +1,12 @@ +[Unit] +Description=Track session ScreenSaver idle inhibitors for Noctalia +After=graphical-session.target +PartOf=graphical-session.target + +[Service] +ExecStart=%h/.local/bin/idle-inhibitor-monitor +Restart=on-failure +RestartSec=2 + +[Install] +WantedBy=default.target diff --git a/system-config/config/user/common/systemd/user/pi-status-bridge.service b/system-config/config/user/common/systemd/user/pi-status-bridge.service new file mode 100644 index 0000000..948c490 --- /dev/null +++ b/system-config/config/user/common/systemd/user/pi-status-bridge.service @@ -0,0 +1,13 @@ +[Unit] +Description=Pi Status Bridge home agent +After=graphical-session.target + +[Service] +Type=simple +Environment=PATH=/home/alex/.npm-global/bin:/usr/local/bin:/usr/bin:/bin +ExecStart=/usr/bin/env pi-status-bridge --session-root %h/.local/state/pi-status-bridge/sessions +Restart=on-failure +RestartSec=3 + +[Install] +WantedBy=default.target diff --git a/system-config/config/user/common/systemd/user/ssh-agent.service b/system-config/config/user/common/systemd/user/ssh-agent.service new file mode 100644 index 0000000..d0aa6be --- /dev/null +++ b/system-config/config/user/common/systemd/user/ssh-agent.service @@ -0,0 +1,10 @@ +[Unit] +Description=OpenSSH key agent + +[Service] +Type=simple +Environment=SSH_AUTH_SOCK=%t/ssh-agent.socket +ExecStart=/usr/bin/ssh-agent -D -a $SSH_AUTH_SOCK + +[Install] +WantedBy=default.target diff --git a/system-config/config/user/common/yazi/flavors/catppuccin-macchiato.yazi/LICENSE b/system-config/config/user/common/yazi/flavors/catppuccin-macchiato.yazi/LICENSE new file mode 100644 index 0000000..fb5b1d6 --- /dev/null +++ b/system-config/config/user/common/yazi/flavors/catppuccin-macchiato.yazi/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 yazi-rs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/system-config/config/user/common/yazi/flavors/catppuccin-macchiato.yazi/LICENSE-tmtheme b/system-config/config/user/common/yazi/flavors/catppuccin-macchiato.yazi/LICENSE-tmtheme new file mode 100644 index 0000000..006383b --- /dev/null +++ b/system-config/config/user/common/yazi/flavors/catppuccin-macchiato.yazi/LICENSE-tmtheme @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Catppuccin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/system-config/config/user/common/yazi/flavors/catppuccin-macchiato.yazi/README.md b/system-config/config/user/common/yazi/flavors/catppuccin-macchiato.yazi/README.md new file mode 100644 index 0000000..752908c --- /dev/null +++ b/system-config/config/user/common/yazi/flavors/catppuccin-macchiato.yazi/README.md @@ -0,0 +1,36 @@ +
+ Yazi logo +
+ +

+ Catppuccin Macchiato Flavor for Yazi +

+ +## 👀 Preview + + + +## 🎨 Installation + +```sh +ya pkg add yazi-rs/flavors:catppuccin-macchiato +``` + +## ⚙️ Usage + +Set the content of your `theme.toml` to enable it as your _dark_ flavor: + +```toml +[flavor] +dark = "catppuccin-macchiato" +``` + +Make sure your `theme.toml` doesn't contain anything other than `[flavor]`, unless you want to override certain styles of this flavor. + +See the [Yazi flavor documentation](https://yazi-rs.github.io/docs/flavors/overview) for more details. + +## 📜 License + +The flavor is MIT-licensed, and the included tmTheme is also MIT-licensed. + +Check the [LICENSE](LICENSE) and [LICENSE-tmtheme](LICENSE-tmtheme) file for more details. diff --git a/system-config/config/user/common/yazi/flavors/catppuccin-macchiato.yazi/flavor.toml b/system-config/config/user/common/yazi/flavors/catppuccin-macchiato.yazi/flavor.toml new file mode 100644 index 0000000..5fa8bc2 --- /dev/null +++ b/system-config/config/user/common/yazi/flavors/catppuccin-macchiato.yazi/flavor.toml @@ -0,0 +1,215 @@ +# vim:fileencoding=utf-8:foldmethod=marker + +# : Manager {{{ + +[mgr] +cwd = { fg = "#8bd5ca" } + +# Find +find_keyword = { fg = "#eed49f", bold = true, italic = true, underline = true } +find_position = { fg = "#f5bde6", bg = "reset", bold = true, italic = true } + +# Marker +marker_copied = { fg = "#a6da95", bg = "#a6da95" } +marker_cut = { fg = "#ed8796", bg = "#ed8796" } +marker_marked = { fg = "#8bd5ca", bg = "#8bd5ca" } +marker_selected = { fg = "#eed49f", bg = "#eed49f" } + +# Count +count_copied = { fg = "#24273a", bg = "#a6da95" } +count_cut = { fg = "#24273a", bg = "#ed8796" } +count_selected = { fg = "#24273a", bg = "#eed49f" } + +# Border +border_symbol = "│" +border_style = { fg = "#8087a2" } + +# : }}} + + +# : Tabs {{{ + +[tabs] +active = { fg = "#24273a", bg = "#8aadf4", bold = true } +inactive = { fg = "#8aadf4", bg = "#363a4f" } + +# : }}} + + +# : Mode {{{ + +[mode] + +normal_main = { fg = "#24273a", bg = "#8aadf4", bold = true } +normal_alt = { fg = "#8aadf4", bg = "#363a4f" } + +# Select mode +select_main = { fg = "#24273a", bg = "#8bd5ca", bold = true } +select_alt = { fg = "#8bd5ca", bg = "#363a4f" } + +# Unset mode +unset_main = { fg = "#24273a", bg = "#f0c6c6", bold = true } +unset_alt = { fg = "#f0c6c6", bg = "#363a4f" } + +# : }}} + + +# : Status bar {{{ + +[status] +# Permissions +perm_sep = { fg = "#8087a2" } +perm_type = { fg = "#8aadf4" } +perm_read = { fg = "#eed49f" } +perm_write = { fg = "#ed8796" } +perm_exec = { fg = "#a6da95" } + +# Progress +progress_label = { fg = "#ffffff", bold = true } +progress_normal = { fg = "#a6da95", bg = "#494d64" } +progress_error = { fg = "#eed49f", bg = "#ed8796" } + +# : }}} + + +# : Pick {{{ + +[pick] +border = { fg = "#8aadf4" } +active = { fg = "#f5bde6", bold = true } +inactive = {} + +# : }}} + + +# : Input {{{ + +[input] +border = { fg = "#8aadf4" } +title = {} +value = {} +selected = { reversed = true } + +# : }}} + + +# : Completion {{{ + +[cmp] +border = { fg = "#8aadf4" } + +# : }}} + + +# : Tasks {{{ + +[tasks] +border = { fg = "#8aadf4" } +title = {} +hovered = { fg = "#f5bde6", bold = true } + +# : }}} + + +# : Which {{{ + +[which] +border = { fg = "#8aadf4" } +cand = { fg = "#8bd5ca" } +rest = { fg = "#939ab7" } +desc = { fg = "#f5bde6" } +separator = "  " +separator_style = { fg = "#5b6078" } + +# : }}} + + +# : Help {{{ + +[help] +on = { fg = "#8bd5ca" } +run = { fg = "#f5bde6" } +hovered = { reversed = true, bold = true } +footer = { fg = "#363a4f", bg = "#cad3f5" } + +# : }}} + + +# : Spotter {{{ + +[spot] +border = { fg = "#8aadf4" } +title = { fg = "#8aadf4" } +tbl_col = { fg = "#8bd5ca" } +tbl_cell = { fg = "#f5bde6", bg = "#494d64" } + +# : }}} + + +# : Notification {{{ + +[notify] +title_info = { fg = "#a6da95" } +title_warn = { fg = "#eed49f" } +title_error = { fg = "#ed8796" } + +# : }}} + + +# : File-specific styles {{{ + +[filetype] + +rules = [ + # Image + { mime = "**/image/*", fg = "#8bd5ca" }, + # Media + { mime = "**/{audio,video}/*", fg = "#eed49f" }, + # Archive + { mime = "**/application/{zip,rar,7z*,tar,gzip,xz,zstd,bzip*,lzma,compress,archive,cpio,arj,xar,ms-cab*}", fg = "#f5bde6" }, + # Document + { mime = "**/application/{pdf,doc,rtf}", fg = "#a6da95" }, + # Virtual file system + { mime = "vfs/{absent,stale}", fg = "#939ab7" }, + # Fallback + { url = "*", fg = "#cad3f5" }, + { url = "*/", fg = "#8aadf4" }, +] + +# : }}} + +[icon] + +dirs = [ + { name = ".config", text = "", fg = "#f5bde6" }, + { name = ".git", text = "", fg = "#8bd5ca" }, + { name = ".github", text = "", fg = "#8aadf4" }, + { name = ".npm", text = "", fg = "#8aadf4" }, + { name = "Desktop", text = "", fg = "#8bd5ca" }, + { name = "Development", text = "", fg = "#8bd5ca" }, + { name = "Documents", text = "", fg = "#8bd5ca" }, + { name = "Downloads", text = "", fg = "#8bd5ca" }, + { name = "Library", text = "", fg = "#8bd5ca" }, + { name = "Movies", text = "", fg = "#8bd5ca" }, + { name = "Music", text = "", fg = "#8bd5ca" }, + { name = "Pictures", text = "", fg = "#8bd5ca" }, + { name = "Public", text = "", fg = "#8bd5ca" }, + { name = "Videos", text = "", fg = "#8bd5ca" }, +] +conds = [ + # Special files + { if = "orphan", text = "", fg = "#cad3f5" }, + { if = "link", text = "", fg = "#8087a2" }, + { if = "block", text = "", fg = "#f0c6c6" }, + { if = "char", text = "", fg = "#f0c6c6" }, + { if = "fifo", text = "", fg = "#f0c6c6" }, + { if = "sock", text = "", fg = "#f0c6c6" }, + { if = "sticky", text = "", fg = "#f0c6c6" }, + { if = "dummy", text = "", fg = "#ed8796" }, + + # Fallback + { if = "dir & hovered", text = "", fg = "#8aadf4" }, + { if = "dir", text = "", fg = "#8aadf4" }, + { if = "exec", text = "", fg = "#a6da95" }, + { if = "!dir", text = "", fg = "#cad3f5" }, +] diff --git a/system-config/config/user/common/yazi/flavors/catppuccin-macchiato.yazi/preview.png b/system-config/config/user/common/yazi/flavors/catppuccin-macchiato.yazi/preview.png new file mode 100644 index 0000000..a200463 Binary files /dev/null and b/system-config/config/user/common/yazi/flavors/catppuccin-macchiato.yazi/preview.png differ diff --git a/system-config/config/user/common/yazi/flavors/catppuccin-macchiato.yazi/tmtheme.xml b/system-config/config/user/common/yazi/flavors/catppuccin-macchiato.yazi/tmtheme.xml new file mode 100644 index 0000000..8f21312 --- /dev/null +++ b/system-config/config/user/common/yazi/flavors/catppuccin-macchiato.yazi/tmtheme.xml @@ -0,0 +1,2112 @@ + + + + + name + Catppuccin Macchiato + semanticClass + theme.dark.catppuccin-macchiato + uuid + 02b2bdf3-9eb7-4396-bf04-f17f1468f99f + author + Catppuccin Org + colorSpaceName + sRGB + settings + + + settings + + background + #24273a + foreground + #cad3f5 + caret + #f4dbd6 + lineHighlight + #363a4f + misspelling + #ed8796 + accent + #c6a0f6 + selection + #939ab740 + activeGuide + #494d64 + findHighlight + #455c6d + gutterForeground + #8087a2 + + + + name + Basic text & variable names (incl. leading punctuation) + scope + text, source, variable.other.readwrite, punctuation.definition.variable + settings + + foreground + #cad3f5 + + + + name + Parentheses, Brackets, Braces + scope + punctuation + settings + + foreground + #939ab7 + fontStyle + + + + + name + Comments + scope + comment, punctuation.definition.comment + settings + + foreground + #939ab7 + fontStyle + italic + + + + scope + string, punctuation.definition.string + settings + + foreground + #a6da95 + + + + scope + constant.character.escape + settings + + foreground + #f5bde6 + + + + name + Booleans, constants, numbers + scope + constant.numeric, variable.other.constant, entity.name.constant, constant.language.boolean, constant.language.false, constant.language.true, keyword.other.unit.user-defined, keyword.other.unit.suffix.floating-point + settings + + foreground + #f5a97f + + + + scope + keyword, keyword.operator.word, keyword.operator.new, variable.language.super, support.type.primitive, storage.type, storage.modifier, punctuation.definition.keyword + settings + + foreground + #c6a0f6 + fontStyle + + + + + scope + entity.name.tag.documentation + settings + + foreground + #c6a0f6 + + + + name + Punctuation + scope + keyword.operator, punctuation.accessor, punctuation.definition.generic, meta.function.closure punctuation.section.parameters, punctuation.definition.tag, punctuation.separator.key-value + settings + + foreground + #8bd5ca + + + + scope + entity.name.function, meta.function-call.method, support.function, support.function.misc, variable.function + settings + + foreground + #8aadf4 + fontStyle + italic + + + + name + Classes + scope + entity.name.class, entity.other.inherited-class, support.class, meta.function-call.constructor, entity.name.struct + settings + + foreground + #eed49f + fontStyle + italic + + + + name + Enum + scope + entity.name.enum + settings + + foreground + #eed49f + fontStyle + italic + + + + name + Enum member + scope + meta.enum variable.other.readwrite, variable.other.enummember + settings + + foreground + #8bd5ca + + + + name + Object properties + scope + meta.property.object + settings + + foreground + #8bd5ca + + + + name + Types + scope + meta.type, meta.type-alias, support.type, entity.name.type + settings + + foreground + #eed49f + fontStyle + italic + + + + name + Decorators + scope + meta.annotation variable.function, meta.annotation variable.annotation.function, meta.annotation punctuation.definition.annotation, meta.decorator, punctuation.decorator + settings + + foreground + #f5a97f + + + + scope + variable.parameter, meta.function.parameters + settings + + foreground + #ee99a0 + fontStyle + italic + + + + name + Built-ins + scope + constant.language, support.function.builtin + settings + + foreground + #ed8796 + + + + scope + entity.other.attribute-name.documentation + settings + + foreground + #ed8796 + + + + name + Preprocessor directives + scope + keyword.control.directive, punctuation.definition.directive + settings + + foreground + #eed49f + + + + name + Type parameters + scope + punctuation.definition.typeparameters + settings + + foreground + #91d7e3 + + + + name + Namespaces + scope + entity.name.namespace + settings + + foreground + #eed49f + + + + name + Property names (left hand assignments in json/yaml/css) + scope + support.type.property-name.css + settings + + foreground + #8aadf4 + fontStyle + + + + + name + This/Self keyword + scope + variable.language.this, variable.language.this punctuation.definition.variable + settings + + foreground + #ed8796 + + + + name + Object properties + scope + variable.object.property + settings + + foreground + #cad3f5 + + + + name + String template interpolation + scope + string.template variable, string variable + settings + + foreground + #cad3f5 + + + + name + `new` as bold + scope + keyword.operator.new + settings + + fontStyle + bold + + + + name + C++ extern keyword + scope + storage.modifier.specifier.extern.cpp + settings + + foreground + #c6a0f6 + + + + name + C++ scope resolution + scope + entity.name.scope-resolution.template.call.cpp, entity.name.scope-resolution.parameter.cpp, entity.name.scope-resolution.cpp, entity.name.scope-resolution.function.definition.cpp + settings + + foreground + #eed49f + + + + name + C++ doc keywords + scope + storage.type.class.doxygen + settings + + fontStyle + + + + + name + C++ operators + scope + storage.modifier.reference.cpp + settings + + foreground + #8bd5ca + + + + name + C# Interpolated Strings + scope + meta.interpolation.cs + settings + + foreground + #cad3f5 + + + + name + C# xml-style docs + scope + comment.block.documentation.cs + settings + + foreground + #cad3f5 + + + + name + Classes, reflecting the className color in JSX + scope + source.css entity.other.attribute-name.class.css, entity.other.attribute-name.parent-selector.css punctuation.definition.entity.css + settings + + foreground + #eed49f + + + + name + Operators + scope + punctuation.separator.operator.css + settings + + foreground + #8bd5ca + + + + name + Pseudo classes + scope + source.css entity.other.attribute-name.pseudo-class + settings + + foreground + #8bd5ca + + + + scope + source.css constant.other.unicode-range + settings + + foreground + #f5a97f + + + + scope + source.css variable.parameter.url + settings + + foreground + #a6da95 + fontStyle + + + + + name + CSS vendored property names + scope + support.type.vendored.property-name + settings + + foreground + #91d7e3 + + + + name + Less/SCSS right-hand variables (@/$-prefixed) + scope + source.css meta.property-value variable, source.css meta.property-value variable.other.less, source.css meta.property-value variable.other.less punctuation.definition.variable.less, meta.definition.variable.scss + settings + + foreground + #ee99a0 + + + + name + CSS variables (--prefixed) + scope + source.css meta.property-list variable, meta.property-list variable.other.less, meta.property-list variable.other.less punctuation.definition.variable.less + settings + + foreground + #8aadf4 + + + + name + CSS Percentage values, styled the same as numbers + scope + keyword.other.unit.percentage.css + settings + + foreground + #f5a97f + + + + name + CSS Attribute selectors, styled the same as strings + scope + source.css meta.attribute-selector + settings + + foreground + #a6da95 + + + + name + JSON/YAML keys, other left-hand assignments + scope + keyword.other.definition.ini, punctuation.support.type.property-name.json, support.type.property-name.json, punctuation.support.type.property-name.toml, support.type.property-name.toml, entity.name.tag.yaml, punctuation.support.type.property-name.yaml, support.type.property-name.yaml + settings + + foreground + #8aadf4 + fontStyle + + + + + name + JSON/YAML constants + scope + constant.language.json, constant.language.yaml + settings + + foreground + #f5a97f + + + + name + YAML anchors + scope + entity.name.type.anchor.yaml, variable.other.alias.yaml + settings + + foreground + #eed49f + fontStyle + + + + + name + TOML tables / ini groups + scope + support.type.property-name.table, entity.name.section.group-title.ini + settings + + foreground + #eed49f + + + + name + TOML dates + scope + constant.other.time.datetime.offset.toml + settings + + foreground + #f5bde6 + + + + name + YAML anchor puctuation + scope + punctuation.definition.anchor.yaml, punctuation.definition.alias.yaml + settings + + foreground + #f5bde6 + + + + name + YAML triple dashes + scope + entity.other.document.begin.yaml + settings + + foreground + #f5bde6 + + + + name + Markup Diff + scope + markup.changed.diff + settings + + foreground + #f5a97f + + + + name + Diff + scope + meta.diff.header.from-file, meta.diff.header.to-file, punctuation.definition.from-file.diff, punctuation.definition.to-file.diff + settings + + foreground + #8aadf4 + + + + name + Diff Inserted + scope + markup.inserted.diff + settings + + foreground + #a6da95 + + + + name + Diff Deleted + scope + markup.deleted.diff + settings + + foreground + #ed8796 + + + + name + dotenv left-hand side assignments + scope + variable.other.env + settings + + foreground + #8aadf4 + + + + name + dotenv reference to existing env variable + scope + string.quoted variable.other.env + settings + + foreground + #cad3f5 + + + + name + GDScript functions + scope + support.function.builtin.gdscript + settings + + foreground + #8aadf4 + + + + name + GDScript constants + scope + constant.language.gdscript + settings + + foreground + #f5a97f + + + + name + Comment keywords + scope + comment meta.annotation.go + settings + + foreground + #ee99a0 + + + + name + go:embed, go:build, etc. + scope + comment meta.annotation.parameters.go + settings + + foreground + #f5a97f + + + + name + Go constants (nil, true, false) + scope + constant.language.go + settings + + foreground + #f5a97f + + + + name + GraphQL variables + scope + variable.graphql + settings + + foreground + #cad3f5 + + + + name + GraphQL aliases + scope + string.unquoted.alias.graphql + settings + + foreground + #f0c6c6 + + + + name + GraphQL enum members + scope + constant.character.enum.graphql + settings + + foreground + #8bd5ca + + + + name + GraphQL field in types + scope + meta.objectvalues.graphql constant.object.key.graphql string.unquoted.graphql + settings + + foreground + #f0c6c6 + + + + name + HTML/XML DOCTYPE as keyword + scope + keyword.other.doctype, meta.tag.sgml.doctype punctuation.definition.tag, meta.tag.metadata.doctype entity.name.tag, meta.tag.metadata.doctype punctuation.definition.tag + settings + + foreground + #c6a0f6 + + + + name + HTML/XML-like <tags/> + scope + entity.name.tag + settings + + foreground + #8aadf4 + fontStyle + + + + + name + Special characters like &amp; + scope + text.html constant.character.entity, text.html constant.character.entity punctuation, constant.character.entity.xml, constant.character.entity.xml punctuation, constant.character.entity.js.jsx, constant.charactger.entity.js.jsx punctuation, constant.character.entity.tsx, constant.character.entity.tsx punctuation + settings + + foreground + #ed8796 + + + + name + HTML/XML tag attribute values + scope + entity.other.attribute-name + settings + + foreground + #eed49f + + + + name + Components + scope + support.class.component, support.class.component.jsx, support.class.component.tsx, support.class.component.vue + settings + + foreground + #f5bde6 + fontStyle + + + + + name + Annotations + scope + punctuation.definition.annotation, storage.type.annotation + settings + + foreground + #f5a97f + + + + name + Java enums + scope + constant.other.enum.java + settings + + foreground + #8bd5ca + + + + name + Java imports + scope + storage.modifier.import.java + settings + + foreground + #cad3f5 + + + + name + Javadoc + scope + comment.block.javadoc.java keyword.other.documentation.javadoc.java + settings + + fontStyle + + + + + name + Exported Variable + scope + meta.export variable.other.readwrite.js + settings + + foreground + #ee99a0 + + + + name + JS/TS constants & properties + scope + variable.other.constant.js, variable.other.constant.ts, variable.other.property.js, variable.other.property.ts + settings + + foreground + #cad3f5 + + + + name + JSDoc; these are mainly params, so styled as such + scope + variable.other.jsdoc, comment.block.documentation variable.other + settings + + foreground + #ee99a0 + fontStyle + + + + + name + JSDoc keywords + scope + storage.type.class.jsdoc + settings + + fontStyle + + + + + scope + support.type.object.console.js + settings + + foreground + #cad3f5 + + + + name + Node constants as keywords (module, etc.) + scope + support.constant.node, support.type.object.module.js + settings + + foreground + #c6a0f6 + + + + name + implements as keyword + scope + storage.modifier.implements + settings + + foreground + #c6a0f6 + + + + name + Builtin types + scope + constant.language.null.js, constant.language.null.ts, constant.language.undefined.js, constant.language.undefined.ts, support.type.builtin.ts + settings + + foreground + #c6a0f6 + + + + scope + variable.parameter.generic + settings + + foreground + #eed49f + + + + name + Arrow functions + scope + keyword.declaration.function.arrow.js, storage.type.function.arrow.ts + settings + + foreground + #8bd5ca + + + + name + Decorator punctuations (decorators inherit from blue functions, instead of styleguide peach) + scope + punctuation.decorator.ts + settings + + foreground + #8aadf4 + fontStyle + italic + + + + name + Extra JS/TS keywords + scope + keyword.operator.expression.in.js, keyword.operator.expression.in.ts, keyword.operator.expression.infer.ts, keyword.operator.expression.instanceof.js, keyword.operator.expression.instanceof.ts, keyword.operator.expression.is, keyword.operator.expression.keyof.ts, keyword.operator.expression.of.js, keyword.operator.expression.of.ts, keyword.operator.expression.typeof.ts + settings + + foreground + #c6a0f6 + + + + name + Julia macros + scope + support.function.macro.julia + settings + + foreground + #8bd5ca + fontStyle + italic + + + + name + Julia language constants (true, false) + scope + constant.language.julia + settings + + foreground + #f5a97f + + + + name + Julia other constants (these seem to be arguments inside arrays) + scope + constant.other.symbol.julia + settings + + foreground + #ee99a0 + + + + name + LaTeX preamble + scope + text.tex keyword.control.preamble + settings + + foreground + #8bd5ca + + + + name + LaTeX be functions + scope + text.tex support.function.be + settings + + foreground + #91d7e3 + + + + name + LaTeX math + scope + constant.other.general.math.tex + settings + + foreground + #f0c6c6 + + + + name + Liquid Builtin Objects & User Defined Variables + scope + variable.language.liquid + settings + + foreground + #f5bde6 + + + + name + Lua docstring keywords + scope + comment.line.double-dash.documentation.lua storage.type.annotation.lua + settings + + foreground + #c6a0f6 + fontStyle + + + + + name + Lua docstring variables + scope + comment.line.double-dash.documentation.lua entity.name.variable.lua, comment.line.double-dash.documentation.lua variable.lua + settings + + foreground + #cad3f5 + + + + scope + heading.1.markdown punctuation.definition.heading.markdown, heading.1.markdown, heading.1.quarto punctuation.definition.heading.quarto, heading.1.quarto, markup.heading.atx.1.mdx, markup.heading.atx.1.mdx punctuation.definition.heading.mdx, markup.heading.setext.1.markdown, markup.heading.heading-0.asciidoc + settings + + foreground + #ed8796 + + + + scope + heading.2.markdown punctuation.definition.heading.markdown, heading.2.markdown, heading.2.quarto punctuation.definition.heading.quarto, heading.2.quarto, markup.heading.atx.2.mdx, markup.heading.atx.2.mdx punctuation.definition.heading.mdx, markup.heading.setext.2.markdown, markup.heading.heading-1.asciidoc + settings + + foreground + #f5a97f + + + + scope + heading.3.markdown punctuation.definition.heading.markdown, heading.3.markdown, heading.3.quarto punctuation.definition.heading.quarto, heading.3.quarto, markup.heading.atx.3.mdx, markup.heading.atx.3.mdx punctuation.definition.heading.mdx, markup.heading.heading-2.asciidoc + settings + + foreground + #eed49f + + + + scope + heading.4.markdown punctuation.definition.heading.markdown, heading.4.markdown, heading.4.quarto punctuation.definition.heading.quarto, heading.4.quarto, markup.heading.atx.4.mdx, markup.heading.atx.4.mdx punctuation.definition.heading.mdx, markup.heading.heading-3.asciidoc + settings + + foreground + #a6da95 + + + + scope + heading.5.markdown punctuation.definition.heading.markdown, heading.5.markdown, heading.5.quarto punctuation.definition.heading.quarto, heading.5.quarto, markup.heading.atx.5.mdx, markup.heading.atx.5.mdx punctuation.definition.heading.mdx, markup.heading.heading-4.asciidoc + settings + + foreground + #7dc4e4 + + + + scope + heading.6.markdown punctuation.definition.heading.markdown, heading.6.markdown, heading.6.quarto punctuation.definition.heading.quarto, heading.6.quarto, markup.heading.atx.6.mdx, markup.heading.atx.6.mdx punctuation.definition.heading.mdx, markup.heading.heading-5.asciidoc + settings + + foreground + #b7bdf8 + + + + scope + markup.bold + settings + + foreground + #ed8796 + fontStyle + bold + + + + scope + markup.italic + settings + + foreground + #ed8796 + fontStyle + italic + + + + scope + markup.strikethrough + settings + + foreground + #a5adcb + fontStyle + strikethrough + + + + name + Markdown auto links + scope + punctuation.definition.link, markup.underline.link + settings + + foreground + #8aadf4 + + + + name + Markdown links + scope + text.html.markdown punctuation.definition.link.title, text.html.quarto punctuation.definition.link.title, string.other.link.title.markdown, string.other.link.title.quarto, markup.link, punctuation.definition.constant.markdown, punctuation.definition.constant.quarto, constant.other.reference.link.markdown, constant.other.reference.link.quarto, markup.substitution.attribute-reference + settings + + foreground + #b7bdf8 + + + + name + Markdown code spans + scope + punctuation.definition.raw.markdown, punctuation.definition.raw.quarto, markup.inline.raw.string.markdown, markup.inline.raw.string.quarto, markup.raw.block.markdown, markup.raw.block.quarto + settings + + foreground + #a6da95 + + + + name + Markdown triple backtick language identifier + scope + fenced_code.block.language + settings + + foreground + #91d7e3 + + + + name + Markdown triple backticks + scope + markup.fenced_code.block punctuation.definition, markup.raw support.asciidoc + settings + + foreground + #939ab7 + + + + name + Markdown quotes + scope + markup.quote, punctuation.definition.quote.begin + settings + + foreground + #f5bde6 + + + + name + Markdown separators + scope + meta.separator.markdown + settings + + foreground + #8bd5ca + + + + name + Markdown list bullets + scope + punctuation.definition.list.begin.markdown, punctuation.definition.list.begin.quarto, markup.list.bullet + settings + + foreground + #8bd5ca + + + + name + Quarto headings + scope + markup.heading.quarto + settings + + fontStyle + bold + + + + name + Nix attribute names + scope + entity.other.attribute-name.multipart.nix, entity.other.attribute-name.single.nix + settings + + foreground + #8aadf4 + + + + name + Nix parameter names + scope + variable.parameter.name.nix + settings + + foreground + #cad3f5 + fontStyle + + + + + name + Nix interpolated parameter names + scope + meta.embedded variable.parameter.name.nix + settings + + foreground + #b7bdf8 + fontStyle + + + + + name + Nix paths + scope + string.unquoted.path.nix + settings + + foreground + #f5bde6 + fontStyle + + + + + name + PHP Attributes + scope + support.attribute.builtin, meta.attribute.php + settings + + foreground + #eed49f + + + + name + PHP Parameters (needed for the leading dollar sign) + scope + meta.function.parameters.php punctuation.definition.variable.php + settings + + foreground + #ee99a0 + + + + name + PHP Constants (null, __FILE__, etc.) + scope + constant.language.php + settings + + foreground + #c6a0f6 + + + + name + PHP functions + scope + text.html.php support.function + settings + + foreground + #91d7e3 + + + + name + PHPdoc keywords + scope + keyword.other.phpdoc.php + settings + + fontStyle + + + + + name + Python argument functions reset to text, otherwise they inherit blue from function-call + scope + support.variable.magic.python, meta.function-call.arguments.python + settings + + foreground + #cad3f5 + + + + name + Python double underscore functions + scope + support.function.magic.python + settings + + foreground + #91d7e3 + fontStyle + italic + + + + name + Python `self` keyword + scope + variable.parameter.function.language.special.self.python, variable.language.special.self.python + settings + + foreground + #ed8796 + fontStyle + italic + + + + name + python keyword flow/logical (for ... in) + scope + keyword.control.flow.python, keyword.operator.logical.python + settings + + foreground + #c6a0f6 + + + + name + python storage type + scope + storage.type.function.python + settings + + foreground + #c6a0f6 + + + + name + python function support + scope + support.token.decorator.python, meta.function.decorator.identifier.python + settings + + foreground + #91d7e3 + + + + name + python function calls + scope + meta.function-call.python + settings + + foreground + #8aadf4 + + + + name + python function decorators + scope + entity.name.function.decorator.python, punctuation.definition.decorator.python + settings + + foreground + #f5a97f + fontStyle + italic + + + + name + python placeholder reset to normal string + scope + constant.character.format.placeholder.other.python + settings + + foreground + #f5bde6 + + + + name + Python exception & builtins such as exit() + scope + support.type.exception.python, support.function.builtin.python + settings + + foreground + #f5a97f + + + + name + entity.name.type + scope + support.type.python + settings + + foreground + #c6a0f6 + + + + name + python constants (True/False) + scope + constant.language.python + settings + + foreground + #f5a97f + + + + name + Arguments accessed later in the function body + scope + meta.indexed-name.python, meta.item-access.python + settings + + foreground + #ee99a0 + fontStyle + italic + + + + name + Python f-strings/binary/unicode storage types + scope + storage.type.string.python + settings + + foreground + #a6da95 + fontStyle + italic + + + + name + Python type hints + scope + meta.function.parameters.python + settings + + fontStyle + + + + + name + Regex string begin/end in JS/TS + scope + string.regexp punctuation.definition.string.begin, string.regexp punctuation.definition.string.end + settings + + foreground + #f5bde6 + + + + name + Regex anchors (^, $) + scope + keyword.control.anchor.regexp + settings + + foreground + #c6a0f6 + + + + name + Regex regular string match + scope + string.regexp.ts + settings + + foreground + #cad3f5 + + + + name + Regex group parenthesis & backreference (\1, \2, \3, ...) + scope + punctuation.definition.group.regexp, keyword.other.back-reference.regexp + settings + + foreground + #a6da95 + + + + name + Regex character class [] + scope + punctuation.definition.character-class.regexp + settings + + foreground + #eed49f + + + + name + Regex character classes (\d, \w, \s) + scope + constant.other.character-class.regexp + settings + + foreground + #f5bde6 + + + + name + Regex range + scope + constant.other.character-class.range.regexp + settings + + foreground + #f4dbd6 + + + + name + Regex quantifier + scope + keyword.operator.quantifier.regexp + settings + + foreground + #8bd5ca + + + + name + Regex constant/numeric + scope + constant.character.numeric.regexp + settings + + foreground + #f5a97f + + + + name + Regex lookaheads, negative lookaheads, lookbehinds, negative lookbehinds + scope + punctuation.definition.group.no-capture.regexp, meta.assertion.look-ahead.regexp, meta.assertion.negative-look-ahead.regexp + settings + + foreground + #8aadf4 + + + + name + Rust attribute + scope + meta.annotation.rust, meta.annotation.rust punctuation, meta.attribute.rust, punctuation.definition.attribute.rust + settings + + foreground + #eed49f + fontStyle + italic + + + + name + Rust attribute strings + scope + meta.attribute.rust string.quoted.double.rust, meta.attribute.rust string.quoted.single.char.rust + settings + + fontStyle + + + + + name + Rust keyword + scope + entity.name.function.macro.rules.rust, storage.type.module.rust, storage.modifier.rust, storage.type.struct.rust, storage.type.enum.rust, storage.type.trait.rust, storage.type.union.rust, storage.type.impl.rust, storage.type.rust, storage.type.function.rust, storage.type.type.rust + settings + + foreground + #c6a0f6 + fontStyle + + + + + name + Rust u/i32, u/i64, etc. + scope + entity.name.type.numeric.rust + settings + + foreground + #c6a0f6 + fontStyle + + + + + name + Rust generic + scope + meta.generic.rust + settings + + foreground + #f5a97f + + + + name + Rust impl + scope + entity.name.impl.rust + settings + + foreground + #eed49f + fontStyle + italic + + + + name + Rust module + scope + entity.name.module.rust + settings + + foreground + #f5a97f + + + + name + Rust trait + scope + entity.name.trait.rust + settings + + foreground + #eed49f + fontStyle + italic + + + + name + Rust struct + scope + storage.type.source.rust + settings + + foreground + #eed49f + + + + name + Rust union + scope + entity.name.union.rust + settings + + foreground + #eed49f + + + + name + Rust enum member + scope + meta.enum.rust storage.type.source.rust + settings + + foreground + #8bd5ca + + + + name + Rust macro + scope + support.macro.rust, meta.macro.rust support.function.rust, entity.name.function.macro.rust + settings + + foreground + #8aadf4 + fontStyle + italic + + + + name + Rust lifetime + scope + storage.modifier.lifetime.rust, entity.name.type.lifetime + settings + + foreground + #8aadf4 + fontStyle + italic + + + + name + Rust string formatting + scope + string.quoted.double.rust constant.other.placeholder.rust + settings + + foreground + #f5bde6 + + + + name + Rust return type generic + scope + meta.function.return-type.rust meta.generic.rust storage.type.rust + settings + + foreground + #cad3f5 + + + + name + Rust functions + scope + meta.function.call.rust + settings + + foreground + #8aadf4 + + + + name + Rust angle brackets + scope + punctuation.brackets.angle.rust + settings + + foreground + #91d7e3 + + + + name + Rust constants + scope + constant.other.caps.rust + settings + + foreground + #f5a97f + + + + name + Rust function parameters + scope + meta.function.definition.rust variable.other.rust + settings + + foreground + #ee99a0 + + + + name + Rust closure variables + scope + meta.function.call.rust variable.other.rust + settings + + foreground + #cad3f5 + + + + name + Rust self + scope + variable.language.self.rust + settings + + foreground + #ed8796 + + + + name + Rust metavariable names + scope + variable.other.metavariable.name.rust, meta.macro.metavariable.rust keyword.operator.macro.dollar.rust + settings + + foreground + #f5bde6 + + + + name + Shell shebang + scope + comment.line.shebang, comment.line.shebang punctuation.definition.comment, comment.line.shebang, punctuation.definition.comment.shebang.shell, meta.shebang.shell + settings + + foreground + #f5bde6 + fontStyle + italic + + + + name + Shell shebang command + scope + comment.line.shebang constant.language + settings + + foreground + #8bd5ca + fontStyle + italic + + + + name + Shell interpolated command + scope + meta.function-call.arguments.shell punctuation.definition.variable.shell, meta.function-call.arguments.shell punctuation.section.interpolation, meta.function-call.arguments.shell punctuation.definition.variable.shell, meta.function-call.arguments.shell punctuation.section.interpolation + settings + + foreground + #ed8796 + + + + name + Shell interpolated command variable + scope + meta.string meta.interpolation.parameter.shell variable.other.readwrite + settings + + foreground + #f5a97f + fontStyle + italic + + + + scope + source.shell punctuation.section.interpolation, punctuation.definition.evaluation.backticks.shell + settings + + foreground + #8bd5ca + + + + name + Shell EOF + scope + entity.name.tag.heredoc.shell + settings + + foreground + #c6a0f6 + + + + name + Shell quoted variable + scope + string.quoted.double.shell variable.other.normal.shell + settings + + foreground + #cad3f5 + + + + scope + markup.heading.typst + settings + + foreground + #ed8796 + + + + name + JSON Keys + scope + source.json meta.mapping.key string + settings + + foreground + #8aadf4 + + + + name + JSON key surrounding quotes + scope + source.json meta.mapping.key punctuation.definition.string.begin, source.json meta.mapping.key punctuation.definition.string.end + settings + + foreground + #939ab7 + + + + scope + markup.heading.synopsis.man, markup.heading.title.man, markup.heading.other.man, markup.heading.env.man + settings + + foreground + #c6a0f6 + + + + scope + markup.heading.commands.man + settings + + foreground + #8aadf4 + + + + scope + markup.heading.env.man + settings + + foreground + #f5bde6 + + + + name + Man page options + scope + entity.name + settings + + foreground + #8bd5ca + + + + scope + markup.heading.1.markdown + settings + + foreground + #ed8796 + + + + scope + markup.heading.2.markdown + settings + + foreground + #f5a97f + + + + scope + markup.heading.markdown + settings + + foreground + #eed49f + + + + + \ No newline at end of file diff --git a/system-config/config/user/common/yazi/init.lua b/system-config/config/user/common/yazi/init.lua new file mode 100644 index 0000000..6de153a --- /dev/null +++ b/system-config/config/user/common/yazi/init.lua @@ -0,0 +1,20 @@ +require("git"):setup { + order = 1500, +} + +require("githead"):setup { + branch_prefix = "on ", + branch_symbol = " ", + branch_borders = "()", + branch_color = "#ffb05c", + remote_branch_color = "#f4c7a1", + tag_color = "#ffcc99", + commit_color = "#f4c7a1", + behind_remote_color = "#ff6b4a", + ahead_remote_color = "#ff8800", + stashes_color = "#c98a45", + state_color = "#ff6b4a", + staged_color = "#ff8800", + unstaged_color = "#ffb05c", + untracked_color = "#f4c7a1", +} diff --git a/system-config/config/user/common/yazi/keymap.toml b/system-config/config/user/common/yazi/keymap.toml new file mode 100644 index 0000000..4bcab57 --- /dev/null +++ b/system-config/config/user/common/yazi/keymap.toml @@ -0,0 +1,16 @@ +#:schema https://yazi-rs.github.io/schemas/keymap.json + +[[mgr.prepend_keymap]] +on = "l" +run = "plugin smart-enter" +desc = "Enter the child directory, or open the file" + +[[mgr.prepend_keymap]] +on = ["g", "d"] +run = "cd ~/downloads" +desc = "Cd to Downloads" + +[[mgr.prepend_keymap]] +on = ["g", "D"] +run = "cd ~/cloud/documents" +desc = "Cd to Documents" diff --git a/system-config/config/user/common/yazi/package.toml b/system-config/config/user/common/yazi/package.toml new file mode 100644 index 0000000..8cd3198 --- /dev/null +++ b/system-config/config/user/common/yazi/package.toml @@ -0,0 +1,19 @@ +[[plugin.deps]] +use = "yazi-rs/plugins:smart-enter" +rev = "0be29a9" +hash = "187cc58ba7ac3befd49c342129e6f1b6" + +[[plugin.deps]] +use = "yazi-rs/plugins:git" +rev = "3f2b882" +hash = "336548d9f67926e12d5584f60b790f1" + +[[plugin.deps]] +use = "llanosrocas/githead" +rev = "317d09f" +hash = "41dc37a724b2afd21da4f1ea6c1182d8" + +[[flavor.deps]] +use = "yazi-rs/flavors:catppuccin-macchiato" +rev = "be0b21d" +hash = "5b1bf96d1cba26376087c37de993c517" diff --git a/system-config/config/user/common/yazi/plugins/git.yazi/LICENSE b/system-config/config/user/common/yazi/plugins/git.yazi/LICENSE new file mode 100644 index 0000000..fb5b1d6 --- /dev/null +++ b/system-config/config/user/common/yazi/plugins/git.yazi/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 yazi-rs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/system-config/config/user/common/yazi/plugins/git.yazi/README.md b/system-config/config/user/common/yazi/plugins/git.yazi/README.md new file mode 100644 index 0000000..f9fc3ae --- /dev/null +++ b/system-config/config/user/common/yazi/plugins/git.yazi/README.md @@ -0,0 +1,84 @@ +# git.yazi + +Show the status of Git file changes as linemode in the file list. + +https://github.com/user-attachments/assets/34976be9-a871-4ffe-9d5a-c4cdd0bf4576 + +## Installation + +```sh +ya pkg add yazi-rs/plugins:git +``` + +## Setup + +Add the following to your `~/.config/yazi/init.lua`: + +```lua +require("git"):setup { + -- Order of status signs showing in the linemode + order = 1500, +} +``` + +And register it as fetchers in your `~/.config/yazi/yazi.toml`: + +```toml +[[plugin.prepend_fetchers]] +url = "*" +run = "git" +group = "git" + +[[plugin.prepend_fetchers]] +url = "*/" +run = "git" +group = "git" +``` + +## Advanced + +You can customize the [Style](https://yazi-rs.github.io/docs/configuration/theme#types.style) of the status sign with: + +- `[git].unknown` - status cannot/not yet determined +- `[git].modified` - modified file +- `[git].added` - added file +- `[git].untracked` - untracked file +- `[git].ignored` - ignored file +- `[git].deleted` - deleted file +- `[git].updated` - updated file +- `[git].clean` - clean file + +For example: + +```toml +# theme.toml / flavor.toml +[git] +modified = { fg = "blue" } +deleted = { fg = "red", bold = true } +``` + +You can also customize the text of the status sign with: + +- `[git].unknown_sign` - status cannot/not yet determined +- `[git].modified_sign` - modified file +- `[git].added_sign` - added file +- `[git].untracked_sign` - untracked file +- `[git].ignored_sign` - ignored file +- `[git].deleted_sign` - deleted file +- `[git].updated_sign` - updated file +- `[git].clean_sign` - clean file + +For example: + +```toml +# theme.toml / flavor.toml +[git] +unknown_sign = " " +modified_sign = "M" +deleted_sign = "D" +clean_sign = "✔" +``` + +## License + +This plugin is MIT-licensed. For more information check the [LICENSE](LICENSE) file. diff --git a/system-config/config/user/common/yazi/plugins/git.yazi/main.lua b/system-config/config/user/common/yazi/plugins/git.yazi/main.lua new file mode 100644 index 0000000..0f8cd71 --- /dev/null +++ b/system-config/config/user/common/yazi/plugins/git.yazi/main.lua @@ -0,0 +1,279 @@ +--- @since 26.5.6 + +local WINDOWS = ya.target_family() == "windows" + +-- The code of supported git status, +-- also used to determine which status to show for directories when they contain different statuses +-- see `bubble_up` +---@enum CODES +local CODES = { + unknown = 100, -- status cannot/not yet determined + excluded = 99, -- ignored directory + ignored = 6, -- ignored file + untracked = 5, + modified = 4, + added = 3, + deleted = 2, + updated = 1, + clean = 0, +} + +local PATTERNS = { + { "!$", CODES.ignored }, + { "?$", CODES.untracked }, + { "[MT]", CODES.modified }, + { "[AC]", CODES.added }, + { "D", CODES.deleted }, + { "U", CODES.updated }, + { "[AD][AD]", CODES.updated }, +} + +local function theme() + local t = th.git or {} + return { + [CODES.unknown] = t.unknown or ui.Style(), + [CODES.ignored] = t.ignored or ui.Style():fg("darkgray"), + [CODES.untracked] = t.untracked or ui.Style():fg("magenta"), + [CODES.modified] = t.modified or ui.Style():fg("yellow"), + [CODES.added] = t.added or ui.Style():fg("green"), + [CODES.deleted] = t.deleted or ui.Style():fg("red"), + [CODES.updated] = t.updated or ui.Style():fg("yellow"), + [CODES.clean] = t.clean or ui.Style(), + }, { + [CODES.unknown] = t.unknown_sign or "", + [CODES.ignored] = t.ignored_sign or " ", + [CODES.untracked] = t.untracked_sign or "? ", + [CODES.modified] = t.modified_sign or " ", + [CODES.added] = t.added_sign or " ", + [CODES.deleted] = t.deleted_sign or " ", + [CODES.updated] = t.updated_sign or " ", + [CODES.clean] = t.clean_sign or "", + } +end + +---@param line string +---@return CODES, string +local function match(line) + local signs = line:sub(1, 2) + for _, p in ipairs(PATTERNS) do + local path, pattern, code = nil, p[1], p[2] + if signs:find(pattern) then + path = line:sub(4, 4) == '"' and line:sub(5, -2) or line:sub(4) + path = WINDOWS and path:gsub("/", "\\") or path + end + if not path then + elseif path:find("[/\\]$") then + -- Mark the ignored directory as `excluded`, so we can process it further within `propagate_down` + return code == CODES.ignored and CODES.excluded or code, path:sub(1, -2) + else + return code, path + end + ---@diagnostic disable-next-line: missing-return + end +end + +---@param cwd Url +---@return string? +local function root(cwd) + local is_worktree = function(url) + local file, head = io.open(tostring(url)), nil + if file then + head = file:read(8) + file:close() + end + return head == "gitdir: " + end + + repeat + local next = cwd:join(".git") + local cha = fs.cha(next) + if cha and (cha.is_dir or is_worktree(next)) then + return tostring(cwd) + end + cwd = cwd.parent + until not cwd +end + +---@param changed Changes +---@return Changes +local function bubble_up(changed) + local new, empty = {}, Url("") + for path, code in pairs(changed) do + if code ~= CODES.ignored then + local url = Url(path).parent + while url and url ~= empty do + local s = tostring(url) + new[s] = (new[s] or CODES.clean) > code and new[s] or code + url = url.parent + end + end + end + return new +end + +---@param excluded string[] +---@param cwd Url +---@param repo Url +---@return Changes +local function propagate_down(excluded, cwd, repo) + local new, rel = {}, cwd:strip_prefix(repo) + for _, path in ipairs(excluded) do + if rel:starts_with(path) then + -- If `cwd` is a subdirectory of an excluded directory, also mark it as `excluded` + new[tostring(cwd)] = CODES.excluded + elseif cwd == repo:join(path).parent then + -- If `path` is a direct subdirectory of `cwd`, mark it as `ignored` + new[path] = CODES.ignored + else + -- Skipping, we only care about `cwd` itself and its direct subdirectories for maximum performance + end + end + return new +end + +---@param cwd string +---@param repo string +---@param changed Changes +local add = ya.sync(function(st, cwd, repo, changed) + ---@cast st State + + st.dirs[cwd] = repo + st.repos[repo] = st.repos[repo] or {} + for path, code in pairs(changed) do + if code == CODES.clean then + st.repos[repo][path] = nil + elseif code == CODES.excluded then + -- Mark the directory with a special value `excluded` so that it can be distinguished during UI rendering + st.dirs[path] = CODES.excluded + else + st.repos[repo][path] = code + end + end + ui.render() +end) + +---@param cwd string +local remove = ya.sync(function(st, cwd) + ---@cast st State + + local repo = st.dirs[cwd] + if not repo then + return + end + + ui.render() + st.dirs[cwd] = nil + if not st.repos[repo] then + return + end + + for _, r in pairs(st.dirs) do + if r == repo then + return + end + end + st.repos[repo] = nil +end) + +---@param st State +---@param opts Options +local function setup(st, opts) + st.dirs = {} + st.repos = {} + + opts = opts or {} + opts.order = opts.order or 1500 + + local styles, signs = theme() + ps.sub("theme", function() + styles, signs = theme() + end) + + Linemode:children_add(function(self) + if not self._file.in_current then + return "" + end + + local url = self._file.url + local repo = st.dirs[tostring(url.base or url.parent)] + local code = CODES.unknown + if repo then + code = repo == CODES.excluded and CODES.ignored or st.repos[repo][tostring(url):sub(#repo + 2)] or CODES.clean + end + + if signs[code] == "" then + return "" + elseif self._file.is_hovered then + return ui.Line { " ", signs[code] } + else + return ui.Line { " ", ui.Span(signs[code]):style(styles[code]) } + end + end, opts.order) +end + +---@type UnstableFetcher +local function fetch(_, job) + local cwd = job.files[1].url.base or job.files[1].url.parent + local repo = root(cwd) + if not repo then + remove(tostring(cwd)) + return true + end + + local paths = {} + for _, file in ipairs(job.files) do + paths[#paths + 1] = tostring(file.url) + end + + -- stylua: ignore + local output, err = Command("git") + :cwd(tostring(cwd)) + :arg({ "--no-optional-locks", "-c", "core.quotePath=", "status", "--porcelain", "-unormal", "--no-renames", "--ignored=matching" }) + :arg(paths) + :output() + if not output then + return true, Err("Cannot spawn `git` command, error: %s", err) + end + + local changed, excluded = {}, {} + for line in output.stdout:gmatch("[^\r\n]+") do + local code, path = match(line) + if code == CODES.excluded then + excluded[#excluded + 1] = path + else + changed[path] = code + end + end + + if job.files[1].cha.is_dir then + ya.dict_merge(changed, bubble_up(changed)) + end + ya.dict_merge(changed, propagate_down(excluded, cwd, Url(repo))) + + -- Reset the status of any files that don't appear in the output of `git status` to `clean`, + -- so that cleaning up outdated statuses from `st.repos` + for _, path in ipairs(paths) do + local s = path:sub(#repo + 2) + changed[s] = changed[s] or CODES.clean + end + + add(tostring(cwd), repo, changed) + + return false +end + +-- TODO: remove +local function fetch_compact(self, job) + if ya.throttle then + fetch(self, job) + return ya.co(function() + for _, file in ipairs(job.files) do + coroutine.yield(file, { retry = true }) + end + end) + else + return fetch(self, job) + end +end + +return { setup = setup, fetch = fetch_compact } diff --git a/system-config/config/user/common/yazi/plugins/git.yazi/types.lua b/system-config/config/user/common/yazi/plugins/git.yazi/types.lua new file mode 100644 index 0000000..9936849 --- /dev/null +++ b/system-config/config/user/common/yazi/plugins/git.yazi/types.lua @@ -0,0 +1,12 @@ +---@class State +---@field dirs table Mapping between a directory and its corresponding repository +---@field repos table Mapping between a repository and the status of each of its files + +---@class Options +---@field order number The order in which the status is displayed +---@field renamed boolean Whether to include renamed files in the status (or treat them as modified) + +-- TODO: move this to `types.yazi` once it's get stable +---@alias UnstableFetcher fun(self: unknown, job: { files: File[] }): boolean, Error? + +---@alias Changes table diff --git a/system-config/config/user/common/yazi/plugins/githead.yazi/LICENSE b/system-config/config/user/common/yazi/plugins/githead.yazi/LICENSE new file mode 100644 index 0000000..82a69f6 --- /dev/null +++ b/system-config/config/user/common/yazi/plugins/githead.yazi/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 llanosrocas + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/system-config/config/user/common/yazi/plugins/githead.yazi/README.md b/system-config/config/user/common/yazi/plugins/githead.yazi/README.md new file mode 100644 index 0000000..c6c4f82 --- /dev/null +++ b/system-config/config/user/common/yazi/plugins/githead.yazi/README.md @@ -0,0 +1,422 @@ +# githead.yazi + +Git status header for yazi inspired by [powerlevel10k](https://github.com/romkatv/powerlevel10k?tab=readme-ov-file#what-do-different-symbols-in-git-status-mean). + +![preview-default](https://github.com/llanosrocas/githead.yazi/blob/main/.github/images/preview-default.png) +![preview-fullscreen](https://github.com/llanosrocas/githead.yazi/blob/main/.github/images/preview-fullscreen.png) + +All supported features are listed [here](#features). More presets are available [here](#presets). + +## Requirements + +- yazi version >= [26.1.22](https://github.com/sxyazi/yazi/releases/tag/v26.1.22). +- Font with symbol support. For example [Nerd Fonts](https://www.nerdfonts.com/). + +## Compatibility + +To keep the plugin up to date, there are two branches: `main` and `nightly`. +The `main` branch follows major yazi releases, while `nightly` is linked to specific yazi commits or changes. + +This setup allows shipping stable versions on time, while giving early access to "cutting-edge" changes. See matrix below. + +
+Compatibility matrix + +| githead | yazi | +| :-------------------------------------------------------------------------------------------------------: | ----------------------------------------------------------------------------------------- | +| [v2.0.2](https://github.com/llanosrocas/githead.yazi/releases/tag/v2.0.2) | [v26.1.22](https://github.com/sxyazi/yazi/releases/tag/v26.1.22) | +| [v2.0.2](https://github.com/llanosrocas/githead.yazi/releases/tag/v2.0.2) | [v25.12.29](https://github.com/sxyazi/yazi/releases/tag/v25.12.29) | +| [v2.0.2-nightly.292d37c](https://github.com/llanosrocas/githead.yazi/releases/tag/v2.0.2-nightly.292d37c) | [2f66561](https://github.com/sxyazi/yazi/commit/2f66561a8251f8788b2b0fd366af90555ecafc86) | +| [v2.0.2-nightly.292d37c](https://github.com/llanosrocas/githead.yazi/releases/tag/v2.0.2-nightly.292d37c) | [6cfa92f](https://github.com/sxyazi/yazi/commit/6cfa92f11205d212155579b5b76d4cbabe723829) | +| [v2.0.2-nightly.292d37c](https://github.com/llanosrocas/githead.yazi/releases/tag/v2.0.2-nightly.292d37c) | [292d37c](https://github.com/sxyazi/yazi/commit/292d37ccde5fb09dc7318247caf552213d16ec36) | +| [v2.0.1](https://github.com/llanosrocas/githead.yazi/releases/tag/v2.0.1) | [917e1f5](https://github.com/sxyazi/yazi/commit/917e1f54a10445f2e25147c4b81a3c77d8233632) | +| [v2.0.0](https://github.com/llanosrocas/githead.yazi/releases/tag/v2.0.0) | [693dff2](https://github.com/sxyazi/yazi/commit/693dff25e3165e357cc9d0b94ca3f2b176741a36) | +| [v1.7.0](https://github.com/llanosrocas/githead.yazi/releases/tag/v1.7.0) | [693dff2](https://github.com/sxyazi/yazi/tree/693dff25e3165e357cc9d0b94ca3f2b176741a36) | +| [v1.6.0](https://github.com/llanosrocas/githead.yazi/releases/tag/v1.6.0) | [v25.5.31](https://github.com/sxyazi/yazi/releases/tag/v25.5.31) | + +
+ +## Installation + +1. Using yazi package manager + +```sh +ya pkg add llanosrocas/githead +``` + +_Or manually copy `main.lua` to the `~/.config/yazi/plugins/githead.yazi/main.lua`_ + +2. Add this line to your `~/.config/yazi/init.lua`: + +```lua +require("githead"):setup() +``` + +## Configuration + +This is default config, if you want to see copy-paste presets go to [this section](#presets). + +```lua +require("githead"):setup({ + order = { + "__spacer__", + "branch", + "remote", + "__spacer__", + "tag", + "__spacer__", + "commit", + "__spacer__", + "behind_ahead_remote", + "__spacer__", + "stashes", + "__spacer__", + "state", + "__spacer__", + "staged", + "__spacer__", + "unstaged", + "__spacer__", + "untracked", + }, + + show_numbers = true, -- shows staged, unstaged, untracked, stashes count + + show_branch = true, + branch_prefix = "", + branch_color = "blue", + branch_symbol = "", + branch_borders = "", + + show_remote_branch = true, -- only shown if different from local branch + always_show_remote_branch = false, -- always show remote branch even if it the same as local branch + always_show_remote_repo = false, -- Adds `origin/` if `always_show_remote_branch` is enabled + remote_branch_prefix = ":", + remote_branch_color = "bright magenta", + + show_tag = true, -- only shown if branch is not available + always_show_tag = false, + tag_color = "magenta", + tag_symbol = "#", + + show_commit = true, -- only shown if branch AND tag are not available + always_show_commit = false, + commit_color = "bright magenta", + commit_symbol = "@", + + show_behind_ahead_remote = true, + behind_remote_color = "bright magenta", + behind_remote_symbol = "⇣", + ahead_remote_color = "bright magenta", + ahead_remote_symbol = "⇡", + + show_stashes = true, + stashes_color = "bright magenta", + stashes_symbol = "$", + + show_state = true, + show_state_prefix = true, + state_color = "red", + state_symbol = "~", + + show_staged = true, + staged_color = "bright yellow", + staged_symbol = "+", + + show_unstaged = true, + unstaged_color = "bright yellow", + unstaged_symbol = "!", + + show_untracked = true, + untracked_color = "blue", + untracked_symbol = "?", +}) +``` + +``` +/cwd on ( feature):origin/main #v1.0.0 #1234567 ⇣2⇡3 $1 rebase 1/2 ~2 +4 !1 ?5 +| | | | ││ | | | | | | | | | | | | +| | | | ││ | | | | | | | | | | | └─── untracked_symbol +| | | | ││ | | | | | | | | | | └────── unstaged_symbol +| | | | ││ | | | | | | | | | └───────── staged_symbol +| | | | ││ | | | | | | | | └──────────── state_symbol +| | | | ││ | | | | | | | └─────────────────────── state_prefix +| | | | ││ | | | | | | └────────────────────────── stashes_symbol +| | | | ││ | | | | | └───────────────────────────── ahead_symbol +| | | | ││ | | | | └─────────────────────────────── behind_symbol +| | | | ││ | | | └──────────────────────────────────────── commit_symbol +| | | | ││ | | └──────────────────────────────────────────────── tag_symbol +| | | | ││ | └───────────────────────────────────────────────────── remote_branch +| | | | ││ └─────────────────────────────────────────────────────────── remote_repo +| | | | |└───────────────────────────────────────────────────────────── remote_branch_prefix +| | | | └────────────────────────────────────────────────────────────── branch_borders +| | | └──────────────────────────────────────────────────────────────────── branch +| | └─────────────────────────────────────────────────────────────────────── branch_symbol +| └─────────────────────────────────────────────────────────────────────────── branch_prefix +└──────────────────────────────────────────────────────────────────────────────── cwd +``` + +## Presets + +- v1 default (My config) + + ![preview-fav](https://github.com/llanosrocas/githead.yazi/blob/main/.github/images/preview-fav.png) + +
+ Config + + ```lua + require("githead"):setup({ + branch_prefix = "on", + branch_symbol = " ", + branch_borders = "()", + }) + ``` + +
+ +- [spaceship-prompt](https://github.com/spaceship-prompt/spaceship-prompt) + + ![preview-spaceship](https://github.com/llanosrocas/githead.yazi/blob/main/.github/images/preview-spaceship.png) + +
+ Config + + ```lua + require("githead"):setup({ + order = { + "__spacer__", + "branch", + "remote", + "__spacer__", + "tag", + "__spacer__", + "commit", + "__spacer__", + "behind_ahead_remote", + "stashes", + "state", + "staged", + "unstaged", + "untracked", + }, + + show_numbers = false, + + branch_symbol = " ", + branch_prefix = "on", + }) + ``` + +
+ +- [robertgzr/porcelain](https://github.com/robertgzr/porcelain) + + ![preview-porcelain](https://github.com/llanosrocas/githead.yazi/blob/main/.github/images/preview-porcelain.png) + +
+ Config + + ```lua + require("githead"):setup({ + order = { + "__spacer__", + "branch", + "commit", + "__spacer__", + "behind_ahead_remote", + "__spacer__", + "untracked", + "state", + "unstaged", + "__spacer__", + "staged", + }, + + show_numbers = false, + + show_branch = true, + branch_prefix = "", + branch_color = "#288BD2", + + always_show_commit = true, + commit_color = "#859A00", + + show_behind_ahead_remote = true, + behind_remote_symbol = "↓", + ahead_remote_symbol = "↑", + behind_remote_color = "#DC322E", + ahead_remote_color = "#4DB6AC", + + show_state = true, + show_state_prefix = false, + state_symbol = "!!", + state_color = "#B58901", + + staged_symbol = "✔", + staged_color = "green", + + unstaged_symbol = "Δ", + unstaged_color = "#288BD2", + + untracked_symbol = "?", + untracked_color = "#415F65", + }) + ``` + +
+ +- Minimal (No color) + + ![preview-minimal-no-color](https://github.com/llanosrocas/githead.yazi/blob/main/.github/images/preview-minimal-no-color.png) + +
+ Config + + ```lua + require("githead"):setup({ + branch_color = "", + + remote_branch_prefix = "@", + remote_branch_color = "", + + tag_color = "", + + commit_color = "", + + ahead_remote_symbol = "+", + ahead_remote_color = "", + behind_remote_symbol = "-", + behind_remote_color = "", + + stashes_symbol = "*", + stashes_color = "", + + show_state_prefix = false, + state_symbol = "~", + state_color = "", + + staged_symbol = "S", + staged_color = "", + + unstaged_symbol = "U", + unstaged_color = "", + + untracked_symbol = "N", + untracked_color = "", + }) + ``` + +
+ +- Vibrant (Full) + + ![preview-vibrant-full](https://github.com/llanosrocas/githead.yazi/blob/main/.github/images/preview-vibrant-full.png) + +
+ Config + + ```lua + require("githead"):setup({ + order = { + "__spacer__", + "stashes", + "__spacer__", + "state", + "__spacer__", + "staged", + "__spacer__", + "unstaged", + "__spacer__", + "untracked", + "__spacer__", + "branch", + "remote_branch", + "__spacer__", + "tag", + "__spacer__", + "commit", + "__spacer__", + "behind_ahead_remote", + "__spacer__", + }, + + branch_borders = "{}", + branch_prefix = "|", + branch_color = "#7aa2f7", + remote_branch_color = "#9ece6a", + always_show_remote_branch = true, + always_show_remote_repo = true, + + tag_symbol = "󰓼", + always_show_tag = true, + tag_color = "#bb9af7", + + commit_symbol = "", + always_show_commit = true, + commit_color = "#e0af68", + + staged_color = "#73daca", + staged_symbol = "●", + + unstaged_color = "#e0af68", + unstaged_symbol = "✗", + + untracked_color = "#f7768e", + untracked_symbol = "?", + + state_color = "#f5c359", + state_symbol = "󱐋", + + stashes_color = "#565f89", + stashes_symbol = "⚑", + }) + ``` + +
+ +## Features + +- [x] Current branch +- [x] Remote and remote branch +- [x] Latest tag +- [x] Latest commit +- [x] Behind/Ahead of the remote +- [x] Stashes +- [x] States + - [x] merge + - [x] cherry + - [x] rebase (+ done counter) + - [x] revert + - [x] bisect +- [x] Staged +- [x] Unstaged +- [x] Untracked + +### Under the hood + +The goal is to use minimum amount of shell commands. + +- Branch, stashes, staged files, unstaged files, untracked files. + +```shell +git status --ignore-submodules=dirty --branch --show-stash --ahead-behind +``` + +- Remote branch and repo: + +```shell +git rev-parse --abbrev-ref --symbolic-full-name @{upstream} +``` + +- Latest tag and commit: + +```shell +git log --format="commit %h%d" -n 1 +``` + +## Credits + +- [yazi source code](https://github.com/sxyazi/yazi) +- [powerlevel10k](https://github.com/romkatv/powerlevel10k) +- [twio142](https://github.com/twio142/githead.yazi) diff --git a/system-config/config/user/common/yazi/plugins/githead.yazi/main.lua b/system-config/config/user/common/yazi/plugins/githead.yazi/main.lua new file mode 100644 index 0000000..6c7f74d --- /dev/null +++ b/system-config/config/user/common/yazi/plugins/githead.yazi/main.lua @@ -0,0 +1,598 @@ +---@diagnostic disable: undefined-global + +local save = ya.sync(function(this, cwd, output) + if cwd:match("%.git[/\\]") or cwd:match("%.git$") then + this.output = nil + return + end + if cx.active.current.cwd == Url(cwd) then + this.output = output + ui.render() + end +end) + +return { + setup = function(this, options) + options = options or {} + + local config = { + order = options.order or { + "__spacer__", + "branch", + "remote_branch", + "__spacer__", + "tag", + "__spacer__", + "commit", + "__spacer__", + "behind_ahead_remote", + "__spacer__", + "stashes", + "__spacer__", + "state", + "__spacer__", + "staged", + "__spacer__", + "unstaged", + "__spacer__", + "untracked", + }, + + show_numbers = options.show_numbers == nil and true + or options.show_numbers, + + show_branch = options.show_branch == nil and true or options.show_branch, + branch_prefix = options.branch_prefix or "", + branch_color = options.branch_color or "blue", + branch_symbol = options.branch_symbol or "", + branch_borders = options.branch_borders or "", + + show_remote_branch = options.show_remote_branch == nil and true + or options.show_remote_branch, + always_show_remote_branch = options.always_show_remote_branch == nil + and false + or options.always_show_remote_branch, + always_show_remote_repo = options.always_show_remote_repo == nil + and false + or options.always_show_remote_repo, + remote_branch_prefix = options.remote_branch_prefix or ":", + remote_branch_color = options.remote_branch_color or "bright magenta", + + show_tag = options.show_tag == nil and true or options.show_tag, + always_show_tag = options.always_show_tag == nil and false + or options.always_show_tag, + tag_color = options.tag_color or "magenta", + tag_symbol = options.tag_symbol == nil and "#" or options.tag_symbol, + + show_commit = options.show_commit == nil and true or options.show_commit, + always_show_commit = options.always_show_commit == nil and false + or options.always_show_commit, + commit_color = options.commit_color or "bright magenta", + commit_symbol = options.commit_symbol == nil and "@" + or options.commit_symbol, + + show_behind_ahead_remote = options.show_behind_ahead_remote == nil + and true + or options.show_behind_ahead_remote, + behind_remote_color = options.behind_remote_color or "bright magenta", + behind_remote_symbol = options.behind_remote_symbol or "⇣", + ahead_remote_color = options.ahead_remote_color or "bright magenta", + ahead_remote_symbol = options.ahead_remote_symbol or "⇡", + + show_stashes = options.show_stashes == nil and true + or options.show_stashes, + stashes_color = options.stashes_color or "bright magenta", + stashes_symbol = options.stashes_symbol or "$", + + show_state = options.show_state == nil and true or options.show_state, + show_state_prefix = options.show_state_prefix == nil and true + or options.show_state_prefix, + state_color = options.state_color or "red", + state_symbol = options.state_symbol or "~", + + show_staged = options.show_staged == nil and true or options.show_staged, + staged_color = options.staged_color or "bright yellow", + staged_symbol = options.staged_symbol or "+", + + show_unstaged = options.show_unstaged == nil and true + or options.show_unstaged, + unstaged_color = options.unstaged_color or "bright yellow", + unstaged_symbol = options.unstaged_symbol or "!", + + show_untracked = options.show_untracked == nil and true + or options.show_untracked, + untracked_color = options.untracked_color or "bright blue", + untracked_symbol = options.untracked_symbol or "?", + } + + --- @param data GitStatusData + function Header:render_branch(data) + local branch = data.branch + + if not branch then + return nil + end + + local left_border = config.branch_borders:sub(1, 1) + local right_border = config.branch_borders:sub(2, 2) + + local branch_string = "" + + if config.branch_symbol == "" then + branch_string = left_border .. branch .. right_border + else + branch_string = left_border + .. config.branch_symbol + .. branch + .. right_border + end + + local branch_prefix = config.branch_prefix == "" and "" + or config.branch_prefix .. " " + + return ui.Line({ + ui.Span(branch_prefix), + ui.Span(branch_string):fg(config.branch_color), + }) + end + + --- @param data GitStatusData + function Header:render_remote_branch(data) + local branch = data.branch + local remote_branch = data.remote_branch + local remote_repo = data.remote_repo + + if not remote_branch then + return nil + end + + local show_remote = config.always_show_remote_branch + or branch ~= remote_branch + if not show_remote then + return nil + end + + local remote_branch_label = config.always_show_remote_repo + and (remote_repo .. "/" .. remote_branch) + or remote_branch + + return ui.Line({ + ui.Span(config.remote_branch_prefix), + ui.Span(remote_branch_label):fg(config.remote_branch_color), + }) + end + + --- @param data GitStatusData + function Header:render_tag(data) + local branch = data.branch + local tag = data.tag + + if not tag then + return nil + end + + if not branch or config.always_show_tag then + return ui.Line({ + ui.Span(config.tag_symbol), + ui.Span(tag):fg(config.tag_color), + }) + end + end + + --- @param data GitStatusData + function Header:render_commit(data) + local branch = data.branch + local tag = data.tag + local commit = data.commit + + if not commit then + return nil + end + + if (not branch and not tag) or config.always_show_commit then + return ui.Line({ + ui.Span(config.commit_symbol), + ui.Span(commit):fg(config.commit_color), + }) + end + end + + --- @param data GitStatusData + function Header:render_behind_ahead_remote(data) + local behind = data.behind_remote + local ahead = data.ahead_remote + + local behind_label = behind + and behind > 0 + and ui.Span( + config.behind_remote_symbol .. (config.show_numbers and behind or "") + ):fg(config.behind_remote_color) + + local ahead_label = ahead + and ahead > 0 + and ui.Span( + config.ahead_remote_symbol .. (config.show_numbers and ahead or "") + ):fg(config.ahead_remote_color) + + if ahead_label and behind_label then + return ui.Line({ + behind_label, + ahead_label, + }) + elseif ahead_label then + return ahead_label + elseif behind_label then + return behind_label + else + return nil + end + end + + --- @param data GitStatusData + function Header:render_stashes(data) + local stashes_count = data.stashes + + if not stashes_count then + return nil + end + + local stashes_label = config.stashes_symbol + + if config.show_numbers then + stashes_label = stashes_label .. stashes_count + end + + return ui.Span(stashes_label):fg(config.stashes_color) + end + + --- @param data GitStatusData + function Header:render_state(data) + local state_prefix = data.state_prefix + local unmerged_count = data.unmerged_count + local rebase_done = data.rebase_done + local rebase_total = data.rebase_total + + if state_prefix then + local state_label = "" + if config.show_state_prefix then + state_label = state_prefix + if + state_prefix == "rebase-i" + and config.show_numbers + and rebase_done + and rebase_total + then + state_label = state_label + .. " " + .. rebase_done + .. "/" + .. rebase_total + end + end + + local unmerged_label = "" + local symbol = "" + + if unmerged_count and unmerged_count > 0 then + symbol = " " .. config.state_symbol + if config.show_numbers then + unmerged_label = tostring(unmerged_count) + end + end + + return ui.Span(state_label .. symbol .. unmerged_label) + :fg(config.state_color) + end + return nil + end + + --- @param data GitStatusData + function Header:render_staged(data) + local staged = data.staged + + if staged then + local staged_label = config.staged_symbol + + if config.show_numbers then + local filtered_staged = staged:gsub("^[%s]*%b()[%s]*", "") + + local staged_count = 0 + for line in filtered_staged:gmatch("[^\r\n]+") do + if line:match("%S") then + staged_count = staged_count + 1 + end + end + + staged_label = staged_label .. staged_count + end + + return ui.Span(staged_label):fg(config.staged_color) + end + + return nil + end + + --- @param data GitStatusData + function Header:render_unstaged(data) + local unstaged = data.unstaged + + if unstaged then + local unstaged_label = config.unstaged_symbol + + if config.show_numbers then + local filtered_unstaged = + unstaged:gsub("^[%s]*%b()[\r\n]*", ""):gsub("^[%s]*%b()[\r\n]*", "") + + local unstaged_count = 0 + for line in filtered_unstaged:gmatch("[^\r\n]+") do + if line:match("%S") then + unstaged_count = unstaged_count + 1 + end + end + + unstaged_label = unstaged_label .. unstaged_count + end + + return ui.Span(unstaged_label):fg(config.unstaged_color) + end + + return nil + end + + --- @param data GitStatusData + function Header:render_untracked(data) + local untracked = data.untracked + + if untracked then + local untracked_label = config.untracked_symbol + + if config.show_numbers then + local filtered_untracked = untracked:gsub("^[%s]*%b()[\r\n]*", "") + local untracked_count = 0 + + for line in filtered_untracked:gmatch("[^\r\n]+") do + if line:match("%S") then + untracked_count = untracked_count + 1 + end + end + + untracked_label = untracked_label .. untracked_count + end + + return ui.Span(untracked_label):fg(config.untracked_color) + end + + return nil + end + + function Header:githead() + --- @type GitStatusData + local data = this.output + + if not data then + return ui.Line({}) + end + + local head = {} + + for _, key in ipairs(config.order) do + if key == "__spacer__" then + if head[#head] ~= " " then + table.insert(head, " ") + end + else + local fn = self["render_" .. key] + local is_shown = config["show_" .. key] + + if fn and is_shown then + local value = fn(self, data) + if value then + table.insert(head, value) + end + end + end + end + + return ui.Line(head) + end + + Header:children_add(Header.githead, 2000, Header.LEFT) + + local callback = function() + local cwd = cx.active.current.cwd + ya.emit("plugin", { + this._id, + ya.quote(tostring(cwd), true), + }) + end + + ps.sub("cd", callback) + ps.sub("rename", callback) + ps.sub("bulk", callback) + ps.sub("move", callback) + ps.sub("trash", callback) + ps.sub("delete", callback) + ps.sub("tab", callback) + end, + + entry = function(_, job) + local args = job.args or job + + ---@class GitStatusData + ---@field ahead_remote? integer + ---@field behind_remote? integer + ---@field branch? string + ---@field commit? string + ---@field rebase_done? integer + ---@field remote_branch? string + ---@field remote_repo? string + ---@field staged? string + ---@field stashes? integer + ---@field state_prefix? "bisect"|"cherry"|"merge"|"rebase-i"|"revert" + ---@field tag? string + ---@field unmerged_count? integer + ---@field unstaged? string + ---@field untracked? string + local data = {} + + --- @param status string + local get_behind_ahead_remote = function(status) + local diverged_ahead, diverged_behind = + status:match("have (%d+) and (%d+) different") + if diverged_ahead and diverged_behind then + data.behind_remote = tonumber(diverged_behind) + data.ahead_remote = tonumber(diverged_ahead) + else + local behind_remote = status:match("behind %S+ by (%d+) commits?") + local ahead_remote = status:match("ahead of %S+ by (%d+) commits?") + + data.behind_remote = tonumber(behind_remote) + data.ahead_remote = tonumber(ahead_remote) + end + end + + --- @param status string + local get_branch = function(status) + data.branch = status:match("On branch (%S+)") + end + + --- @param status string + local get_stashes = function(status) + data.stashes = tonumber(status:match("Your stash currently has (%d+)")) + end + + --- @param status string + local get_state = function(status) + local unmerged = status:match("Unmerged paths:%s*(.-)%s*\n\n") + if unmerged then + local filtered_unmerged = + unmerged:gsub("^[%s]*%b()[%s]*", ""):gsub("^[%s]*%b()[%s]*", "") + local count = 0 + for line in filtered_unmerged:gmatch("[^\r\n]+") do + if line:match("%S") then + count = count + 1 + end + end + data.unmerged_count = count + end + + if status:find("git merge") then + data.state_prefix = "merge" + elseif status:find("git cherry%-pick") then + data.state_prefix = "cherry" + elseif status:find("git rebase") then + data.state_prefix = "rebase-i" + local commands_done = status:match("%((%d+) commands? done%)") + if commands_done then + data.rebase_done = commands_done + if not data.unmerged_count then + data.rebase_total = commands_done + else + data.rebase_total = data.unmerged_count + end + end + elseif status:find("git revert") then + data.state_prefix = "revert" + elseif status:find("git bisect") then + data.state_prefix = "bisect" + end + end + + --- @param status string + local get_staged = function(status) + data.staged = status:match("Changes to be committed:%s*(.-)%s*\n\n") + end + + --- @param status string + local get_unstaged = function(status) + data.unstaged = + status:match("Changes not staged for commit:%s*(.-)%s*\n\n") + end + + --- @param status string + local get_untracked = function(status) + data.untracked = status:match("Untracked files:%s*(.-)%s*\n\n") + end + + local get_status = function() + local cmd = Command("git") + :arg({ + "status", + "--ignore-submodules=dirty", + "--branch", + "--show-stash", + "--ahead-behind", + }) + :cwd(args[1]) + :env("LANGUAGE", "en_US.UTF-8") + :stdout(Command.PIPED) + local cmd_output = cmd:output() + + if cmd_output then + local status = cmd_output.stdout + + get_branch(status) + get_behind_ahead_remote(status) + get_stashes(status) + get_state(status) + get_staged(status) + get_unstaged(status) + get_untracked(status) + end + end + + local get_remote_branch = function() + local cmd = Command("git") + :arg({ + "rev-parse", + "--abbrev-ref", + "--symbolic-full-name", + "@{upstream}", + }) + :cwd(args[1]) + :env("LANGUAGE", "en_US.UTF-8") + :stdout(Command.PIPED) + local cmd_output = cmd:output() + + if cmd_output then + local remote_branch = cmd_output.stdout + + data.remote_branch = + remote_branch:gsub("[\r\n]", ""):match("^[^/]+/(.+)") + data.remote_repo = remote_branch:gsub("[\r\n]", ""):match("^([^/]+)/") + end + end + + --- @param log string + local get_tag = function(log) + data.tag = log:match("tag: ([^, )]+)") + end + + --- @param log string + local get_commit = function(log) + data.commit = log:match("^commit%s+([a-f0-9]+)") + end + + local get_git_log = function() + local cmd = Command("git") + :arg({ "log", "--format=commit %h%d", "-n", "1" }) + :cwd(args[1]) + :env("LANGUAGE", "en_US.UTF-8") + :stdout(Command.PIPED) + local cmd_output = cmd:output() + + if cmd_output then + local log = cmd_output.stdout + + get_tag(log) + get_commit(log) + end + end + + get_status() + get_remote_branch() + get_git_log() + + save(args[1], data) + end, +} diff --git a/system-config/config/user/common/yazi/plugins/smart-enter.yazi/LICENSE b/system-config/config/user/common/yazi/plugins/smart-enter.yazi/LICENSE new file mode 100644 index 0000000..fb5b1d6 --- /dev/null +++ b/system-config/config/user/common/yazi/plugins/smart-enter.yazi/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 yazi-rs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/system-config/config/user/common/yazi/plugins/smart-enter.yazi/README.md b/system-config/config/user/common/yazi/plugins/smart-enter.yazi/README.md new file mode 100644 index 0000000..9c19bc7 --- /dev/null +++ b/system-config/config/user/common/yazi/plugins/smart-enter.yazi/README.md @@ -0,0 +1,40 @@ +# smart-enter.yazi + +[`Open`][open] files or [`enter`][enter] directories all in one key! + +## Installation + +```sh +ya pkg add yazi-rs/plugins:smart-enter +``` + +## Usage + +Bind your l key to the plugin, in your `~/.config/yazi/keymap.toml`: + +```toml +[[mgr.prepend_keymap]] +on = "l" +run = "plugin smart-enter" +desc = "Enter the child directory, or open the file" +``` + +## Advanced + +By default, `--hovered` is passed to the [`open`][open] action, make the behavior consistent with [`enter`][enter] avoiding accidental triggers, +which means both will only target the currently hovered file. + +If you still want `open` to target multiple selected files, add this to your `~/.config/yazi/init.lua`: + +```lua +require("smart-enter"):setup { + open_multi = true, +} +``` + +## License + +This plugin is MIT-licensed. For more information check the [LICENSE](LICENSE) file. + +[open]: https://yazi-rs.github.io/docs/configuration/keymap/#mgr.open +[enter]: https://yazi-rs.github.io/docs/configuration/keymap/#mgr.enter diff --git a/system-config/config/user/common/yazi/plugins/smart-enter.yazi/main.lua b/system-config/config/user/common/yazi/plugins/smart-enter.yazi/main.lua new file mode 100644 index 0000000..e9e2ec6 --- /dev/null +++ b/system-config/config/user/common/yazi/plugins/smart-enter.yazi/main.lua @@ -0,0 +1,11 @@ +--- @since 25.5.31 +--- @sync entry + +local function setup(self, opts) self.open_multi = opts.open_multi end + +local function entry(self) + local h = cx.active.current.hovered + ya.emit(h and h.cha.is_dir and "enter" or "open", { hovered = not self.open_multi }) +end + +return { entry = entry, setup = setup } diff --git a/system-config/config/user/common/yazi/theme.toml b/system-config/config/user/common/yazi/theme.toml new file mode 100644 index 0000000..ea509c2 --- /dev/null +++ b/system-config/config/user/common/yazi/theme.toml @@ -0,0 +1,125 @@ +# Ember OLED: aligned with Noctalia's "Ember OLED" palette and tmux status line. +[flavor] +dark = "catppuccin-macchiato" + +[mgr] +cwd = { fg = "#ffb05c", bold = true } +find_keyword = { fg = "#ffcc99", bold = true, underline = true } +find_position = { fg = "#ff8800", bold = true } +marker_copied = { fg = "#c98a45", bg = "#c98a45" } +marker_cut = { fg = "#ff6b4a", bg = "#ff6b4a" } +marker_marked = { fg = "#ff8800", bg = "#ff8800" } +marker_selected = { fg = "#ffcc99", bg = "#ffcc99" } +count_copied = { fg = "#050403", bg = "#c98a45" } +count_cut = { fg = "#050403", bg = "#ff6b4a" } +count_selected = { fg = "#050403", bg = "#ffcc99" } +border_symbol = "│" +border_style = { fg = "#6e4a31" } + +[tabs] +active = { fg = "#fff1e6", bg = "#1a110c", bold = true } +inactive = { fg = "#f4c7a1", bg = "#050403" } + +[mode] +normal_main = { fg = "#050403", bg = "#ff8800", bold = true } +normal_alt = { fg = "#ffb05c", bg = "#1a110c" } +select_main = { fg = "#050403", bg = "#ffb05c", bold = true } +select_alt = { fg = "#ffb05c", bg = "#1a110c" } +unset_main = { fg = "#050403", bg = "#ff6b4a", bold = true } +unset_alt = { fg = "#ff6b4a", bg = "#1a110c" } + +[status] +perm_sep = { fg = "#6e4a31" } +perm_type = { fg = "#ffb05c" } +perm_read = { fg = "#fff1e6" } +perm_write = { fg = "#ff6b4a" } +perm_exec = { fg = "#c98a45" } +progress_label = { fg = "#fff1e6", bold = true } +progress_normal = { fg = "#ff8800", bg = "#1a110c" } +progress_error = { fg = "#fff1e6", bg = "#ff6b4a" } + +[pick] +border = { fg = "#ff8800" } +active = { fg = "#ffb05c", bold = true } + +[input] +border = { fg = "#ff8800" } +selected = { fg = "#050403", bg = "#ffb05c" } + +[cmp] +border = { fg = "#ff8800" } + +[tasks] +border = { fg = "#ff8800" } +title = { fg = "#fff1e6" } +hovered = { fg = "#ffb05c", bold = true } + +[which] +border = { fg = "#ff8800" } +cand = { fg = "#ffb05c" } +rest = { fg = "#f4c7a1" } +desc = { fg = "#fff1e6" } +separator = "  " +separator_style = { fg = "#6e4a31" } + +[help] +on = { fg = "#ffb05c" } +run = { fg = "#fff1e6" } +hovered = { fg = "#050403", bg = "#ffb05c", bold = true } +footer = { fg = "#fff1e6", bg = "#1a110c" } + +[spot] +border = { fg = "#ff8800" } +title = { fg = "#ffb05c" } +tbl_col = { fg = "#ffb05c" } +tbl_cell = { fg = "#fff1e6", bg = "#1a110c" } + +[notify] +title_info = { fg = "#c98a45" } +title_warn = { fg = "#ffb05c" } +title_error = { fg = "#ff6b4a" } + +[filetype] +rules = [ + { mime = "**/image/*", fg = "#ffcc99" }, + { mime = "**/{audio,video}/*", fg = "#c98a45" }, + { mime = "**/application/{zip,rar,7z*,tar,gzip,xz,zstd,bzip*,lzma,compress,archive,cpio,arj,xar,ms-cab*}", fg = "#ffb05c" }, + { mime = "**/application/{pdf,doc,rtf}", fg = "#f4c7a1" }, + { mime = "vfs/{absent,stale}", fg = "#6e4a31" }, + { url = "*", fg = "#fff1e6" }, + { url = "*/", fg = "#ffb05c" }, +] + +[icon] +dirs = [ + { name = ".config", text = "", fg = "#ffb05c" }, + { name = ".git", text = "", fg = "#ff8800" }, + { name = ".github", text = "", fg = "#ffb05c" }, + { name = "Desktop", text = "", fg = "#ffb05c" }, + { name = "Documents", text = "", fg = "#ffb05c" }, + { name = "Downloads", text = "", fg = "#ffb05c" }, + { name = "Pictures", text = "", fg = "#ffcc99" }, + { name = "Videos", text = "", fg = "#c98a45" }, +] +conds = [ + { if = "orphan", text = "", fg = "#ff6b4a" }, + { if = "link", text = "", fg = "#6e4a31" }, + { if = "dir & hovered", text = "", fg = "#ffcc99" }, + { if = "dir", text = "", fg = "#ffb05c" }, + { if = "exec", text = "", fg = "#c98a45" }, + { if = "!dir", text = "", fg = "#fff1e6" }, +] + +[git] +modified = { fg = "#ffb05c" } +added = { fg = "#c98a45" } +untracked = { fg = "#f4c7a1" } +deleted = { fg = "#ff6b4a", bold = true } +updated = { fg = "#ff8800" } +ignored = { fg = "#6e4a31" } +modified_sign = "M " +added_sign = "A " +untracked_sign = "? " +deleted_sign = "D " +updated_sign = "U " +ignored_sign = "I " diff --git a/system-config/config/user/common/yazi/yazi.toml b/system-config/config/user/common/yazi/yazi.toml new file mode 100644 index 0000000..c30aa15 --- /dev/null +++ b/system-config/config/user/common/yazi/yazi.toml @@ -0,0 +1,9 @@ +[[plugin.prepend_fetchers]] +url = "*" +run = "git" +group = "git" + +[[plugin.prepend_fetchers]] +url = "*/" +run = "git" +group = "git" diff --git a/system-config/config/user/desktop/hypr/machine.lua b/system-config/config/user/desktop/hypr/machine.lua new file mode 100644 index 0000000..2c4f2b8 --- /dev/null +++ b/system-config/config/user/desktop/hypr/machine.lua @@ -0,0 +1,23 @@ +-- Desktop-only display and startup profile. +return { + monitors = { + { + output = "HDMI-A-1", + mode = "3840x2160@120", + position = "0x0", + scale = 1, + }, + }, + startup = { + "noctalia", + "hypridle", + -- Electron's tray icon registers only when the SNI watcher already exists. + "sh -c 'sleep 2; exec mullvad-vpn'", + "obsidian", + "thunderbird", + "nextcloud", + "spotify", + "brave", + "alacritty -e tmux", + }, +} diff --git a/system-config/config/user/desktop/noctalia/90-machine.toml b/system-config/config/user/desktop/noctalia/90-machine.toml new file mode 100644 index 0000000..4b01449 --- /dev/null +++ b/system-config/config/user/desktop/noctalia/90-machine.toml @@ -0,0 +1,91 @@ +# Desktop-specific shell layout and hardware telemetry. +# Pi Status Bridge is installed locally from ~/projects/pi-status-bridge. +[plugins] +enabled = ["alex/system-status", "alex/pi-status-bridge", "alex/knowledge-lookup"] + +[widget.pi-status] +type = "alex/pi-status-bridge:bridge" + +[bar.main] +position = "bottom" +layer = "top" +scale = 0.95 +capsule_radius = 5 +center = ["group:g3", "notifications"] +end = ["group:g2", "group:g6", "group:g1", "session"] +font_weight = 400 +margin_ends = 0 +padding = 0 +panel_overlap = 3 +radius = 0 +shadow = false +# Keep the desktop status bar visible and reserve its screen edge. +auto_hide = false +smart_auto_hide = false +reserve_space = true +start = ["tray", "group:g4", "privacy"] +thickness = 39 +widget_spacing = 10 + +[[bar.main.capsule_group]] +enabled = true +fill = "surface_variant" +id = "g1" +members = ["idle-inhibitors", "caffeine", "date", "clock"] +opacity = 1.0 +padding = 6.0 + +[[bar.main.capsule_group]] +enabled = true +fill = "surface_variant" +id = "g2" +members = ["network", "bluetooth"] +opacity = 1.0 +padding = 6.0 + +[[bar.main.capsule_group]] +enabled = true +fill = "surface_variant" +id = "g3" +members = ["launcher", "media", "control-center", "clipboard"] +opacity = 1.0 +padding = 6.0 +radius = 5.0 + +[[bar.main.capsule_group]] +enabled = true +fill = "surface_variant" +id = "g4" +members = ["pi-status", "cpu", "temp", "ram", "gpu", "gpu_temp", "disk"] +opacity = 1.0 +padding = 6.0 + +[[bar.main.capsule_group]] +enabled = true +fill = "surface_variant" +id = "g6" +members = ["input_volume", "volume"] +opacity = 1.0 +padding = 6.0 +radius = 5.0 + +[widget.gpu] +type = "sysmon" +stat = "gpu_usage" +visualization = "graph" +show_value = true + +[widget.gpu_temp] +type = "sysmon" +stat = "gpu_temp" +visualization = "none" +show_value = true + +[control_center] +sidebar = "full" +sidebar_section = "full" +width = 880 + +[system.monitor] +# GPU polling is disabled by Noctalia by default; enable it for the RTX 3090 widgets. +gpu_poll_seconds = 2.0 diff --git a/system-config/config/user/laptop/hypr/machine.lua b/system-config/config/user/laptop/hypr/machine.lua new file mode 100644 index 0000000..5493998 --- /dev/null +++ b/system-config/config/user/laptop/hypr/machine.lua @@ -0,0 +1,30 @@ +-- Laptop profile: leave monitor selection to Hyprland's hotplug support. +-- Noctalia replaces the i3-era Redshift, Polybar, Blueman Applet, and Picom stack. +return { + input = { + touchpad = { + -- Standard laptop behavior: one/two/three-finger taps map to + -- left/right/middle click, and a second tap held down starts a drag. + tap_to_click = true, + tap_button_map = "lrm", + tap_and_drag = true, + drag_lock = false, + clickfinger_behavior = true, + disable_while_typing = true, + + -- Preserve natural two-finger scrolling at the default sensitivity. + natural_scroll = true, + scroll_factor = 1.0, + }, + }, + monitors = {}, + startup = { + "noctalia", + -- Start after Noctalia has claimed the StatusNotifier watcher. + "sh -c 'sleep 2; exec mullvad-vpn'", + "obsidian", + "thunderbird", + "nextcloud", + "spotify", + }, +} diff --git a/system-config/config/user/laptop/noctalia/90-machine.toml b/system-config/config/user/laptop/noctalia/90-machine.toml new file mode 100644 index 0000000..802a381 --- /dev/null +++ b/system-config/config/user/laptop/noctalia/90-machine.toml @@ -0,0 +1,88 @@ +# Laptop-specific Noctalia layout. Shared privacy/theme defaults live in 10-base.toml. +[bar.main] +position = "top" +layer = "top" +scale = 0.95 +capsule_radius = 5 +center = ["group:g3", "notifications"] +end = ["group:g2", "group:g6", "group:g5", "group:g1", "session"] +font_weight = 400 +margin_ends = 0 +padding = 0 +panel_overlap = 3 +radius = 0 +shadow = false +auto_hide = false +smart_auto_hide = false +reserve_space = true +start = ["tray", "group:g4"] +# Accommodate the battery-rate widget's wattage + ETA lines without clipping. +thickness = 39 +widget_spacing = 10 + +[[bar.main.capsule_group]] +enabled = true +fill = "surface_variant" +id = "g1" +members = ["idle-inhibitors", "caffeine", "date", "clock"] +opacity = 1.0 +padding = 6.0 + +[[bar.main.capsule_group]] +enabled = true +fill = "surface_variant" +id = "g2" +members = ["network", "bluetooth"] +opacity = 1.0 +padding = 6.0 + +[[bar.main.capsule_group]] +enabled = true +fill = "surface_variant" +id = "g3" +members = ["launcher", "media", "control-center", "clipboard"] +opacity = 1.0 +padding = 6.0 +radius = 5.0 + +[[bar.main.capsule_group]] +enabled = true +fill = "surface_variant" +id = "g4" +members = ["cpu", "temp", "ram", "gpu-mode", "disk"] +opacity = 1.0 +padding = 6.0 + +[[bar.main.capsule_group]] +enabled = true +fill = "surface_variant" +id = "g5" +members = ["battery-rate", "battery", "power_profile"] +opacity = 1.0 +padding = 6.0 +radius = 5.0 + +[[bar.main.capsule_group]] +enabled = true +fill = "surface_variant" +id = "g6" +members = ["input_volume", "volume"] +opacity = 1.0 +padding = 6.0 +radius = 5.0 + +[widget.battery-rate] +type = "alex/system-status:battery-rate" + +[widget.gpu-mode] +type = "alex/system-status:gpu-mode" + +# Only suspend-then-hibernate after 30 minutes when UPower confirms battery power. +# The command fails closed on AC or when UPower is unavailable. +[idle.behavior.suspend-then-hibernate] +command = "busctl get-property org.freedesktop.UPower /org/freedesktop/UPower org.freedesktop.UPower OnBattery 2>/dev/null | grep -qx 'b true' && loginctl lock-session && systemctl suspend-then-hibernate" + +[control_center] +sidebar = "full" +sidebar_section = "full" +width = 720 diff --git a/system-config/config/user/laptop/noctalia/99-extend.toml b/system-config/config/user/laptop/noctalia/99-extend.toml new file mode 100644 index 0000000..c5c8186 --- /dev/null +++ b/system-config/config/user/laptop/noctalia/99-extend.toml @@ -0,0 +1,59 @@ +# Local, untracked Noctalia overrides. +# This file is loaded after the shared base (10-base.toml) and machine profile +# (90-machine.toml). Keep host-only experiments and machine-local adjustments here. +# +# Example: +# [bar.main] +# thickness = 32 + +[plugins] +enabled = [ + "alex/system-status", + "alex/pi-status-bridge", + "alex/knowledge-lookup", +] + +[widget.pi-status] +type = "alex/pi-status-bridge:bridge" + +[widget.knowledge-lookup] +type = "alex/knowledge-lookup:bar" + +[bar.main] +center = ["pi-status", "knowledge-lookup", "group:g3", "notifications"] +end = ["group:g2", "group:g6", "group:g5", "group:g1", "session"] + +# Migrated from Noctalia's GUI-managed state so these host preferences survive +# resetting the state override file. +[location] +address = "Leipzig, Germany" + +[lockscreen_widgets] +enabled = false +schema_version = 2 +widget_order = ["lockscreen-login-box@eDP-1"] + +[lockscreen_widgets.grid] +cell_size = 16 +major_interval = 4 +visible = true + +[lockscreen_widgets.widget."lockscreen-login-box@eDP-1"] +box_height = 70.0 +box_width = 400.0 +cx = 960.0 +cy = 1081.0 +output = "eDP-1" +rotation = 0.0 +type = "login_box" + +[lockscreen_widgets.widget."lockscreen-login-box@eDP-1".settings] +background_color = "surface_variant" +background_opacity = 0.88 +background_radius = 12.0 +center_password_text = false +input_opacity = 1.0 +input_radius = 6.0 +show_caps_lock = true +show_keyboard_layout = true +show_login_button = true diff --git a/system-config/hosts.py b/system-config/hosts.py new file mode 100644 index 0000000..84b8a30 --- /dev/null +++ b/system-config/hosts.py @@ -0,0 +1,32 @@ +"""Machine profiles compose concerns; concerns never depend on a host.""" + +from modules.host_system import desktop_system, laptop_system +from modules.packages import PackageConcern + +COMMON = ( + "common/core", + "common/wayland", + "common/fonts", + "common/development", + "common/productivity", + "common/media", + "common/research", + "common/network", + "common/services", +) +LAPTOP = ("laptop/hardware",) +DESKTOP = () + + +def _concerns(names: tuple[str, ...]) -> list[PackageConcern]: + return [PackageConcern(name) for name in names] + + +def laptop() -> list: + """Laptop: shared concerns, laptop hardware, and laptop system configuration.""" + return [*_concerns((*COMMON, *LAPTOP)), *laptop_system()] + + +def desktop() -> list: + """Future desktop: shared concerns plus desktop-only concerns when declared.""" + return [*_concerns((*COMMON, *DESKTOP)), *desktop_system()] diff --git a/system-config/manifests/common/base-system.txt b/system-config/manifests/common/base-system.txt new file mode 100644 index 0000000..8cbe903 --- /dev/null +++ b/system-config/manifests/common/base-system.txt @@ -0,0 +1,7 @@ +getty@.service +remote-fs.target +systemd-resolved-monitor.socket +systemd-resolved-varlink.socket +systemd-resolved.service +systemd-timesyncd.service +systemd-userdbd.socket diff --git a/system-config/manifests/common/desktop-user.txt b/system-config/manifests/common/desktop-user.txt new file mode 100644 index 0000000..f726d94 --- /dev/null +++ b/system-config/manifests/common/desktop-user.txt @@ -0,0 +1,6 @@ +gnome-keyring-daemon.socket +p11-kit-server.socket +pipewire-pulse.socket +pipewire.socket +wireplumber.service +xdg-user-dirs.service diff --git a/system-config/manifests/common/development-system.txt b/system-config/manifests/common/development-system.txt new file mode 100644 index 0000000..b408ae7 --- /dev/null +++ b/system-config/manifests/common/development-system.txt @@ -0,0 +1 @@ +docker.service diff --git a/system-config/manifests/common/local-user.txt b/system-config/manifests/common/local-user.txt new file mode 100644 index 0000000..0f9eb5f --- /dev/null +++ b/system-config/manifests/common/local-user.txt @@ -0,0 +1,3 @@ +idle-inhibitor-monitor.service +pi-status-bridge.service +ssh-agent.service diff --git a/system-config/manifests/common/network-services-system.txt b/system-config/manifests/common/network-services-system.txt new file mode 100644 index 0000000..4d24fbc --- /dev/null +++ b/system-config/manifests/common/network-services-system.txt @@ -0,0 +1,6 @@ +avahi-daemon.service +avahi-daemon.socket +avahi-dnsconfd.service +cups.path +cups.service +cups.socket diff --git a/system-config/manifests/common/network-system.txt b/system-config/manifests/common/network-system.txt new file mode 100644 index 0000000..4b73e03 --- /dev/null +++ b/system-config/manifests/common/network-system.txt @@ -0,0 +1,3 @@ +NetworkManager-dispatcher.service +NetworkManager-wait-online.service +NetworkManager.service diff --git a/system-config/manifests/common/privacy-system.txt b/system-config/manifests/common/privacy-system.txt new file mode 100644 index 0000000..44704d1 --- /dev/null +++ b/system-config/manifests/common/privacy-system.txt @@ -0,0 +1,2 @@ +mullvad-daemon.service +mullvad-early-boot-blocking.service diff --git a/system-config/manifests/laptop/hardware-system.txt b/system-config/manifests/laptop/hardware-system.txt new file mode 100644 index 0000000..c5d6e2a --- /dev/null +++ b/system-config/manifests/laptop/hardware-system.txt @@ -0,0 +1,6 @@ +bluetooth.service +keyd.service +lm_sensors.service +thermald.service +tlp-pd.service +tlp.service diff --git a/system-config/modules/README.md b/system-config/modules/README.md new file mode 100644 index 0000000..2298efd --- /dev/null +++ b/system-config/modules/README.md @@ -0,0 +1,10 @@ +# Modules + +Only two adapters belong here: + +- `packages.py` turns package concern lists into Decman package declarations. +- `host_system.py` deploys reviewed system policy, user configuration, and enabled units for a host. + +Keep policy in `packages/`, `config/`, and `manifests/`; keep this directory +thin and reusable. User configuration belongs under `config/user/{common,laptop,desktop}/` +and is installed with owner `alex`. diff --git a/system-config/modules/__init__.py b/system-config/modules/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/system-config/modules/host_system.py b/system-config/modules/host_system.py new file mode 100644 index 0000000..66b855d --- /dev/null +++ b/system-config/modules/host_system.py @@ -0,0 +1,238 @@ +"""System files and concern-scoped unit enablement for machine profiles.""" + +from __future__ import annotations + +from pathlib import Path + +import decman +from decman.plugins import systemd + +ROOT = Path(__file__).resolve().parents[1] +MANIFESTS = ROOT / "manifests" + + +def _units(*names: str) -> set[str]: + units: set[str] = set() + for name in names: + units |= { + line.strip() + for line in (MANIFESTS / name).read_text(encoding="utf-8").splitlines() + if line.strip() + } + return units + + +class UserConfigConcern(decman.Module): + """Home-directory configuration owned by a named local user.""" + + def __init__( + self, + name: str, + files: dict[str, str] | None = None, + directories: dict[str, str] | None = None, + user: str = "alex", + ) -> None: + super().__init__(f"user:{name}") + self._files = files or {} + self._directories = directories or {} + self.user = user + + def files(self) -> dict[str, decman.File]: + return { + target: decman.File( + source_file=str(ROOT / source), owner=self.user, permissions=0o644 + ) + for target, source in self._files.items() + } + + def directories(self) -> dict[str, decman.Directory]: + return { + target: decman.Directory( + source_directory=str(ROOT / source), + bin_files=True, + owner=self.user, + permissions=0o644, + ) + for target, source in self._directories.items() + } + + +class SystemConcern(decman.Module): + """One host/system concern with explicit files and unit manifests.""" + + def __init__( + self, + name: str, + files: dict[str, str | tuple[str, int]] | None = None, + system_manifests: tuple[str, ...] = (), + user_manifests: tuple[str, ...] = (), + user: str = "alex", + ) -> None: + super().__init__(f"system:{name}") + self._files = files or {} + self._system_manifests = system_manifests + self._user_manifests = user_manifests + self.user = user + + def files(self) -> dict[str, decman.File]: + return { + target: decman.File( + source_file=str( + ROOT / (source[0] if isinstance(source, tuple) else source) + ), + permissions=source[1] if isinstance(source, tuple) else 0o644, + ) + for target, source in self._files.items() + } + + def on_change(self, store: decman.Store) -> None: + if self.name == "system:locale": + decman.prg(["locale-gen"]) + if self.name == "system:boot": + decman.prg(["mkinitcpio", "-P"]) + + @systemd.units + def system_units(self) -> set[str]: + return _units(*self._system_manifests) + + @systemd.user_units + def user_units(self) -> dict[str, set[str]]: + return {self.user: _units(*self._user_manifests)} + + +def common_user_config() -> list[UserConfigConcern]: + return [ + UserConfigConcern( + "common-config", + { + "/home/alex/.config/environment.d/pi-status-bridge.conf": ( + "config/user/common/environment.d/pi-status-bridge.conf" + ), + "/home/alex/.config/fish/config.fish": "config/user/common/fish/config.fish", + "/home/alex/.config/hypr/hypridle.conf": "config/user/common/hypr/hypridle.conf", + "/home/alex/.config/hypr/hyprland.lua": "config/user/common/hypr/hyprland.lua", + "/home/alex/.config/hypr/hyprlock.conf": "config/user/common/hypr/hyprlock.conf", + "/home/alex/.config/kitty/kitty.conf": "config/user/common/kitty/kitty.conf", + "/home/alex/.config/noctalia/10-base.toml": "config/user/common/noctalia/10-base.toml", + "/home/alex/.config/noctalia/palettes/Ember OLED.json": ( + "config/user/common/noctalia/palettes/Ember OLED.json" + ), + "/home/alex/.config/systemd/user/idle-inhibitor-monitor.service": ( + "config/user/common/systemd/user/idle-inhibitor-monitor.service" + ), + "/home/alex/.config/systemd/user/pi-status-bridge.service": ( + "config/user/common/systemd/user/pi-status-bridge.service" + ), + "/home/alex/.config/systemd/user/ssh-agent.service": ( + "config/user/common/systemd/user/ssh-agent.service" + ), + }, + {"/home/alex/.config/yazi": "config/user/common/yazi"}, + ) + ] + + +def laptop_user_config() -> list[UserConfigConcern]: + return [ + UserConfigConcern( + "laptop-config", + { + "/home/alex/.config/hypr/machine.lua": "config/user/laptop/hypr/machine.lua", + "/home/alex/.config/noctalia/90-machine.toml": ( + "config/user/laptop/noctalia/90-machine.toml" + ), + "/home/alex/.config/noctalia/99-extend.toml": ( + "config/user/laptop/noctalia/99-extend.toml" + ), + }, + ) + ] + + +def desktop_user_config() -> list[UserConfigConcern]: + return [ + UserConfigConcern( + "desktop-config", + { + "/home/alex/.config/hypr/machine.lua": "config/user/desktop/hypr/machine.lua", + "/home/alex/.config/noctalia/90-machine.toml": ( + "config/user/desktop/noctalia/90-machine.toml" + ), + }, + ) + ] + + +def common_system() -> list[SystemConcern | UserConfigConcern]: + return [ + SystemConcern( + "package-policy", + { + "/etc/pacman.conf": "config/system/common/pacman/pacman.conf", + "/etc/makepkg.conf": "config/system/common/makepkg/makepkg.conf", + }, + ), + SystemConcern( + "locale", + { + "/etc/locale.conf": "config/system/common/locale/locale.conf", + "/etc/locale.gen": "config/system/common/locale/locale.gen", + }, + ), + SystemConcern( + "keyboard", + { + "/etc/vconsole.conf": "config/system/common/keyboard/vconsole.conf", + "/etc/X11/xorg.conf.d/00-keyboard.conf": "config/system/common/keyboard/x11.conf", + }, + ), + SystemConcern("base", system_manifests=("common/base-system.txt",)), + SystemConcern("network", system_manifests=("common/network-system.txt",)), + SystemConcern("desktop", user_manifests=("common/desktop-user.txt",)), + SystemConcern("local", user_manifests=("common/local-user.txt",)), + SystemConcern( + "network-services", system_manifests=("common/network-services-system.txt",) + ), + SystemConcern( + "development", system_manifests=("common/development-system.txt",) + ), + SystemConcern("privacy", system_manifests=("common/privacy-system.txt",)), + *common_user_config(), + ] + + +def laptop_system() -> list[SystemConcern | UserConfigConcern]: + return [ + *common_system(), + SystemConcern( + "laptop", + { + "/etc/keyd/default.conf": "config/system/common/keyd.conf", + "/etc/NetworkManager/conf.d/wifi-powersave.conf": "config/system/laptop/wifi-powersave.conf", + "/etc/modprobe.d/iwlwifi.conf": "config/system/laptop/iwlwifi.conf", + "/etc/X11/xorg.conf.d/30-touchpad.conf": "config/system/laptop/touchpad.conf", + "/etc/tlp.conf": "config/system/laptop/power/tlp.conf", + "/etc/systemd/sleep.conf.d/90-laptop.conf": "config/system/laptop/sleep.conf.d/90-laptop.conf", + "/etc/systemd/logind.conf.d/90-noctalia-battery-idle.conf": "config/system/laptop/systemd/90-noctalia-battery-idle.conf", + "/boot/EFI/refind/refind.conf": ( + "config/system/laptop/refind/refind.conf", + 0o755, + ), + "/boot/refind_linux.conf": ( + "config/system/laptop/refind/refind_linux.conf", + 0o755, + ), + }, + system_manifests=("laptop/hardware-system.txt",), + ), + SystemConcern( + "boot", + {"/etc/mkinitcpio.conf": "config/system/laptop/boot/mkinitcpio.conf"}, + ), + *laptop_user_config(), + ] + + +def desktop_system() -> list[SystemConcern | UserConfigConcern]: + """Desktop: shared policy plus desktop-specific home configuration.""" + return [*common_system(), *desktop_user_config()] diff --git a/system-config/modules/packages.py b/system-config/modules/packages.py new file mode 100644 index 0000000..40c1135 --- /dev/null +++ b/system-config/modules/packages.py @@ -0,0 +1,43 @@ +"""Package concern loading for Decman profiles.""" + +from __future__ import annotations + +from pathlib import Path + +import decman +from decman.plugins import aur, pacman + +ROOT = Path(__file__).resolve().parents[1] +PACKAGES = ROOT / "packages" + + +def _names(path: Path) -> set[str]: + return { + line.strip() + for line in path.read_text(encoding="utf-8").splitlines() + if line.strip() + } + + +class PackageConcern(decman.Module): + """One named concern backed by native and AUR package lists.""" + + def __init__(self, concern: str) -> None: + super().__init__(f"packages:{concern}") + self.concern = concern + + @pacman.packages + def native_packages(self) -> set[str]: + return _names(PACKAGES / f"{self.concern}-repo.txt") + + @aur.packages + def aur_packages(self) -> set[str]: + return _names(PACKAGES / f"{self.concern}-aur.txt") + + +def configure_no_removal_guard() -> None: + """Keep Decman from pruning current dependency-orphans during adoption.""" + if decman.pacman is None or decman.aur is None: + raise decman.SourceError("pacman and AUR plugins must be available") + decman.pacman.ignored_packages |= _names(PACKAGES / "ignored/repo-dependencies.txt") + decman.aur.ignored_packages |= _names(PACKAGES / "ignored/aur-dependencies.txt") diff --git a/system-config/packages/README.md b/system-config/packages/README.md new file mode 100644 index 0000000..e39dc49 --- /dev/null +++ b/system-config/packages/README.md @@ -0,0 +1,11 @@ +# Package lists + +Each concern owns package names exactly once: + +- `common/` is portable workstation functionality. +- `laptop/` is hardware and power support for this laptop. +- `desktop/` is empty until a desktop-specific dependency exists. +- `ignored/` contains dependency packages Decman must not prune during adoption. + +A host composes concerns in `../hosts.py`. Add a package to the narrowest +concern that explains why it exists, then run `../scripts/verify.py`. diff --git a/system-config/packages/common/core-aur.txt b/system-config/packages/common/core-aur.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/system-config/packages/common/core-aur.txt @@ -0,0 +1 @@ + diff --git a/system-config/packages/common/core-repo.txt b/system-config/packages/common/core-repo.txt new file mode 100644 index 0000000..5886f05 --- /dev/null +++ b/system-config/packages/common/core-repo.txt @@ -0,0 +1,36 @@ +7zip +arch-install-scripts +base +base-devel +catdoc +cowsay +cryptsetup +dmidecode +ex-vi-compat +fd +fish +fwupd +fzf +git +glances +htop +hunspell-de +jq +libsmbios +linux +linux-firmware +linux-headers +man-db +mtools +ntfs-3g +odt2txt +openssh +os-prober +refind +ripgrep +rsync +starship +unzip +yadm +zoxide +zsh diff --git a/system-config/packages/common/development-aur.txt b/system-config/packages/common/development-aur.txt new file mode 100644 index 0000000..c97ff71 --- /dev/null +++ b/system-config/packages/common/development-aur.txt @@ -0,0 +1,9 @@ +decman +engram-bin +gti +mongodb-compass-bin +pi +postman-bin +pycharm +vagrant +yay diff --git a/system-config/packages/common/development-repo.txt b/system-config/packages/common/development-repo.txt new file mode 100644 index 0000000..f32fa39 --- /dev/null +++ b/system-config/packages/common/development-repo.txt @@ -0,0 +1,14 @@ +cloudflared +docker +docker-compose +fcron +jdk-openjdk +npm +openbsd-netcat +podman +pyenv +python-pip +uv +virtualbox +virtualbox-host-dkms +yarn diff --git a/system-config/packages/common/fonts-aur.txt b/system-config/packages/common/fonts-aur.txt new file mode 100644 index 0000000..84a4527 --- /dev/null +++ b/system-config/packages/common/fonts-aur.txt @@ -0,0 +1,2 @@ +ttf-icomoon-feather +ttf-meslo-nerd-font-powerlevel10k diff --git a/system-config/packages/common/fonts-repo.txt b/system-config/packages/common/fonts-repo.txt new file mode 100644 index 0000000..4173e5a --- /dev/null +++ b/system-config/packages/common/fonts-repo.txt @@ -0,0 +1,4 @@ +noto-fonts +noto-fonts-cjk +noto-fonts-emoji +ttf-jetbrains-mono-nerd diff --git a/system-config/packages/common/media-aur.txt b/system-config/packages/common/media-aur.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/system-config/packages/common/media-aur.txt @@ -0,0 +1 @@ + diff --git a/system-config/packages/common/media-repo.txt b/system-config/packages/common/media-repo.txt new file mode 100644 index 0000000..30f7911 --- /dev/null +++ b/system-config/packages/common/media-repo.txt @@ -0,0 +1,6 @@ +audacity +cudnn +imagemagick +smplayer +steam +vlc diff --git a/system-config/packages/common/network-aur.txt b/system-config/packages/common/network-aur.txt new file mode 100644 index 0000000..e32005c --- /dev/null +++ b/system-config/packages/common/network-aur.txt @@ -0,0 +1 @@ +mullvad-vpn-beta-bin diff --git a/system-config/packages/common/network-repo.txt b/system-config/packages/common/network-repo.txt new file mode 100644 index 0000000..2d46b9d --- /dev/null +++ b/system-config/packages/common/network-repo.txt @@ -0,0 +1,8 @@ +avahi +cups +cups-pdf +dhcpcd +gvfs-smb +network-manager-applet +networkmanager +nss-mdns diff --git a/system-config/packages/common/productivity-aur.txt b/system-config/packages/common/productivity-aur.txt new file mode 100644 index 0000000..ed5ce1b --- /dev/null +++ b/system-config/packages/common/productivity-aur.txt @@ -0,0 +1,4 @@ +brave-bin +nomacs +spotify +zotero-bin diff --git a/system-config/packages/common/productivity-repo.txt b/system-config/packages/common/productivity-repo.txt new file mode 100644 index 0000000..1f36cd4 --- /dev/null +++ b/system-config/packages/common/productivity-repo.txt @@ -0,0 +1,15 @@ +bitwarden +chromium +evince +firefox +libreoffice-fresh +nextcloud-client +obsidian +okular +qalculate-gtk +qbittorrent +signal-desktop +teamspeak3 +telegram-desktop +thunderbird +torbrowser-launcher diff --git a/system-config/packages/common/research-aur.txt b/system-config/packages/common/research-aur.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/system-config/packages/common/research-aur.txt @@ -0,0 +1 @@ + diff --git a/system-config/packages/common/research-repo.txt b/system-config/packages/common/research-repo.txt new file mode 100644 index 0000000..ae00ac3 --- /dev/null +++ b/system-config/packages/common/research-repo.txt @@ -0,0 +1,25 @@ +biber +r +resvg +texlive-basic +texlive-bibtexextra +texlive-binextra +texlive-context +texlive-fontsextra +texlive-fontsrecommended +texlive-fontutils +texlive-formatsextra +texlive-games +texlive-humanities +texlive-latex +texlive-latexextra +texlive-latexrecommended +texlive-luatex +texlive-mathscience +texlive-metapost +texlive-music +texlive-pictures +texlive-plaingeneric +texlive-pstricks +texlive-publishers +texlive-xetex diff --git a/system-config/packages/common/services-aur.txt b/system-config/packages/common/services-aur.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/system-config/packages/common/services-aur.txt @@ -0,0 +1 @@ + diff --git a/system-config/packages/common/services-repo.txt b/system-config/packages/common/services-repo.txt new file mode 100644 index 0000000..a2fa66b --- /dev/null +++ b/system-config/packages/common/services-repo.txt @@ -0,0 +1,4 @@ +libva-utils +ly +pcmanfm +sddm diff --git a/system-config/packages/common/wayland-aur.txt b/system-config/packages/common/wayland-aur.txt new file mode 100644 index 0000000..4a25dc2 --- /dev/null +++ b/system-config/packages/common/wayland-aur.txt @@ -0,0 +1,2 @@ +noctalia-git +ranger-git diff --git a/system-config/packages/common/wayland-repo.txt b/system-config/packages/common/wayland-repo.txt new file mode 100644 index 0000000..3d07e7f --- /dev/null +++ b/system-config/packages/common/wayland-repo.txt @@ -0,0 +1,31 @@ +alacritty +blueman +bluez +bluez-utils +flameshot +gnome-keyring +grim +hypridle +hyprland +hyprlock +kitty +mako +neovim +pamixer +pavucontrol +pipewire-alsa +pipewire-pulse +playerctl +satty +slurp +tmux +ueberzugpp +vim +waybar +wireplumber +wl-clipboard +wlr-randr +wofi +xdg-desktop-portal-hyprland +xorg-xwayland +yazi diff --git a/system-config/packages/ignored/aur-dependencies.txt b/system-config/packages/ignored/aur-dependencies.txt new file mode 100644 index 0000000..40c5d9f --- /dev/null +++ b/system-config/packages/ignored/aur-dependencies.txt @@ -0,0 +1,20 @@ +accounts-qml-module +electron36 +gti-debug +gtk2 +libmongocrypt-debug +light-debug +litehtml0.9 +mbedtls2 +mongodb-compass-debug +mullvad-vpn-beta-bin-debug +python-pkg_resources +python-pulsectl +qtkeychain-qt5 +teams-debug +vagrant-debug +way-displays-debug +xautolock-debug +yay-debug +zotero-bin-debug +zsh-theme-powerlevel10k-git-debug diff --git a/system-config/packages/ignored/repo-dependencies.txt b/system-config/packages/ignored/repo-dependencies.txt new file mode 100644 index 0000000..dc48a0a --- /dev/null +++ b/system-config/packages/ignored/repo-dependencies.txt @@ -0,0 +1,188 @@ +adobe-source-code-pro-fonts +aribb24 +attica5 +boost +cantarell-fonts +clang +cmake +compiler-rt +cppdap +cython +doxygen +extra-cmake-modules +ffmpeg4.4 +git-lfs +go +gperf +ibus +imake +karchive5 +kauth +kauth5 +kbookmarks5 +kcodecs5 +kcompletion5 +kconfig5 +kconfigwidgets5 +kcoreaddons5 +kcrash5 +kdbusaddons5 +kdeclarative5 +kded5 +kglobalaccel5 +kguiaddons5 +ki18n5 +kiconthemes5 +kio5 +kirigami2 +kitemviews5 +kjobwidgets5 +knewstuff5 +knotifications5 +kpackage5 +kpty +kservice5 +ktextwidgets5 +kwallet5 +kwidgetsaddons5 +kwindowsystem5 +kxmlgui5 +lib32-audit +lib32-harfbuzz +lib32-libcap +lib32-libnsl +lib32-libtirpc +lib32-pam +libcamera +libcamera-ipa +libdbusmenu-qt5 +libdca +libdispatch +libdvbpsi +libgit2 +libibus +libindicator +libjcat +libmad +libmfx +libmpcdec +libmpeg2 +libomxil-bellagio +libtar +libupnp +libxdg-basedir +libxres +lld +llhttp +meson +minizip-ng +ninja +nlohmann-json +nvm +perl-sub-identify +plasma-activities +polkit-qt5 +polkit-qt6 +python-babel +python-build +python-chardet +python-dbus +python-docopt +python-docutils +python-fastjsonschema +python-html5lib +python-imagesize +python-inflect +python-iniconfig +python-installer +python-jaraco.collections +python-numpy +python-ordered-set +python-pillow +python-pluggy +python-pygments +python-pyproject-hooks +python-pyqt5 +python-pyqt5-sip +python-pyqt6 +python-pyqt6-sip +python-pytest +python-pytest-mock +python-pytz +python-pyxdg +python-roman-numerals-py +python-setproctitle +python-setuptools +python-snowballstemmer +python-sphinx +python-sphinx-alabaster-theme +python-sphinxcontrib-applehelp +python-sphinxcontrib-devhelp +python-sphinxcontrib-htmlhelp +python-sphinxcontrib-jsmath +python-sphinxcontrib-qthelp +python-sphinxcontrib-serializinghtml +python-tomli +python-tqdm +python-trove-classifiers +python-typeguard +python-validate-pyproject +python-webencodings +qca-qt5 +qt5-graphicaleffects +qt5-multimedia +qt5-quickcontrols +qt5-quickcontrols2 +qt5-speech +qt5-tools +qt6-doc +rapidjson +rhash +rtkit +ruby-abbrev +ruby-base64 +ruby-bigdecimal +ruby-bundled-gems +ruby-bundler +ruby-csv +ruby-debug +ruby-default-gems +ruby-drb +ruby-erb +ruby-getoptlong +ruby-irb +ruby-matrix +ruby-minitest +ruby-mutex_m +ruby-net-ftp +ruby-net-imap +ruby-net-pop +ruby-net-smtp +ruby-nkf +ruby-observer +ruby-power_assert +ruby-prime +ruby-racc +ruby-rake +ruby-rbs +ruby-rdoc +ruby-repl_type_completor +ruby-resolv-replace +ruby-rexml +ruby-rinda +ruby-rss +ruby-stdlib +ruby-syslog +ruby-test-unit +ruby-typeprof +rust +scdoc +solid5 +sonnet5 +stb +syndication5 +tree-sitter-bash +tree-sitter-python +vte-common +vulkan-headers +xorg-xset diff --git a/system-config/packages/laptop/hardware-aur.txt b/system-config/packages/laptop/hardware-aur.txt new file mode 100644 index 0000000..cdcb07c --- /dev/null +++ b/system-config/packages/laptop/hardware-aur.txt @@ -0,0 +1,4 @@ +caffeine +envycontrol +light +woeusb diff --git a/system-config/packages/laptop/hardware-repo.txt b/system-config/packages/laptop/hardware-repo.txt new file mode 100644 index 0000000..136e819 --- /dev/null +++ b/system-config/packages/laptop/hardware-repo.txt @@ -0,0 +1,19 @@ +ddcutil +intel-gpu-tools +intel-media-driver +intel-ucode +keyd +mesa-utils +nvidia-open +nvidia-prime +nvtop +nwg-displays +powertop +psensor +thermald +tlp +tlp-pd +tlp-rdw +vulkan-intel +vulkan-tools +wireless_tools diff --git a/system-config/pyrightconfig.json b/system-config/pyrightconfig.json new file mode 100644 index 0000000..b292365 --- /dev/null +++ b/system-config/pyrightconfig.json @@ -0,0 +1,4 @@ +{ + "include": ["source.py", "hosts.py", "modules", "scripts"], + "executionEnvironments": [{ "root": ".", "extraPaths": ["."] }] +} diff --git a/system-config/scripts/refresh_orphan_guards.py b/system-config/scripts/refresh_orphan_guards.py new file mode 100755 index 0000000..c37ea0e --- /dev/null +++ b/system-config/scripts/refresh_orphan_guards.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +"""Refresh dependency packages that Decman must ignore to guarantee no removal.""" + +from __future__ import annotations + +import subprocess +from pathlib import Path + +import pyalpm + +ROOT = Path(__file__).resolve().parents[1] +OUTPUT = ROOT / "packages" / "ignored" + + +def repository_names(handle: pyalpm.Handle) -> set[str]: + return { + package.name + for database in handle.get_syncdbs() + for package in database.pkgcache + } + + +def deep_orphans(handle: pyalpm.Handle, predicate) -> set[str]: + local = {package.name: package for package in handle.get_localdb().pkgcache} + candidates = { + package.name + for package in local.values() + if package.reason == pyalpm.PKG_REASON_DEPEND and predicate(package.name) + } + while True: + retained = { + name + for name in candidates + if not (set(local[name].compute_requiredby()) - candidates) + } + if retained == candidates: + return retained + candidates = retained + + +def write(name: str, values: set[str]) -> None: + (OUTPUT / name).write_text("\n".join(sorted(values)) + "\n", encoding="utf-8") + + +def main() -> None: + handle = pyalpm.Handle("/", "/var/lib/pacman") + repositories = subprocess.run( + ["pacman-conf", "--repo-list"], check=True, text=True, stdout=subprocess.PIPE + ).stdout.split() + for repository in repositories: + handle.register_syncdb(repository, 0) + native = repository_names(handle) + write("repo-dependencies.txt", deep_orphans(handle, lambda name: name in native)) + write("aur-dependencies.txt", deep_orphans(handle, lambda name: name not in native)) + + +if __name__ == "__main__": + main() diff --git a/system-config/scripts/split_packages.py b/system-config/scripts/split_packages.py new file mode 100755 index 0000000..590873e --- /dev/null +++ b/system-config/scripts/split_packages.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +"""Generate disjoint, concern-scoped package lists from the current machine.""" + +from __future__ import annotations + +import subprocess +from pathlib import Path +from typing import Final + +ROOT = Path(__file__).resolve().parents[1] +PACKAGES = ROOT / "packages" + +# A concern is reusable; a machine profile composes concerns elsewhere. +# Every installed explicit package must be assigned exactly once on this laptop. +CONCERNS: Final = { + "common/core": """base base-devel linux linux-firmware linux-headers cryptsetup refind + arch-install-scripts git yadm openssh man-db jq ripgrep fd fzf 7zip unzip rsync + ntfs-3g os-prober fwupd fish zsh zoxide htop glances starship cowsay ex-vi-compat + mtools dmidecode libsmbios catdoc odt2txt hunspell-de""", + "common/wayland": """hyprland hypridle hyprlock xdg-desktop-portal-hyprland + pipewire-alsa pipewire-pulse wireplumber pavucontrol pamixer mako waybar wofi grim + slurp satty wl-clipboard wlr-randr alacritty kitty tmux neovim vim yazi ueberzugpp + flameshot playerctl bluez bluez-utils blueman gnome-keyring xorg-xwayland + noctalia-git ranger-git""", + "common/fonts": """noto-fonts noto-fonts-cjk noto-fonts-emoji ttf-jetbrains-mono-nerd + ttf-icomoon-feather ttf-meslo-nerd-font-powerlevel10k""", + "common/development": """nodejs npm yarn python python-pip pyenv uv rust cargo go ruby + jdk-openjdk docker docker-compose podman vagrant virtualbox virtualbox-host-dkms + cloudflared openbsd-netcat pycharm postman-bin mongodb-compass-bin pi engram-bin + decman yay gti fcron""", + "common/productivity": """brave-bin firefox chromium thunderbird obsidian bitwarden + nextcloud-client signal-desktop telegram-desktop libreoffice-fresh evince okular + nomacs zotero-bin qalculate-gtk qbittorrent teams teamspeak3 cerebro-bin chatgtk + torbrowser-launcher spotify""", + "common/media": """vlc mpv smplayer audacity imagemagick steam cudnn""", + "common/research": """biber r texlive-basic texlive-bibtexextra texlive-binextra + texlive-context texlive-fontsextra texlive-fontsrecommended texlive-fontutils + texlive-formatsextra texlive-games texlive-humanities texlive-latex texlive-latexextra + texlive-latexrecommended texlive-luatex texlive-mathscience texlive-metapost + texlive-music texlive-pictures texlive-plaingeneric texlive-pstricks texlive-publishers + texlive-xetex resvg""", + "common/network": """networkmanager network-manager-applet avahi nss-mdns dhcpcd gvfs-smb + cups cups-pdf mullvad-vpn-beta-bin""", + "laptop/hardware": """intel-ucode thermald tlp tlp-pd tlp-rdw powertop psensor lm_sensors + ddcutil intel-gpu-tools intel-media-driver mesa-utils vulkan-intel vulkan-tools + nvidia-open nvidia-prime nvtop envycontrol light keyd nwg-displays + wireless_tools caffeine woeusb""", + "common/services": """sddm ly pcmanfm libva-utils""", +} + + +def explicit(command: str) -> set[str]: + return set( + subprocess.run( + ["pacman", command], check=True, text=True, stdout=subprocess.PIPE + ).stdout.split() + ) + + +def write(path: Path, values: set[str]) -> None: + resolved = path.resolve() + package_root = PACKAGES.resolve() + if package_root not in resolved.parents: + raise ValueError(f"refusing to write outside packages: {path}") + resolved.parent.mkdir(parents=True, exist_ok=True) + resolved.write_text("\n".join(sorted(values)) + "\n", encoding="utf-8") + + +def main() -> None: + official, aur = explicit("-Qqen"), explicit("-Qqem") + assigned: set[str] = set() + for name, raw in CONCERNS.items(): + selected = set(raw.split()) & (official | aur) + overlap = selected & assigned + if overlap: + raise SystemExit(f"duplicate ownership in {name}: {sorted(overlap)}") + assigned |= selected + write(PACKAGES / f"{name}-repo.txt", selected & official) + write(PACKAGES / f"{name}-aur.txt", selected & aur) + remaining = (official | aur) - assigned + if remaining: + raise SystemExit( + "unclassified explicit packages; add them to a named concern: " + + ", ".join(sorted(remaining)) + ) + print(f"assigned={len(assigned)}") + + +if __name__ == "__main__": + main() diff --git a/system-config/scripts/verify.py b/system-config/scripts/verify.py new file mode 100755 index 0000000..0406bc5 --- /dev/null +++ b/system-config/scripts/verify.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +"""Read-only invariant checks for the active Decman configuration.""" + +from __future__ import annotations + +import os +import runpy +import subprocess +import sys +from collections import Counter +from collections.abc import Callable +from pathlib import Path +from typing import cast + +ROOT = Path(__file__).resolve().parents[1] +PACKAGES = ROOT / "packages" + + +def lines(path: Path) -> set[str]: + return { + line.strip() + for line in path.read_text(encoding="utf-8").splitlines() + if line.strip() + } + + +def actual(command: str) -> set[str]: + return set( + subprocess.run( + ["pacman", command], check=True, text=True, stdout=subprocess.PIPE + ).stdout.split() + ) + + +def main() -> None: + repo_files = list(PACKAGES.glob("**/*-repo.txt")) + aur_files = list(PACKAGES.glob("**/*-aur.txt")) + repo = [name for path in repo_files for name in lines(path)] + aur = [name for path in aur_files for name in lines(path)] + duplicate = {name for name, count in Counter(repo + aur).items() if count > 1} + if duplicate: + raise SystemExit(f"duplicate package ownership: {sorted(duplicate)}") + installed_repo = actual("-Qqen") + installed_aur = actual("-Qqem") + + os.chdir(ROOT) + sys.path.insert(0, str(ROOT)) + import decman + + runpy.run_path(str(ROOT / "source.py")) + loaded_repo: set[str] = set() + loaded_aur: set[str] = set() + for module in decman.modules: + native = getattr(module, "native_packages", None) + foreign = getattr(module, "aur_packages", None) + if callable(native): + loaded_repo |= cast(Callable[[], set[str]], native)() + if callable(foreign): + loaded_aur |= cast(Callable[[], set[str]], foreign)() + if loaded_repo != set(repo) or loaded_aur != set(aur): + raise SystemExit("active profile does not compose the full laptop package set") + expected_system = set().union( + *(lines(path) for path in (ROOT / "manifests").glob("**/*-system.txt")) + ) + expected_user = set().union( + *(lines(path) for path in (ROOT / "manifests").glob("**/*-user.txt")) + ) + loaded_system: set[str] = set() + loaded_user: set[str] = set() + for module in decman.modules: + system = getattr(module, "system_units", None) + user = getattr(module, "user_units", None) + if callable(system): + loaded_system |= cast(Callable[[], set[str]], system)() + if callable(user): + loaded_user |= cast(Callable[[], dict[str, set[str]]], user)().get( + "alex", set() + ) + if loaded_system != expected_system or loaded_user != expected_user: + raise SystemExit("active profile does not compose the complete unit manifests") + pending_remove = sorted((installed_repo - set(repo)) | (installed_aur - set(aur))) + pending_install = sorted((set(repo) - installed_repo) | (set(aur) - installed_aur)) + print( + f"OK: repo={len(repo)} aur={len(aur)} system={len(loaded_system)} " + f"user={len(loaded_user)} concerns={len(repo_files)} profile=laptop" + ) + print(f"PENDING REMOVE: {' '.join(pending_remove) or 'none'}") + print(f"PENDING INSTALL: {' '.join(pending_install) or 'none'}") + + +if __name__ == "__main__": + main() diff --git a/system-config/source.py b/system-config/source.py new file mode 100644 index 0000000..41e896d --- /dev/null +++ b/system-config/source.py @@ -0,0 +1,14 @@ +"""Decman entrypoint for this machine. + +Select one profile only. The package lists describe the complete current laptop +state, so the first Decman run must not remove a package. +""" + +import decman + +from modules.packages import configure_no_removal_guard +from hosts import laptop + +configure_no_removal_guard() +decman.modules += laptop() +decman.execution_order = ["files", "pacman", "aur", "systemd"]