diff --git a/configuration.nix b/configuration.nix
index 76d01cd..59b4382 100644
--- a/configuration.nix
+++ b/configuration.nix
@@ -103,11 +103,15 @@
# List packages installed in system profile. To search, run:
# $ nix search wget
environment.systemPackages = with pkgs; [
+ xdg-terminal-exec.defaultPackage
neovim
git
gay # very important, do not remove
];
+ # Enable dconf; necessary for some programs
+ programs.dconf.enable = true;
+
# Some programs need SUID wrappers, can be configured further or are
# started in user sessions.
# programs.mtr.enable = true;
diff --git a/flake.nix b/flake.nix
index b81ea3a..42a5d56 100644
--- a/flake.nix
+++ b/flake.nix
@@ -2,8 +2,12 @@
description = "NixOS configuration with flakes";
inputs = {
nixpkgs.url = "nixpkgs/nixos-unstable";
- # nixos-hardware.url = "github:NixOS/nixos-hardware/master" # Why does this exist on my macbook?
+ xdg-terminal-exec = {
+ url = "path:./xdg-terminal-exec";
+ inputs.nixpkgs.follows = "nixpkgs";
+ };
+
home-manager = {
url = "github:nix-community/home-manager";
inputs.nixpkgs.follows = "nixpkgs";
@@ -11,7 +15,7 @@
# nur.url = "github:nix-community/NUR";
};
- outputs = { self, nixpkgs, home-manager }:
+ outputs = { self, nixpkgs, home-manager, xdg-terminal-exec }:
let
system = "x86_64-linux";
in
@@ -19,6 +23,7 @@
nixosConfigurations = {
oyvoLaptop = nixpkgs.lib.nixosSystem {
inherit system;
+ _module.args = { inherit xdg-terminal-exec; };
modules = [
./configuration.nix
home-manager.nixosModules.home-manager
diff --git a/flakes/xdg-terminal-exec/builder.sh b/flakes/xdg-terminal-exec/builder.sh
new file mode 100644
index 0000000..340c56f
--- /dev/null
+++ b/flakes/xdg-terminal-exec/builder.sh
@@ -0,0 +1,4 @@
+export PATH="$coreutils/bin"
+mkdir $out
+mkdir $out/bin
+cp $src $out/bin/xdg-terminal-exec
diff --git a/flakes/xdg-terminal-exec/flake.lock b/flakes/xdg-terminal-exec/flake.lock
new file mode 100644
index 0000000..d473d92
--- /dev/null
+++ b/flakes/xdg-terminal-exec/flake.lock
@@ -0,0 +1,25 @@
+{
+ "nodes": {
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1704161960,
+ "narHash": "sha256-QGua89Pmq+FBAro8NriTuoO/wNaUtugt29/qqA8zeeM=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "63143ac2c9186be6d9da6035fa22620018c85932",
+ "type": "github"
+ },
+ "original": {
+ "id": "nixpkgs",
+ "type": "indirect"
+ }
+ },
+ "root": {
+ "inputs": {
+ "nixpkgs": "nixpkgs"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/flakes/xdg-terminal-exec/flake.nix b/flakes/xdg-terminal-exec/flake.nix
new file mode 100644
index 0000000..889a75f
--- /dev/null
+++ b/flakes/xdg-terminal-exec/flake.nix
@@ -0,0 +1,11 @@
+{
+ description = "A very basic flake";
+
+ outputs = { self, nixpkgs }: {
+
+ packages.x86_64-linux.xdg-terminal-exec = nixpkgs.legacyPackages.x86_64-linux.xdg-terminal-exec;
+
+ packages.x86_64-linux.default = self.packages.x86_64-linux.xdg-terminal-exec;
+
+ };
+}
diff --git a/flakes/xdg-terminal-exec/result b/flakes/xdg-terminal-exec/result
new file mode 120000
index 0000000..d6cc285
--- /dev/null
+++ b/flakes/xdg-terminal-exec/result
@@ -0,0 +1 @@
+/nix/store/gb9xw4vfrq50f63hpzwq1rxda25hjc2k-xdg-terminal-exec
\ No newline at end of file
diff --git a/flakes/xdg-terminal-exec/xdg-terminal-exec b/flakes/xdg-terminal-exec/xdg-terminal-exec
new file mode 100755
index 0000000..e54779c
--- /dev/null
+++ b/flakes/xdg-terminal-exec/xdg-terminal-exec
@@ -0,0 +1,599 @@
+#!/bin/sh
+# Proposal for XDG terminal execution utility
+#
+# by Vladimir Kudrya
+# https://github.com/Vladimir-csp/
+#
+# This script is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version. See .
+#
+# Contributors:
+# Roman Chistokhodov https://github.com/FreeSlave/
+# fluvf https://github.com/fluvf
+
+# Treat non-zero exit status from simple commands as an error
+# Treat unset variables as errors when performing parameter expansion
+# Disable pathname expansion
+set -euf
+
+# Store original IFS value, assumed to contain the default:
+OIFS="$IFS"
+# Newline, utility variable used throughout the script
+N='
+'
+
+# Utility function to print messages to stderr
+error() { printf '%s\n' "$@" >&2; }
+
+check_bool() {
+ case "$1" in
+ true | True | TRUE | yes | Yes | YES | 1) return 0 ;;
+ false | False | FALSE | no | No | NO | 0) return 1 ;;
+ *)
+ error "Assuming '$1' means no"
+ return 1
+ ;;
+ esac
+}
+
+# Utility function to print debug messages to stderr (or not)
+if check_bool "${DEBUG-0}"; then
+ debug() { printf 'D: %s\n' "$@" >&2; }
+else
+ debug() { :; }
+fi
+
+# Populates global constants and lists for later use and iteration
+make_paths() {
+ IFS=':'
+
+ # Populate list of config files to read, in descending order of preference
+ for dir in ${XDG_CONFIG_HOME:-"${HOME}/.config"}${IFS}${XDG_CONFIG_DIRS:-/etc/xdg}; do
+ # Normalise base path and append the data subdirectory with a trailing '/'
+ for desktop in ${LOWERCASE_XDG_CURRENT_DESKTOP}; do
+ CONFIGS=${CONFIGS:+${CONFIGS}${IFS}}${dir%/}/${desktop}-xdg-terminals.list
+ done
+ CONFIGS=${CONFIGS:+${CONFIGS}${IFS}}${dir%/}/xdg-terminals.list
+ done
+
+ # Populate list of directories to search for entries in, in ascending order of preference
+ for dir in ${XDG_DATA_HOME:-${HOME}/.local/share}${IFS}${XDG_DATA_DIRS:-/usr/local/share:/usr/share}; do
+ # Normalise base path and append the data subdirectory with a trailing '/'
+ APPLICATIONS_DIRS=${dir%/}/applications/${APPLICATIONS_DIRS:+${IFS}${APPLICATIONS_DIRS}}
+ done
+
+ # cache
+ XDG_CACHE_HOME=${XDG_CACHE_HOME:-"${HOME}/.cache"}
+ CACHE_FILE="${XDG_CACHE_HOME}/xdg-terminal-exec"
+
+ debug "paths:" "CONFIGS=${CONFIGS}" "APPLICATIONS_DIRS=${APPLICATIONS_DIRS}"
+}
+# Mask IFS withing function to allow temporary changes
+alias make_paths='IFS= make_paths'
+
+gen_hash() {
+ # return md5 of XDG_CURRENT DESKTOP and ls -LRl output for config and data paths
+ # md5 is 4x faster than sha*, and there is no need for cryptography here
+ # shellcheck disable=SC2034
+ read -r hash drop <<- EOH
+ $(
+ hash_paths="${CONFIGS}:${APPLICATIONS_DIRS}"
+ {
+ echo "${XDG_CURRENT_DESKTOP-}"
+ IFS=':'
+ # shellcheck disable=SC2086
+ debug "> hashing '${XDG_CURRENT_DESKTOP-}' and listing of:" $hash_paths "^ end of hash listing"
+ # shellcheck disable=SC2012,SC2086
+ LANG=C ls -LRl ${hash_paths} 2> /dev/null
+ } | md5sum 2> /dev/null
+ )
+ EOH
+ case "$hash" in
+ [0-9a-f]??????????????????????????????[0-9a-f])
+ debug "got fresh hash '$hash'"
+ echo "$hash"
+ return 0
+ ;;
+ *)
+ debug "failed to get fresh hash, got '$hash'"
+ return 1
+ ;;
+ esac
+}
+
+read_cache() {
+ # reads $cached_hash, $cached_exec, $cached_execarg, $cached_cmd from cache file,
+ # checks if cache is actual and applies it, otherwise returns 1
+ # tries to bail out as soon as possible if something does not fit
+ if [ -f "${CACHE_FILE}" ]; then
+ IFS=${N}
+ line_num=0
+ while read -r line; do
+ line_num=$((line_num + 1))
+ case "${line_num}_${line}" in
+ 1_[0-9a-f]??????????????????????????????[0-9a-f]) cached_hash=$line ;;
+ 2_*) cached_exec=$line ;;
+ 3_ | 3_*) cached_execarg=$line ;;
+ 4_*)
+ # get cmd and break right away, line_num will be left at 4
+ cached_cmd=$line
+ break
+ ;;
+ *)
+ debug "cache line ${line_num} is invalid: ${line}"
+ return 1
+ ;;
+ esac
+ done < "${CACHE_FILE}"
+ if [ "$line_num" = "4" ]; then
+ debug "got cache:" "${cached_hash}" "${cached_exec}" "${cached_execarg}" "${cached_cmd}"
+ IFS=$OIFS
+ HASH=$(gen_hash) || return 1
+ if [ "$HASH" = "$cached_hash" ] && command -v "$cached_cmd" > /dev/null; then
+ debug "cache is actual"
+ EXEC=${cached_exec}
+ EXECARG=${cached_execarg}
+ return 0
+ else
+ debug "cache is out-of-date"
+ return 1
+ fi
+ else
+ debug "invalid cache data"
+ return 1
+ fi
+ else
+ debug "no cache data"
+ return 1
+ fi
+}
+# Mask IFS withing function to allow temporary changes
+alias read_cache='IFS= read_cache'
+
+save_cache() {
+ # saves $HASH, $EXEC, $EXECARG, $1 (executable) to cache file or removes it if CACHE_ENABLE is false
+ if check_bool "$CACHE_ENABLED"; then
+ [ ! -d "${XDG_CACHE_HOME}" ] && mkdir -p "${XDG_CACHE_HOME}"
+ if [ -z "${HASH-}" ]; then
+ HASH=$(gen_hash) || {
+ echo "could not hash listing, removing '${CACHE_FILE}'" >&2
+ rm -f "${CACHE_FILE}"
+ return 0
+ }
+ fi
+ UM=$(umask)
+ umask 0077
+ printf '%s\n' "${HASH}" "${EXEC}" "${EXECARG}" "${1}" > "${CACHE_FILE}"
+ umask "$UM"
+ debug "> saved cache:" "${HASH}" "${EXEC}" "${EXECARG}" "${1}" "^ end of saved cache"
+ else
+ debug "cache is disabled, removing '${CACHE_FILE}'"
+ rm -f "${CACHE_FILE}"
+ return 0
+ fi
+}
+
+# Parse all config files and populate $ENTRY_IDS with read desktop entry IDs
+read_config_paths() {
+ # All config files are read immediatelly, rather than on demand, even if it's more IO intensive
+ # This way all IDs are already known, and in order of preference, before iterating over them
+ IFS=':'
+ for config_path in ${CONFIGS}; do
+ debug "reading config '$config_path'"
+ # Nonexistant file is not an error
+ [ -f "$config_path" ] || continue
+ # Let `read` trim leading/trailing whitespace from the line
+ while IFS="$OIFS" read -r line; do
+ #debug "read line '$line'"
+ case $line in
+
+ # Catch directives first
+
+ # cache control
+ /enable_cache)
+ debug "found '$line' directive${XTE_CACHE_ENABLED+ (ignored)}"
+ CACHE_ENABLED=${XTE_CACHE_ENABLED-true}
+ ;;
+ /disable_cache)
+ debug "found '$line' directive${XTE_CACHE_ENABLED+ (ignored)}"
+ CACHE_ENABLED=${XTE_CACHE_ENABLED-false}
+ ;;
+
+ # `[The extensionless entry filename] should be a valid D-Bus well-known name.`
+ # `a sequence of non-empty elements separated by dots (U+002E FULL STOP),
+ # none of which starts with a digit, and each of which contains only characters from the set [a-zA-Z0-9-_]`
+ # Stricter parts seem to be related only to reversed DNS notation but not common naming
+ # i.e. there is `2048-qt.desktop`.
+ # I do not know of any terminal that starts with a number, but it's valid.
+
+ # Catch and validate potential entry ID with action ID (be graceful about an empty one)
+ [a-zA-Z0-9_]*)
+ # consider only the first ':' as a delimiter
+ IFS=':' read -r entry_id action_id <<- EOL
+ $line
+ EOL
+ if validate_entry_id "${entry_id}" && validate_action_id "${action_id}"; then
+ ENTRY_IDS=${ENTRY_IDS:+${ENTRY_IDS}${N}}$line
+ debug "added entry ID with action ID '$line'"
+ else
+ error "Discarded possibly misspelled entry '$line'"
+ fi
+ ;;
+
+ esac
+ # By default empty lines and comments get ignored
+ done < "$config_path"
+ done
+}
+# Mask IFS withing function to allow temporary changes
+alias read_config_paths='IFS= read_config_paths'
+
+replace() {
+ # takes $1, finds $2, replaces with $3
+ # does it in large chunks
+
+ # var to be modified
+ string=${1}
+ # right part of string
+ r_string=${1}
+ # left part of string
+ l_string=''
+ # previous right part of string
+ prev_r_string=''
+ while true; do
+ # save previous r_string
+ prev_r_string=${r_string}
+ # cut the right part with search string from the left
+ r_string=${r_string#*"${2}"}
+ # cut the left part with search string and rigth part from the right
+ l_string=${string%"${2}${r_string}"}
+ case "$r_string" in
+ # if the right part was not unmodified, there is nothing to replace
+ "$prev_r_string") break ;;
+ # if the right part was is modified, update string with:
+ # the left part, replace string, the right part
+ *) string=${l_string}${3}${r_string} ;;
+ esac
+ done
+ echo "$string"
+}
+
+# Find and map all desktop entry files from standardised paths into aliases
+find_entry_paths() {
+ debug "registering entries"
+ # Append application directory paths to be searched
+ IFS=':'
+ for directory in $APPLICATIONS_DIRS; do
+ # Append '.' to delimit start of entry ID
+ set -- "$@" "$directory".
+ done
+
+ # Find all desktop entries with valid names
+ set -- "$@" -type f -name '[a-zA-Z0-9_]*.desktop' ! -path '*[^a-zA-Z0-9_./-]*'
+
+ # Loop through found entry paths and IDs
+ IFS=$N
+ while read -r entry_path && read -r entry_id; do
+ # Entries are checked in ascending order of preference, so use last found if duplicate
+ # shellcheck disable=SC2139
+ alias "$entry_id"="entry_path='$entry_path'"
+ debug "registered '$entry_path' as entry '$entry_id'"
+ # Add as a fallback ID regardles if it's a duplicate
+ FALLBACK_ENTRY_IDS=${entry_id}${FALLBACK_ENTRY_IDS:+${N}${FALLBACK_ENTRY_IDS}}
+ debug "added fallback ID '$entry_id'"
+ done <<- EOE
+ $(
+ # Don't complain about nonexistent directories
+ find -L "$@" 2> /dev/null |
+ # Print entry path and convert it into an ID and print that too
+ awk '{ print; sub(".*/[.]/", ""); gsub("/", "-"); print }'
+ )
+ EOE
+}
+# Mask IFS withing function to allow temporary changes
+alias find_entry_paths='IFS= find_entry_paths'
+
+# Check validity of a given entry key - value pair
+# Modifies following global variables:
+# EXEC : Program to execute, possibly with arguments. See spec for details.
+# EXECARG : Execution argument for the terminal emulator.
+# TERMINAL : Set if application has been categorized as a terminal emulator
+check_entry_key() {
+ key="$1"
+ value="$2"
+ action="$3"
+ read_exec="$4"
+ de_checks="$5"
+
+ # Order of checks is important
+ case $key in
+ 'Categories'*=*)
+ debug "checking for 'TerminalEmulator' in Categories '$value'"
+ IFS=';'
+ for category in $value; do
+ [ "$category" = "TerminalEmulator" ] && {
+ TERMINAL=true
+ return 0
+ }
+ done
+ # Default in this case is to fail
+ return 1
+ ;;
+ 'Actions'*=*)
+ # `It is not valid to have an action group for an action identifier not mentioned in the Actions key.
+ # Such an action group must be ignored by implementors.`
+ # ignore if no action requested
+ [ -z "$action" ] && return 0
+ debug "checking for '$action' in Actions '$value'"
+ IFS=';'
+ for check_action in $value; do
+ [ "$check_action" = "$action" ] && return 0
+ done
+ # Default in this case is to fail
+ return 1
+ ;;
+ 'OnlyShowIn'*=*)
+ case "$de_checks" in
+ true) debug "checking for intersecion between '${XDG_CURRENT_DESKTOP-}' and OnlyShowIn '$value'" ;;
+ false)
+ debug "skipping OnlyShowIn check"
+ return 0
+ ;;
+ esac
+ IFS=';'
+ for target in $value; do
+ IFS=':'
+ for desktop in ${XDG_CURRENT_DESKTOP-}; do
+ [ "$desktop" = "$target" ] && return 0
+ done
+ done
+ # Default in this case is to fail
+ return 1
+ ;;
+ 'NotShowIn'*=*)
+ case "$de_checks" in
+ true) debug "checking for intersecion between '${XDG_CURRENT_DESKTOP-}' and NotShowIn '$value'" ;;
+ false)
+ debug "skipping NotShowIn check"
+ return 0
+ ;;
+ esac
+ IFS=';'
+ for target in $value; do
+ IFS=':'
+ for desktop in ${XDG_CURRENT_DESKTOP-}; do
+ debug "checking NotShowIn match '$desktop'='$target'"
+ [ "$desktop" = "$target" ] && return 1
+ done
+ done
+ # Default in this case is to succeed
+ return 0
+ ;;
+ 'X-ExecArg'*=* | 'ExecArg'*=*)
+ # Set global variable
+ EXECARG=$value
+ debug "read ExecArg '$EXECARG'"
+ ;;
+ 'TryExec'*=*)
+ debug "checking TryExec executable '$value'"
+ command -v "$value" > /dev/null || return 1
+ ;;
+ 'Hidden'*=*)
+ debug "checking boolean Hidden '$value'"
+ [ "$value" = 'true' ] && return 1
+ ;;
+ 'Exec'*=*)
+ case "$read_exec" in
+ false)
+ debug "ignored Exec from wrong section"
+ return 0
+ ;;
+ esac
+ debug "read Exec '$value'"
+ # Set global variable
+ EXEC=$value
+ # Get first word from read Exec value
+ IFS="$OIFS"
+ eval "set -- $EXEC"
+ debug "checking Exec[0] executable '$1'"
+ command -v "$1" > /dev/null || return 1
+ ;;
+ esac
+ # By default unrecognised keys, empty lines and comments get ignored
+}
+# Mask IFS withing function to allow temporary changes
+alias check_entry='IFS= check_entry'
+
+# Read entry from given path
+read_entry_path() {
+ entry_path="$1"
+ entry_action="$2"
+ de_checks="$3"
+ read_exec=false
+ # shellcheck disable=SC2016
+ debug "reading desktop entry '$entry_path'${entry_action:+ action '}$entry_action${entry_action:+'}"
+ # Let `read` trim leading/trailing whitespace from the line
+ while IFS="$OIFS" read -r line; do
+ case $line in
+ # `There should be nothing preceding [the Desktop Entry group] in the desktop entry file but [comments]`
+ # if entry_action is not requested, allow reading Exec right away from the main group
+ '[Desktop Entry]'*) [ -z "$entry_action" ] && read_exec=true ;;
+ # A `Key=Value` pair
+ [a-zA-Z0-9-]*)
+ # Split value from pair
+ value=${line#*=}
+ # Remove all but leading spaces, and trim that from the value
+ value=${value#"${value%%[! ]*}"}
+ # Check the key
+ check_entry_key "$line" "$value" "$entry_action" "$read_exec" "$de_checks" && continue
+ # Reset values that might have been set
+ unset EXEC
+ unset EXECARG
+ unset TERMINAL
+ # shellcheck disable=SC2016
+ debug "entry discarded"
+ return 1
+ ;;
+ # found requested action, allow reading Exec
+ "[Desktop Action ${entry_action}]"*) read_exec=true ;;
+ # Start of the next group header, stop if already read exec
+ '['*) [ "$read_exec" = "true" ] && break ;;
+ esac
+ # By default empty lines and comments get ignored
+ done < "$entry_path"
+}
+
+validate_entry_id() {
+ # validates entry ID ($1)
+
+ case "$1" in
+ # invalid characters or degrees of emptiness
+ *[!a-zA-Z0-9_.-]* | *[!a-zA-Z0-9_.-] | [!a-zA-Z0-9_.-]* | [!a-zA-Z0-9_.-] | '' | .desktop)
+ debug "string not valid as Entry ID: '$1'"
+ return 1
+ ;;
+ # all that left with .desktop
+ *.desktop) return 0 ;;
+ # and without
+ *)
+ debug "string not valid as Entry ID '$1'"
+ return 1
+ ;;
+ esac
+}
+
+validate_action_id() {
+ # validates action ID ($1)
+
+ case "$1" in
+ # empty is ok
+ '') return 0 ;;
+ # invalid characters
+ *[!a-zA-Z0-9-]* | *[!a-zA-Z0-9-] | [!a-zA-Z0-9-]* | [!a-zA-Z0-9-])
+ debug "string not valid as Action ID: '$1'"
+ return 1
+ ;;
+ # all that left
+ *) return 0 ;;
+ esac
+}
+
+# Loop through IDs and try to find a valid entry
+find_entry() {
+ # for explicitly listed entries do not apply DE *ShowIn limits
+ de_checks=false
+ IFS="$N"
+ for entry_id in ${ENTRY_IDS}${N}//fallback_start//${N}$FALLBACK_ENTRY_IDS; do
+ case "$entry_id" in
+ # entry has an action appended
+ *:*)
+ entry_action=${entry_id#*:}
+ entry_id=${entry_id%:*}
+ ;;
+ # skip empty line
+ '') continue ;;
+ # fallback entries ahead, enable *ShowIn checks
+ '//fallback_start//')
+ de_checks=true
+ continue
+ ;;
+ # nullify action
+ *) entry_action='' ;;
+ esac
+
+ debug "matching path for entry ID '$entry_id'"
+ # Check if a matching path was found for ID
+ alias "$entry_id" > /dev/null 2>&1 || continue
+ # Evaluates the alias, it sets $entry_path
+ eval "$entry_id"
+ # Unset the alias, so duplicate entries are skipped
+ unalias "$entry_id"
+ read_entry_path "$entry_path" "$entry_action" "$de_checks" || continue
+ # Check that the entry is actually executable
+ [ -z "${EXEC-}" ] && continue
+ # ensure entry is a Terminal Emulator
+ [ -z "${TERMINAL-}" ] && continue
+ # Set defaults
+ : "${EXECARG="-e"}"
+ # Entry is valid, stop
+ return 0
+ done
+ # shellcheck disable=SC2086
+ IFS=':' error "No valid terminal entry was found in:" ${APPLICATIONS_DIRS}
+ return 1
+}
+# Mask IFS withing function to allow temporary changes
+alias find_entry='IFS= find_entry'
+
+## globals
+LOWERCASE_XDG_CURRENT_DESKTOP=$(echo "${XDG_CURRENT_DESKTOP-}" | tr '[:upper:]' '[:lower:]')
+
+# this will receive proper value later
+APPLICATIONS_DIRS=''
+
+# path iterators
+make_paths
+
+# At this point we have no way of telling if cache is enabled or not, unless XTE_CACHE_ENABLED is set,
+# so just try reading it as if default is true, otherwise do the usual thing.
+# Editing config to disable cache should invalidate the cache.
+# The true default is false though:
+CACHE_ENABLED=${XTE_CACHE_ENABLED-false}
+
+# HASH can be reused
+HASH=''
+
+if check_bool "${XTE_CACHE_ENABLED-true}" && read_cache; then
+ CACHE_USED=true
+else
+ # continue with globals
+ CACHE_USED=false
+
+ # All desktop entry ids in descending order of preference from *xdg-terminals.list configs,
+ # with duplicates removed
+ ENTRY_IDS=''
+ # All desktop entry ids found in data dirs in descending order of preference,
+ # with duplicates (including those in $ENTRY_IDS) removed
+ FALLBACK_ENTRY_IDS=''
+
+ # Modifies $ENTRY_IDS
+ read_config_paths
+ # Modifies $ENTRY_IDS and sets global aliases
+ find_entry_paths
+
+ # shellcheck disable=SC2086
+ IFS="$N" debug "> final entry ID list:" ${ENTRY_IDS} "^ end of final entry ID list"
+ # shellcheck disable=SC2086
+ IFS="$N" debug "> final fallback entry ID list:" ${FALLBACK_ENTRY_IDS} "^ end of final fallback entry ID list"
+
+ # walk ID lists and find first applicable
+ find_entry || exit 1
+fi
+
+# Store original argument list, before it's modified
+debug "> original args:" "$@" "^ end of original args" "EXEC=$EXEC" "EXECARG=$EXECARG"
+
+# drop -e or custom ExecArg if given as the first arg
+case "${1-}" in
+'-e' | "$EXECARG")
+ debug "dropping '$1' from received args"
+ shift ;;
+esac
+
+# `Implementations must undo quoting [in the Exec argument(s)][...]`
+if [ "$#" -gt 0 ]; then
+ eval "set -- $EXEC ${EXECARG:+'"$EXECARG"'} \"\$@\""
+else
+ eval "set -- $EXEC"
+fi
+
+debug "> final args:" "$@" "^ end of final args"
+
+if [ "$CACHE_USED" = "false" ]; then
+ # saves or removes cache, forked out of the way
+ save_cache "$1" &
+fi
+
+exec "$@"
diff --git a/flakes/xdg-terminal-exec/xdg-terminal-exec.nix b/flakes/xdg-terminal-exec/xdg-terminal-exec.nix
new file mode 100644
index 0000000..beb49dc
--- /dev/null
+++ b/flakes/xdg-terminal-exec/xdg-terminal-exec.nix
@@ -0,0 +1,11 @@
+let
+ pkgs = import {};
+in
+pkgs.stdenv.mkDerivation {
+ name = "xdg-terminal-exec";
+ builder = "${pkgs.bash}/bin/bash";
+ args = [ ./builder.sh ];
+ coreutils = pkgs.coreutils;
+ src = ./xdg-terminal-exec;
+ system = builtins.currentSystem;
+}
diff --git a/home.nix b/home.nix
index 71c6427..d16e941 100644
--- a/home.nix
+++ b/home.nix
@@ -49,6 +49,9 @@
# configure fonts correctly
fonts.fontconfig.enable = true;
xdg.configFile."fontconfig/conf.d/20-default.fonts.conf".source = ./config/20-default-fonts.conf;
+
+ # fix nemo terminal integration
+ dconf.settings."org/cinnamon/desktop/applications/terminal".exec = "kitty";
programs.kitty = {
enable = true;