{ ... }: { flake.nixosModules.desktopWaydroid = { config, lib, pkgs, ... }: # `virtualisation.waydroid.package` defaults to `waydroid-nftables` when `networking.nftables.enable` # is true — required on recent kernels where legacy `ip_tables` / `waydroid-net.sh` (iptables) fails # with `waydroid session start` (nixpkgs#459520). let cfg = config.chiasson.desktop.waydroid; in { options.chiasson.desktop.waydroid = { enable = lib.mkEnableOption '' Waydroid + synced base props / nav mode. Needs Wayland; desktop hosts only. This module also sets `networking.nftables.enable` (default) so NixOS uses `waydroid-nftables` — needed on newer kernels where `waydroid-net.sh` / iptables fails. For **Google Play** apps (e.g. Hot Wheels Showcase), run `sudo waydroid init -s GAPPS -f` once after the first `nixos-rebuild` (or reset the container if you already initialized without GAPPS); then sign in and install from the Play Store. If `dnsmasq` reports port 53 in use, free that port (Waydroid needs it). ''; width = lib.mkOption { type = lib.types.int; default = 1920; description = "Waydroid rendering width (`persist.waydroid.width`)."; }; height = lib.mkOption { type = lib.types.int; default = 1080; description = "Waydroid rendering height (`persist.waydroid.height`)."; }; multiWindows = lib.mkOption { type = lib.types.bool; default = false; description = "Enable Waydroid multi-window integration."; }; navigationMode = lib.mkOption { type = lib.types.enum [ "3button" "gestures" ]; default = "gestures"; description = "Maps to Waydroid `navigation_mode` secure prop (3button=0, gestures=2)."; }; extraBaseProperties = lib.mkOption { type = lib.types.attrsOf lib.types.str; default = { }; description = '' Extra `key = value` pairs written into `/var/lib/waydroid/waydroid_base.prop` on activation (same mechanism as width/height). Use for upstream tweaks — e.g. NVIDIA hosts often need `ro.hardware.gralloc=default` and `ro.hardware.egl=swiftshader`; Linux 5.18+ may need `sys.use_memfd=true` ([NixOS Waydroid wiki](https://wiki.nixos.org/wiki/Waydroid)). ''; }; }; config = lib.mkIf cfg.enable { networking.nftables.enable = lib.mkDefault true; virtualisation.waydroid.enable = true; system.activationScripts.waydroidProps = { text = '' PROP_FILE="/var/lib/waydroid/waydroid_base.prop" if [ ! -f "$PROP_FILE" ]; then echo "waydroid: $PROP_FILE not found yet, skipping prop sync." echo "waydroid: run 'sudo waydroid init' once, then rebuild." exit 0 fi set_prop() { key="$1" value="$2" if ${lib.getExe pkgs.gnugrep} -q "^''${key}=" "$PROP_FILE"; then ${pkgs.gnused}/bin/sed -i "s|^''${key}=.*|''${key}=''${value}|" "$PROP_FILE" else printf "%s=%s\n" "$key" "$value" >> "$PROP_FILE" fi } set_prop "persist.waydroid.multi_windows" "${if cfg.multiWindows then "true" else "false"}" set_prop "persist.waydroid.width" "${toString cfg.width}" set_prop "persist.waydroid.height" "${toString cfg.height}" ${lib.concatStringsSep "\n" ( lib.mapAttrsToList (k: v: '' set_prop ${lib.escapeShellArg k} ${lib.escapeShellArg v}'') cfg.extraBaseProperties )} ''; }; systemd.services.waydroid-navigation-mode = { description = "Set Waydroid Android navigation mode"; wantedBy = [ "multi-user.target" ]; after = [ "waydroid-container.service" ]; wants = [ "waydroid-container.service" ]; path = [ config.virtualisation.waydroid.package ]; serviceConfig = { Type = "oneshot"; RemainAfterExit = true; }; script = '' set -eu nav_mode="${if cfg.navigationMode == "gestures" then "2" else "0"}" if ! systemctl -q is-active waydroid-container.service; then exit 0 fi for _ in $(seq 1 30); do if waydroid shell settings put secure navigation_mode "$nav_mode" >/dev/null 2>&1; then exit 0 fi sleep 1 done echo "waydroid: warning: could not set navigation_mode=$nav_mode (container not ready?)" >&2 exit 0 ''; }; }; }; }