From 2ffedb783553b30af81056ab9465f5d6caf513f0 Mon Sep 17 00:00:00 2001 From: Sweetbread Date: Sun, 31 May 2026 19:52:51 +0300 Subject: [PATCH] yazi: filechooser --- modules/host.nix | 33 +++++++++++++++++-- modules/host/env.nix | 13 ++++++-- modules/user/hyprland.nix | 23 +++++++++++-- modules/user/qt.nix | 12 ++++++- modules/user/yazi.nix | 69 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 141 insertions(+), 9 deletions(-) diff --git a/modules/host.nix b/modules/host.nix index 0c9c834..5530c44 100644 --- a/modules/host.nix +++ b/modules/host.nix @@ -1,4 +1,4 @@ -{ +{ pkgs, ... }: { imports = [ ./host/bluetooth.nix ./host/bootloader.nix @@ -17,7 +17,36 @@ ./host/yggdrasil.nix ]; - programs.hyprland.enable = true; + programs = { + dconf.enable = true; + hyprland.enable = true; + }; + + xdg.portal = { + enable = true; + xdgOpenUsePortal = true; + + extraPortals = with pkgs; [ + xdg-desktop-portal-hyprland + xdg-desktop-portal-gtk + xdg-desktop-portal-termfilechooser + ]; + + config = { + common = { + default = [ "hyprland" "gtk" ]; + "org.freedesktop.impl.portal.FileChooser" = [ "termfilechooser" "gtk" ]; + "org.freedesktop.impl.portal.Settings" = [ "gtk" ]; + }; + + hyprland = { + default = [ "hyprland" "gtk" ]; + "org.freedesktop.impl.portal.FileChooser" = [ "termfilechooser" "gtk" ]; + "org.freedesktop.impl.portal.Settings" = [ "gtk" ]; + }; + }; + }; + services = { udisks2.enable = true; fstrim.enable = true; diff --git a/modules/host/env.nix b/modules/host/env.nix index 5161cf9..301510d 100644 --- a/modules/host/env.nix +++ b/modules/host/env.nix @@ -1,8 +1,15 @@ -{ +{ lib, ... }: { environment.variables = { EDITOR = "hx"; RANGER_LOAD_DEFAULT_RC = "FALSE"; - QT_QPA_PLATFORMTHEME = "qt5ct"; - GSETTINGS_BACKEND = "keyfile"; + # GTK/Electron apps do not consistently use the portal picker unless this + # variable is present in the graphical session environment. + GTK_USE_PORTAL = "1"; + + # Qt/Telegram-like apps otherwise keep using their own picker. This makes + # them ask xdg-desktop-portal for file/folder selection. + QT_QPA_PLATFORMTHEME = lib.mkForce "xdgdesktopportal"; + TDESKTOP_USE_GTK_FILE_DIALOG = "1"; + }; } diff --git a/modules/user/hyprland.nix b/modules/user/hyprland.nix index 417233f..d51c0fc 100644 --- a/modules/user/hyprland.nix +++ b/modules/user/hyprland.nix @@ -41,6 +41,11 @@ in { env = [ "XDG_SESSION_TYPE,wayland" "QT_QPA_PLATFORM,wayland" + "QT_QPA_PLATFORMTHEME,xdgdesktopportal" + "GTK_USE_PORTAL,1" + "TDESKTOP_USE_GTK_FILE_DIALOG,1" + "NIXOS_OZONE_WL,1" + "ELECTRON_OZONE_PLATFORM_HINT,auto" "XDG_CURRENT_DESKTOP,Hyprland" "XDG_SESSION_DESKTOP,Hyprland" @@ -115,6 +120,8 @@ in { exec-once = lib.mkBefore [ "systemctl --user start plasma-polkit-agent" + "${lib.getExe' pkgs.dbus "dbus-update-activation-environment"} --systemd --all" + "systemctl --user import-environment XDG_CURRENT_DESKTOP XDG_SESSION_DESKTOP XDG_SESSION_TYPE WAYLAND_DISPLAY DISPLAY GTK_USE_PORTAL QT_QPA_PLATFORMTHEME TDESKTOP_USE_GTK_FILE_DIALOG NIXOS_OZONE_WL ELECTRON_OZONE_PLATFORM_HINT" "awww-daemon" "wl-clip-persist --clipboard both" "clipse -listen" @@ -228,11 +235,21 @@ in { extraPortals = [ pkgs.xdg-desktop-portal-gtk + pkgs.xdg-desktop-portal-termfilechooser ]; - config.common = { - default = [ "hyprland" "gtk" ]; - "org.freedesktop.impl.portal.FileChooser" = [ "gtk" ]; + config = { + common = { + default = [ "hyprland" "gtk" ]; + "org.freedesktop.impl.portal.FileChooser" = [ "termfilechooser" "gtk" ]; + "org.freedesktop.impl.portal.Settings" = [ "gtk" ]; + }; + + hyprland = { + default = [ "hyprland" "gtk" ]; + "org.freedesktop.impl.portal.FileChooser" = [ "termfilechooser" "gtk" ]; + "org.freedesktop.impl.portal.Settings" = [ "gtk" ]; + }; }; }; } diff --git a/modules/user/qt.nix b/modules/user/qt.nix index e4ef095..937af4e 100644 --- a/modules/user/qt.nix +++ b/modules/user/qt.nix @@ -1,4 +1,14 @@ -{ +{ lib, ... }: { + # Keep qtct/Kvantum for styling, but tell qtct to delegate standard file + # dialogs to xdg-desktop-portal. This is what makes AyuGram/Telegram-like Qt + # apps use the Yazi picker instead of Qt's built-in dialog. + stylix.targets.qt.standardDialogs = "xdgdesktopportal"; + + home.sessionVariables = { + QT_QPA_PLATFORMTHEME = lib.mkForce "xdgdesktopportal"; + TDESKTOP_USE_GTK_FILE_DIALOG = "1"; + }; + qt = { enable = true; platformTheme.name = "qtct"; diff --git a/modules/user/yazi.nix b/modules/user/yazi.nix index 9f39273..272a578 100644 --- a/modules/user/yazi.nix +++ b/modules/user/yazi.nix @@ -27,6 +27,61 @@ exec ${lib.getExe pkgs.ghostty} --title="Yazi" -e ${lib.getExe yaziPkg} "$target" ''; + yaziFileChooser = pkgs.writeShellScriptBin "yazi-file-chooser" '' + set -efu + + directory="''${2:-0}" + save="''${3:-0}" + path="''${4:-}" + out="''${5:?missing termfilechooser output path}" + + state_dir="''${XDG_STATE_HOME:-$HOME/.local/state}/xdg-desktop-portal-termfilechooser" + last_selected_path="$state_dir/last-selected" + ${lib.getExe' pkgs.coreutils "mkdir"} -p "$state_dir" + + if [ -s "$last_selected_path" ]; then + IFS= read -r last_selected < "$last_selected_path" || true + if [ -d "$last_selected" ]; then + if [ "$save" = "1" ] && [ -n "$path" ]; then + path="$last_selected/''${path##*/}" + else + path="$last_selected" + fi + fi + fi + + if [ -z "$path" ]; then + path="$HOME" + fi + + created_placeholder=0 + if [ "$save" = "1" ] && [ ! -e "$path" ]; then + placeholder_dir="''${path%/*}" + if [ "$placeholder_dir" != "$path" ]; then + ${lib.getExe' pkgs.coreutils "mkdir"} -p "$placeholder_dir" + fi + + { + printf '%s\n' 'This file was created by xdg-desktop-portal-termfilechooser.' + printf '%s\n' 'Move/rename it in Yazi, then open/select it to choose the save path.' + } > "$path" + created_placeholder=1 + fi + + ${lib.getExe pkgs.ghostty} --title="Yazi File Picker" -e ${lib.getExe yaziPkg} --chooser-file="$out" --cwd-file="$last_selected_path" "$path" + + if [ "$directory" = "1" ] && [ ! -s "$out" ] && [ -s "$last_selected_path" ]; then + IFS= read -r cwd < "$last_selected_path" || true + if [ -n "''${cwd:-}" ] && [ -d "$cwd" ]; then + printf '%s\n' "$cwd" > "$out" + fi + fi + + if [ "$created_placeholder" = "1" ] && [ ! -s "$out" ]; then + ${lib.getExe' pkgs.coreutils "rm"} -f "$path" + fi + ''; + fileManager1 = pkgs.writeShellScriptBin "yazi-filemanager1" '' exec ${pkgs.python3.withPackages (ps: [ ps.dbus-next ])}/bin/python ${pkgs.writeText "yazi-filemanager1.py" '' import asyncio @@ -97,6 +152,18 @@ in { yaziOpen ]; + home.sessionVariables = { + GTK_USE_PORTAL = "1"; + }; + + xdg.configFile."xdg-desktop-portal-termfilechooser/config".text = '' + [filechooser] + cmd=${lib.getExe yaziFileChooser} + default_dir=$HOME + open_mode=suggested + save_mode=last + ''; + xdg.desktopEntries.yazi = { name = "Yazi"; genericName = "File Manager"; @@ -123,6 +190,8 @@ in { wayland.windowManager.hyprland.settings.windowrule = [ "match:class dragon-drop, move cursor_x-window_w/2 cursor_y-window_h/2" + "match:title Yazi File Picker, float 1" + "match:title Yazi File Picker, size 1200 800" ]; programs.yazi = {