diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore index 1f895bd..040cada 100644 --- a/.gitignore +++ b/.gitignore @@ -33,4 +33,9 @@ test-results playwright-report/ # Vitest browser mode screenshots -__screenshots__/ \ No newline at end of file +__screenshots__/ + +# Nix +result +result-* +.direnv/ \ No newline at end of file diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..77972fb --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1775710090, + "narHash": "sha256-ar3rofg+awPB8QXDaFJhJ2jJhu+KqN/PRCXeyuXR76E=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "4c1018dae018162ec878d42fec712642d214fdfa", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..a44e9c7 --- /dev/null +++ b/flake.nix @@ -0,0 +1,122 @@ +{ + description = "OpenScreen — desktop screen recorder with built-in editor"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + }; + + outputs = + { self, nixpkgs }: + let + systems = [ + "x86_64-linux" + "aarch64-linux" + ]; + forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f nixpkgs.legacyPackages.${system}); + in + { + # -- Per-system outputs (packages, dev shells) -- + + packages = forAllSystems (pkgs: { + openscreen = pkgs.callPackage ./nix/package.nix { }; + default = self.packages.${pkgs.stdenv.hostPlatform.system}.openscreen; + }); + + devShells = forAllSystems ( + pkgs: + let + electron = pkgs.electron; + + # Libraries Electron needs at runtime on Linux + runtimeLibs = with pkgs; [ + # X11 + libx11 + libxcomposite + libxdamage + libxext + libxfixes + libxrandr + libxtst + libxcb + libxshmfence + + # Wayland + wayland + + # GTK / UI toolkit + gtk3 + glib + pango + cairo + gdk-pixbuf + atk + at-spi2-atk + at-spi2-core + + # Graphics + mesa + libGL + libdrm + vulkan-loader + + # Networking / crypto (NSS for Chromium) + nss + nspr + + # Audio + alsa-lib + pipewire + pulseaudio + + # System + dbus + cups + expat + libnotify + libsecret + util-linux # libuuid + ]; + in + { + default = pkgs.mkShell { + packages = with pkgs; [ + nodejs_22 + electron + + # Native module compilation + python3 + pkg-config + gcc + + # Playwright browser tests + playwright-driver.browsers + ]; + + # Electron's prebuilt binary needs these at runtime + LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath runtimeLibs; + + # Tell the npm `electron` package to use the Nix-provided binary + # instead of downloading its own. vite-plugin-electron respects this. + ELECTRON_OVERRIDE_DIST_PATH = "${electron}/lib/electron"; + + # Playwright browser path for test:browser / test:e2e + PLAYWRIGHT_BROWSERS_PATH = "${pkgs.playwright-driver.browsers}"; + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD = "1"; + + shellHook = '' + echo "OpenScreen dev shell — node $(node --version), electron v$(electron --version 2>/dev/null | tr -d 'v')" + ''; + }; + } + ); + + # -- System-wide outputs (modules, overlay) -- + + overlays.default = final: _prev: { + openscreen = self.packages.${final.stdenv.hostPlatform.system}.openscreen; + }; + + nixosModules.default = import ./nix/module.nix self; + homeManagerModules.default = import ./nix/hm-module.nix self; + }; +} diff --git a/nix/hm-module.nix b/nix/hm-module.nix new file mode 100644 index 0000000..b04f827 --- /dev/null +++ b/nix/hm-module.nix @@ -0,0 +1,36 @@ +# Home Manager module for OpenScreen +# Usage in flake-based Home Manager config: +# +# inputs.openscreen.url = "github:siddharthvaddem/openscreen"; +# +# { inputs, ... }: { +# imports = [ inputs.openscreen.homeManagerModules.default ]; +# programs.openscreen.enable = true; +# } +self: +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.programs.openscreen; +in +{ + options.programs.openscreen = { + enable = lib.mkEnableOption "OpenScreen screen recorder"; + + package = lib.mkOption { + type = lib.types.package; + default = self.packages.${pkgs.stdenv.hostPlatform.system}.openscreen; + defaultText = lib.literalExpression "inputs.openscreen.packages.\${pkgs.stdenv.hostPlatform.system}.openscreen"; + description = "The OpenScreen package to use."; + }; + }; + + config = lib.mkIf cfg.enable { + home.packages = [ cfg.package ]; + }; +} diff --git a/nix/module.nix b/nix/module.nix new file mode 100644 index 0000000..3282d2d --- /dev/null +++ b/nix/module.nix @@ -0,0 +1,42 @@ +# NixOS module for OpenScreen +# Usage in flake-based NixOS config: +# +# inputs.openscreen.url = "github:siddharthvaddem/openscreen"; +# +# { inputs, ... }: { +# imports = [ inputs.openscreen.nixosModules.default ]; +# programs.openscreen.enable = true; +# } +self: +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.programs.openscreen; +in +{ + options.programs.openscreen = { + enable = lib.mkEnableOption "OpenScreen screen recorder"; + + package = lib.mkOption { + type = lib.types.package; + default = self.packages.${pkgs.stdenv.hostPlatform.system}.openscreen; + defaultText = lib.literalExpression "inputs.openscreen.packages.\${pkgs.stdenv.hostPlatform.system}.openscreen"; + description = "The OpenScreen package to use."; + }; + }; + + config = lib.mkIf cfg.enable { + environment.systemPackages = [ cfg.package ]; + + # Screen capture on Wayland requires xdg-desktop-portal. + # We enable the base portal; users should also enable a + # desktop-specific portal (e.g. xdg-desktop-portal-gtk, + # xdg-desktop-portal-hyprland) in their DE config. + xdg.portal.enable = lib.mkDefault true; + }; +} diff --git a/nix/package.nix b/nix/package.nix new file mode 100644 index 0000000..489fa13 --- /dev/null +++ b/nix/package.nix @@ -0,0 +1,130 @@ +{ + lib, + buildNpmPackage, + nodejs_22, + electron, + makeWrapper, + makeDesktopItem, + copyDesktopItems, +}: + +buildNpmPackage { + nodejs = nodejs_22; + pname = "openscreen"; + version = "1.3.0"; + + src = + let + fs = lib.fileset; + maybe = fs.maybeMissing; + in + fs.toSource { + root = ../.; + fileset = fs.difference ../. ( + fs.unions [ + ../nix + ../flake.nix + ../flake.lock + (maybe ../release) + (maybe ../test-results) + (maybe ../playwright-report) + (maybe ../.github) + (maybe ../.vscode) + (maybe ../.idea) + (maybe ../.kiro) + (maybe ../.envrc) + (maybe ../.direnv) + (fs.fileFilter (file: file.hasExt "md") ../.) + ] + ); + }; + + npmDepsHash = "sha256-Pd6J9TuggA9vM4s/LjdoK4MoBEivSzAWc/G2+pFOM2U="; + + env.ELECTRON_SKIP_BINARY_DOWNLOAD = "1"; + + # electron-builder is not needed — we wrap system electron directly + npmFlags = [ "--ignore-scripts" ]; + makeCacheWritable = true; + + # vite-plugin-electron compiles electron/ sources into dist-electron/ + # tsconfig has noEmit — tsc is type-check only + buildPhase = '' + runHook preBuild + npx vite build + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + + mkdir -p "$out/lib/openscreen" + + # Renderer build output (index.html, JS chunks, copied public/ assets) + cp -r dist "$out/lib/openscreen/" + + # Main process + preload (compiled by vite-plugin-electron) + cp -r dist-electron "$out/lib/openscreen/" + + # Package manifest (electron reads "main" field to find entry point) + cp package.json "$out/lib/openscreen/" + + # Strip devDependencies (electron, vitest, biome, playwright, etc.) + npm prune --omit=dev --no-save + cp -r node_modules "$out/lib/openscreen/" + + # Asset resolution: when app.isPackaged is false, the main process resolves + # assets at /public/assets/. Mirror the electron-builder + # extraResources layout so wallpapers load correctly. + mkdir -p "$out/lib/openscreen/public/assets" + cp -r public/wallpapers "$out/lib/openscreen/public/assets/wallpapers" + + # Wrap system electron with the app directory + mkdir -p "$out/bin" + makeWrapper "${electron}/bin/electron" "$out/bin/openscreen" \ + --add-flags "$out/lib/openscreen" \ + --set ELECTRON_IS_DEV 0 + + # Install icons to hicolor theme + for size in 16 24 32 48 64 128 256 512 1024; do + icon="icons/icons/png/''${size}x''${size}.png" + if [ -f "$icon" ]; then + install -Dm644 "$icon" \ + "$out/share/icons/hicolor/''${size}x''${size}/apps/openscreen.png" + fi + done + + runHook postInstall + ''; + + nativeBuildInputs = [ + makeWrapper + copyDesktopItems + ]; + + desktopItems = [ + (makeDesktopItem { + name = "openscreen"; + desktopName = "OpenScreen"; + genericName = "Screen Recorder"; + exec = "openscreen %U"; + icon = "openscreen"; + comment = "Desktop screen recorder with built-in editor"; + categories = [ + "AudioVideo" + "Video" + "Recorder" + ]; + startupWMClass = "Openscreen"; + terminal = false; + }) + ]; + + meta = { + description = "Desktop screen recorder with built-in editor"; + homepage = "https://github.com/siddharthvaddem/openscreen"; + license = lib.licenses.mit; + mainProgram = "openscreen"; + platforms = lib.platforms.linux; + }; +}