Skip to content

Add Nix flake with NixOS and home-manager modules - #115

Open
AlexBSoD wants to merge 19 commits into
ergohaven:mainfrom
AlexBSoD:nix-flake
Open

Add Nix flake with NixOS and home-manager modules#115
AlexBSoD wants to merge 19 commits into
ergohaven:mainfrom
AlexBSoD:nix-flake

Conversation

@AlexBSoD

Copy link
Copy Markdown

Adds a Nix flake so Entropy can be installed and developed on NixOS.

Two of the Linux setup steps documented in the README cannot work there, and the flake handles both declaratively:

  • Vial udev rules. linux/udev/install-vial-rules.sh writes /etc/udev/rules.d/59-vial.rules, which NixOS discards on the next activation. The NixOS module emits the same two rules through services.udev.
  • IBus engine. linux/ibus/install-user.sh installs into ~/.local/share/ibus/component, but on NixOS ibus-with-plugins pins IBUS_COMPONENT_PATH to its own store path with --set, so nothing outside it is ever loaded. The module registers the engine through i18n.inputMethod.ibus.engines, which is the only path IBus honours.

The engine is a separate package: the bundled python3 script is wrapped with PyGObject and the IBus typelib, because its #!/usr/bin/env python3 shebang would otherwise resolve to an interpreter without the gi bindings. Its --entropy-check passes.

What's included

Output
packages.<system>.entropy the app, with desktop entry and icon
packages.<system>.entropy-ibus-engine wrapped engine + IBus component XML
nixosModules.default app, udev rule, IBus engine
homeManagerModules.default app and IBus engine (no udev, needs root)
devShells.<system>.default Rust toolchain and native dependencies

x86_64-linux and aarch64-linux. Usage is documented in a new ## NixOS section in the README.

Packaging notes

A few things that differ from what the CI installs, in case they are useful beyond this PR:

  • hidapi is built with the linux-native-basic-udev feature, which resolves to the pure-Rust basic-udev crate — neither libudev nor libhidapi is actually needed to build.
  • gtk3 is needed by rfd for file dialogs, not by the app itself (gtk-sys appears in Cargo.lock).
  • TLS comes from rustls via ureq, so libssl is unused.
  • X11, Wayland, GL and libxkbcommon are dlopened at runtime, so they are passed through LD_LIBRARY_PATH in the wrapper rather than through buildInputs alone.
  • The repository ships no Linux icon or desktop entry, so the flake generates both, reusing the SVG artwork from scripts/build_linux_appimage.sh.

Verification

  • nix build .#entropy .#entropy-ibus-engine — both build, cargo test runs as part of the package build and passes
  • the built binary starts and ldd reports no missing libraries
  • pkgs.ibus-with-plugins.override { plugins = [ engine ]; } builds, i.e. the engine is accepted as an IBus engine
  • evaluating a throwaway NixOS configuration with programs.entropy.enable = true yields the expected udev rules and engine registration, with no warnings
  • nix flake check passes

Two decisions worth a second opinion

  1. programs.entropy.ibus.enable defaults to true and sets i18n.inputMethod.enable/type via mkDefault, so enabling Entropy turns IBus on for the whole system. That may be more than you want as a default — happy to flip it to false.
  2. The lock pins nixos-unstable, and the flake uses the current package names (libx11 rather than xorg.libX11), so it will not evaluate on 25.05. If stable support matters, the old attribute paths can be restored.

flake.lock is committed, as is usual for an application flake, so builds are reproducible. It will need an occasional nix flake update.

Comment thread nix/hm-module.nix Outdated

# IBus reads components from this list only; keep the distro default so
# the other engines do not disappear.
home.sessionVariables.IBUS_COMPONENT_PATH = "${cfg.ibus.package}/share/ibus/component:${pkgs.ibus}/share/ibus/component";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This replaces IBUS_COMPONENT_PATH with only Entropy and the base IBus directories, which can hide other engines installed in the user profile.

Could we prepend Entropy's component directory while preserving the existing path?

@AlexBSoD AlexBSoD Aug 2, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed — the module now prepends through home.sessionSearchVariables.IBUS_COMPONENT_PATH, so Entropy and the base IBus directories go in front and whatever was already on the path is kept. Thanks for catching it.

AlexBSoD added a commit to AlexBSoD/entropy that referenced this pull request Jul 29, 2026
Use home.sessionSearchVariables so other IBus engines that also rely on
IBUS_COMPONENT_PATH keep working instead of being hidden.

Addresses review comment on PR ergohaven#115.
AlexBSoD added a commit to AlexBSoD/entropy that referenced this pull request Aug 1, 2026
Use home.sessionSearchVariables so other IBus engines that also rely on
IBUS_COMPONENT_PATH keep working instead of being hidden.

Addresses review comment on PR ergohaven#115.
@kissetfall

Copy link
Copy Markdown
Member

The core Nix packaging is still relevant, so I am keeping this PR open. Please update it against current Entropy: Universal Symbols are now native RMK actions gated by firmware capability; IBus remains only for Text Expander. The README and module options still describe IBus as serving Universal Symbols, which is now stale.

AlexBSoD added a commit to AlexBSoD/entropy that referenced this pull request Aug 2, 2026
Use home.sessionSearchVariables so other IBus engines that also rely on
IBUS_COMPONENT_PATH keep working instead of being hidden.

Addresses review comment on PR ergohaven#115.
@AlexBSoD

AlexBSoD commented Aug 2, 2026

Copy link
Copy Markdown
Author

Updated and rebased onto 1ce74b5.

  • Both module options and the README section no longer describe the engine as serving Universal Symbols — it is now documented as backing Text Expander on Wayland, with a note that Universal Symbols need no input method on firmware exposing native RMK key actions.
  • Fixed the input source name while I was there: the component registers Entropy Symbols (plus the layout variants Entropy EN, Entropy RU, …), not "Entropy Universal Symbols".
  • Added a line that programs.entropy.ibus.enable = false; is enough when Text Expander is not used.

nix build .#entropy .#entropy-ibus-engine, a NixOS-module eval and nix fmt are all clean on the rebased branch.

One thing I could not confirm from the repository, so please correct me if I am looking in the wrong place: on 1ce74b5 the app still routes Universal Symbols through the host rather than through native RMK actions. The picker writes F13F24 carriers (smart_input_symbols.rs, keycode_picker.rs:382), linux_recommended_input_backend() returns IBus for Wayland (smart_input.rs:210), the setup wizard shells out to linux/ibus/install-user.sh (ui/universal_symbols_setup.rs), and rmk_native.rs only carries TapHold / Modifier / KeyWithModifier. If the native path lands in a later release, the wording here is already written for it — if it is expected to work on main today, point me at it and I will adjust.

Related: the upstream "Linux IBus Backend" section (README, above the NixOS one) still says IBus serves Universal Symbols. I left it untouched to keep this PR scoped to the Nix bits, but I am happy to update it here if you would rather have both in one go.

AlexBSoD added a commit to AlexBSoD/entropy that referenced this pull request Aug 3, 2026
Use home.sessionSearchVariables so other IBus engines that also rely on
IBUS_COMPONENT_PATH keep working instead of being hidden.

Addresses review comment on PR ergohaven#115.
@AlexBSoD

AlexBSoD commented Aug 3, 2026

Copy link
Copy Markdown
Author

Rebased onto 92f0969 (v0.3.2-rc.1). Nothing in the flake needed adjusting for it: the only manifest change was the version bump, both packages build (nix build .#entropy .#entropy-ibus-engine), and the NixOS module still evaluates clean.

My earlier question is moot — smart_input_symbols.rs is gone and universal_symbols.rs plus the native RMK actions are on main now, which matches the documentation wording already in this branch.

One new thing worth flagging: nixpkgs has picked up Entropy on its own — pkgs.ergohaven-entropy (0.3.1) plus nixos/modules/programs/ergohaven-entropy.nix, maintained by @geekiot-hub. That module declares the same programs.entropy.enable option, so importing this flake's module alongside a current nixpkgs aborted evaluation with "The option programs.entropy.enable ... is already declared". I've handled it with disabledModules = [ "programs/ergohaven-entropy.nix" ] so this flake's module stays authoritative — it tracks the version in this repo rather than the last released tag, and it lets the package be overridden.

Separately, and independently of this PR: the nixpkgs module is currently broken. pkgs.ergohaven-entropy does not set meta.isIbusEngine = true, so programs.entropy.enable = true on a plain unstable checkout fails with

error: A definition for option `i18n.inputMethod.ibus.engines."[definition 1-entry 1]"' is not of type `ibus-engine'

Happy to send that one-line fix, along with a 0.3.2 bump, to nixpkgs if that is useful.

@kissetfall kissetfall left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution. The core Nix packaging is useful, but the following changes are required before merge:

  1. Make the IBus integration opt-in in both modules (default false). Enabling Entropy alone must not enable or select a system-wide input method.
  2. Do not set the system-wide input method merely because the app is enabled. Require an explicit IBus choice, and warn or document when the active input method is not IBus.
  3. Align the integration and documentation with the current Text Expander implementation. Entropy should recognize the declaratively installed engine instead of telling users to ignore an incorrect installation state; the documented input-source names must match the actual Entropy Text Expander names; and the stale Universal Symbols package description should be removed.
  4. Add automated Nix coverage: nix flake check, builds of both packages, and a NixOS module evaluation. The existing Build workflow does not validate the flake.
  5. Clarify and test coexistence with the Entropy module now shipped by nixpkgs. If this module intentionally supersedes it, keep that behavior explicit and covered so we do not maintain two silently conflicting sources of truth.

Please rebase onto current main after addressing these points.

AlexBSoD added a commit to AlexBSoD/entropy that referenced this pull request Aug 3, 2026
Use home.sessionSearchVariables so other IBus engines that also rely on
IBUS_COMPONENT_PATH keep working instead of being hidden.

Addresses review comment on PR ergohaven#115.
@AlexBSoD

AlexBSoD commented Aug 3, 2026

Copy link
Copy Markdown
Author

Thanks — all five points are addressed, rebased onto b9fa8c3.

1. IBus is opt-in. programs.entropy.ibus.enable now defaults to false in both the NixOS and the home-manager module. programs.entropy.enable on its own installs the app and the Vial udev rule, nothing else.

2. No input method is chosen for you. The module no longer sets i18n.inputMethod.enable or i18n.inputMethod.type, not even with mkDefault. With ibus.enable = true it only appends the engine to i18n.inputMethod.ibus.engines, and warns when the active input method is disabled or is not IBus — the case where the engine would silently never load. The README shows the explicit i18n.inputMethod block next to it.

3. Aligned with the current Text Expander implementation. Entropy now recognises an engine registered outside $XDG_DATA_HOME: it resolves the component the way IBus does (IBUS_COMPONENT_PATH first, then every XDG_DATA_DIRS entry) and, when the registration is not user-local, replaces the Install IBus / Uninstall IBus actions with a note instead of offering an action that would only add an unmanaged copy. Five unit tests cover missing / user / system / both / unrelated-component cases. The documented input sources are now the real names — Entropy Text Expander and its layout variants — and the engine package no longer claims to serve Universal Symbols.

4. Automated Nix coverage. New .github/workflows/nix.yml on pull requests and pushes to main: nix fmt -- --ci, the module evaluation, nix build of both packages, the engine's own --entropy-check, and nix flake check. It needs your "Approve and run" — the run currently sits at action_required, as does the Build workflow.

5. Coexistence with the nixpkgs module, explicit and tested. This module deliberately supersedes nixos/modules/programs/ergohaven-entropy.nix via disabledModules, which is now stated in the README as a choice made by importing it. nix/checks.nix evaluates the module on a throwaway machine, and since nixosSystem pulls in all of nixpkgs' modules, every assertion also proves the two do not collide: it checks that the app and the rule are installed, that no input method is touched, that ibus.enable registers exactly the Entropy engine and warns otherwise, that programs.entropy.ibus still exists (i.e. our module won), and that programs.entropy.package is this flake's package rather than pkgs.ergohaven-entropy.

Local run: cargo test --all-targets 524 passed, nix flake check clean, both packages build, actionlint clean on the new workflow.

One thing you may want to know regardless of this PR: the nixpkgs module is broken today. pkgs.ergohaven-entropy does not set meta.isIbusEngine = true, so programs.entropy.enable = true on plain unstable fails with A definition for option i18n.inputMethod.ibus.engines."[definition 1-entry 1]" is not of type ibus-engine. Happy to send that one-line fix and a 0.3.2 bump there if you would rather keep the nixpkgs path working.

Comment thread nix/module.nix
description = "The Entropy package to use.";
};

group = lib.mkOption {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for making the access group configurable!

Could we either create the selected group or explicitly validate/document that it must already exist?

A custom value can evaluate and reach the udev rule without a corresponding group, leaving the hidraw device inaccessible.

@AlexBSoD AlexBSoD Aug 3, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now created by the module: users.groups.${cfg.group} = { }. A no-op for the default input (its gid 174 is kept) and works for any custom value; covered in nix/checks.nix.

Comment thread nix/hm-module.nix Outdated
"${pkgs.ibus}/share/ibus/component"
];

# IBus caches the registry — after switching generations run

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One lifecycle gap: after a Home Manager switch, Entropy sees the new engine and hides its recovery actions, while the running IBus daemon can still use its old registry.

Could we surface the required IBus restart in the user-facing guidance, or keep a safe recovery path until it reloads?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — hiding the install actions closed the only recovery path.

Entropy now shows Reload IBus registry in their place when the engine is registered outside $XDG_DATA_HOME: it reruns ibus write-cache and restarts the daemon, writing nothing. The README and the module comment point at it.

AlexBSoD added a commit to AlexBSoD/entropy that referenced this pull request Aug 3, 2026
Use home.sessionSearchVariables so other IBus engines that also rely on
IBUS_COMPONENT_PATH keep working instead of being hidden.

Addresses review comment on PR ergohaven#115.
@AlexBSoD

AlexBSoD commented Aug 3, 2026

Copy link
Copy Markdown
Author

Rebased onto 613fdb4; both review notes are answered in their threads. The udev group is now created by the module, and a system-registered engine gets a Reload IBus registry action instead of a dead end.

@AlexBSoD

AlexBSoD commented Aug 3, 2026

Copy link
Copy Markdown
Author

Self-review caught three problems in my own detection code, now fixed: it scanned the component directories on every frame, a user install could be misread as a system one (XDG_DATA_DIRS often contains paths under $HOME), and a user copy next to a system registration lost its uninstall action. User and system presence are tracked separately now, the result is cached, and the daemon reload runs off the UI thread.

Also: the group check no longer hardcodes gid 174, and the CI build step names its outputs instead of relying on result-1.

Sorry for the number of pushes on this one — embarrassing for what started as a packaging PR.

AlexBSoD added a commit to AlexBSoD/entropy that referenced this pull request Aug 9, 2026
Use home.sessionSearchVariables so other IBus engines that also rely on
IBUS_COMPONENT_PATH keep working instead of being hidden.

Addresses review comment on PR ergohaven#115.
AlexBSoD added a commit to AlexBSoD/entropy that referenced this pull request Aug 12, 2026
Use home.sessionSearchVariables so other IBus engines that also rely on
IBUS_COMPONENT_PATH keep working instead of being hidden.

Addresses review comment on PR ergohaven#115.

@kissetfall kissetfall left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks — the five points from the previous review appear to be addressed. One runtime-state issue remains before merge.

EntropyApp::ibus_registration caches the first component-directory scan indefinitely. A NixOS/home-manager rebuild can add or remove the system component while Entropy is still running; if the setup page was opened before that rebuild, the cached system: false value remains and the documented Reload IBus registry action never appears until the app is restarted or an in-app setup action happens.

Please make externally managed registration changes observable without restarting Entropy (for example, rescan on page entry/focus, use a bounded refresh interval, or invalidate from a cheap directory fingerprint) and add regression coverage for an external registration change. Please also route the two new reload failure prefixes/statuses through i18n instead of showing hard-coded English in the Russian UI.

After that, please rebase the remaining one commit onto current main. A synthetic merge passed 570/570 Rust tests, i18n, and Clippy locally, but both GitHub workflows are still action_required; the Build and Nix jobs need maintainer approval and must be green before merge.

AlexBSoD added a commit to AlexBSoD/entropy that referenced this pull request Aug 12, 2026
Use home.sessionSearchVariables so other IBus engines that also rely on
IBUS_COMPONENT_PATH keep working instead of being hidden.

Addresses review comment on PR ergohaven#115.
Packages Entropy for Nix and covers the two setup steps that cannot work
on NixOS: the in-app "Install Vial udev rules" action writes to
/etc/udev/rules.d, which is wiped on the next activation, and "Install
IBus" copies the engine into ~/.local/share, which IBus never reads
because ibus-with-plugins pins IBUS_COMPONENT_PATH to its own store path.
Both are handled declaratively by the NixOS module instead.

The IBus engine ships as its own package: the bundled python3 script is
wrapped with PyGObject and the IBus typelib, since its `env python3`
shebang would otherwise find an interpreter without the gi bindings.

Notes on the package: hidapi is built with the linux-native-basic-udev
feature, so neither libudev nor libhidapi is needed; gtk3 is required by
rfd for file dialogs, not by the app itself; TLS comes from rustls, so
openssl is unused. X11, Wayland and GL are dlopened at runtime and are
passed through LD_LIBRARY_PATH rather than buildInputs alone. The
desktop entry and icon are generated here, reusing the artwork from
scripts/build_linux_appimage.sh, as the repository ships neither.
Use home.sessionSearchVariables so other IBus engines that also rely on
IBUS_COMPONENT_PATH keep working instead of being hidden.

Addresses review comment on PR ergohaven#115.
Upstream main now depends on the git-sourced rmk-config crate,
which cargoLock.lockFile can't vendor without an explicit hash.
Universal Symbols are native RMK key actions gated by firmware capability,
so the IBus engine only matters for Text Expander on Wayland. Also fix the
input source name to match the component (Entropy Symbols).
nixpkgs now ships nixos/modules/programs/ergohaven-entropy.nix, which
declares the same programs.entropy.enable option. Importing this flake's
module alongside it aborts evaluation with "already declared", so disable
the nixpkgs one and keep this module authoritative.
linux_vial_udev_rules_installed() only looks at 59-vial.rules under /etc,
/run, /usr/lib and /lib, so a rule fed through services.udev.extraRules —
which NixOS merges into 99-local.rules — left the app reporting the rule as
missing and nagging on every device scan. Ship it as a package under
services.udev.packages so it lands as /etc/udev/rules.d/59-vial.rules.
The setup screen offered "Install IBus" unconditionally, even where the
engine is already registered outside the user's data directory — by a
distribution package or a declarative setup such as the NixOS module.
Pressing it there does not fix anything, it just drops an unmanaged copy of
the engine beside the managed one.

Look the component up the way IBus does — IBUS_COMPONENT_PATH first, then
every XDG_DATA_DIRS entry — and replace the install/uninstall actions with a
note when the registration is not ours to change. A user-local install keeps
the old behaviour.
Enabling a keyboard configurator must not decide what input method the
machine runs. programs.entropy.ibus.enable now defaults to false in both
modules, and the NixOS one no longer sets i18n.inputMethod.enable or .type:
it only appends the engine to i18n.inputMethod.ibus.engines and warns when
the active input method is not IBus, in which case the engine would never be
loaded anyway.
nix flake check had nothing to check. Add an evaluation of the module on a
throwaway machine asserting that enable installs the app and the Vial rule,
that it touches no input method on its own, that ibus.enable registers the
engine and warns when IBus is not the active input method, and that this
module — not the one nixpkgs ships for pkgs.ergohaven-entropy — is the one
that wins.

Also drop the stale Universal Symbols mention from the engine's description:
symbols are native RMK actions now, the engine only backs Text Expander.
The Build workflow compiles with cargo and apt dependencies, so a broken
flake reaches main unnoticed. Check formatting, evaluate the NixOS module,
build both packages, run the engine's dependency self-check and nix flake
check on every pull request.
Describe programs.entropy.ibus as opt-in next to an explicit i18n.inputMethod
choice, name the input sources as they actually appear (Entropy Text
Expander, plus the layout variants), drop the advice to ignore the install
button now that Entropy detects a system-registered engine, and state that
this module deliberately supersedes the Entropy module in nixpkgs.
A udev rule naming a group that does not exist installs fine and then leaves
the device unreachable, which is exactly the failure the group option is
meant to prevent. Declare the group so any value works out of the box;
for groups NixOS already ships, such as the default "input", this is a no-op
that keeps their well-known gid.
A declarative install puts the engine in place while the running daemon
still serves the registry it read at startup, so the layouts are missing
until it reloads — and with the install actions hidden there was nothing
left in the UI to recover with. Offer "Reload IBus registry" instead, which
reruns write-cache and restarts the daemon without writing anything.
Note that programs.entropy.group is created and needs members, and that a
daemon started before the rebuild keeps serving its old registry until it is
reloaded.
Three problems with the detection added in 3646037:

- it ran on every frame of the setup page, scanning the component
  directories and reading every XML in them; the result is now cached and
  invalidated by the actions that can change it;
- XDG_DATA_DIRS routinely carries paths inside $HOME, so a user install
  could be reported as a system one, hiding the action that removes it. The
  user directory is now filtered out of both lookup paths;
- a user copy alongside a system registration left no way to remove it. User
  and system presence are tracked separately, so the uninstall action stays
  available for the copy it can actually delete.

The reload also moved off the UI thread — restarting the daemon can take a
while and can hang.
Compare the input group against ids.gids.input instead of a hardcoded 174,
drop the bootloader and filesystem stubs the evaluation never needed, name
the build outputs explicitly rather than relying on result-1 ordering, and
fix a stale sentence about the install action disappearing.
The setup page scanned the component directories once and kept the
answer for the lifetime of the process, invalidating it only after an
action of its own. A rebuild that registers or drops the engine happens
outside the app, so a page opened before it kept showing the old state —
and with it either an install action that would only add an unmanaged
copy, or no reload action at all.

Move the cache into linux_setup as IbusRegistrationCache with a two
second refresh window, keep the explicit invalidation for our own
actions, and ask for a repaint once the window elapses so an idle page
still catches up. The scan is injectable, so the tests cover a
registration appearing and disappearing behind the app's back.
Both failure paths of the reload action built their status message in
English, so a Russian UI showed "IBus reload failed: ..." verbatim.
@AlexBSoD

Copy link
Copy Markdown
Author

Both points fixed, rebased onto b42a357.

Registration refresh. The cache moved out of the UI into linux_setup::IbusRegistrationCache: a scan is reused for two seconds, our own install/uninstall/reload still invalidate it outright, and the page requests a repaint once the window elapses so an idle screen catches up on its own. The scan is injectable, so three tests cover it — reuse inside the window, a system registration appearing and then disappearing behind the app's back, and invalidation.

i18n. Both reload failure paths now go through the catalogs (ibus_reload_failed_status, ibus_reload_lost_status, en + ru).

576 tests pass, nix flake check, nix fmt -- --ci, both packages build as 0.3.10, engine --entropy-check ok.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants