Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,10 @@ jobs:

build-nix:
needs: ["check", "test"]
runs-on: macos-latest
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
steps:
- uses: actions/checkout@v6
with:
Expand Down
11 changes: 9 additions & 2 deletions desktop/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion desktop/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ open = "5.3.2"
parking_lot = "0.12"
percent-encoding = "2.3"
pulldown-cmark = "0.13.0"
rfd = { version = "0.15.4", default-features = false, features = ["tokio"] }
rfd = { version = "0.15.4", default-features = false, features = ["tokio", "xdg-portal"] }
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0"
serde_yaml = "0.9"
Expand Down Expand Up @@ -70,3 +70,8 @@ inherits = "dev"

[profile.android-dev]
inherits = "dev"

[patch.crates-io]
# Fix overlapping cfg conditions that cause duplicate function definitions on Linux.
# https://github.com/DioxusLabs/sdk/pull/103
dioxus-sdk-window = { git = "https://github.com/iynaix/dioxus-sdk", rev = "28fc2264" }
2 changes: 1 addition & 1 deletion desktop/Dioxus.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ default_platform = "desktop"
[bundle]
identifier = "com.lambdalisue.Arto"
publisher = "lambdalisue"
icon = ["../extras/mac/arto-app.icns"]
icon = ["../extras/mac/arto-app.icns", "assets/arto-app.png"]
copyright = "Copyright 2025 lambdalisue"
category = "Utility"
short_description = "A GitHub Markdown viewer"
Expand Down
12 changes: 12 additions & 0 deletions desktop/justfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,27 @@ verify: fmt check test
clean:
cargo clean

[macos]
build:
@rm -rf target/dx/arto/release/macos/Arto.app/Contents/Resources/assets
@rm -rf target/dx/arto/bundle/macos/bundle/macos/Arto.app/Contents/Resources/assets
dx bundle --release --macos

[linux]
build:
@rm -rf target/dx/arto/release/linux/app/assets
dx bundle --release --linux --package-types deb

[macos]
open:
./target/dx/arto/bundle/macos/bundle/macos/Arto.app/Contents/MacOS/arto

[linux]
open:
./target/dx/arto/release/linux/app/arto

[confirm]
[macos]
install:
@rm -rf /Applications/Arto.app
@cp -af target/dx/arto/bundle/macos/bundle/macos/Arto.app /Applications/.
1 change: 1 addition & 0 deletions desktop/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ fn init_tracing() {
.with(fmt_layer);

// On macOS, log to Console.app via oslog
#[cfg(target_os = "macos")]
let registry = registry.with(
tracing_oslog::OsLogger::new("com.lambdalisue.Arto", "default").with_filter(silence_filter),
);
Expand Down
2 changes: 2 additions & 0 deletions desktop/src/menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ fn menu_action_for_id(id: MenuId) -> Option<&'static str> {

/// Build the application menu bar
pub fn build_menu() -> Menu {
#[cfg(target_os = "macos")]
disable_automatic_window_tabbing();

let menu = Menu::new();
Expand Down Expand Up @@ -511,6 +512,7 @@ fn pick_directory() -> Option<PathBuf> {
dir
}

#[cfg(target_os = "macos")]
fn disable_automatic_window_tabbing() {
use objc2::MainThreadMarker;
use objc2_app_kit::NSWindow;
Expand Down
15 changes: 11 additions & 4 deletions desktop/src/theme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,17 @@ pub fn resolve_theme(theme: Theme) -> DioxusTheme {
// We cannot use dioxus_sdk_window::theme::get_theme here because
// it requires a Dioxus runtime and cannot be called from outside
// of Dioxus context. That's why we use dark_light crate instead.
Theme::Auto => match dark_light::detect() {
Ok(dark_light::Mode::Light) => DioxusTheme::Light,
Ok(dark_light::Mode::Dark) => DioxusTheme::Dark,
Ok(dark_light::Mode::Unspecified) | Err(_) => DioxusTheme::Light,
// On Linux, dark_light uses D-Bus (zbus) which requires a Tokio
// runtime. This function may be called before the runtime starts
// (e.g. from build_custom_index in main()), so catch_unwind
// prevents the panic and falls back to Light.
Theme::Auto => match std::panic::catch_unwind(dark_light::detect)
.ok()
.and_then(|r| r.ok())
Comment on lines +32 to +34

Copilot AI Feb 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using catch_unwind to handle the D-Bus panic on Linux is a workaround that masks the underlying issue. A more robust approach would be to use platform-specific implementations: on Linux, skip automatic theme detection if called before the Tokio runtime starts, or check if the runtime is available before calling dark_light::detect. This would avoid the overhead and uncertainty of panic catching. Consider adding a #[cfg(target_os = "linux")] guard to return a default theme or check runtime availability.

Copilot uses AI. Check for mistakes.
{
Some(dark_light::Mode::Light) => DioxusTheme::Light,
Some(dark_light::Mode::Dark) => DioxusTheme::Dark,
Some(dark_light::Mode::Unspecified) | None => DioxusTheme::Light,
},
Theme::Light => DioxusTheme::Light,
Theme::Dark => DioxusTheme::Dark,
Expand Down
2 changes: 2 additions & 0 deletions desktop/src/utils/file_operations.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use std::path::Path;

#[cfg(target_os = "macos")]
use std::process::Command;

/// Reveal a file in Finder (macOS) or file explorer
Expand Down
84 changes: 74 additions & 10 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
let
systems = [
"aarch64-darwin"
"aarch64-linux"
"x86_64-darwin"
"x86_64-linux"
];
eachSystem = nixpkgs.lib.genAttrs systems;
in
Expand All @@ -46,11 +48,18 @@

# Platform detection
isDarwin = pkgs.stdenv.hostPlatform.isDarwin;
isLinux = pkgs.stdenv.hostPlatform.isLinux;

# App bundle paths (used in build and apps)
appBundleName = "Arto.app";
appExecutableName = "arto"; # lowercase executable name
dxBundlePath = "target/dx/${packageMeta.pname}/bundle/macos/bundle/macos";
dxBundlePath =
if isDarwin then
"target/dx/${packageMeta.pname}/bundle/macos/bundle/macos"
# dx build (not bundle) outputs here; bundle fails in Nix sandbox
# due to permission errors in the .deb/.AppImage packagers.
else
"target/dx/${packageMeta.pname}/release/linux/app";

renderer-assets = pkgs.stdenvNoCC.mkDerivation (finalAttrs: {
pname = "${packageMeta.pname}-renderer-assets";
Expand Down Expand Up @@ -104,9 +113,21 @@
strictDeps = true;
# Pass version to build.rs via environment variable
ARTO_BUILD_VERSION = artoVersion;
buildInputs = lib.optionals isDarwin [
pkgs.libiconv
nativeBuildInputs = lib.optionals isLinux [
pkgs.pkg-config
];
buildInputs =
lib.optionals isDarwin [
pkgs.libiconv
]
++ lib.optionals isLinux [
pkgs.webkitgtk_4_1
pkgs.gtk3
pkgs.libsoup_3
pkgs.glib
pkgs.openssl
pkgs.xdotool
Comment thread
natsukium marked this conversation as resolved.
];
};

cargoArtifacts = craneLib.buildDepsOnly commonArgs;
Expand Down Expand Up @@ -145,6 +166,10 @@
]
++ lib.optionals isDarwin [
pkgs.darwin.autoSignDarwinBinariesHook
]
++ lib.optionals isLinux [
pkgs.pkg-config
pkgs.wrapGAppsHook3
];

postPatch = ''
Expand All @@ -160,12 +185,19 @@
# Use buildPhaseCargoCommand instead of cargoBuildCommand because crane's
# additional build argument `--message-format` cannot be passed to dioxus-cli properly.
# https://crane.dev/API.html#cranelibbuildpackage
buildPhaseCargoCommand = ''
dx bundle --release --platform desktop --package-types macos
'';

# The build output is a macOS .app bundle, and crane cannot infer the install
# destination, so we manually install without capturing cargoBuildLog in buildPhase.
buildPhaseCargoCommand =
if isDarwin then
''
dx bundle --release --platform desktop --package-types macos
''
else
''
dx build --release --platform desktop
'';

# The build output is a platform-specific bundle, and crane cannot infer the
# install destination, so we manually install without capturing cargoBuildLog
# in buildPhase.
# https://crane.dev/API.html#cranelibinstallfromcargobuildloghook
doNotPostBuildInstallCargoBinaries = true;

Expand All @@ -186,6 +218,24 @@
# Create symlink for CLI usage (enables `arto` command in PATH)
mkdir -p $out/bin
ln -s "$out/Applications/${appBundleName}/Contents/MacOS/${appExecutableName}" "$out/bin/${appExecutableName}"
''
+ lib.optionalString isLinux ''
app_dir="${dxBundlePath}"

if [[ ! -d "$app_dir" ]]; then
echo "Error: Expected build output not found at $app_dir"
echo "Searching for build output in target/dx..."
find target/dx -type d 2>/dev/null || true
exit 1
fi

# Install the entire app directory (binary + assets) since
# Dioxus asset!() macro resolves paths relative to the binary.
mkdir -p $out/lib/${appExecutableName}
cp -r "$app_dir"/. $out/lib/${appExecutableName}/

mkdir -p $out/bin
ln -s $out/lib/${appExecutableName}/${appExecutableName} $out/bin/${appExecutableName}
'';
}
);
Expand All @@ -201,13 +251,18 @@
let
# Access packageMeta from packages let-binding
inherit (self.packages.${system}) arto;
pkgs = nixpkgs.legacyPackages.${system};
appBundleName = "Arto.app";
appExecutableName = "arto";
in
{
default = {
type = "app";
program = "${arto}/Applications/${appBundleName}/Contents/MacOS/${appExecutableName}";
program =
if pkgs.stdenv.hostPlatform.isDarwin then
"${arto}/Applications/${appBundleName}/Contents/MacOS/${appExecutableName}"
else
"${arto}/bin/${appExecutableName}";
};
}
);
Expand Down Expand Up @@ -237,6 +292,15 @@
]
++ pkgs.lib.optionals pkgs.stdenv.hostPlatform.isDarwin [
pkgs.libiconv
]
++ pkgs.lib.optionals pkgs.stdenv.hostPlatform.isLinux [
pkgs.pkg-config
pkgs.webkitgtk_4_1
pkgs.gtk3
pkgs.libsoup_3
pkgs.glib
pkgs.openssl
pkgs.xdotool
];

# Workaround: Nix sets DEVELOPER_DIR to its apple-sdk, which breaks `just build` dmg creation.
Expand Down
1 change: 1 addition & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ build: renderer::assets desktop::build

open: desktop::open

[macos]
install: desktop::install
Loading