diff --git a/CHANGELOG.md b/CHANGELOG.md
index 520f630..da9476c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,8 +12,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
-Nothing is released yet. See
-[ROADMAP.md](https://github.com/onyks-os/USBGuardGUI/blob/main/ROADMAP.md) for the phase order.
+## [0.1.0] - 2026-09-27
+
+The first release.
### Added
@@ -64,12 +65,5 @@ Nothing is released yet. See
- `docs/architecture.md`: `listRules` takes a label filter, not a query — `""` lists every rule,
`"match"` would have listed none. Found by introspection before any code depended on it.
-### Deprecated
-
-### Removed
-
-### Fixed
-
-### Security
-
-[Unreleased]: https://github.com/onyks-os/USBGuardGUI/commits/main
+[Unreleased]: https://github.com/onyks-os/USBGuardGUI/compare/v0.1.0...HEAD
+[0.1.0]: https://github.com/onyks-os/USBGuardGUI/releases/tag/v0.1.0
diff --git a/README.md b/README.md
index 19bc690..ac7da93 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@ SPDX-License-Identifier: MIT
-
+
@@ -38,6 +38,10 @@ SPDX-License-Identifier: MIT
Plug in a device, see it appear as **blocked**, allow it for this session with one click — and when
something does not work, be told *exactly* which layer refused and what to run to fix it.
+
+
+
+
> [!NOTE]
> USBGuardGUI is a front end. The protection comes from the **USBGuard daemon**, which decides what
> the kernel lets through; this program only asks it, with your authorization, to change its mind.
diff --git a/data/io.github.onyks_os.UsbguardGui.metainfo.xml b/data/io.github.onyks_os.UsbguardGui.metainfo.xml
index 79a1f79..94a3444 100644
--- a/data/io.github.onyks_os.UsbguardGui.metainfo.xml
+++ b/data/io.github.onyks_os.UsbguardGui.metainfo.xml
@@ -69,13 +69,40 @@
keyboard
touch
+
+
+ Every connected device and whether it is authorized
+ Ogni dispositivo collegato e se è autorizzato
+ https://raw.githubusercontent.com/onyks-os/USBGuardGUI/v0.1.0/data/screenshots/01-devices.png
+
+
+ Allow, block, or reject a device, for this session or permanently
+ Consenti, blocca o rifiuta un dispositivo, per questa sessione o permanentemente
+ https://raw.githubusercontent.com/onyks-os/USBGuardGUI/v0.1.0/data/screenshots/02-device-action.png
+
+
+ The policy rules in the order USBGuard evaluates them
+ Le regole della policy nell’ordine in cui USBGuard le valuta
+ https://raw.githubusercontent.com/onyks-os/USBGuardGUI/v0.1.0/data/screenshots/03-policy.png
+
+
+ A guided editor for new rules
+ Un editor guidato per le nuove regole
+ https://raw.githubusercontent.com/onyks-os/USBGuardGUI/v0.1.0/data/screenshots/04-new-rule.png
+
+
+ Diagnostics that name the exact cause of an access problem
+ Una diagnostica che indica la causa esatta di un problema di accesso
+ https://raw.githubusercontent.com/onyks-os/USBGuardGUI/v0.1.0/data/screenshots/05-diagnostics.png
+
+
#8ff0a4
#26a269
-
+
First release.
Prima versione.
diff --git a/data/io.github.onyks_os.UsbguardGui.metainfo.xml.in b/data/io.github.onyks_os.UsbguardGui.metainfo.xml.in
index ad31e1c..a34be60 100644
--- a/data/io.github.onyks_os.UsbguardGui.metainfo.xml.in
+++ b/data/io.github.onyks_os.UsbguardGui.metainfo.xml.in
@@ -62,6 +62,29 @@
touch
+
+
+ Every connected device and whether it is authorized
+ https://raw.githubusercontent.com/onyks-os/USBGuardGUI/v0.1.0/data/screenshots/01-devices.png
+
+
+ Allow, block, or reject a device, for this session or permanently
+ https://raw.githubusercontent.com/onyks-os/USBGuardGUI/v0.1.0/data/screenshots/02-device-action.png
+
+
+ The policy rules in the order USBGuard evaluates them
+ https://raw.githubusercontent.com/onyks-os/USBGuardGUI/v0.1.0/data/screenshots/03-policy.png
+
+
+ A guided editor for new rules
+ https://raw.githubusercontent.com/onyks-os/USBGuardGUI/v0.1.0/data/screenshots/04-new-rule.png
+
+
+ Diagnostics that name the exact cause of an access problem
+ https://raw.githubusercontent.com/onyks-os/USBGuardGUI/v0.1.0/data/screenshots/05-diagnostics.png
+
+
+
#8ff0a4
#26a269
@@ -70,7 +93,7 @@
-
+
First release.
diff --git a/data/screenshots/01-devices.png b/data/screenshots/01-devices.png
new file mode 100644
index 0000000..394b5c4
Binary files /dev/null and b/data/screenshots/01-devices.png differ
diff --git a/data/screenshots/02-device-action.png b/data/screenshots/02-device-action.png
new file mode 100644
index 0000000..f3e3d47
Binary files /dev/null and b/data/screenshots/02-device-action.png differ
diff --git a/data/screenshots/03-policy.png b/data/screenshots/03-policy.png
new file mode 100644
index 0000000..6ecfb92
Binary files /dev/null and b/data/screenshots/03-policy.png differ
diff --git a/data/screenshots/04-new-rule.png b/data/screenshots/04-new-rule.png
new file mode 100644
index 0000000..1d7098c
Binary files /dev/null and b/data/screenshots/04-new-rule.png differ
diff --git a/data/screenshots/05-diagnostics.png b/data/screenshots/05-diagnostics.png
new file mode 100644
index 0000000..6fde81d
Binary files /dev/null and b/data/screenshots/05-diagnostics.png differ
diff --git a/po/it.po b/po/it.po
index 1ab246b..f2f214d 100644
--- a/po/it.po
+++ b/po/it.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: USBGuard GUI\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2026-09-26 13:04+0200\n"
+"POT-Creation-Date: 2026-09-27 22:21+0200\n"
"PO-Revision-Date: 2026-09-25 19:39+0200\n"
"Last-Translator: onyks-os\n"
"Language-Team: Italian\n"
@@ -164,21 +164,17 @@ msgstr "Permanentemente"
msgid "Written to the policy; survives a restart"
msgstr "Scritto nelle regole; resta dopo un riavvio"
-#: src/ui/diagnostic_dialog.rs:18
-msgid "USBGuard access"
-msgstr "Accesso a USBGuard"
+#: src/ui/diagnostic_dialog.rs:21
+msgid "Copy commands"
+msgstr "Copia i comandi"
-#: src/ui/diagnostic_dialog.rs:19 src/ui/window.rs:44
+#: src/ui/diagnostic_dialog.rs:29 src/ui/window.rs:44
msgid "Checking…"
msgstr "Verifica in corso…"
-#: src/ui/diagnostic_dialog.rs:21
-msgid "Close"
-msgstr "Chiudi"
-
-#: src/ui/diagnostic_dialog.rs:49
-msgid "Copy commands"
-msgstr "Copia i comandi"
+#: src/ui/diagnostic_dialog.rs:76
+msgid "USBGuard access"
+msgstr "Accesso a USBGuard"
#: src/ui/i18n.rs:60 src/ui/window.rs:45
msgid "Connected"
@@ -975,11 +971,31 @@ msgstr "dispositivo"
msgid "security"
msgstr "sicurezza"
+#: data/io.github.onyks_os.UsbguardGui.metainfo.xml.in:67
+msgid "Every connected device and whether it is authorized"
+msgstr "Ogni dispositivo collegato e se è autorizzato"
+
+#: data/io.github.onyks_os.UsbguardGui.metainfo.xml.in:71
+msgid "Allow, block, or reject a device, for this session or permanently"
+msgstr "Consenti, blocca o rifiuta un dispositivo, per questa sessione o permanentemente"
+
#: data/io.github.onyks_os.UsbguardGui.metainfo.xml.in:75
+msgid "The policy rules in the order USBGuard evaluates them"
+msgstr "Le regole della policy nell’ordine in cui USBGuard le valuta"
+
+#: data/io.github.onyks_os.UsbguardGui.metainfo.xml.in:79
+msgid "A guided editor for new rules"
+msgstr "Un editor guidato per le nuove regole"
+
+#: data/io.github.onyks_os.UsbguardGui.metainfo.xml.in:83
+msgid "Diagnostics that name the exact cause of an access problem"
+msgstr "Una diagnostica che indica la causa esatta di un problema di accesso"
+
+#: data/io.github.onyks_os.UsbguardGui.metainfo.xml.in:98
msgid "First release."
msgstr "Prima versione."
-#: data/io.github.onyks_os.UsbguardGui.metainfo.xml.in:77
+#: data/io.github.onyks_os.UsbguardGui.metainfo.xml.in:100
msgid ""
"Live device list; allow, block, or reject a device for this session or "
"permanently"
@@ -987,7 +1003,7 @@ msgstr ""
"Elenco dei dispositivi in tempo reale; consenti, blocca o rifiuta un "
"dispositivo per questa sessione o permanentemente"
-#: data/io.github.onyks_os.UsbguardGui.metainfo.xml.in:78
+#: data/io.github.onyks_os.UsbguardGui.metainfo.xml.in:101
msgid ""
"Policy view in evaluation order, with a guided rule editor and safe rule "
"removal"
@@ -995,14 +1011,17 @@ msgstr ""
"Vista delle regole nell’ordine di valutazione, con un editor guidato e una "
"rimozione sicura"
-#: data/io.github.onyks_os.UsbguardGui.metainfo.xml.in:79
+#: data/io.github.onyks_os.UsbguardGui.metainfo.xml.in:102
msgid "Notifications for newly inserted devices that are not authorized"
msgstr "Notifiche per i dispositivi appena collegati e non autorizzati"
-#: data/io.github.onyks_os.UsbguardGui.metainfo.xml.in:80
+#: data/io.github.onyks_os.UsbguardGui.metainfo.xml.in:103
msgid ""
"Diagnostics that name the exact cause of an access problem, and usbguard-gui "
"--diagnose"
msgstr ""
"Una diagnostica che indica la causa esatta di un problema di accesso, e "
"usbguard-gui --diagnose"
+
+#~ msgid "Close"
+#~ msgstr "Chiudi"
diff --git a/po/usbguard-gui.pot b/po/usbguard-gui.pot
index a10be3b..b86c3be 100644
--- a/po/usbguard-gui.pot
+++ b/po/usbguard-gui.pot
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2026-09-26 13:04+0200\n"
+"POT-Creation-Date: 2026-09-27 22:21+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -162,20 +162,16 @@ msgstr ""
msgid "Written to the policy; survives a restart"
msgstr ""
-#: src/ui/diagnostic_dialog.rs:18
-msgid "USBGuard access"
+#: src/ui/diagnostic_dialog.rs:21
+msgid "Copy commands"
msgstr ""
-#: src/ui/diagnostic_dialog.rs:19 src/ui/window.rs:44
+#: src/ui/diagnostic_dialog.rs:29 src/ui/window.rs:44
msgid "Checking…"
msgstr ""
-#: src/ui/diagnostic_dialog.rs:21
-msgid "Close"
-msgstr ""
-
-#: src/ui/diagnostic_dialog.rs:49
-msgid "Copy commands"
+#: src/ui/diagnostic_dialog.rs:76
+msgid "USBGuard access"
msgstr ""
#: src/ui/i18n.rs:60 src/ui/window.rs:45
@@ -914,27 +910,47 @@ msgstr ""
msgid "security"
msgstr ""
+#: data/io.github.onyks_os.UsbguardGui.metainfo.xml.in:67
+msgid "Every connected device and whether it is authorized"
+msgstr ""
+
+#: data/io.github.onyks_os.UsbguardGui.metainfo.xml.in:71
+msgid "Allow, block, or reject a device, for this session or permanently"
+msgstr ""
+
#: data/io.github.onyks_os.UsbguardGui.metainfo.xml.in:75
+msgid "The policy rules in the order USBGuard evaluates them"
+msgstr ""
+
+#: data/io.github.onyks_os.UsbguardGui.metainfo.xml.in:79
+msgid "A guided editor for new rules"
+msgstr ""
+
+#: data/io.github.onyks_os.UsbguardGui.metainfo.xml.in:83
+msgid "Diagnostics that name the exact cause of an access problem"
+msgstr ""
+
+#: data/io.github.onyks_os.UsbguardGui.metainfo.xml.in:98
msgid "First release."
msgstr ""
-#: data/io.github.onyks_os.UsbguardGui.metainfo.xml.in:77
+#: data/io.github.onyks_os.UsbguardGui.metainfo.xml.in:100
msgid ""
"Live device list; allow, block, or reject a device for this session or "
"permanently"
msgstr ""
-#: data/io.github.onyks_os.UsbguardGui.metainfo.xml.in:78
+#: data/io.github.onyks_os.UsbguardGui.metainfo.xml.in:101
msgid ""
"Policy view in evaluation order, with a guided rule editor and safe rule "
"removal"
msgstr ""
-#: data/io.github.onyks_os.UsbguardGui.metainfo.xml.in:79
+#: data/io.github.onyks_os.UsbguardGui.metainfo.xml.in:102
msgid "Notifications for newly inserted devices that are not authorized"
msgstr ""
-#: data/io.github.onyks_os.UsbguardGui.metainfo.xml.in:80
+#: data/io.github.onyks_os.UsbguardGui.metainfo.xml.in:103
msgid ""
"Diagnostics that name the exact cause of an access problem, and usbguard-gui "
"--diagnose"
diff --git a/src/cli.rs b/src/cli.rs
index 7bde246..da61789 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -173,13 +173,9 @@ fn yes_no(value: Option, yes: &str, no: &str) -> String {
#[must_use]
pub fn format_report(report: &ProbeReport, distro: Distro) -> String {
let mut out = String::new();
- let _ = writeln!(
- out,
- "USBGuard GUI {} — access diagnostics\n",
- crate::VERSION
- );
+ let _ = writeln!(out, "USBGuardGUI {} — access diagnostics\n", crate::VERSION);
let line = |out: &mut String, label: &str, value: &str| {
- let _ = writeln!(out, " {label:<28}{value}");
+ let _ = writeln!(out, " {label:<24}{value}");
};
let bus = if report.state == AccessState::BusUnavailable {
"unavailable"
diff --git a/src/ui/diagnostic_dialog.rs b/src/ui/diagnostic_dialog.rs
index e1f7bbf..60c4cd9 100644
--- a/src/ui/diagnostic_dialog.rs
+++ b/src/ui/diagnostic_dialog.rs
@@ -13,21 +13,73 @@ use crate::runtime::runtime;
use gettextrs::gettext;
/// Opens the dialog over `parent`.
+///
+/// A full dialog rather than an alert: the report is the `--diagnose` text,
+/// aligned in columns, and an alert is too narrow to show it unwrapped.
pub(super) fn present(parent: &impl IsA) {
- let dialog = adw::AlertDialog::new(
- Some(&gettext("USBGuard access")),
- Some(&gettext("Checking…")),
- );
- dialog.add_response("close", &gettext("Close"));
- dialog.set_close_response("close");
+ let copy = gtk::Button::builder()
+ .label(gettext("Copy commands"))
+ .css_classes(["suggested-action"])
+ .visible(false)
+ .build();
+ let header = adw::HeaderBar::new();
+ header.pack_start(©);
+ let summary = gtk::Label::builder()
+ .label(gettext("Checking…"))
+ .xalign(0.0)
+ .wrap(true)
+ .css_classes(["title-4"])
+ .build();
let report_label = gtk::Label::builder()
.selectable(true)
.xalign(0.0)
- .wrap(true)
+ .yalign(0.0)
.css_classes(["monospace"])
.build();
- dialog.set_extra_child(Some(&report_label));
+ let report_frame = gtk::Frame::builder()
+ .child(
+ >k::ScrolledWindow::builder()
+ .child(&report_label)
+ .vscrollbar_policy(gtk::PolicyType::Never)
+ .propagate_natural_height(true)
+ .build(),
+ )
+ .build();
+ report_label.set_margin_top(12);
+ report_label.set_margin_bottom(12);
+ report_label.set_margin_start(12);
+ report_label.set_margin_end(12);
+
+ let column = gtk::Box::builder()
+ .orientation(gtk::Orientation::Vertical)
+ .spacing(12)
+ .margin_top(6)
+ .margin_bottom(18)
+ .margin_start(18)
+ .margin_end(18)
+ .build();
+ column.append(&summary);
+ column.append(&report_frame);
+
+ let toolbar = adw::ToolbarView::new();
+ toolbar.add_top_bar(&header);
+ toolbar.set_content(Some(
+ >k::ScrolledWindow::builder()
+ .child(&column)
+ .hscrollbar_policy(gtk::PolicyType::Never)
+ .propagate_natural_height(true)
+ .build(),
+ ));
+
+ let dialog = adw::Dialog::builder()
+ .title(gettext("USBGuard access"))
+ .content_width(800)
+ // Fixed: the report arrives after the dialog is sized, and a dialog
+ // does not grow to fit it. Longer reports (with a remedy) scroll.
+ .content_height(480)
+ .child(&toolbar)
+ .build();
dialog.present(Some(parent));
// The probe runs on Tokio; the result comes back over a one-shot channel
@@ -41,16 +93,18 @@ pub(super) fn present(parent: &impl IsA) {
return;
};
let distro = Distro::detect();
- dialog.set_body(&super::i18n::access_summary(&report.state));
- report_label.set_text(&format_report(&report, distro));
+ summary.set_label(&super::i18n::access_summary(&report.state));
+ report_label.set_text(format_report(&report, distro).trim_end());
+ // The selectable report is otherwise the first focusable widget, and
+ // opens with a text cursor in it.
+ dialog.set_focus(None::<>k::Widget>);
if let Some(remedy) = remedy(&report.state, distro).filter(|r| !r.commands.is_empty()) {
let commands = remedy.commands.join("\n");
- dialog.add_response("copy", &gettext("Copy commands"));
- dialog.set_response_appearance("copy", adw::ResponseAppearance::Suggested);
- dialog.connect_response(Some("copy"), move |d, _| {
- d.clipboard().set_text(&commands);
+ copy.connect_clicked(move |button| {
+ button.clipboard().set_text(&commands);
});
+ copy.set_visible(true);
}
});
}