Added local flake for xdg-terminal-exec; let's see if it works

This commit is contained in:
vorboyvo 2024-01-05 23:54:40 -05:00
parent c4408bba73
commit d60f246610
9 changed files with 665 additions and 2 deletions

View file

@ -103,11 +103,15 @@
# List packages installed in system profile. To search, run: # List packages installed in system profile. To search, run:
# $ nix search wget # $ nix search wget
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
xdg-terminal-exec.defaultPackage
neovim neovim
git git
gay # very important, do not remove 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 # Some programs need SUID wrappers, can be configured further or are
# started in user sessions. # started in user sessions.
# programs.mtr.enable = true; # programs.mtr.enable = true;

View file

@ -2,7 +2,11 @@
description = "NixOS configuration with flakes"; description = "NixOS configuration with flakes";
inputs = { inputs = {
nixpkgs.url = "nixpkgs/nixos-unstable"; 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 = { home-manager = {
url = "github:nix-community/home-manager"; url = "github:nix-community/home-manager";
@ -11,7 +15,7 @@
# nur.url = "github:nix-community/NUR"; # nur.url = "github:nix-community/NUR";
}; };
outputs = { self, nixpkgs, home-manager }: outputs = { self, nixpkgs, home-manager, xdg-terminal-exec }:
let let
system = "x86_64-linux"; system = "x86_64-linux";
in in
@ -19,6 +23,7 @@
nixosConfigurations = { nixosConfigurations = {
oyvoLaptop = nixpkgs.lib.nixosSystem { oyvoLaptop = nixpkgs.lib.nixosSystem {
inherit system; inherit system;
_module.args = { inherit xdg-terminal-exec; };
modules = [ modules = [
./configuration.nix ./configuration.nix
home-manager.nixosModules.home-manager home-manager.nixosModules.home-manager

View file

@ -0,0 +1,4 @@
export PATH="$coreutils/bin"
mkdir $out
mkdir $out/bin
cp $src $out/bin/xdg-terminal-exec

View file

@ -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
}

View file

@ -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;
};
}

View file

@ -0,0 +1 @@
/nix/store/gb9xw4vfrq50f63hpzwq1rxda25hjc2k-xdg-terminal-exec

View file

@ -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 <http://www.gnu.org/licenses/>.
#
# 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: <space><tab><newline>
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 "$@"

View file

@ -0,0 +1,11 @@
let
pkgs = import <nixpkgs> {};
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;
}

View file

@ -50,6 +50,9 @@
fonts.fontconfig.enable = true; fonts.fontconfig.enable = true;
xdg.configFile."fontconfig/conf.d/20-default.fonts.conf".source = ./config/20-default-fonts.conf; 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 = { programs.kitty = {
enable = true; enable = true;
font = { font = {