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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,6 @@ jobs:
-Dsonar.javascript.lcov.reportPaths=app/coverage/lcov.info
-Dsonar.cpd.exclusions=app/src/i18n.ts
-Dsonar.rust.clippy.enabled=false
-Dsonar.coverage.exclusions=app/src/main.tsx,app/src-tauri/src/**
-Dsonar.coverage.exclusions=app/src/main.tsx,app/src-tauri/src/**,crates/linkunbound-shell/src/main.rs,crates/linkunbound-shell/src/hotkey.rs
-Dsonar.qualitygate.wait=true
-Dsonar.qualitygate.timeout=300
43 changes: 27 additions & 16 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ jobs:
tested:
name: Tested when it landed
runs-on: ubuntu-latest
timeout-minutes: 25
timeout-minutes: 45
permissions:
contents: read
actions: read
Expand All @@ -58,7 +58,7 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
for attempt in $(seq 1 40); do
for attempt in $(seq 1 80); do
if ! said=$(gh run list --workflow ci.yml --commit "$GITHUB_SHA" --event push \
--limit 1 --json status,conclusion \
--jq 'if length == 0 then "" else .[0] | "\(.status) \(.conclusion)" end' \
Expand All @@ -77,16 +77,16 @@ jobs:
exit 1
;;
"")
echo "no CI run for ${GITHUB_SHA:0:8} yet (${attempt}/40)"
echo "no CI run for ${GITHUB_SHA:0:8} yet (${attempt}/80)"
sleep 30
;;
*)
echo "CI on ${GITHUB_SHA:0:8} is still running (${attempt}/40)"
echo "CI on ${GITHUB_SHA:0:8} is still running (${attempt}/80)"
sleep 30
;;
esac
done
echo "::error::twenty minutes and CI on ${GITHUB_SHA:0:8} has not finished, or never \
echo "::error::forty minutes and CI on ${GITHUB_SHA:0:8} has not finished, or never \
ran: a tag belongs on a commit that reached main and was tested there"
exit 1

Expand Down Expand Up @@ -209,7 +209,7 @@ jobs:

bundle-windows:
name: linkunbound-windows-installer
needs: version
needs: [version, tested]
runs-on: windows-latest
timeout-minutes: 60
outputs:
Expand Down Expand Up @@ -440,9 +440,18 @@ jobs:
retention-days: 7
if-no-files-found: error

- name: Take the certificate back out of the store
if: always() && env.CERT_THUMBPRINT != ''
shell: pwsh
run: |
$gone = Get-ChildItem Cert:\CurrentUser\My |
Where-Object { $_.Thumbprint -eq $env:CERT_THUMBPRINT }
$gone | Remove-Item -Force -ErrorAction SilentlyContinue
Write-Host "removed $($gone.Count) certificate(s) from the runner store"

bundle-macos:
name: linkunbound-macos-installer (${{ matrix.arch }})
needs: version
needs: [version, tested]
if: needs.version.outputs.macos == 'true'
runs-on: macos-latest
timeout-minutes: 60
Expand Down Expand Up @@ -1228,6 +1237,7 @@ jobs:
if [ "$code" -ne 0 ] && grep -q "CommitStarted" publish.log; then
echo "::warning::the Store took the submission and is processing it; the client lost \
the poll it was holding. Nothing here has to be sent again."
echo "tolerated=yes" >>"$GITHUB_OUTPUT"
exit 0
fi
if [ "$code" -eq 0 ]; then
Expand All @@ -1236,13 +1246,15 @@ jobs:
exit "$code"

- name: What the Store made of it
if: always() && steps.gate.outputs.go == 'yes' && steps.sent.outcome == 'failure'
if: >-
always() && steps.gate.outputs.go == 'yes'
&& (steps.sent.outcome == 'failure' || steps.sent.outputs.tolerated == 'yes')
shell: bash
run: |
echo "::warning::publishing failed before the Store took the submission; asking what state it is in"
echo "::warning::asking the Store what state the submission is in"
msstore submission status "$STORE_APP_ID" || true
echo "::error::the package reached the Store but no commit was confirmed. Read the status \
above before tagging again: a submission already committed must not be sent twice."
echo "::warning::the package reached the Store and no commit was confirmed here. Read the \
status above before tagging again: a submission already committed must not be sent twice."

homebrew:
name: Homebrew tap
Expand Down Expand Up @@ -1295,9 +1307,8 @@ jobs:
ARM_SHA=$(sha256sum "/tmp/$DMG_ARM" | awk '{print $1}')
INTEL_SHA=$(sha256sum "/tmp/$DMG_INTEL" | awk '{print $1}')

git clone https://github.com/rgdevment/homebrew-tap.git /tmp/tap
git -C /tmp/tap config --local http.extraheader \
"AUTHORIZATION: basic $(printf 'x-access-token:%s' "$GIST_TOKEN" | base64 -w0)"
AUTH="AUTHORIZATION: basic $(printf 'x-access-token:%s' "$GIST_TOKEN" | base64 -w0)"
git -c http.extraheader="$AUTH" clone https://github.com/rgdevment/homebrew-tap.git /tmp/tap
cd /tmp/tap
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
Expand Down Expand Up @@ -1368,11 +1379,11 @@ jobs:
git commit -m "linkunbound ${VERSION}"

for attempt in 1 2 3 4 5; do
if git push origin HEAD:main; then
if git -c http.extraheader="$AUTH" push origin HEAD:main; then
exit 0
fi
echo "push rejected (attempt ${attempt}/5); rebasing onto the concurrent release"
git fetch origin main
git -c http.extraheader="$AUTH" fetch origin main
if ! git rebase origin/main; then
git rebase --abort || true
echo "::error::the tap moved under this commit and it will not rebase cleanly"
Expand Down
18 changes: 1 addition & 17 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ slint = { version = "1.17", default-features = false, features = [
] }
slint-build = "1.17"
tray-icon = "0.24"
global-hotkey = "0.8"
muda = "0.19"
interprocess = "2.4"
# No tray feature: the resident owns the tray, through the `tray-icon` crate.
Expand Down
1 change: 0 additions & 1 deletion app/package-lock.json

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

1 change: 0 additions & 1 deletion app/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{
"name": "linkunbound",
"private": true,
"version": "2.0.0",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
1 change: 0 additions & 1 deletion app/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ tauri.workspace = true

tauri-plugin-single-instance.workspace = true

tauri-plugin-global-shortcut.workspace = true
tauri-plugin-opener.workspace = true
tauri-plugin-updater.workspace = true
reqwest.workspace = true
Expand Down
11 changes: 11 additions & 0 deletions app/src-tauri/nsis/hooks.nsh
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,17 @@ FunctionEnd
DeleteRegKey HKCU "Software\Clients\StartMenuInternet\LinkUnbound"
DeleteRegKey HKCU "Software\LinkUnbound"
DeleteRegValue HKCU "Software\RegisteredApplications" "LinkUnbound"

DeleteRegValue HKCU "Software\Classes\.htm\OpenWithProgIds" "LinkUnboundURL"
DeleteRegValue HKCU "Software\Classes\.html\OpenWithProgIds" "LinkUnboundURL"
DeleteRegValue HKCU "Software\Classes\.xhtml\OpenWithProgIds" "LinkUnboundURL"
DeleteRegValue HKCU "Software\Classes\.xht\OpenWithProgIds" "LinkUnboundURL"
DeleteRegValue HKCU "Software\Classes\.pdf\OpenWithProgIds" "LinkUnboundURL"
DeleteRegValue HKCU "Software\Classes\.svg\OpenWithProgIds" "LinkUnboundURL"
DeleteRegValue HKCU "Software\Classes\.mhtml\OpenWithProgIds" "LinkUnboundURL"
DeleteRegValue HKCU "Software\Classes\.mht\OpenWithProgIds" "LinkUnboundURL"
DeleteRegValue HKCU "Software\Classes\.shtml\OpenWithProgIds" "LinkUnboundURL"
DeleteRegValue HKCU "Software\Classes\.webp\OpenWithProgIds" "LinkUnboundURL"
${EndIf}

; The sign-in entry names the resident being removed; left behind, it runs nothing every
Expand Down
72 changes: 32 additions & 40 deletions app/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,15 @@
mod shell;
mod shop;
mod shortcut;
mod system;
mod update;

use std::sync::Mutex;

use linkunbound_core::{
Asking, Browser, Language, Preferences, Rule, Scope, Store, Strings, Target, host_of, merge,
normalise, site_of,
};
use serde::Serialize;
use tauri::{AppHandle, Emitter, Manager};

/// The combination actually claimed, which the settings screen needs and only
/// the registration knows.
#[derive(Default)]
struct Held {
shortcut: Option<String>,
}

#[cfg(any(windows, target_os = "macos"))]
fn icons_dir() -> std::path::PathBuf {
linkunbound_core::data_dir().join("icons")
Expand Down Expand Up @@ -554,7 +544,6 @@
.map_err(|e| e.to_string())
}

/// The shortcut the app really holds, which is not always the one asked for.
#[derive(Serialize)]
struct Settings {
prefs: Preferences,
Expand All @@ -563,29 +552,17 @@
language: &'static str,
}

fn claim(app: &AppHandle, prefs: &Preferences) -> Settings {
let shortcut_held = shortcut::install(app, prefs.shortcut.as_deref());
if let Ok(mut held) = app.state::<Mutex<Held>>().lock() {
held.shortcut.clone_from(&shortcut_held);
}
fn claim(prefs: &Preferences) -> Settings {
Settings {
language: spoken(prefs),
prefs: prefs.clone(),
shortcut_held,
shortcut_held: store().shortcut_held(),
}
}

/// Reads without touching the registration: the picker asks for this too, and
/// re-claiming the combination on every link would be gratuitous.
#[tauri::command]
fn prefs_get(state: tauri::State<'_, Mutex<Held>>) -> Settings {
let prefs = store().prefs();
let shortcut_held = state.lock().ok().and_then(|h| h.shortcut.clone());
Settings {
language: spoken(&prefs),
prefs,
shortcut_held,
}
fn prefs_get() -> Settings {
claim(&store().prefs())
}

fn spoken(prefs: &Preferences) -> &'static str {
Expand All @@ -607,7 +584,7 @@
})
.map_err(|e| e.to_string())?;
shell::repaint(&app, settled.theme);
Ok(claim(&app, &settled))
Ok(claim(&settled))
}

#[tauri::command]
Expand Down Expand Up @@ -648,57 +625,62 @@

#[tauri::command]
fn maintenance_report() -> Result<String, String> {
let state = system::state();
let facts = vec![
("versión".to_owned(), env!("CARGO_PKG_VERSION").to_owned()),
("sistema".to_owned(), std::env::consts::OS.to_owned()),
("registrado".to_owned(), state.registered.to_string()),
("predeterminado".to_owned(), state.is_default.to_string()),
("diagnóstico".to_owned(), format!("{:?}", state.health)),
(
"asociaciones".to_owned(),
format!(
"{} de {}",
state.associations.iter().filter(|a| a.held).count(),
state.associations.len()
),
),
(
"arranca con el sistema".to_owned(),
state.starts_with_system.to_string(),
),
(
"Edge instalado".to_owned(),
state.edge_installed.to_string(),
),
(
"navegadores".to_owned(),
catalogue()
.iter()
.map(|b| b.name.clone())
.collect::<Vec<_>>()
.join(", "),
),
];
let store = store();
let prefs = store.prefs();
let words = Language::chosen(prefs.locale).strings();
let body = linkunbound_core::diagnostics(
env!("CARGO_PKG_VERSION"),
&facts,
&store.rules().unwrap_or_default(),
&store.prefs(),
&prefs,
&words,
);

let named = format!("{}.md", words.report_file);
let target = std::env::var_os("USERPROFILE")
.or_else(|| std::env::var_os("HOME"))
.map_or_else(std::env::temp_dir, std::path::PathBuf::from)
.join("Desktop")
.join("linkunbound-diagnostico.md");
.join(&named);
let target = if target.parent().is_some_and(std::path::Path::is_dir) {
target
} else {
std::env::temp_dir().join("linkunbound-diagnostico.md")
std::env::temp_dir().join(&named)
};
std::fs::write(&target, body).map_err(|e| e.to_string())?;
Ok(target.to_string_lossy().into_owned())

Check warning on line 683 in app/src-tauri/src/lib.rs

View workflow job for this annotation

GitHub Actions / settings / the lines this branch changed

Missed mutant

replace maintenance_report -> Result<String, String> with Ok("xyzzy".into())

Check warning on line 683 in app/src-tauri/src/lib.rs

View workflow job for this annotation

GitHub Actions / settings / the lines this branch changed

Missed mutant

replace maintenance_report -> Result<String, String> with Ok(String::new())
}

#[tauri::command]
Expand All @@ -706,6 +688,21 @@
system::state()
}

#[tauri::command]
fn system_legacy() -> Option<system::Legacy> {
system::legacy()

Check warning on line 693 in app/src-tauri/src/lib.rs

View workflow job for this annotation

GitHub Actions / settings / the lines this branch changed

Missed mutant

replace system_legacy -> Option<system::Legacy> with None
}

#[tauri::command(async)]
fn system_retire_legacy() -> Result<(), String> {
system::retire_legacy()

Check warning on line 698 in app/src-tauri/src/lib.rs

View workflow job for this annotation

GitHub Actions / settings / the lines this branch changed

Missed mutant

replace system_retire_legacy -> Result<(), String> with Ok(())
}

#[tauri::command]
fn system_reveal_legacy() {
system::reveal_legacy();

Check warning on line 703 in app/src-tauri/src/lib.rs

View workflow job for this annotation

GitHub Actions / settings / the lines this branch changed

Missed mutant

replace system_reveal_legacy with ()
}

#[tauri::command(async)]
fn system_set_registered(enabled: bool) -> Result<system::SystemState, String> {
system::set_registered(enabled)
Expand Down Expand Up @@ -1261,7 +1258,6 @@
let errand = errand_in(&args);

let builder = tauri::Builder::default()
.manage(Mutex::new(Held::default()))
.manage(Updating::default())
.invoke_handler(tauri::generate_handler![
rules_list,
Expand All @@ -1281,6 +1277,9 @@
prefs_get,
prefs_set,
system_state,
system_legacy,
system_retire_legacy,
system_reveal_legacy,
system_set_registered,
system_set_startup,
system_open_default_apps,
Expand All @@ -1296,12 +1295,8 @@
star_done
])
.plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_updater::Builder::new().build())
.plugin(tauri_plugin_global_shortcut::Builder::new().build());
.plugin(tauri_plugin_updater::Builder::new().build());

// Two tray clicks used to mean two processes, each writing the registry and each claiming
// the shortcut. The second now raises the first. An errand is not a second copy of the
// window, though: it must neither knock on the one that is open nor answer knocks itself.
let builder = if errand.is_some() {
builder
} else {
Expand Down Expand Up @@ -1333,9 +1328,6 @@
}

system::reconcile();
// The resident owns the tray and the shortcut; this binary is only
// the settings window, opened and closed on demand.
claim(app.handle(), &store().prefs());
#[cfg(target_os = "macos")]
relay_links(app.handle().clone());
shell::open_settings(app.handle(), store().prefs().theme);
Expand Down
Loading
Loading