From 10d9c5372a0a29b50ff19d9776af4ee93795c6ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Fri, 15 May 2026 00:05:39 -0400 Subject: [PATCH] handy: init at 0.8.3 --- pkgs/by-name/ha/handy/package.nix | 275 ++++++++++++++++++++++++++++++ 1 file changed, 275 insertions(+) create mode 100644 pkgs/by-name/ha/handy/package.nix diff --git a/pkgs/by-name/ha/handy/package.nix b/pkgs/by-name/ha/handy/package.nix new file mode 100644 index 000000000000..4ac67b07ecc7 --- /dev/null +++ b/pkgs/by-name/ha/handy/package.nix @@ -0,0 +1,275 @@ +{ + lib, + stdenv, + fetchFromGitHub, + rustPlatform, + pkg-config, + cmake, + bun, + nodejs, + cctools, + cargo-tauri, + jq, + writableTmpDirAsHomeHook, + makeBinaryWrapper, + swift, + apple-sdk_26, + symlinkJoin, + + # Linux-only + webkitgtk_4_1, + gtk3, + glib, + libsoup_3, + alsa-lib, + libayatana-appindicator, + libevdev, + libxtst, + gtk-layer-shell, + vulkan-loader, + vulkan-headers, + shaderc, + gst_all_1, + glib-networking, + libx11, + pipewire, + alsa-plugins, + wrapGAppsHook4, + + # Cross-platform + onnxruntime, + openssl, +}: +let + gstPlugins = lib.optionals stdenv.hostPlatform.isLinux ( + with gst_all_1; + [ + gstreamer + gst-plugins-base + gst-plugins-good + gst-plugins-bad + gst-plugins-ugly + ] + ); +in +rustPlatform.buildRustPackage (finalAttrs: { + pname = "handy"; + version = "0.8.3"; + + __structuredAttrs = true; + + src = fetchFromGitHub { + owner = "cjpais"; + repo = "Handy"; + tag = "v${finalAttrs.version}"; + hash = "sha256-sCCtp6UAxmCAcYeOM9+RW2czATh4Geqf1H8wXNMniYc="; + }; + + cargoRoot = "src-tauri"; + cargoHash = "sha256-mvOThNqfE24iMkVBM2zYexJkQxpMMgE4PPNXKy39hSg="; + + nativeInstallInputs = [ jq ]; + + postPatch = '' + patch_json() { + local file=$1 filter=$2 + jq "$filter" "$file" > "$file.tmp" && mv "$file.tmp" "$file" + } + + # De-structuring the Tauri build process + # So we can: + # - Handle the frontend building in a fixed way. + # - Not worry about signing + # - And avoid the auto-updater + patch_json "src-tauri/tauri.conf.json" ' + del(.build.beforeBuildCommand) | + .bundle.createUpdaterArtifacts = false | + .bundle.macOS += { signingIdentity: null, hardenedRuntime: false } + ' + + # Strip cbindgen build steps + find "$cargoDepsCopy" -path "*/ferrous-opencc-*/build.rs" \ + -exec sed -i -e '/cbindgen::Builder::new/{:l;/write_to_file/!{N;bl};d}' {} + + '' + + lib.optionalString stdenv.hostPlatform.isLinux '' + substituteInPlace "$cargoDepsCopy"/source-registry-0/libappindicator-sys-*/src/lib.rs \ + --replace-fail 'libayatana-appindicator3.so.1' '${libayatana-appindicator}/lib/libayatana-appindicator3.so.1' + ''; + + nativeBuildInputs = [ + pkg-config + cmake + bun + nodejs + cargo-tauri.hook + jq + writableTmpDirAsHomeHook + rustPlatform.bindgenHook + ] + ++ lib.optionals stdenv.hostPlatform.isLinux [ + wrapGAppsHook4 + shaderc + ] + ++ lib.optionals stdenv.hostPlatform.isDarwin [ + makeBinaryWrapper + cctools + swift + apple-sdk_26 + ]; + + buildInputs = [ + onnxruntime + openssl + ] + ++ gstPlugins + ++ lib.optionals stdenv.hostPlatform.isLinux [ + webkitgtk_4_1 + gtk3 + glib + libsoup_3 + alsa-lib + libayatana-appindicator + libevdev + libxtst + gtk-layer-shell + vulkan-loader + vulkan-headers + glib-networking + libx11 + ]; + + ortLibLocation = "${lib.getLib onnxruntime}/lib"; + + # Handy relies on onnx and gstreamer being available at runtime, picked up by wrapGapps. + env = { + ORT_LIB_LOCATION = "${lib.getLib onnxruntime}/lib"; + ORT_PREFER_DYNAMIC_LINK = "1"; + + # Normally GST plugins will live in their own folder. This conjoins them into one as the application expects. + GST_PLUGIN_SYSTEM_PATH_1_0 = lib.optionalString stdenv.hostPlatform.isLinux ( + lib.makeSearchPathOutput "lib" "lib/gstreamer-1.0" gstPlugins + ); + } + // lib.optionalAttrs stdenv.hostPlatform.isDarwin { + SWIFTC = lib.getExe' swift "swiftc"; # Explicit so the Handy build system can avoid xcrun + }; + + preBuild = '' + cp -R ${finalAttrs.passthru.frontendDeps}/node_modules . + chmod -R u+w node_modules + patchShebangs node_modules + bun run build + ''; + + # If removed, the binary is produced, but not the app bundle for any platform. + installPhase = '' + runHook preInstall + mkdir -p $out + '' + + lib.optionalString stdenv.hostPlatform.isLinux '' + mv target/${stdenv.hostPlatform.rust.rustcTarget}/release/bundle/deb/*/data/usr/* $out/ + '' + + lib.optionalString stdenv.hostPlatform.isDarwin '' + mkdir -p $out/Applications $out/bin + mv target/${stdenv.hostPlatform.rust.rustcTarget}/release/bundle/macos/Handy.app \ + $out/Applications/ + ln -s "$out/Applications/Handy.app/Contents/MacOS/handy" "$out/bin/handy" + '' + + '' + runHook postInstall + ''; + + preFixup = lib.optionalString stdenv.hostPlatform.isLinux '' + gappsWrapperArgs+=( + --set WEBKIT_DISABLE_DMABUF_RENDERER 1 + --set ALSA_PLUGIN_DIR "${finalAttrs.passthru.alsaPluginDirectory}" + ) + ''; + + postFixup = lib.optionalString stdenv.hostPlatform.isDarwin '' + install_name_tool -add_rpath "${finalAttrs.env.ORT_LIB_LOCATION}" \ + "$out/Applications/Handy.app/Contents/MacOS/handy" + ''; + + preCheck = '' + cd ${finalAttrs.cargoRoot} + '' + + lib.optionalString stdenv.hostPlatform.isDarwin '' + export DYLD_LIBRARY_PATH="${finalAttrs.env.ORT_LIB_LOCATION}''${DYLD_LIBRARY_PATH:+:''${DYLD_LIBRARY_PATH}}" + ''; + + # Skip broken tests, mirroring CI configuration (https://github.com/cjpais/Handy/blob/main/.github/workflows/test.yml) + checkFlags = [ + "--skip=helpers::clamshell::tests::test_is_laptop" + "--skip=helpers::clamshell::tests::test_clamshell_check" + ]; + + passthru = { + # The hook and deps fetcher in https://github.com/NixOS/nixpkgs/pull/376299 should simplify this dramatically. + frontendDeps = stdenv.mkDerivation { + pname = "handy-frontend-deps"; + version = "0.8.3"; + inherit (finalAttrs) src; + + nativeBuildInputs = [ + bun + writableTmpDirAsHomeHook + ]; + + dontConfigure = true; + + buildPhase = '' + runHook preBuild + export BUN_INSTALL_CACHE_DIR=$(mktemp -d) + bun install --linker=isolated --force --frozen-lockfile \ + --ignore-scripts --no-progress + bun --bun "$PWD/.nix/scripts/normalize-install.ts" + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + mkdir -p $out + cp -R node_modules $out/ + runHook postInstall + ''; + + dontFixup = true; + + outputHash = + { + "x86_64-linux" = "sha256-tJ6LK99dELOiR0BcsTRTt/vLyNamntujLxhBy5Xl/lc="; + "aarch64-linux" = "sha256-S+dX6ZVgv9dexxIHoa5PxP7e0nxf/d7cKUGty5eEi8A="; + "aarch64-darwin" = "sha256-DQbogNBQ9izK5GPmoOudqiB2lJvct1vZI2U5lp3WFy8="; + } + .${stdenv.hostPlatform.system}; + + outputHashMode = "recursive"; + }; + + alsaPluginDirectory = symlinkJoin { + name = "combined-alsaPlugins"; + paths = [ + "${pipewire}/lib/alsa-lib" + "${alsa-plugins}/lib/alsa-lib" + ]; + }; + }; + + meta = { + description = "Free, open source, offline speech-to-text application"; + longDescription = '' + Handy is a cross-platform desktop application providing simple, + privacy-focused speech transcription. Press a shortcut, speak, and + have your words appear in any text field — entirely on your own + computer, with no audio sent to the cloud. + ''; + homepage = "https://handy.computer"; + changelog = "https://github.com/cjpais/Handy/releases/tag/v${finalAttrs.version}"; + license = lib.licenses.mit; + mainProgram = "handy"; + maintainers = with lib.maintainers; [ philocalyst ]; + platforms = with lib.platforms; linux ++ darwin; + badPlatforms = [ "x86_64-darwin" ]; # We weren't able to get hashes here + }; +})