diff --git a/hosts/copland.ulu.nix b/hosts/copland.ulu.nix index bfd8df5..530fe55 100644 --- a/hosts/copland.ulu.nix +++ b/hosts/copland.ulu.nix @@ -22,6 +22,7 @@ in home-manager = { useGlobalPkgs = true; useUserPackages = true; + backupFileExtension = "hm-bak"; sharedModules = [ inputs.niri.homeModules.niri ]; extraSpecialArgs = { inherit inputs system; @@ -53,6 +54,8 @@ in config.flake.homeModules.home-qt config.flake.homeModules.home-browser config.flake.homeModules.home-mimeapps + config.flake.homeModules.home-file-manager + config.flake.homeModules.home-file-manager-actions config.flake.homeModules.home-easyeffects config.flake.homeModules.home-discord config.flake.homeModules.home-noctalia @@ -89,6 +92,8 @@ in config.flake.nixosModules.service-printing config.flake.nixosModules.service-security config.flake.nixosModules.service-ssh + config.flake.nixosModules.service-tumbler + config.flake.nixosModules.service-xfconf config.flake.nixosModules.common-locale config.flake.nixosModules.common-nix config.flake.nixosModules.common-nix-access-tokens diff --git a/modules/home/home-file-manager-actions.ulu.nix b/modules/home/home-file-manager-actions.ulu.nix new file mode 100644 index 0000000..585b64c --- /dev/null +++ b/modules/home/home-file-manager-actions.ulu.nix @@ -0,0 +1,553 @@ +{ ... }: +{ + flake.homeModules.home-file-manager-actions = + { pkgs, lib, ... }: + let + inherit (lib) + concatStringsSep + concatMapStringsSep + escapeXML + optional + ; + fmActions = pkgs.writeShellApplication { + name = "fm-actions"; + runtimeInputs = with pkgs; [ + poppler-utils + wl-clipboard + imagemagick + libarchive + coreutils + libnotify + tesseract + ffmpeg + gnutar + p7zip + zstd + zip + ]; + text = '' + op="''${1:-}" + if [ "$#" -gt 0 ]; then shift; fi + + notify() { + notify-send -a Thunar -i system-file-manager "$1" "''${2:-}" || true + } + finish() { + status=$? + if [ -t 0 ]; then + printf '\n[fm-actions] exit %s — press enter to close\n' "$status" + read -r _ || true + elif [ "$status" -ne 0 ]; then + notify-send -a Thunar -u critical "action failed" \ + "fm-actions ''${op:-?} exited with $status" || true + fi + } + trap finish EXIT + stem() { printf '%s' "''${1%.*}"; } + escape_own_folder() { + if [ ! -e "$1" ] && [ "$(basename -- "$PWD")" = "$1" ]; then + cd .. || exit 1 + fi + } + case "$op" in + copy-path) + printf '%s\n' "$@" | wl-copy + notify "path copied" "$# item(s)" + ;; + copy-content) + cat -- "$@" | wl-copy + notify "contents copied" "$# file(s)" + ;; + duplicate) + for f in "$@"; do cp -a -- "$f" "$f.copy"; done + notify "duplicated" "$# item(s)" + ;; + symlink) + for f in "$@"; do ln -s -- "$f" "$f.link"; done + notify "symlinked" "$# item(s)" + ;; + checksum) + sha256sum -- "$@" + ;; + img-png) + for f in "$@"; do magick "$f" "$(stem "$f").png"; done + notify "converted to png" "$# image(s)" + ;; + img-jpg) + for f in "$@"; do magick "$f" -quality 92 "$(stem "$f").jpg"; done + notify "converted to jpeg" "$# image(s)" + ;; + img-webp) + for f in "$@"; do magick "$f" -quality 90 "$(stem "$f").webp"; done + notify "converted to webp" "$# image(s)" + ;; + img-resize) + for f in "$@"; do + magick "$f" -resize '1920x1920>' "$(stem "$f")-1920.''${f##*.}" + done + notify "resized to 1920px" "$# image(s)" + ;; + img-strip) + for f in "$@"; do magick "$f" -strip "$f"; done + notify "metadata stripped" "$# image(s)" + ;; + ocr) + tesseract "$1" - -l eng+tur 2>/dev/null | wl-copy + notify "OCR" "recognised text copied to clipboard" + ;; + wallpaper) + noctalia msg wallpaper-set "$1" + ;; + vid-mp4) + for f in "$@"; do + ffmpeg -nostdin -hide_banner -i "$f" \ + -c:v libx264 -crf 20 -preset medium -c:a aac -b:a 192k \ + "$(stem "$f").mp4" + done + ;; + vid-audio) + for f in "$@"; do + ffmpeg -nostdin -hide_banner -i "$f" -vn -c:a libmp3lame -q:a 2 \ + "$(stem "$f").mp3" + done + ;; + vid-gif) + for f in "$@"; do + palette="$(mktemp --suffix=.png)" + ffmpeg -nostdin -hide_banner -y -i "$f" \ + -vf 'fps=15,scale=640:-1:flags=lanczos,palettegen' "$palette" + ffmpeg -nostdin -hide_banner -y -i "$f" -i "$palette" \ + -lavfi 'fps=15,scale=640:-1:flags=lanczos[x];[x][1:v]paletteuse' \ + "$(stem "$f").gif" + rm -f "$palette" + done + ;; + media-info) + for f in "$@"; do + printf '\n=== %s ===\n' "$f" + ffprobe -hide_banner "$f" 2>&1 || true + done + ;; + + zip) + escape_own_folder "$1" + zip -r -- "$1.zip" "$@" + notify "archive created" "$1.zip" + ;; + 7z) + escape_own_folder "$1" + 7z a -- "$1.7z" "$@" + notify "archive created" "$1.7z" + ;; + tar-zst) + escape_own_folder "$1" + tar --zstd -cf "$1.tar.zst" -- "$@" + notify "archive created" "$1.tar.zst" + ;; + extract) + for f in "$@"; do bsdtar -xf "$f"; done + notify "extracted" "$# archive(s)" + ;; + extract-to-folder) + for f in "$@"; do + d="$(stem "$f")" + mkdir -p "$d" + bsdtar -xf "$f" -C "$d" + done + notify "extracted into folders" "$# archive(s)" + ;; + + pdf-images) + for f in "$@"; do pdftoppm -png -r 150 "$f" "$(stem "$f")"; done + notify "PDF rendered" "$# document(s)" + ;; + pdf-merge) + pdfunite "$@" merged.pdf + notify "PDFs merged" "merged.pdf" + ;; + + *) + echo "fm-actions: unknown operation: $op" >&2 + exit 2 + ;; + esac + ''; + }; + + fm = lib.getExe fmActions; + term = "ghostty --working-directory=%f"; + dirs = [ "directories" ]; + files = [ + "audio-files" + "image-files" + "other-files" + "text-files" + "video-files" + ]; + everything = dirs ++ files; + archivePatterns = [ + "*.7z" + "*.bz2" + "*.cab" + "*.gz" + "*.iso" + "*.rar" + "*.tar" + "*.tar.bz2" + "*.tar.gz" + "*.tar.xz" + "*.tar.zst" + "*.tbz2" + "*.tgz" + "*.txz" + "*.xz" + "*.zip" + "*.zst" + ]; + + mkAction = + { + id, + name, + command, + icon ? "", + description ? "", + patterns ? [ "*" ], + types ? [ ], + submenu ? null, + range ? null, + startupNotify ? true, + }: + concatStringsSep "\n" ( + [ " " ] + ++ map (line: " " + line) ( + [ + "${escapeXML icon}" + "${escapeXML name}" + "${escapeXML id}" + "${escapeXML command}" + "${escapeXML description}" + "${escapeXML (concatStringsSep ";" patterns)}" + ] + ++ optional (submenu != null) "${escapeXML submenu}" + ++ optional (range != null) "${escapeXML range}" + ++ optional startupNotify "" + ++ map (t: "<${t}/>") types + ) + ++ [ " " ] + ); + + # this is slop but idc. + actions = [ + { + id = "open-terminal-here"; + name = "Open Terminal Here"; + description = "browse folder in ghostty"; + icon = "utilities-terminal"; + command = term; + types = dirs; + } + { + id = "open-root-terminal-here"; + name = "Open Root Terminal Here"; + description = "browse folder in ghostty as root"; + icon = "dialog-password"; + command = "${term} -e sudo -E -s"; + types = dirs; + } + { + id = "open-yazi-here"; + name = "Open Yazi Here"; + description = "browse folder in yazi"; + icon = "system-file-manager"; + command = "${term} -e yazi"; + types = dirs; + } + { + id = "open-as-root"; + name = "Open as Root"; + description = "reopen folder as root"; + icon = "security-high"; + command = "thunar admin://%f"; + types = dirs; + } + { + id = "open-in-code"; + name = "Open in VS Code"; + description = "open in vsc"; + icon = "applications-development"; + command = "code %F"; + types = everything; + } + { + id = "search-here"; + name = "Search Here"; + description = "search in this folder with catfish"; + icon = "system-search"; + command = "catfish --path=%f"; + types = dirs; + } + { + id = "open-lazyjj-here"; + name = "Open lazyjj Here"; + description = "inspect with lazyjj"; + icon = "folder-open"; + command = "${term} -e lazyjj"; + types = dirs; + } + { + id = "open-lazygit-here"; + name = "Open lazygit Here"; + description = "inspect with lazygit"; + icon = "folder-open"; + command = "${term} -e lazygit"; + types = dirs; + } + { + id = "play-in-mpv"; + name = "Play in mpv"; + description = "play with mpv"; + icon = "multimedia-player"; + command = "mpv -- %F"; + types = [ + "audio-files" + "video-files" + ]; + } + { + id = "compare-in-meld"; + name = "Compare in Meld"; + description = "diff exactly two files or folders"; + icon = "document-properties"; + command = "meld %F"; + range = "2-2"; + types = everything; + } + { + id = "archive-extract-here"; + name = "Extract Here"; + description = "unpack content into the current folder"; + icon = "package-x-generic"; + command = "${fm} extract %F"; + patterns = archivePatterns; + submenu = "Archive"; + types = [ "other-files" ]; + } + { + id = "archive-extract-to-folder"; + name = "Extract to Folder"; + description = "unpack into a folder named after the archive"; + icon = "package-x-generic"; + command = "${fm} extract-to-folder %F"; + patterns = archivePatterns; + submenu = "Archive"; + types = [ "other-files" ]; + } + { + id = "archive-zip"; + name = "Compress to .zip"; + icon = "package-x-generic"; + command = "${fm} zip %N"; + submenu = "Archive"; + types = everything; + } + { + id = "archive-7z"; + name = "Compress to .7z"; + icon = "package-x-generic"; + command = "${fm} 7z %N"; + submenu = "Archive"; + types = everything; + } + { + id = "archive-tar-zst"; + name = "Compress to .tar.zst"; + icon = "package-x-generic"; + command = "${fm} tar-zst %N"; + submenu = "Archive"; + types = everything; + } + { + id = "image-to-png"; + name = "Convert to PNG"; + icon = "image-x-generic"; + command = "${fm} img-png %F"; + submenu = "Image"; + types = [ "image-files" ]; + } + { + id = "image-to-jpg"; + name = "Convert to JPEG"; + icon = "image-x-generic"; + command = "${fm} img-jpg %F"; + submenu = "Image"; + types = [ "image-files" ]; + } + { + id = "image-to-webp"; + name = "Convert to WebP"; + icon = "image-x-generic"; + command = "${fm} img-webp %F"; + submenu = "Image"; + types = [ "image-files" ]; + } + { + id = "image-resize-1920"; + name = "Resize to 1920px"; + description = "shrink to 1920px wide but keep aspect ratio"; + icon = "image-x-generic"; + command = "${fm} img-resize %F"; + submenu = "Image"; + types = [ "image-files" ]; + } + { + id = "image-strip-metadata"; + name = "Strip Metadata"; + description = "remove EXIF and other metadata from images"; + icon = "image-x-generic"; + command = "${fm} img-strip %F"; + submenu = "Image"; + types = [ "image-files" ]; + } + { + id = "image-ocr"; + name = "OCR to Clipboard"; + description = "ocr to clipboard"; + icon = "edit-copy"; + command = "${fm} ocr %f"; + submenu = "Image"; + types = [ "image-files" ]; + } + { + id = "image-set-wallpaper"; + name = "Set as Wallpaper"; + icon = "preferences-desktop-wallpaper"; + command = "${fm} wallpaper %f"; + submenu = "Image"; + types = [ "image-files" ]; + } + { + id = "media-to-mp4"; + name = "Convert to MP4"; + description = "H.264 / AAC, CRF 20"; + icon = "video-x-generic"; + command = "ghostty -e ${fm} vid-mp4 %F"; + submenu = "Media"; + types = [ "video-files" ]; + } + { + id = "media-extract-audio"; + name = "Extract Audio (MP3)"; + icon = "audio-x-generic"; + command = "ghostty -e ${fm} vid-audio %F"; + submenu = "Media"; + types = [ + "audio-files" + "video-files" + ]; + } + { + id = "media-to-gif"; + name = "Make GIF"; + description = "640px, 15 fps, optimized"; + icon = "video-x-generic"; + command = "ghostty -e ${fm} vid-gif %F"; + submenu = "Media"; + types = [ "video-files" ]; + } + { + id = "media-info"; + name = "Media Info"; + description = "ffprobe the selection"; + icon = "dialog-information"; + command = "ghostty -e ${fm} media-info %F"; + submenu = "Media"; + types = [ + "audio-files" + "image-files" + "video-files" + ]; + } + { + id = "pdf-to-images"; + name = "Render to PNG"; + description = "150 dpi, one file per page"; + icon = "application-pdf"; + command = "${fm} pdf-images %F"; + patterns = [ "*.pdf" ]; + submenu = "PDF"; + types = [ "other-files" ]; + } + { + id = "pdf-merge"; + name = "Merge into merged.pdf"; + icon = "application-pdf"; + command = "${fm} pdf-merge %F"; + patterns = [ "*.pdf" ]; + range = "2-64"; + submenu = "PDF"; + types = [ "other-files" ]; + } + { + id = "tools-copy-path"; + name = "Copy Full Path"; + icon = "edit-copy"; + command = "${fm} copy-path %F"; + submenu = "Tools"; + types = everything; + } + { + id = "tools-copy-content"; + name = "Copy File Contents"; + icon = "edit-copy"; + command = "${fm} copy-content %F"; + submenu = "Tools"; + types = [ "text-files" ]; + } + { + id = "tools-checksum"; + name = "SHA-256 Checksum"; + icon = "dialog-information"; + command = "ghostty -e ${fm} checksum %F"; + submenu = "Tools"; + types = files; + } + { + id = "tools-duplicate"; + name = "Duplicate"; + description = "copy alongside as .copy"; + icon = "edit-copy"; + command = "${fm} duplicate %F"; + submenu = "Tools"; + types = everything; + } + { + id = "tools-symlink"; + name = "Create Symlink Here"; + icon = "emblem-symbolic-link"; + command = "${fm} symlink %F"; + submenu = "Tools"; + types = everything; + } + { + id = "tools-dos2unix"; + name = "Fix Line Endings"; + description = "convert CRLF to LF"; + icon = "text-x-generic"; + command = "dos2unix -- %F"; + submenu = "Tools"; + types = [ "text-files" ]; + } + ]; + in + { + home.packages = [ fmActions ]; + + xdg.configFile."Thunar/uca.xml".text = '' + + + ${concatMapStringsSep "\n" mkAction actions} + + ''; + }; +} diff --git a/modules/home/home-file-manager.ulu.nix b/modules/home/home-file-manager.ulu.nix new file mode 100644 index 0000000..5bb4af5 --- /dev/null +++ b/modules/home/home-file-manager.ulu.nix @@ -0,0 +1,141 @@ +{ ... }: +{ + flake.homeModules.home-file-manager = + { config, pkgs, ... }: + let + home = config.home.homeDirectory; + thunar = pkgs.thunar.override { + thunarPlugins = with pkgs; [ + thunar-media-tags-plugin + thunar-archive-plugin + thunar-vcs-plugin + thunar-volman + ]; + }; + in + { + home.packages = [ thunar ] ++ (with pkgs; [ catfish ]); + xfconf.settings.thunar = { + "default-view" = "ThunarIconsView"; + "last-view" = "ThunarIconsView"; + "last-icon-view-zoom-level" = "THUNAR_ZOOM_LEVEL_100_PERCENT"; + "last-details-view-zoom-level" = "THUNAR_ZOOM_LEVEL_38_PERCENT"; + "last-compact-view-zoom-level" = "THUNAR_ZOOM_LEVEL_50_PERCENT"; + "last-details-view-visible-columns" = + "THUNAR_COLUMN_NAME,THUNAR_COLUMN_SIZE,THUNAR_COLUMN_TYPE,THUNAR_COLUMN_DATE_MODIFIED"; + "last-details-view-fixed-columns" = false; + "misc-expandable-folders" = true; + "misc-compact-view-max-chars" = 50; + + # UI + "last-location-bar" = "ThunarLocationButtons"; + "last-side-pane" = "THUNAR_SIDEPANE_TYPE_SHORTCUTS"; + "last-menubar-visible" = false; + "last-statusbar-visible" = true; + "last-separator-position" = 180; + "misc-use-csd" = true; + "misc-small-toolbar-icons" = false; + "misc-text-beside-icons" = false; + "misc-symbolic-icons-in-toolbar" = true; + "misc-symbolic-icons-in-sidepane" = true; + "misc-window-title-style" = "THUNAR_WINDOW_TITLE_STYLE_FOLDER_NAME_WITHOUT_THUNAR_SUFFIX"; + "misc-change-window-icon" = true; + "misc-highlighting-enabled" = true; + "misc-status-bar-active-info" = { + type = "uint"; + value = 61; + }; + "misc-image-size-in-statusbar" = true; + "last-toolbar-items" = + "menu:0,back:1,forward:1,open-parent:1,open-home:1," + + "new-tab:0,new-window:0,toggle-split-view:1," + + "undo:1,redo:1,zoom-out:0,zoom-in:0,zoom-reset:0," + + "view-as-icons:0,view-as-detailed-list:0,view-as-compact-list:0,view-switcher:1," + + "location-bar:1,reload:1,search:1"; + + # SIDE PANE + "shortcuts-icon-size" = "THUNAR_ICON_SIZE_16"; + "shortcuts-icon-emblems" = true; + "tree-icon-size" = "THUNAR_ICON_SIZE_16"; + "tree-icon-emblems" = true; + "misc-tree-lines-in-tree-sidepane" = true; + + # SORT + "last-sort-column" = "THUNAR_COLUMN_NAME"; + "last-sort-order" = "GTK_SORT_ASCENDING"; + "misc-folders-first" = true; + "misc-case-sensitive" = false; + "last-show-hidden" = true; + + # TABS + "last-restore-tabs" = true; + "misc-open-new-window-as-tab" = true; + "misc-switch-to-new-tab" = true; + "misc-always-show-tabs" = false; + "misc-tab-close-middle-click" = true; + "misc-middle-click-in-tab" = true; + "misc-confirm-close-multiple-tabs" = true; + "misc-remember-geometry" = true; + "misc-vertical-split-pane" = false; + "misc-always-enable-split-view" = false; + + # THUMB + "misc-thumbnail-mode" = "THUNAR_THUMBNAIL_MODE_ONLY_LOCAL"; + "misc-thumbnail-draw-frames" = true; + "misc-thumbnail-max-file-size" = { + type = "uint64"; + value = 0; + }; + "misc-image-preview-mode" = "THUNAR_IMAGE_PREVIEW_MODE_EMBEDDED"; + "last-image-preview-visible" = false; + "misc-folder-item-count" = "THUNAR_FOLDER_ITEM_COUNT_ONLY_LOCAL"; + + # INTERACTION + "misc-single-click" = false; + "misc-ctrl-scroll-wheel-to-zoom" = true; + "misc-horizontal-wheel-navigates" = true; + "misc-date-style" = "THUNAR_DATE_STYLE_YYYYMMDD"; + "misc-file-size-binary" = true; + "misc-recursive-search" = "THUNAR_RECURSIVE_SEARCH_LOCAL"; + "misc-show-about-templates" = false; + "misc-directory-specific-settings" = false; + + # FILE OPERATIONS + "misc-volume-management" = true; + "misc-exec-shell-scripts-by-default" = "THUNAR_EXECUTE_SHELL_SCRIPT_ASK"; + "misc-recursive-permissions" = "THUNAR_RECURSIVE_PERMISSIONS_ASK"; + "misc-confirm-move-to-trash" = false; + "misc-show-delete-action" = true; + "misc-undo-redo-history-size" = 30; + "misc-parallel-copy-mode" = "THUNAR_PARALLEL_COPY_MODE_ONLY_LOCAL_SAME_DEVICES"; + "misc-transfer-use-partial" = "THUNAR_USE_PARTIAL_MODE_REMOTE"; + "misc-transfer-verify-file" = "THUNAR_VERIFY_FILE_MODE_REMOTE"; + }; + + xfconf.settings.thunar-volman = { + "automount-drives/enabled" = true; + "automount-media/enabled" = true; + "autobrowse/enabled" = true; + "autoopen/enabled" = false; + "autorun/enabled" = false; + "autoburn/enabled" = false; + "autoplay-audio-cds/enabled" = false; + "autoplay-video-cds/enabled" = false; + "autophoto/enabled" = false; + "autoipod/enabled" = false; + "autokeyboard/enabled" = false; + "automouse/enabled" = false; + "autoprinter/enabled" = false; + "autotablet/enabled" = false; + }; + + gtk.gtk3.bookmarks = [ + "file://${home}/copland nix" + "file://${home}/Projects" + "file://${home}/ProjectsHDD" + "file://${home}/Videos" + "file://${home}/Videos-HDD" + "file://${home}/.local/share/Steam/steamapps Steam_SSD" + ]; + }; +} diff --git a/modules/home/home-mimeapps.ulu.nix b/modules/home/home-mimeapps.ulu.nix index e3fcc76..eecf89d 100644 --- a/modules/home/home-mimeapps.ulu.nix +++ b/modules/home/home-mimeapps.ulu.nix @@ -6,7 +6,7 @@ xdg.mimeApps = { enable = true; defaultApplications = { - "inode/directory" = [ "org.kde.dolphin.desktop" ]; + "inode/directory" = [ "thunar.desktop" ]; "x-scheme-handler/http" = [ "zen-beta.desktop" ]; "x-scheme-handler/https" = [ "zen-beta.desktop" ]; "x-scheme-handler/about" = [ "zen-beta.desktop" ]; diff --git a/modules/nixos/services/service-tumbler.ulu.nix b/modules/nixos/services/service-tumbler.ulu.nix new file mode 100644 index 0000000..a2c2e2d --- /dev/null +++ b/modules/nixos/services/service-tumbler.ulu.nix @@ -0,0 +1,8 @@ +{ ... }: +{ + flake.nixosModules.service-tumbler = + { ... }: + { + services.tumbler.enable = true; + }; +} diff --git a/modules/nixos/services/service-xfconf.ulu.nix b/modules/nixos/services/service-xfconf.ulu.nix new file mode 100644 index 0000000..d5de6d0 --- /dev/null +++ b/modules/nixos/services/service-xfconf.ulu.nix @@ -0,0 +1,8 @@ +{ ... }: +{ + flake.nixosModules.service-xfconf = + { ... }: + { + programs.xfconf.enable = true; + }; +}