# nix-env-apps: Manage declarative nix-env with a GUI # # This program 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, version 3. import os import subprocess from typing import Any, Callable import gi gi.require_version("Gtk", "4.0") from gi.repository import Gtk # nopep8 pylint: disable=wrong-import-position def on_edit(_: Any) -> None: config_dir = os.path.join( os.environ.get('XDG_CONFIG_HOME', os.path.expanduser('~/.config')), 'nixpkgs', 'overlays') os.makedirs(config_dir, exist_ok=True) config_file = os.path.join(config_dir, 'userPackages.nix') try: with open(config_file, mode="x", encoding='utf-8') as f: f.write('''final: prev: { userPackages = final.buildEnv { name = "userPackages"; paths = (with final; [ ]; }; } ''') except FileExistsError: pass subprocess.run(['xdg-open', config_file], check=True) def try_exec_terminal(terminal: str, args: list[str]) -> None: try: os.execvp(terminal, [terminal] + args) except FileNotFoundError: pass def on_apply(_: Any) -> None: command = ['nix-env', '-riA', 'nixos.userPackages'] command_string = ' '.join(command) close_string = ( ''' && read -p "SUCCESS: Press ENTER to close this window"''' + ''' || read -p "FAILURE: Press ENTER to close this window"''') # This should be a simple `xdg-terminal` invocation, but as of 2025, # xdg-terminal is extremely broken in Gnome: # * It doesn't cause a terminal window to appear # * It doesn't run the given command # * It exits with status 0, so you can't even tell that it failed # * There's no way for the user to configure their preferred terminal? # So we just try launching a whole bunch of terminals until we find one # that works if os.fork() == 0: try_exec_terminal('kitty', ['sh', '-c', command_string + close_string]) try_exec_terminal( 'alactritty', [ '-e', 'sh', '-c', command_string + close_string]) try_exec_terminal('kgx', ['--'] + command) try_exec_terminal( 'konsole', [ '-e', 'sh', '-c', command_string + close_string]) try_exec_terminal('xfce4-terminal', ['--hold', '-e', command_string]) try_exec_terminal('st', ['sh', '-c', command_string + close_string]) try_exec_terminal( 'urxvt', [ '-e', 'sh', '-c', command_string + close_string]) try_exec_terminal( 'xterm', [ '-e', 'sh', '-c', command_string + close_string]) # Don't even try gnome-terminal. Sometimes it will start, but not run the command, # and exit with status 0 so we wouldn't even be able to tell that there was a problem. # try_exec_terminal('gnome-terminal', ['--', 'sh', '-c', command_string + close_string]) # As a last resort, run the command directly, without a terminal, where # the user probably can't see it. :( os.execvp(command[0], command) def make_button(label: str, action: Callable[[Any], None]) -> Gtk.Button: button = Gtk.Button(label=label) button.connect('clicked', action) button.set_margin_top(10) button.set_margin_start(10) button.set_margin_end(10) button.set_margin_bottom(10) return button def on_activate(app: Gtk.Application) -> None: win = Gtk.ApplicationWindow(application=app) win.set_title('Apps') box = Gtk.Box() box.set_orientation(Gtk.Orientation.VERTICAL) edit = make_button("Edit Configuration", on_edit) apply = make_button("Apply Configuration", on_apply) box.append(edit) box.append(apply) win.set_child(box) win.present() def main() -> None: app = Gtk.Application(application_id='net.chkno.nix-env-apps') app.connect('activate', on_activate) app.run(None) if __name__ == '__main__': main()