diff --git a/.github/workflows/build-linux-bundle.yml b/.github/workflows/build-linux-bundle.yml new file mode 100644 index 0000000000..25a1e814c5 --- /dev/null +++ b/.github/workflows/build-linux-bundle.yml @@ -0,0 +1,28 @@ +name: Build Linux Bundle + +on: + workflow_dispatch: {} + push: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + - uses: DeterminateSystems/nix-installer-action@main + - uses: DeterminateSystems/magic-nix-cache-action@main + + - name: Free disk space + run: sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache + + - name: Build Linux Bundle + run: nix build .nix#graphite-bundle.tar.xz && cp ./result ./graphite-bundle.tar.xz + + - name: Upload Linux Bundle + uses: actions/upload-artifact@v4 + with: + path: graphite-bundle.tar.xz diff --git a/.gitignore b/.gitignore index f410829a7b..34ac06f894 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ branding/ target/ result/ +.flatpak-builder/ *.spv *.exrc perf.data* diff --git a/.nix/deps/cef.nix b/.nix/deps/cef.nix index f31219be53..c64a1a1c7e 100644 --- a/.nix/deps/cef.nix +++ b/.nix/deps/cef.nix @@ -1,24 +1,26 @@ { pkgs, inputs, ... }: let - cef = pkgs.cef-binary.overrideAttrs (_: _: { + cef = pkgs.cef-binary.overrideAttrs { postInstall = '' strip $out/Release/*.so* ''; - }); + }; - cefPath = pkgs.runCommand "cef-path" {} '' + cefPath = pkgs.runCommand "cef-path" { } '' mkdir -p $out ln -s ${cef}/include $out/include find ${cef}/Release -name "*" -type f -exec ln -s {} $out/ \; find ${cef}/Resources -name "*" -maxdepth 1 -exec ln -s {} $out/ \; - echo '${builtins.toJSON { - type = "minimal"; - name = builtins.baseNameOf cef.src.url; - sha1 = ""; - }}' > $out/archive.json + echo '${ + builtins.toJSON { + type = "minimal"; + name = builtins.baseNameOf cef.src.url; + sha1 = ""; + } + }' > $out/archive.json ''; in { diff --git a/.nix/flake.nix b/.nix/flake.nix index 83f8c448b2..5a1f419347 100644 --- a/.nix/flake.nix +++ b/.nix/flake.nix @@ -33,7 +33,10 @@ info = { pname = "graphite"; version = "unstable"; - src = ./..; + src = pkgs.lib.cleanSourceWith { + src = ./..; + filter = path: type: !(type == "directory" && builtins.baseNameOf path == ".nix"); + }; }; pkgs = import inputs.nixpkgs { @@ -129,6 +132,13 @@ embeddedResources = false; dev = true; }; + graphite-bundle = import ./pkgs/graphite-bundle.nix { + inherit pkgs graphite; + }; + graphite-flatpak-manifest = import ./pkgs/graphite-flatpak-manifest.nix { + inherit pkgs; + archive = graphite-bundle.tar; + }; #TODO: graphene-cli = import ./pkgs/graphene-cli.nix { inherit info pkgs inputs deps libs tools; }; raster-nodes-shaders = import ./pkgs/raster-nodes-shaders.nix { inherit diff --git a/.nix/pkgs/graphite-bundle.nix b/.nix/pkgs/graphite-bundle.nix new file mode 100644 index 0000000000..f6e685d65d --- /dev/null +++ b/.nix/pkgs/graphite-bundle.nix @@ -0,0 +1,91 @@ +{ + pkgs, + graphite, +}: +let + bundle = + { + pkgs, + graphite, + archive ? false, + compression ? null, + passthru ? {}, + }: + ( + let + tar = if compression == null then archive else true; + nameArchiveSuffix = if tar then ".tar" else ""; + nameCompressionSuffix = if compression == null then "" else "." + compression; + name = "graphite-bundle${nameArchiveSuffix}${nameCompressionSuffix}"; + build = '' + mkdir -p out + mkdir -p out/bin + cp ${graphite}/bin/.graphite-wrapped out/bin/graphite + chmod -v +w out/bin/graphite + patchelf --set-rpath '$ORIGIN/../lib:$ORIGIN/../lib/cef' --set-interpreter '/lib64/ld-linux-x86-64.so.2' out/bin/graphite + mkdir -p out/lib/cef + mkdir -p ./cef + tar -xvf ${pkgs.cef-binary.src} -C ./cef --strip-components=1 + cp -r ./cef/Release/* out/lib/cef/ + cp -r ./cef/Resources/* out/lib/cef/ + find "out/lib/cef/locales" -type f ! -name 'en-US*' -delete + ${pkgs.bintools}/bin/strip out/lib/cef/*.so* + cp -r ${graphite}/share out/share + ''; + install = + if tar then + '' + cd out + tar -c \ + --sort=name \ + --mtime='@1' --clamp-mtime \ + --owner=0 --group=0 --numeric-owner \ + --mode='u=rwX,go=rX' \ + --format=posix \ + --pax-option=delete=atime,delete=ctime \ + --no-acls --no-xattrs --no-selinux \ + * ${ + if compression == "xz" then + "| xz " + else if compression == "gz" then + "| gzip -n " + else + "" + }> $out + '' + else + '' + mkdir -p $out + cp -r out/* $out/ + ''; + in + + pkgs.runCommand name + { + inherit passthru; + } + '' + ${build} + ${install} + '' + ); +in +bundle { + inherit pkgs graphite; + passthru = { + tar = bundle { + inherit pkgs graphite; + archive = true; + passthru = { + gz = bundle { + inherit pkgs graphite; + compression = "gz"; + }; + xz = bundle { + inherit pkgs graphite; + compression = "xz"; + }; + }; + }; + }; +} diff --git a/.nix/pkgs/graphite-flatpak-manifest.nix b/.nix/pkgs/graphite-flatpak-manifest.nix new file mode 100644 index 0000000000..03d7146f74 --- /dev/null +++ b/.nix/pkgs/graphite-flatpak-manifest.nix @@ -0,0 +1,37 @@ +{ + pkgs, + archive, +}: + +(pkgs.formats.json { }).generate "art.graphite.Graphite.json" { + app-id = "art.graphite.Graphite"; + runtime = "org.freedesktop.Platform"; + runtime-version = "25.08"; + sdk = "org.freedesktop.Sdk"; + command = "graphite"; + finish-args = [ + "--device=dri" + "--share=ipc" + "--socket=wayland" + "--socket=fallback-x11" + "--share=network" + ]; + modules = [ + { + name = "app"; + buildsystem = "simple"; + build-commands = [ + "mkdir -p /app" + "cp -r ./* /app/" + "chmod +x /app/bin/*" + ]; + sources = [ + { + type = "archive"; + path = archive; + strip-components = 0; + } + ]; + } + ]; +} diff --git a/.nix/pkgs/graphite.nix b/.nix/pkgs/graphite.nix index 31fd507254..58261f89b7 100644 --- a/.nix/pkgs/graphite.nix +++ b/.nix/pkgs/graphite.nix @@ -124,7 +124,7 @@ deps.crane.lib.buildPackage ( cp $src/desktop/assets/*.desktop $out/share/applications/ mkdir -p $out/share/icons/hicolor/scalable/apps - cp ${branding}/app-icons/graphite.svg $out/share/icons/hicolor/scalable/apps/ + cp ${branding}/app-icons/graphite.svg $out/share/icons/hicolor/scalable/apps/art.graphite.Graphite.svg ''; postFixup = '' diff --git a/Cargo.lock b/Cargo.lock index 15906567ed..aa887a9423 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -774,7 +774,7 @@ dependencies = [ "thiserror 2.0.16", "tracing", "wgpu", - "windows 0.58.0", + "windows", "windows-sys 0.61.2", ] @@ -1685,6 +1685,17 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "fd-lock" +version = "4.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" +dependencies = [ + "cfg-if", + "rustix", + "windows-sys 0.59.0", +] + [[package]] name = "fdeflate" version = "0.3.7" @@ -1829,7 +1840,7 @@ dependencies = [ "read-fonts 0.35.0", "roxmltree", "smallvec", - "windows 0.58.0", + "windows", "windows-core 0.58.0", "yeslogic-fontconfig-sys", ] @@ -2147,7 +2158,7 @@ dependencies = [ "log", "presser", "thiserror 1.0.69", - "windows 0.58.0", + "windows", ] [[package]] @@ -2330,6 +2341,7 @@ dependencies = [ "ctrlc", "derivative", "dirs", + "fd-lock", "futures", "glam", "graphite-desktop-embedded-resources", @@ -2339,7 +2351,6 @@ dependencies = [ "objc2-app-kit 0.3.2", "objc2-foundation 0.3.2", "open", - "pidlock", "rand 0.9.2", "rfd", "ron", @@ -2350,7 +2361,7 @@ dependencies = [ "vello", "wgpu", "window_clipboard", - "windows 0.58.0", + "windows", "winit", ] @@ -4370,17 +4381,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" -[[package]] -name = "pidlock" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f837924d5368f9f35a1c404699de3c074311358035c77d7164f5948c08b31382" -dependencies = [ - "nix", - "thiserror 1.0.69", - "windows 0.62.2", -] - [[package]] name = "pin-project" version = "1.1.10" @@ -7224,7 +7224,7 @@ dependencies = [ "wasm-bindgen", "web-sys", "wgpu-types", - "windows 0.58.0", + "windows", "windows-core 0.58.0", ] @@ -7297,27 +7297,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" -dependencies = [ - "windows-collections", - "windows-core 0.62.2", - "windows-future", - "windows-numerics", -] - -[[package]] -name = "windows-collections" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" -dependencies = [ - "windows-core 0.62.2", -] - [[package]] name = "windows-core" version = "0.58.0" @@ -7344,30 +7323,6 @@ dependencies = [ "windows-strings 0.4.2", ] -[[package]] -name = "windows-core" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" -dependencies = [ - "windows-implement 0.60.2", - "windows-interface 0.59.3", - "windows-link 0.2.1", - "windows-result 0.4.1", - "windows-strings 0.5.1", -] - -[[package]] -name = "windows-future" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" -dependencies = [ - "windows-core 0.62.2", - "windows-link 0.2.1", - "windows-threading", -] - [[package]] name = "windows-implement" version = "0.58.0" @@ -7424,16 +7379,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-numerics" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" -dependencies = [ - "windows-core 0.62.2", - "windows-link 0.2.1", -] - [[package]] name = "windows-registry" version = "0.5.3" @@ -7463,15 +7408,6 @@ dependencies = [ "windows-link 0.1.3", ] -[[package]] -name = "windows-result" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" -dependencies = [ - "windows-link 0.2.1", -] - [[package]] name = "windows-strings" version = "0.1.0" @@ -7491,15 +7427,6 @@ dependencies = [ "windows-link 0.1.3", ] -[[package]] -name = "windows-strings" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" -dependencies = [ - "windows-link 0.2.1", -] - [[package]] name = "windows-sys" version = "0.45.0" @@ -7593,15 +7520,6 @@ dependencies = [ "windows_x86_64_msvc 0.53.0", ] -[[package]] -name = "windows-threading" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" -dependencies = [ - "windows-link 0.2.1", -] - [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" diff --git a/desktop/Cargo.toml b/desktop/Cargo.toml index 1df9331466..e0baf67aad 100644 --- a/desktop/Cargo.toml +++ b/desktop/Cargo.toml @@ -43,7 +43,7 @@ open = { workspace = true } rand = { workspace = true, features = ["thread_rng"] } serde = { workspace = true } clap = { workspace = true, features = ["derive"] } -pidlock = "0.2.2" +fd-lock = "4.0.4" ctrlc = "3.5.1" window_clipboard = "0.5" diff --git a/desktop/assets/art.graphite.Graphite.desktop b/desktop/assets/art.graphite.Graphite.desktop index 8e69d2c4de..2e17c64fec 100644 --- a/desktop/assets/art.graphite.Graphite.desktop +++ b/desktop/assets/art.graphite.Graphite.desktop @@ -5,7 +5,7 @@ Comment=Open-source vector & raster graphics editor. Featuring node based proced Exec=graphite Terminal=false Type=Application -Icon=graphite +Icon=art.graphite.Graphite Categories=Graphics;VectorGraphics;RasterGraphics; Keywords=graphite;editor;vector;raster;procedural;design; StartupWMClass=art.graphite.Graphite diff --git a/desktop/src/cef/context/builder.rs b/desktop/src/cef/context/builder.rs index 566ef1a0b8..071648eb74 100644 --- a/desktop/src/cef/context/builder.rs +++ b/desktop/src/cef/context/builder.rs @@ -111,6 +111,8 @@ impl CefContextBuilder { let settings = Settings { multi_threaded_message_loop: 1, + #[cfg(target_os = "linux")] + no_sandbox: 1, ..Self::common_settings(&instance_dir) }; diff --git a/desktop/src/cef/internal/browser_process_app.rs b/desktop/src/cef/internal/browser_process_app.rs index de75b5bb8e..55df7c22c4 100644 --- a/desktop/src/cef/internal/browser_process_app.rs +++ b/desktop/src/cef/internal/browser_process_app.rs @@ -34,6 +34,7 @@ impl ImplApp for BrowserProcessAppImpl { fn on_before_command_line_processing(&self, _process_type: Option<&cef::CefString>, command_line: Option<&mut cef::CommandLine>) { if let Some(cmd) = command_line { cmd.append_switch_with_value(Some(&CefString::from("renderer-process-limit")), Some(&CefString::from("1"))); + cmd.append_switch_with_value(Some(&CefString::from("password-store")), Some(&CefString::from("basic"))); cmd.append_switch_with_value(Some(&CefString::from("disk-cache-size")), Some(&CefString::from("0"))); cmd.append_switch(Some(&CefString::from("incognito"))); cmd.append_switch(Some(&CefString::from("no-first-run"))); diff --git a/desktop/src/lib.rs b/desktop/src/lib.rs index 1ae86052b9..93ac5c58e8 100644 --- a/desktop/src/lib.rs +++ b/desktop/src/lib.rs @@ -1,4 +1,5 @@ use clap::Parser; +use std::io::Write; use std::process::exit; use tracing_subscriber::EnvFilter; use winit::event_loop::EventLoop; @@ -38,26 +39,35 @@ pub fn start() { return; } - let mut lock = pidlock::Pidlock::new_validated(dirs::app_data_dir().join(APP_LOCK_FILE_NAME)).unwrap(); - match lock.acquire() { - Ok(lock) => { + let cli = Cli::parse(); + + let Ok(lock_file) = std::fs::OpenOptions::new() + .read(true) + .write(true) + .create(true) + .truncate(true) + .open(dirs::app_data_dir().join(APP_LOCK_FILE_NAME)) + else { + tracing::error!("Failed to open lock file, Exiting."); + exit(1); + }; + let mut lock = fd_lock::RwLock::new(lock_file); + let lock = match lock.try_write() { + Ok(mut guard) => { tracing::info!("Acquired application lock"); - lock + let _ = guard.set_len(0); + let _ = write!(guard, "{}", std::process::id()); + let _ = guard.sync_all(); + guard } - Err(pidlock::PidlockError::LockExists) => { + Err(_) => { tracing::error!("Another instance is already running, Exiting."); - exit(0); - } - Err(err) => { - tracing::error!("Failed to acquire application lock: {err}"); exit(1); } }; App::init(); - let cli = Cli::parse(); - let wgpu_context = futures::executor::block_on(gpu_context::create_wgpu_context()); let event_loop = EventLoop::new().unwrap(); @@ -94,6 +104,9 @@ pub fn start() { event_loop.run_app(&mut app).unwrap(); + // Explicitly drop the instance lock + drop(lock); + // Workaround for a Windows-specific exception that occurs when `app` is dropped. // The issue causes the window to hang for a few seconds before closing. // Appears to be related to CEF object destruction order.