]> git.scottworley.com Git - nix-env-apps/blob - apps.py
Desktop Entry
[nix-env-apps] / apps.py
1 # nix-env-apps: Manage declarative nix-env with a GUI
2 #
3 # This program is free software: you can redistribute it and/or modify it
4 # under the terms of the GNU General Public License as published by the
5 # Free Software Foundation, version 3.
6
7 import os
8 import subprocess
9 from typing import Any, Callable
10
11 import gi
12 gi.require_version("Gtk", "4.0")
13 from gi.repository import Gtk # nopep8 pylint: disable=wrong-import-position
14
15
16 def on_edit(_: Any) -> None:
17 config_dir = os.path.join(
18 os.environ.get('XDG_CONFIG_HOME', os.path.expanduser('~/.config')),
19 'nixpkgs',
20 'overlays')
21 os.makedirs(config_dir, exist_ok=True)
22 config_file = os.path.join(config_dir, 'userPackages.nix')
23 try:
24 with open(config_file, mode="x", encoding='utf-8') as f:
25 f.write('''final: prev: {
26 userPackages = final.buildEnv {
27 name = "userPackages";
28 paths = (with final; [
29
30
31
32 ];
33 };
34 }
35 ''')
36 except FileExistsError:
37 pass
38 subprocess.run(['xdg-open', config_file], check=True)
39
40
41 def try_exec_terminal(terminal: str, args: list[str]) -> None:
42 try:
43 os.execvp(terminal, [terminal] + args)
44 except FileNotFoundError:
45 pass
46
47
48 def on_apply(_: Any) -> None:
49 command = ['nix-env', '-riA', 'nixos.userPackages']
50 command_string = ' '.join(command)
51 close_string = (
52 ''' && read -p "SUCCESS: Press ENTER to close this window"''' +
53 ''' || read -p "FAILURE: Press ENTER to close this window"''')
54 # This should be a simple `xdg-terminal` invocation, but as of 2025,
55 # xdg-terminal is extremely broken in Gnome:
56 # * It doesn't cause a terminal window to appear
57 # * It doesn't run the given command
58 # * It exits with status 0, so you can't even tell that it failed
59 # * There's no way for the user to configure their preferred terminal?
60 # So we just try launching a whole bunch of terminals until we find one
61 # that works
62 if os.fork() == 0:
63 try_exec_terminal('kitty', ['sh', '-c', command_string + close_string])
64 try_exec_terminal(
65 'alactritty', [
66 '-e', 'sh', '-c', command_string + close_string])
67 try_exec_terminal('kgx', ['--'] + command)
68 try_exec_terminal(
69 'konsole', [
70 '-e', 'sh', '-c', command_string + close_string])
71 try_exec_terminal('xfce4-terminal', ['--hold', '-e', command_string])
72 try_exec_terminal('st', ['sh', '-c', command_string + close_string])
73 try_exec_terminal(
74 'urxvt', [
75 '-e', 'sh', '-c', command_string + close_string])
76 try_exec_terminal(
77 'xterm', [
78 '-e', 'sh', '-c', command_string + close_string])
79
80 # Don't even try gnome-terminal. Sometimes it will start, but not run the command,
81 # and exit with status 0 so we wouldn't even be able to tell that there was a problem.
82 # try_exec_terminal('gnome-terminal', ['--', 'sh', '-c', command_string + close_string])
83
84 # As a last resort, run the command directly, without a terminal, where
85 # the user probably can't see it. :(
86 os.execvp(command[0], command)
87
88
89 def make_button(label: str, action: Callable[[Any], None]) -> Gtk.Button:
90 button = Gtk.Button(label=label)
91 button.connect('clicked', action)
92 button.set_margin_top(10)
93 button.set_margin_start(10)
94 button.set_margin_end(10)
95 button.set_margin_bottom(10)
96 return button
97
98
99 def on_activate(app: Gtk.Application) -> None:
100 win = Gtk.ApplicationWindow(application=app)
101 win.set_title('Apps')
102 box = Gtk.Box()
103 box.set_orientation(Gtk.Orientation.VERTICAL)
104 edit = make_button("Edit Configuration", on_edit)
105 apply = make_button("Apply Configuration", on_apply)
106 box.append(edit)
107 box.append(apply)
108 win.set_child(box)
109 win.present()
110
111
112 def main() -> None:
113 app = Gtk.Application(application_id='net.chkno.nix-env-apps')
114 app.connect('activate', on_activate)
115 app.run(None)
116
117
118 if __name__ == '__main__':
119 main()