diff --git a/nixos/modules/programs/opendeck.nix b/nixos/modules/programs/opendeck.nix new file mode 100644 index 0000000000000..c6bccbf1705c0 --- /dev/null +++ b/nixos/modules/programs/opendeck.nix @@ -0,0 +1,102 @@ +{ + config, + lib, + pkgs, +}: +let + cfg = config.programs.opendeck; + format = pkgs.formats.json { }; +in +{ + options.programs.opendeck = { + enable = lib.mkEnableOption "OpenDeck, a cross-platform desktop application that provides functionality for stream controller devices. "; + + package = lib.mkPackageOption pkgs "opendeck" { }; + + settings = lib.mkOption { + type = lib.types.nullOr lib.types.submodule { + freeformType = format.type; + + options = { + language = lib.mkOption { + type = lib.types.str; + default = "en"; + example = "de"; + description = '' + Display language for the OpenDeck. + + While OpenDeck itself is not yet translated, any installed plugins that supports the + selected language would display its text in that language. + ''; + }; + background = lib.mkOption { + type = lib.types.bool; + default = true; + example = false; + description = '' + Whether OpenDeck should be minimized to tray and run in the background when its window is closed. + ''; + }; + autolaunch = lib.mkOption { + type = lib.types.bool; + default = false; + example = true; + description = '' + Whether to automatically start OpenDeck on login. + ''; + }; + darktheme = lib.mkOption { + type = lib.types.bool; + default = true; + example = false; + description = '' + Whether to use dark theme for OpenDeck. + ''; + }; + brightness = lib.mkOption { + type = lib.types.ints.between 0 100; + default = 50; + example = 100; + description = '' + Brightness of devices connected to OpenDeck. + ''; + }; + developer = lib.mkOption { + type = lib.types.bool; + default = false; + example = true; + description = '' + Whether to enable developer mode and features that make plugin debugging and development + easier, while also exposing *all* file paths on your device to a local webserver in + order to symbolically link plugins. + ''; + }; + }; + }; + default = null; + description = "Settings for OpenDeck."; + }; + + pluginsDir = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + example = lib.literalExpression ''/path/to/my/plugins''; + description = '' + Path to a folder containing installed plugins. + + The folder should contain folders that each contain a `manifest.json`, as well as the plugin itself. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + environment.systemPackages = [ cfg.package ]; + services.udev.packages = [ cfg.package ]; + + environment.etc."xdg/opendeck/settings.json".source = format.generate "opendeck-settings.json" cfg.settings; + + systemd.tmpfiles.settings."10-opendeck" = { + "etc/xdg/opendeck/plugins"."L+".argument = lib.mkIf (cfg.pluginsDir != null) "${cfg.pluginsDir}"; + }; + }; +} diff --git a/pkgs/by-name/de/deno/fetch-deps/default.nix b/pkgs/by-name/de/deno/fetch-deps/default.nix new file mode 100644 index 0000000000000..01817a1b394bc --- /dev/null +++ b/pkgs/by-name/de/deno/fetch-deps/default.nix @@ -0,0 +1,107 @@ +{ + lib, + stdenv, + deno, + jq, +}: +{ + hash ? "", + pname, + denoLock ? "", + denoJson ? "", + denoEntrypoints ? [ ], + preDenoInstall ? "", + denoInstallFlags ? [ ], + ... +}@args: +let + args' = builtins.removeAttrs args [ + "hash" + "pname" + ]; +in +stdenv.mkDerivation ( + args' + // { + name = "${pname}-deno-deps"; + + nativeBuildInputs = [ + deno + jq + ]; + + dontConfigure = true; + dontBuild = true; + + denoInstallFlags = + (lib.cli.toGNUCommandLine { } { + vendor = true; + frozen = true; + node-modules-dir = true; + entrypoint = if denoEntrypoints == [ ] then null else denoEntrypoints; + }) + ++ denoInstallFlags; + + inherit denoLock denoJson denoEntrypoints; + + installPhase = '' + export DENO_DIR=$(mktemp -d) + export DENO_NO_UPDATE_CHECK=true + + if [[ -z "$denoLock" ]]; then + echo "autodetecting lock file" + else + if [[ ! -f "$denoLock" ]]; then + echo "error: lock file '$denoLock' is specified but not found" >&2 + exit 1 + fi + cp "$denoLock" deno.lock + fi + + if [[ -z "$denoJson" ]]; then + echo "autodetecting Deno config" + else + if [[ ! -f "$denoJson" ]]; then + echo "error: Deno config '$denoJson' is specified but not found" >&2 + exit 1 + fi + cp "$denoJson" deno.json + fi + + for entrypoint in "''${denoEntrypoints[@]}"; do + if [[ -n "$entrypoint" && ! -f "$entrypoint" ]]; then + echo "error: entrypoint '$entrypoint' is specified but not found" >&2 + exit 1 + fi + done + + ${preDenoInstall} + + deno install ''${denoInstallFlags[@]} + + # Remove redundant & unreproducible files + rm -f node_modules/.deno/.deno.lock{,.poll} + + # TODO: I'm not exactly sure if this is necessary anymore + if [[ -f vendor/manifest.json ]]; then + jq -S '.' vendor/manifest.json > vendor/manifest.json.tmp + mv vendor/manifest.json.tmp vendor/manifest.json + fi + + + mkdir -p $out + [[ -d node_modules ]] && cp -a node_modules -t $out || true + [[ -d vendor ]] && cp -a vendor -t $out || true + [[ -n deno.json ]] && cp deno.json -t $out || true + [[ -n deno.lock ]] && cp deno.lock -t $out || true + ''; + + dontFixup = true; + + outputHash = hash; + outputHashMode = "recursive"; + } + // lib.optionalAttrs (hash == "") { + outputHashAlgo = "sha256"; + } +) diff --git a/pkgs/by-name/de/deno/hooks/compile-hook.sh b/pkgs/by-name/de/deno/hooks/compile-hook.sh new file mode 100644 index 0000000000000..6db8f80a7f8a0 --- /dev/null +++ b/pkgs/by-name/de/deno/hooks/compile-hook.sh @@ -0,0 +1,51 @@ +# shellcheck shell=bash + +denoCompileBuildPhase() { + export DENORT_BIN="@deno@/bin/denort" + export DENO_COMPILE_OUT=$(mktemp -d) + + if [[ -z "${denoCompileEntrypoints[@]}" ]]; then + if [[ -z "${denoEntrypoints[@]}" ]]; then + echo "error: neither denoEntrypoints nor denoCompileEntrypoints is set - can't compile anything!" >&2 + exit 1 + fi + + denoCompileEntrypoints=("${denoEntrypoints[@]}") + fi + for entrypoint in "${denoCompileEntrypoints[@]}"; do + if [[ -n "$entrypoint" && ! -f "$entrypoint" ]]; then + echo "error: entrypoint '$entrypoint' is specified but not found" >&2 + exit 1 + fi + done + + deno compile \ + --output $DENO_COMPILE_OUT/ \ + --cached-only \ + --vendor \ + --node-modules-dir \ + ${denoCompileFlags[@]} \ + ${denoCompileEntrypoints[@]} + + # `deno compile` works by inserting JavaScript in the data segments of the executables. + # Stripping them would just completely defeat the point and render the executable unusable. + for file in $DENO_COMPILE_OUT/*; do + stripExclude+=("${file#"$DENO_COMPILE_OUT/"}") + done +} + +denoCompileInstallPhase() { + mkdir -p $out/bin + cp -a $DENO_COMPILE_OUT/. -t $out/bin +} + +# Why don't we have any kind of dontDenoCompile flag? Because if you don't want to run `deno compile`, +# you shouldn't include this hook in your nativeBuildInputs. Simple as. +if [[ -z "${buildPhase-}" ]]; then + setOutputFlags= + buildPhase=denoCompileBuildPhase +fi +if [[ -z "${installPhase-}" ]]; then + setOutputFlags= + installPhase=denoCompileInstallPhase +fi diff --git a/pkgs/by-name/de/deno/hooks/setup-hook.sh b/pkgs/by-name/de/deno/hooks/setup-hook.sh new file mode 100644 index 0000000000000..43f6ad9dd0a60 --- /dev/null +++ b/pkgs/by-name/de/deno/hooks/setup-hook.sh @@ -0,0 +1,36 @@ +# shellcheck shell=bash + +denoConfigHook() { + echo "Executing denoConfigHook" + + if [[ -n "${denoRoot-}" ]]; then + pushd "$denoRoot" + fi + + if [[ -z "${denoDeps-}" ]]; then + echo "denoDeps not set - skipping" + exit 0 + fi + + export DENO_DIR=$(mktemp -d) + + if [[ -d $denoDeps/node_modules ]]; then + cp -a $denoDeps/node_modules -t . + chmod -R +w node_modules + + echo "Patching scripts" + patchShebangs node_modules/{*,.*} + fi + + [[ -d $denoDeps/vendor ]] && cp -a $denoDeps/vendor -t . || true + [[ -f $denoDeps/deno.json ]] && cp $denoDeps/deno.json deno.json || true + [[ -f $denoDeps/deno.lock ]] && cp $denoDeps/deno.lock deno.lock || true + + if [[ -n "${denoRoot-}" ]]; then + popd + fi + + echo "Finished denoConfigHook" +} + +postConfigureHooks+=(denoConfigHook) diff --git a/pkgs/by-name/de/deno/package.nix b/pkgs/by-name/de/deno/package.nix index ae16f88a4e85a..9ddbd7f56484e 100644 --- a/pkgs/by-name/de/deno/package.nix +++ b/pkgs/by-name/de/deno/package.nix @@ -12,6 +12,8 @@ librusty_v8 ? callPackage ./librusty_v8.nix { inherit (callPackage ./fetchers.nix { }) fetchLibrustyV8; }, + makeSetupHook, + deno, }: let @@ -83,8 +85,20 @@ rustPlatform.buildRustPackage rec { runHook postInstallCheck ''; - passthru.updateScript = ./update/update.ts; - passthru.tests = callPackage ./tests { }; + passthru = { + updateScript = ./update/update.ts; + tests = callPackage ./tests { }; + fetchDeps = callPackage ./fetch-deps { }; + setupHook = makeSetupHook { + name = "deno-setup-hook"; + propagatedBuildInputs = [ deno ]; + } ./hooks/setup-hook.sh; + compileHook = makeSetupHook { + name = "deno-compile-hook"; + propagatedBuildInputs = [ deno ]; + substitutions.deno = deno; + } ./hooks/compile-hook.sh; + }; meta = with lib; { homepage = "https://deno.land/"; diff --git a/pkgs/by-name/op/opendeck/package.nix b/pkgs/by-name/op/opendeck/package.nix new file mode 100644 index 0000000000000..9de0f3f3a1483 --- /dev/null +++ b/pkgs/by-name/op/opendeck/package.nix @@ -0,0 +1,89 @@ +{ + lib, + stdenv, + rustPlatform, + fetchFromGitHub, + + deno, + nodejs, + cargo-tauri, + pkg-config, + wrapGAppsHook3, + makeBinaryWrapper, + + openssl, + webkitgtk_4_1, + udev, + libayatana-appindicator, +}: +let + pname = "opendeck"; + version = "2.2.1"; + + src = fetchFromGitHub { + owner = "ninjadev64"; + repo = "OpenDeck"; + rev = "refs/tags/v${version}"; + hash = "sha256-P6STPT/JMKrPeTbxSwHt2y0q8XexPVjpgFPRMWtvJt4="; + }; +in +rustPlatform.buildRustPackage { + inherit pname version src; + + postPatch = '' + # Very strangely, OpenDeck does not use a Cargo workspace for Tauri and instead has its Rust + # component entirely with src-tauri — buildRustPackage really dislikes this so we symlink the + # Cargo.lock to the root directory. Setting sourceRoot also breaks tauri build as it assumes + # the build directory to be at the project root, where the node_modules are. + # + # It's a mess. + ln -s src-tauri/Cargo.lock Cargo.lock + ''; + + denoDeps = deno.fetchDeps { + inherit pname src; + hash = "sha256-2Oal3jpbK+QfaoR3RW5WlRX4SQustNpgOdLYYH6PjT0="; + denoInstallFlags = [ "--allow-scripts" ]; + }; + + cargoHash = "sha256-7CZJZFZukEQjjH+ri32LfJ+/dR7Xo4vDv69lDt2ItO8="; + cargoRoot = "src-tauri"; + buildAndTestSubdir = "src-tauri"; + + nativeBuildInputs = [ + deno.setupHook + nodejs + cargo-tauri.hook + pkg-config + wrapGAppsHook3 + makeBinaryWrapper + ]; + + buildInputs = + [ openssl ] + ++ lib.optionals stdenv.hostPlatform.isLinux [ + webkitgtk_4_1 + udev + libayatana-appindicator + ]; + + postInstall = '' + # Somehow the udev rules aren't autoinstalled + install -Dm644 src-tauri/bundle/40-streamdeck.rules -t $out/lib/udev/rules.d + ''; + + postFixup = '' + wrapProgram $out/bin/opendeck \ + --prefix LD_LIBRARY_PATH : ${lib.makeLibraryPath [ libayatana-appindicator ]} + ''; + + meta = { + description = "Cross-platform desktop application that provides functionality for stream controller devices"; + homepage = "https://github.com/ninjadev64/OpenDeck"; + changelog = "https://github.com/ninjadev64/OpenDeck/releases/tag/v${version}"; + license = with lib.licenses; [ gpl3Plus ]; + maintainers = with lib.maintainers; [ pluiedev ]; + platforms = with lib.platforms; linux ++ darwin ++ windows; + mainProgram = "opendeck"; + }; +}