From b718bf5ead92dffaeadce6dbefc65e7188b7fb20 Mon Sep 17 00:00:00 2001 From: Scott Worley Date: Sat, 13 Sep 2025 10:24:52 -0700 Subject: [PATCH 1/1] Basic functionality --- .gitignore | 2 ++ apps.py | 101 ++++++++++++++++++++++++++++++++++++++++++++++++++++ default.nix | 14 ++++++++ setup.py | 11 ++++++ 4 files changed, 128 insertions(+) create mode 100644 .gitignore create mode 100644 apps.py create mode 100644 default.nix create mode 100644 setup.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1d26af3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.mypy_cache +result diff --git a/apps.py b/apps.py new file mode 100644 index 0000000..4b0d0d5 --- /dev/null +++ b/apps.py @@ -0,0 +1,101 @@ +from gi.repository import Gtk +import os +import subprocess + +import gi +gi.require_version("Gtk", "4.0") + + +def on_edit(_): + 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") 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, args): + try: + os.execvp(terminal, [terminal] + args) + except FileNotFoundError: + pass + + +def on_apply(_): + 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 on_activate(app): + win = Gtk.ApplicationWindow(application=app) + box = Gtk.Box() + box.set_orientation(Gtk.Orientation.VERTICAL) + edit = Gtk.Button(label="Edit Configuration") + apply = Gtk.Button(label="Apply Configuration") + edit.connect('clicked', on_edit) + apply.connect('clicked', on_apply) + box.append(edit) + box.append(apply) + win.set_child(box) + win.present() + + +def main(): + app = Gtk.Application(application_id='net.chkno.nix-env-apps') + app.connect('activate', on_activate) + app.run(None) + + +if __name__ == '__main__': + main() diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..17ddac7 --- /dev/null +++ b/default.nix @@ -0,0 +1,14 @@ +{ pkgs ? import { } }: +pkgs.python3Packages.callPackage ({ buildPythonPackage, gobject-introspection + , gtk4, lib, pygobject3, wrapGAppsHook4, }: + buildPythonPackage rec { + pname = "apps"; + version = "1.0"; + src = lib.cleanSource ./.; + nativeBuildInputs = [ gobject-introspection wrapGAppsHook4 ]; + + buildInputs = [ gtk4 ]; + + pythonPath = [ pygobject3 ]; + + }) { } diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..63dd5f7 --- /dev/null +++ b/setup.py @@ -0,0 +1,11 @@ +from setuptools import setup + +setup( + name='apps', + py_modules=['apps'], + entry_points={ + 'console_scripts': [ + 'apps = apps:main', + ], + } +) -- 2.50.1