diff --git a/Cargo.lock b/Cargo.lock index c3bf92a..ffa8678 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -35,7 +35,7 @@ dependencies = [ "atspi-common", "serde", "thiserror 1.0.69", - "zvariant", + "zvariant 4.2.0", ] [[package]] @@ -77,7 +77,7 @@ dependencies = [ "futures-lite", "futures-util", "serde", - "zbus", + "zbus 4.4.0", ] [[package]] @@ -214,6 +214,28 @@ dependencies = [ "libloading", ] +[[package]] +name = "ashpd" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f3f79755c74fd155000314eb349864caa787c6592eace6c6882dad873d9c39" +dependencies = [ + "async-fs", + "async-net", + "enumflags2", + "futures-channel", + "futures-util", + "rand 0.9.5", + "raw-window-handle", + "serde", + "serde_repr", + "url", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "zbus 5.18.0", +] + [[package]] name = "async-broadcast" version = "0.7.2" @@ -292,6 +314,17 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "async-net" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" +dependencies = [ + "async-io", + "blocking", + "futures-lite", +] + [[package]] name = "async-process" version = "2.5.0" @@ -382,11 +415,11 @@ dependencies = [ "enumflags2", "serde", "static_assertions", - "zbus", + "zbus 4.4.0", "zbus-lockstep", "zbus-lockstep-macros", - "zbus_names", - "zvariant", + "zbus_names 3.0.0", + "zvariant 4.2.0", ] [[package]] @@ -398,7 +431,7 @@ dependencies = [ "atspi-common", "atspi-proxies", "futures-lite", - "zbus", + "zbus 4.4.0", ] [[package]] @@ -409,8 +442,8 @@ checksum = "a5e6c5de3e524cf967569722446bcd458d5032348554d9a17d7d72b041ab7496" dependencies = [ "atspi-common", "serde", - "zbus", - "zvariant", + "zbus 4.4.0", + "zvariant 4.2.0", ] [[package]] @@ -470,6 +503,15 @@ dependencies = [ "objc2 0.5.2", ] +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2 0.6.4", +] + [[package]] name = "blocking" version = "1.6.2" @@ -825,6 +867,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ "bitflags 2.13.1", + "block2 0.6.2", + "libc", "objc2 0.6.4", ] @@ -1157,6 +1201,15 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures-channel" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" +dependencies = [ + "futures-core", +] + [[package]] name = "futures-core" version = "0.3.33" @@ -1796,6 +1849,7 @@ dependencies = [ "eframe", "egui", "pulldown-cmark", + "rfd", "serde", "serde_json", "zip", @@ -2001,7 +2055,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ "bitflags 2.13.1", - "block2", + "block2 0.5.1", "libc", "objc2 0.5.2", "objc2-core-data", @@ -2017,6 +2071,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ "bitflags 2.13.1", + "block2 0.6.2", "objc2 0.6.4", "objc2-core-foundation", "objc2-core-graphics", @@ -2030,7 +2085,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ "bitflags 2.13.1", - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-core-location", "objc2-foundation 0.2.2", @@ -2042,7 +2097,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" dependencies = [ - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", ] @@ -2054,7 +2109,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ "bitflags 2.13.1", - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", ] @@ -2089,7 +2144,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" dependencies = [ - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", "objc2-metal", @@ -2101,7 +2156,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" dependencies = [ - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-contacts", "objc2-foundation 0.2.2", @@ -2120,7 +2175,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ "bitflags 2.13.1", - "block2", + "block2 0.5.1", "dispatch", "libc", "objc2 0.5.2", @@ -2154,7 +2209,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" dependencies = [ - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-app-kit 0.2.2", "objc2-foundation 0.2.2", @@ -2167,7 +2222,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ "bitflags 2.13.1", - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", ] @@ -2179,7 +2234,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ "bitflags 2.13.1", - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", "objc2-metal", @@ -2202,7 +2257,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" dependencies = [ "bitflags 2.13.1", - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-cloud-kit", "objc2-core-data", @@ -2222,7 +2277,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" dependencies = [ - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", ] @@ -2234,7 +2289,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ "bitflags 2.13.1", - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-core-location", "objc2-foundation 0.2.2", @@ -2398,6 +2453,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "pollster" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" + [[package]] name = "potential_utf" version = "0.1.5" @@ -2504,8 +2565,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", ] [[package]] @@ -2515,7 +2586,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", ] [[package]] @@ -2527,6 +2608,15 @@ dependencies = [ "getrandom 0.2.17", ] +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + [[package]] name = "raw-window-handle" version = "0.6.2" @@ -2577,6 +2667,30 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" +[[package]] +name = "rfd" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef2bee61e6cffa4635c72d7d81a84294e28f0930db0ddcb0f66d10244674ebed" +dependencies = [ + "ashpd", + "block2 0.6.2", + "dispatch2", + "js-sys", + "log", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "pollster", + "raw-window-handle", + "urlencoding", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-sys 0.59.0", +] + [[package]] name = "rustc-hash" version = "1.1.0" @@ -3168,14 +3282,32 @@ dependencies = [ "idna", "percent-encoding", "serde", + "serde_derive", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "uuid" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" +dependencies = [ + "js-sys", + "serde_core", + "wasm-bindgen", +] + [[package]] name = "version_check" version = "0.9.5" @@ -3901,7 +4033,7 @@ dependencies = [ "android-activity", "atomic-waker", "bitflags 2.13.1", - "block2", + "block2 0.5.1", "bytemuck", "calloop 0.13.0", "cfg_aliases 0.2.2", @@ -4084,7 +4216,7 @@ dependencies = [ "hex", "nix", "ordered-stream", - "rand", + "rand 0.8.7", "serde", "serde_repr", "sha1", @@ -4093,9 +4225,44 @@ dependencies = [ "uds_windows", "windows-sys 0.52.0", "xdg-home", - "zbus_macros", - "zbus_names", - "zvariant", + "zbus_macros 4.4.0", + "zbus_names 3.0.0", + "zvariant 4.2.0", +] + +[[package]] +name = "zbus" +version = "5.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe18fb60dc696039e738717b76eaea21e7a4489bbb1885020b43c94236d7e98a" +dependencies = [ + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "libc", + "ordered-stream", + "rustix 1.1.4", + "serde", + "serde_repr", + "tracing", + "uds_windows", + "uuid", + "windows-sys 0.61.2", + "winnow", + "zbus_macros 5.18.0", + "zbus_names 4.3.4", + "zvariant 5.13.1", ] [[package]] @@ -4105,7 +4272,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ca2c5dceb099bddaade154055c926bb8ae507a18756ba1d8963fd7b51d8ed1d" dependencies = [ "zbus_xml", - "zvariant", + "zvariant 4.2.0", ] [[package]] @@ -4119,7 +4286,7 @@ dependencies = [ "syn 2.0.119", "zbus-lockstep", "zbus_xml", - "zvariant", + "zvariant 4.2.0", ] [[package]] @@ -4132,7 +4299,22 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.119", - "zvariant_utils", + "zvariant_utils 2.1.0", +] + +[[package]] +name = "zbus_macros" +version = "5.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe96480bed92df2b442a1a30df364e12d08eed03aeb061f2b8dc6afb2be91119" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.119", + "zbus_names 4.3.4", + "zvariant 5.13.1", + "zvariant_utils 3.5.0", ] [[package]] @@ -4143,7 +4325,18 @@ checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" dependencies = [ "serde", "static_assertions", - "zvariant", + "zvariant 4.2.0", +] + +[[package]] +name = "zbus_names" +version = "4.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e" +dependencies = [ + "serde", + "winnow", + "zvariant 5.13.1", ] [[package]] @@ -4155,8 +4348,8 @@ dependencies = [ "quick-xml 0.30.0", "serde", "static_assertions", - "zbus_names", - "zvariant", + "zbus_names 3.0.0", + "zvariant 4.2.0", ] [[package]] @@ -4278,7 +4471,22 @@ dependencies = [ "enumflags2", "serde", "static_assertions", - "zvariant_derive", + "zvariant_derive 4.2.0", +] + +[[package]] +name = "zvariant" +version = "5.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee2a0bcd2a907786a456fff45aaaaf54c9ba5f50b71ae9ec1a4edd200c94911" +dependencies = [ + "endi", + "enumflags2", + "serde", + "url", + "winnow", + "zvariant_derive 5.13.1", + "zvariant_utils 3.5.0", ] [[package]] @@ -4291,7 +4499,20 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.119", - "zvariant_utils", + "zvariant_utils 2.1.0", +] + +[[package]] +name = "zvariant_derive" +version = "5.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38a708216a18780796770bfe3f4739c7c83a3e8f789b755534bbbc06e4e23e12" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.119", + "zvariant_utils 3.5.0", ] [[package]] @@ -4304,3 +4525,16 @@ dependencies = [ "quote", "syn 2.0.119", ] + +[[package]] +name = "zvariant_utils" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90cb9383f9b45290407a1258b202d3f8f01db719eb60b4e4055c6375af4fc7c7" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn 2.0.119", + "winnow", +] diff --git a/Cargo.toml b/Cargo.toml index 51828c1..e7c5164 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,8 @@ serde_json = "1" pulldown-cmark = { version = "0.12", default-features = false } zip = { version = "2", default-features = false, features = ["deflate"] } dirs = "5" +# Native open/save dialogs via the XDG Desktop Portal (no GTK build dependency). +rfd = { version = "0.15", default-features = false, features = ["xdg-portal", "async-std"] } [profile.release] opt-level = 2 diff --git a/INSTALL-debian12-surface.md b/INSTALL-debian12-surface.md index 25b7206..263d2cc 100644 --- a/INSTALL-debian12-surface.md +++ b/INSTALL-debian12-surface.md @@ -44,6 +44,14 @@ sudo apt install -y \ libx11-6 libx11-xcb1 libxcursor1 libxi6 libxrandr2 libxinerama1 libxrender1 ``` +The **📂 Browse** buttons open native file dialogs through the XDG Desktop +Portal. On GNOME this is already installed, but if the dialogs don't appear, +ensure the portal and a file-chooser backend are present: + +```sh +sudo apt install -y xdg-desktop-portal xdg-desktop-portal-gtk +``` + --- ## 2. Get the source onto the tablet @@ -188,6 +196,7 @@ rm -f ~/.local/share/applications/md-manuscript.desktop |---|---| | `error while loading shared libraries: libGL.so.1` (or similar) | Install the runtime libraries in step 1b. | | Window fails with *"neither WAYLAND_DISPLAY nor DISPLAY is set"* | You're in a text-only session/SSH; run it from the tablet's graphical desktop. | +| **📂 Browse** buttons do nothing / no dialog appears | Install `xdg-desktop-portal` and `xdg-desktop-portal-gtk` (step 1b); you can still type paths by hand. | | Build error mentioning `rustc … is not supported` | `rustup update`, then `cargo build --release` again. | | `cargo: command not found` | Run `. "$HOME/.cargo/env"` or open a new terminal after installing rustup. | | UI text far too small/large | Adjust GNOME *Displays → Scale*, or set `WINIT_X11_SCALE_FACTOR` (X11). | diff --git a/README.md b/README.md index 343c099..bf56adc 100644 --- a/README.md +++ b/README.md @@ -51,8 +51,9 @@ Terminal=false ## Use -1. Set the **Workspace** path in the top bar (default: `~/Manuscript`) and press - **Open**. The directory is created if it does not exist. +1. Set the **Workspace** path in the top bar (default: `~/Manuscript`) — type it, + or click **📂** to pick a folder in a native file browser — and press **Open**. + The directory is created if it does not exist. 2. Press **Init git** once to make the workspace a git repository. To sync with another machine, add a remote yourself: ```sh @@ -69,7 +70,8 @@ Terminal=false blank falls back to a `# Title:` line, then the file's first level-1 heading (`# ...`), then the file name. Titles are saved to `titles.json` and sync with git. -5. Set the **Export** path and press **Export ODT** to produce the concatenated +5. Set the **Export** path (type it, or click **📂** to choose the `.odt` file in a + native save dialog) and press **Export ODT** to produce the concatenated document. Each file becomes a chapter headed by its title, optionally followed by a caption from its `# Slug:` line. diff --git a/src/app.rs b/src/app.rs index 7279917..30f658b 100644 --- a/src/app.rs +++ b/src/app.rs @@ -340,6 +340,50 @@ impl App { } } + /// Open a native folder picker to choose the workspace directory. + fn browse_workspace(&mut self) { + let start = PathBuf::from(self.workspace_input.trim()); + let mut dialog = rfd::FileDialog::new().set_title("Choose workspace folder"); + if start.is_dir() { + dialog = dialog.set_directory(&start); + } + if let Some(path) = dialog.pick_folder() { + self.save_current(); + self.workspace_input = path.display().to_string(); + self.config.workspace = path; + self.config.save(); + self.open_workspace(); + } + } + + /// Open a native save dialog to choose the export `.odt` path. + fn browse_export(&mut self) { + let current = PathBuf::from(self.export_input.trim()); + let mut dialog = rfd::FileDialog::new() + .set_title("Choose export file") + .add_filter("OpenDocument Text", &["odt"]); + if let Some(parent) = current.parent().filter(|p| p.is_dir()) { + dialog = dialog.set_directory(parent); + } + let name = current + .file_name() + .and_then(|s| s.to_str()) + .unwrap_or("manuscript.odt"); + if let Some(mut path) = dialog.set_file_name(name).save_file() { + // Ensure the chosen path ends in .odt even if the user omitted it. + let has_odt = path + .extension() + .and_then(|e| e.to_str()) + .is_some_and(|e| e.eq_ignore_ascii_case("odt")); + if !has_odt { + path.set_extension("odt"); + } + self.export_input = path.display().to_string(); + self.config.export_path = path; + self.config.save(); + } + } + fn reorder(&mut self, from: usize, mut to: usize) { if from >= self.files.len() || from == to { return; @@ -370,8 +414,11 @@ impl App { ui.label("Workspace:"); let resp = ui.add( egui::TextEdit::singleline(&mut self.workspace_input) - .desired_width(360.0), + .desired_width(300.0), ); + if ui.button("📂").on_hover_text("Browse for workspace folder").clicked() { + self.browse_workspace(); + } if ui.button("Open").clicked() || (resp.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter))) { @@ -396,8 +443,11 @@ impl App { ui.horizontal(|ui| { ui.label("Export:"); ui.add( - egui::TextEdit::singleline(&mut self.export_input).desired_width(360.0), + egui::TextEdit::singleline(&mut self.export_input).desired_width(300.0), ); + if ui.button("📂").on_hover_text("Browse for export .odt file").clicked() { + self.browse_export(); + } if ui.button("Export ODT").clicked() { self.export_odt(); }