diff --git a/configuration.nix b/configuration.nix
index 9f3a031..0e630c6 100644
--- a/configuration.nix
+++ b/configuration.nix
@@ -12,6 +12,7 @@
./modules/users.nix # User accounts and global configurations
./modules/apps.nix # All installed packages: apps, CLI tools, dev toolchains
./modules/scripts.nix # Custom system scripts and helpers
+ ./modules/default-apps.nix # Default app associations (terminal, image viewer, archives), LibreOffice
];
# Do not change this value. Read documentation before updating.
diff --git a/dotfiles/quickshell/Theme.qml b/dotfiles/quickshell/Theme.qml
index 5de1909..f06537a 100644
--- a/dotfiles/quickshell/Theme.qml
+++ b/dotfiles/quickshell/Theme.qml
@@ -104,6 +104,11 @@ Item {
theme.dark ? "1" : "0"];
gtkSyncProc.running = true;
+ qtSyncProc.command = ["qt-theme-set",
+ theme.accent, theme.bg, theme.bgAlt, theme.fg, theme.fgDim, theme.border,
+ theme.dark ? "1" : "0"];
+ qtSyncProc.running = true;
+
kittySyncProc.command = ["kitty-theme-set",
theme.accent, theme.bg, theme.bgAlt, theme.fg, theme.fgDim, theme.border];
kittySyncProc.running = true;
@@ -117,6 +122,7 @@ Item {
Process { id: saveProc }
Process { id: hyprSyncProc }
Process { id: gtkSyncProc }
+ Process { id: qtSyncProc }
Process { id: kittySyncProc }
Process { id: rofiSyncProc }
Process {
diff --git a/hyprland/hyprland.conf b/hyprland/hyprland.conf
index 0683b41..8d9c604 100644
--- a/hyprland/hyprland.conf
+++ b/hyprland/hyprland.conf
@@ -42,6 +42,13 @@ env = XDG_SESSION_DESKTOP,Hyprland
env = GDK_BACKEND,wayland,x11
env = QT_QPA_PLATFORM,wayland;xcb
env = QT_QPA_PLATFORMTHEME,qt6ct
+# Kirigami-based KDE apps (confirmed live: Gwenview's toolbar/statusbar)
+# draw their chrome in QML, not QWidget — outside a real Plasma session
+# they default to their own hardcoded light "Basic" QQC2 style instead of
+# reading qt6ct or kdeglobals at all. org.kde.desktop is the QQC2 style
+# that actually pulls from KColorScheme/kdeglobals like the rest of the
+# theming pipeline here does.
+env = QT_QUICK_CONTROLS_STYLE,org.kde.desktop
# --- Input ------------------------------------------------------------
input {
diff --git a/modules/default-apps.nix b/modules/default-apps.nix
new file mode 100644
index 0000000..0ef6b29
--- /dev/null
+++ b/modules/default-apps.nix
@@ -0,0 +1,65 @@
+{ config, pkgs, lib, ... }:
+
+{
+ environment.systemPackages = with pkgs; [
+ libreoffice-qt6 # Qt6-styled build — fits the Qt theming work in theme.nix
+ # better than the default GTK3 build would.
+ ];
+
+ # System-level fallback defaults (XDG spec: only consulted for a
+ # mimetype the user's own ~/.config/mimeapps.list doesn't already
+ # claim), so a future "Open With" choice always wins over this without
+ # needing to touch it. Mimetype lists taken directly from Gwenview's
+ # and Ark's own .desktop files, not guessed.
+ #
+ # One mimetype=desktopfile pair per line: the semicolon only separates
+ # multiple desktop-file candidates for the SAME key, it doesn't chain
+ # separate mimetype=value pairs on one line — mimeapps.list parsers
+ # (verified against xdg-utils' own xdg-mime script) match by scanning
+ # for a line that STARTS WITH "mimetype=", so cramming several pairs
+ # onto one semicolon-joined line silently hides every key after the
+ # first from ever being found.
+ environment.etc."xdg/mimeapps.list".text =
+ let
+ gwenviewMimes = [
+ "inode/directory" "image/avif" "image/gif" "image/heif" "image/jpeg"
+ "image/jxl" "image/png" "image/bmp" "image/x-eps" "image/x-icns"
+ "image/x-ico" "image/x-portable-bitmap" "image/x-portable-graymap"
+ "image/x-portable-pixmap" "image/x-xbitmap" "image/x-xpixmap"
+ "image/tiff" "image/x-psd" "image/x-webp" "image/webp" "image/x-tga"
+ "image/x-xcf" "image/openraster" "image/svg+xml" "image/svg+xml-compressed"
+ ];
+ arkMimes = [
+ "application/x-cd-image" "application/x-bcpio" "application/x-cpio"
+ "application/x-cpio-compressed" "application/x-rpm" "application/x-compress"
+ "application/gzip" "application/x-bzip" "application/x-bzip2"
+ "application/x-lzma" "application/x-xz" "application/zstd" "application/x-lz4"
+ "application/x-source-rpm" "application/vnd.debian.binary-package"
+ "application/vnd.ms-cab-compressed" "application/x-archive" "application/x-tar"
+ "application/x-compressed-tar" "application/x-bzip-compressed-tar"
+ "application/x-bzip2-compressed-tar" "application/x-tarz"
+ "application/x-xz-compressed-tar" "application/x-lzma-compressed-tar"
+ "application/x-7z-compressed" "application/vnd.rar" "application/zip"
+ "application/x-java-archive" "application/x-arj"
+ ];
+ line = desktop: mime: "${mime}=${desktop}";
+ in
+ ''
+ [Default Applications]
+ ${lib.concatMapStringsSep "\n" (line "org.kde.gwenview.desktop") gwenviewMimes}
+ ${lib.concatMapStringsSep "\n" (line "org.kde.ark.desktop") arkMimes}
+ '';
+
+ # Thunar's own "Open Terminal Here" custom action (Edit > Configure
+ # Custom Actions) shipped pointing at `exo-open --launch
+ # TerminalEmulator` — exo isn't installed on this system at all (no
+ # XFCE session, so its helpers.rc mechanism has nothing to resolve
+ # against), so the action silently did nothing. `f`, not `w`: only
+ # creates this baseline for a fresh account — once it exists, further
+ # edits made through Thunar's own UI (adding more custom actions) are
+ # never overwritten by a rebuild.
+ systemd.tmpfiles.rules = [
+ "d /home/nexoniarz/.config/Thunar 0755 nexoniarz users -"
+ ''f /home/nexoniarz/.config/Thunar/uca.xml 0644 nexoniarz users - utilities-terminalOpen Terminal Here1788014825097143-1kitty --working-directory %fOpen a terminal in this folder*''
+ ];
+}
diff --git a/modules/scripts.nix b/modules/scripts.nix
index fa6d770..ffa3cd7 100644
--- a/modules/scripts.nix
+++ b/modules/scripts.nix
@@ -12,6 +12,7 @@ let
displaySet = pkgs.writeShellScriptBin "display-set" (builtins.readFile ./scripts/display-set.sh);
hyprThemeSet = pkgs.writeShellScriptBin "hypr-theme-set" (builtins.readFile ./scripts/hypr-theme-set.sh);
gtkThemeSet = pkgs.writeShellScriptBin "gtk-theme-set" (builtins.readFile ./scripts/gtk-theme-set.sh);
+ qtThemeSet = pkgs.writeShellScriptBin "qt-theme-set" (builtins.readFile ./scripts/qt-theme-set.sh);
kittyThemeSet = pkgs.writeShellScriptBin "kitty-theme-set" (builtins.readFile ./scripts/kitty-theme-set.sh);
rofiThemeSet = pkgs.writeShellScriptBin "rofi-theme-set" (builtins.readFile ./scripts/rofi-theme-set.sh);
listCursorThemes = pkgs.writeShellScriptBin "list-cursor-themes" (builtins.readFile ./scripts/list-cursor-themes.sh);
@@ -36,6 +37,7 @@ in
displaySet
hyprThemeSet
gtkThemeSet
+ qtThemeSet
kittyThemeSet
rofiThemeSet
listCursorThemes
diff --git a/modules/scripts/display-set.sh b/modules/scripts/display-set.sh
index 5471e6c..b7c74f5 100644
--- a/modules/scripts/display-set.sh
+++ b/modules/scripts/display-set.sh
@@ -25,7 +25,15 @@ touch "$conf"
line="monitor = $descriptor"
if grep -q "^monitor = $mon," "$conf" 2>/dev/null; then
- tmp=$(mktemp)
+ # mktemp in the same directory as $conf, not the default /tmp: /tmp is
+ # a different filesystem here, which makes `mv` below a non-atomic
+ # copy+unlink instead of a rename — Hyprland's file watcher (it
+ # watches every `source=`d file, including this one) could catch the
+ # destination mid-replace and its `source=` glob would find nothing,
+ # throwing "globbing error: found no match" (observed live after an
+ # orientation change, which always hits this branch since DP-1
+ # already has an existing line by then).
+ tmp=$(mktemp "$conf.XXXXXX")
awk -v prefix="monitor = $mon," -v newline="$line" '
index($0, prefix) == 1 { print newline; next } { print }
' "$conf" > "$tmp"
diff --git a/modules/scripts/gtk-theme-set.sh b/modules/scripts/gtk-theme-set.sh
index d656beb..efa4034 100644
--- a/modules/scripts/gtk-theme-set.sh
+++ b/modules/scripts/gtk-theme-set.sh
@@ -117,3 +117,16 @@ if command -v dconf >/dev/null 2>&1; then
dconf write /org/gnome/desktop/interface/icon-theme "'$icon_theme'"
dconf write /org/gnome/desktop/interface/color-scheme "'$color_scheme'"
fi
+
+# GTK2 compat: separate config format (~/.gtkrc-2.0, not settings.ini),
+# and no live theme-switching mechanism at all — GTK2 apps only read this
+# at their own startup. Only icon theme and font actually follow the
+# current theme; gnome-themes-extra's bundled GTK2 Adwaita engine has no
+# dark variant to switch to (GTK2 predates that concept), so gtk-theme-
+# name here is always the one real option, not a broken attempt at dark
+# mode.
+cat > "$HOME/.gtkrc-2.0" < }"
+bg="${2:?bg required}"
+bg_alt="${3:?bgAlt required}"
+fg="${4:?fg required}"
+fg_dim="${5:?fgDim required}"
+border="${6:?border required}"
+dark="${7:?dark required}"
+
+dir="$HOME/.config/qt6ct"
+mkdir -p "$dir/colors"
+
+# qt6ct color scheme files store QPalette::ColorRole values as a fixed-
+# order, comma-separated #aarrggbb list — order and format verified
+# against the example schemes qt6ct itself ships
+# (share/qt6ct/colors/darker.conf etc.), not guessed: WindowText, Button,
+# Light, Midlight, Dark, Mid, Text, BrightText, ButtonText, Base, Window,
+# Shadow, Highlight, HighlightedText, Link, LinkVisited, AlternateBase,
+# NoRole, ToolTipBase, ToolTipText, PlaceholderText.
+a() { printf '#ff%s' "${1#\#}"; } # opaque
+t() { printf '#80%s' "${1#\#}"; } # translucent, for PlaceholderText
+
+active="$(a "$fg"), $(a "$bg_alt"), $(a "$border"), $(a "$bg_alt"), $(a "$bg"), $(a "$border"), $(a "$fg"), $(a "$fg"), $(a "$fg"), $(a "$bg"), $(a "$bg"), $(a "$border"), $(a "$accent"), $(a "$bg"), $(a "$accent"), $(a "$accent"), $(a "$bg_alt"), $(a "$bg"), $(a "$bg_alt"), $(a "$fg"), $(t "$fg_dim")"
+disabled="$(a "$fg_dim"), $(a "$bg_alt"), $(a "$border"), $(a "$bg_alt"), $(a "$bg"), $(a "$border"), $(a "$fg_dim"), $(a "$fg_dim"), $(a "$fg_dim"), $(a "$bg"), $(a "$bg"), $(a "$border"), $(a "$accent"), $(a "$fg_dim"), $(a "$accent"), $(a "$accent"), $(a "$bg_alt"), $(a "$bg"), $(a "$bg_alt"), $(a "$fg_dim"), $(t "$fg_dim")"
+
+color_file="$dir/colors/quickshell-theme.conf"
+cat > "$color_file" < "$dir/qt6ct.conf" < "$HOME/.config/kdeglobals" <