diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c2e2baa..4eb8d51 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -48,6 +48,10 @@ jobs: binary=$(find dist -maxdepth 1 -type f -name '*.bin' -print -quit) test -n "${binary}" test -x "${binary}" + version=$(sed -n '/^\[workspace\.package\]$/,/^\[/ { s/^version = "\([^"]*\)"/\1/p; }' Cargo.toml) + reported_source=$("${binary}" --source | head -n 1) + test "${reported_source}" = \ + "OneNote Viewer ${version} source revision: ${GITHUB_SHA}" G_DEBUG=fatal-warnings \ GTK_A11Y=none \ GSK_RENDERER=cairo \ @@ -73,8 +77,13 @@ jobs: run: | version=$(sed -n '/^\[workspace\.package\]$/,/^\[/ { s/^version = "\([^"]*\)"/\1/p; }' Cargo.toml) test -n "${version}" + mkdir -p target/flatpak + ONENOTE_VIEWER_SOURCE_REVISION="${GITHUB_SHA}" \ + ./scripts/source-revision.sh > target/flatpak/source-revision + revision=$(cat target/flatpak/source-revision) echo "version=${version}" >> "${GITHUB_OUTPUT}" echo "bundle=OneNoteViewer-${version}-linux-x86_64.flatpak" >> "${GITHUB_OUTPUT}" + echo "revision=${revision}" >> "${GITHUB_OUTPUT}" - name: Build Flatpak bundle uses: flatpak/flatpak-github-actions/flatpak-builder@401fe28a8384095fc1531b9d320b292f0ee45adb @@ -88,6 +97,7 @@ jobs: - name: Verify bundled application and extractor env: BUNDLE: ${{ steps.release.outputs.bundle }} + REVISION: ${{ steps.release.outputs.revision }} VERSION: ${{ steps.release.outputs.version }} run: | flatpak install --noninteractive "./${BUNDLE}" @@ -97,6 +107,10 @@ jobs: reported_version=$(flatpak run io.github.emsi.OneNoteViewer --version | head -n 1) test "${reported_version}" = "OneNote Viewer ${VERSION}" + reported_source=$(flatpak run io.github.emsi.OneNoteViewer --source | + head -n 1) + test "${reported_source}" = \ + "OneNote Viewer ${VERSION} source revision: ${REVISION}" flatpak run --command=7zz io.github.emsi.OneNoteViewer i | grep 'Cab' >/dev/null xvfb-run -a \ @@ -154,6 +168,11 @@ jobs: test -n "${appimage}" test -x "${appimage}" APPIMAGE_EXTRACT_AND_RUN=1 "${appimage}" --version + reported_source=$(APPIMAGE_EXTRACT_AND_RUN=1 "${appimage}" --source | + head -n 1) + version=$(sed -n '/^\[workspace\.package\]$/,/^\[/ { s/^version = "\([^"]*\)"/\1/p; }' Cargo.toml) + test "${reported_source}" = \ + "OneNote Viewer ${version} source revision: ${GITHUB_SHA}" APPIMAGE_EXTRACT_AND_RUN=1 \ G_DEBUG=fatal-warnings \ GTK_A11Y=none \ diff --git a/crates/onenote-viewer/src/app.rs b/crates/onenote-viewer/src/app.rs index ef672fa..0b2ec7f 100644 --- a/crates/onenote-viewer/src/app.rs +++ b/crates/onenote-viewer/src/app.rs @@ -339,6 +339,7 @@ impl Viewer { let open_folder = gio::SimpleAction::new("open-folder", None); let import_package = gio::SimpleAction::new("import-package", None); let open_settings = gio::SimpleAction::new("settings", None); + let show_about = gio::SimpleAction::new("about", None); let quit = gio::SimpleAction::new("quit", None); let close_source = icon_button("onenote-close-symbolic", "Close selected notebook"); let spinner = gtk::Spinner::new(); @@ -362,6 +363,9 @@ impl Viewer { let preferences_menu = gio::Menu::new(); preferences_menu.append(Some("Settings"), Some("win.settings")); application_menu.append_section(None, &preferences_menu); + let information_menu = gio::Menu::new(); + information_menu.append(Some("About OneNote Viewer"), Some("win.about")); + application_menu.append_section(None, &information_menu); let quit_menu = gio::Menu::new(); quit_menu.append(Some("Quit OneNote Viewer"), Some("win.quit")); application_menu.append_section(None, &quit_menu); @@ -606,6 +610,7 @@ impl Viewer { viewer.window.add_action(&open_folder); viewer.window.add_action(&import_package); viewer.window.add_action(&open_settings); + viewer.window.add_action(&show_about); viewer.window.add_action(&quit); application.set_accels_for_action("win.open-file", &["o"]); application.set_accels_for_action("win.open-folder", &["o"]); @@ -620,6 +625,7 @@ impl Viewer { &quit, &close_source, ); + viewer.connect_about(&show_about); viewer.connect_system_theme(); viewer.connect_import_activity(); viewer.connect_zoom(&zoom_out, &zoom_in, &zoom_reset); @@ -727,6 +733,15 @@ impl Viewer { }); } + fn connect_about(self: &Rc, show_about: &gio::SimpleAction) { + let weak = Rc::downgrade(self); + show_about.connect_activate(move |_, _| { + if let Some(viewer) = weak.upgrade() { + crate::dialogs::present_about(&viewer.window); + } + }); + } + fn connect_system_theme(self: &Rc) { let Some(settings) = gtk::Settings::default() else { return; diff --git a/crates/onenote-viewer/src/dialogs.rs b/crates/onenote-viewer/src/dialogs.rs index 90da3f2..618bb70 100644 --- a/crates/onenote-viewer/src/dialogs.rs +++ b/crates/onenote-viewer/src/dialogs.rs @@ -7,6 +7,30 @@ use std::rc::Rc; type ErrorHandler = Rc; +pub(crate) fn present_about(parent_window: >k::ApplicationWindow) { + let system_information = format!( + "Version: {}\nSource revision: {}\nApplication ID: io.github.emsi.OneNoteViewer", + env!("CARGO_PKG_VERSION"), + crate::SOURCE_REVISION, + ); + let dialog = gtk::AboutDialog::builder() + .transient_for(parent_window) + .modal(true) + .program_name("OneNote Viewer") + .version(env!("CARGO_PKG_VERSION")) + .comments("A native Linux viewer for local Microsoft OneNote notebooks.") + .copyright("Copyright (c) 2026 Mariusz Woloszyn") + .authors(["Mariusz Woloszyn"]) + .license_type(gtk::License::Gpl30) + .logo_icon_name("io.github.emsi.OneNoteViewer") + .website("https://github.com/emsi/OneNoteViewer") + .website_label("Project website") + .system_information(system_information) + .build(); + dialog.add_css_class("settings-dialog"); + dialog.present(); +} + #[allow(clippy::too_many_lines)] pub(crate) fn present_package_import( parent_window: >k::ApplicationWindow, diff --git a/packaging/flatpak/io.github.emsi.OneNoteViewer.yml b/packaging/flatpak/io.github.emsi.OneNoteViewer.yml index dff6bd2..a15558c 100644 --- a/packaging/flatpak/io.github.emsi.OneNoteViewer.yml +++ b/packaging/flatpak/io.github.emsi.OneNoteViewer.yml @@ -40,7 +40,9 @@ modules: CARGO_NET_OFFLINE: 'true' build-commands: - install -Dm0644 cargo/config .cargo/config.toml - - cargo build --offline --frozen --release -p onenote-viewer + - >- + ONENOTE_VIEWER_SOURCE_REVISION="$(cat onenote-viewer-source-revision)" + cargo build --offline --frozen --release -p onenote-viewer - install -Dm0755 target/release/onenote-viewer /app/bin/onenote-viewer - install -Dm0644 packaging/flatpak/io.github.emsi.OneNoteViewer.desktop /app/share/applications/io.github.emsi.OneNoteViewer.desktop - install -Dm0644 packaging/flatpak/io.github.emsi.OneNoteViewer.metainfo.xml /app/share/metainfo/io.github.emsi.OneNoteViewer.metainfo.xml @@ -62,4 +64,7 @@ modules: - onepkg - target - test-corpus + - type: file + path: ../../target/flatpak/source-revision + dest-filename: onenote-viewer-source-revision - cargo-sources.json diff --git a/scripts/build-appimage-release.sh b/scripts/build-appimage-release.sh index 04bf3b5..9e34657 100755 --- a/scripts/build-appimage-release.sh +++ b/scripts/build-appimage-release.sh @@ -1,7 +1,7 @@ #!/bin/sh set -eu -root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +root=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd) cd "$root" for command in cargo curl file glib-compile-schemas install pkg-config sha256sum; do @@ -30,6 +30,7 @@ if [ -z "$version" ]; then printf '%s\n' 'could not determine workspace version' >&2 exit 1 fi +revision=$(./scripts/source-revision.sh) linuxdeploy_url=https://github.com/linuxdeploy/linuxdeploy/releases/download/1-alpha-20251107-1/linuxdeploy-x86_64.AppImage linuxdeploy_sha256=c20cd71e3a4e3b80c3483cef793cda3f4e990aca14014d23c544ca3ce1270b4d @@ -73,7 +74,8 @@ download_verified "$sevenzip_source_url" "$sevenzip_source" "$sevenzip_source_sh chmod +x "$linuxdeploy" "$appimagetool" cargo fetch --locked -cargo build --offline --frozen --locked --release -p onenote-viewer +ONENOTE_VIEWER_SOURCE_REVISION="$revision" \ + cargo build --offline --frozen --locked --release -p onenote-viewer rm -rf "$appdir" mkdir -p \ diff --git a/scripts/build-flatpak-release.sh b/scripts/build-flatpak-release.sh index 76a4b20..796c9fc 100755 --- a/scripts/build-flatpak-release.sh +++ b/scripts/build-flatpak-release.sh @@ -1,7 +1,7 @@ #!/bin/sh set -eu -root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +root=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd) cd "$root" for command in flatpak flatpak-builder; do @@ -41,6 +41,10 @@ manifest=packaging/flatpak/io.github.emsi.OneNoteViewer.yml build_dir=target/flatpak/build repo_dir=target/flatpak/repo bundle="dist/OneNoteViewer-${version}-linux-${arch}.flatpak" +revision_file=target/flatpak/source-revision + +mkdir -p "$(dirname "$revision_file")" dist +./scripts/source-revision.sh >"$revision_file" flatpak remote-add \ --user \ @@ -56,7 +60,6 @@ flatpak-builder \ --repo="$repo_dir" \ "$build_dir" \ "$manifest" -mkdir -p dist flatpak build-bundle \ "$repo_dir" \ "$bundle" \ diff --git a/scripts/package-native-release.sh b/scripts/package-native-release.sh index 44d68ab..9310cd1 100755 --- a/scripts/package-native-release.sh +++ b/scripts/package-native-release.sh @@ -1,7 +1,7 @@ #!/bin/sh set -eu -root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +root=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd) cd "$root" case "$(uname -m)" in @@ -28,10 +28,11 @@ if ! git diff --quiet || ! git diff --cached --quiet; then 'native releases require a committed source tree so the source archive matches the binary' >&2 exit 1 fi -revision=$(git rev-parse --verify HEAD) +revision=$(./scripts/source-revision.sh) ./scripts/check-licenses.sh -cargo build --locked --release -p onenote-viewer +ONENOTE_VIEWER_SOURCE_REVISION="$revision" \ + cargo build --locked --release -p onenote-viewer archive="OneNoteViewer-${version}-linux-${arch}.tar.gz" executable="OneNoteViewer-${version}-linux-${arch}.bin" diff --git a/scripts/source-revision.sh b/scripts/source-revision.sh new file mode 100755 index 0000000..961fadd --- /dev/null +++ b/scripts/source-revision.sh @@ -0,0 +1,32 @@ +#!/bin/sh +set -eu + +root=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd) +revision=${ONENOTE_VIEWER_SOURCE_REVISION:-} + +if [ -z "$revision" ]; then + revision=$(git -C "$root" rev-parse --verify HEAD) + if ! git -C "$root" diff --quiet || ! git -C "$root" diff --cached --quiet; then + revision="${revision}-dirty" + fi +fi + +case "$revision" in + *-dirty) object_id=${revision%-dirty} ;; + *) object_id=$revision ;; +esac +case "${#object_id}" in + 40|64) ;; + *) + printf 'invalid source revision: %s\n' "$revision" >&2 + exit 1 + ;; +esac +case "$object_id" in + ''|*[!0-9a-f]*) + printf 'invalid source revision: %s\n' "$revision" >&2 + exit 1 + ;; +esac + +printf '%s\n' "$revision"