fix(linux): focus browser on external links

This commit is contained in:
Hakan Seven committed 2026-07-30 21:07:31 +03:00
1 parent dba8ec3d63
commit 9eb47c43a9
5 files changed
+135 -19

No files matched your search

+17 -5
View File
@@ -4,19 +4,28 @@ impl OpenCADStudio {
pub(super) fn dispatch_view(&mut self, cmd: &str, i: usize) -> Option<Task<Message>> {
match cmd {
"DONATE" => {
crate::sys::open_url("https://patreon.com/HakanSeven12");
self.command_line.push_info("Opening Patreon page...");
return Some(crate::sys::open_url(
"https://patreon.com/HakanSeven12",
self.main_window,
));
}
"WEBVERSION" => {
crate::sys::open_url("https://hakanseven12.github.io/OpenCADStudio/");
self.command_line.push_info("Opening OCS Web...");
return Some(crate::sys::open_url(
"https://hakanseven12.github.io/OpenCADStudio/",
self.main_window,
));
}
"HELP" => {
crate::sys::open_url("https://github.com/HakanSeven12/OpenCADStudio/discussions");
self.command_line
.push_info("Opening OCS Discussions for help and questions...");
return Some(crate::sys::open_url(
"https://github.com/HakanSeven12/OpenCADStudio/discussions",
self.main_window,
));
}
// ── DWGPROPS — print round-trip-only HeaderVariables ─────────
@@ -204,8 +213,8 @@ impl OpenCADStudio {
"https://github.com/HakanSeven12/OpenCADStudio/issues/new?body={}",
crate::sys::percent_encode(&body)
);
crate::sys::open_url(&url);
self.command_line.push_info("Opening feedback page...");
return Some(crate::sys::open_url(&url, self.main_window));
}
"ABOUT" => {
@@ -217,8 +226,11 @@ impl OpenCADStudio {
}
"CHANGELOG" => {
crate::sys::open_url("https://github.com/HakanSeven12/OpenCADStudio/releases");
self.command_line.push_info("Opening release notes...");
return Some(crate::sys::open_url(
"https://github.com/HakanSeven12/OpenCADStudio/releases",
self.main_window,
));
}
// ── ALIASEDIT — command-alias editor ───────────────────────────
+6 -6
View File
@@ -290,10 +290,7 @@ impl OpenCADStudio {
Message::OpenPathPicked(None) => Task::none(),
Message::OpenUrl(url) => {
crate::sys::open_url(&url);
Task::none()
}
Message::OpenUrl(url) => crate::sys::open_url(&url, self.main_window),
Message::StartSectionSelect(section) => {
self.start_section = section;
@@ -5040,9 +5037,12 @@ impl OpenCADStudio {
Task::none()
}
Message::UpdateNoticeOpenRelease => {
crate::sys::open_url(crate::io::update_check::RELEASES_PAGE);
let open = crate::sys::open_url(
crate::io::update_check::RELEASES_PAGE,
self.main_window,
);
self.close_active_modal();
Task::none()
open
}
Message::AssocPromptYes => {
self.file_assoc_enabled = true;
+89 -8
View File
@@ -1,20 +1,101 @@
// Small platform shims for things the desktop build does natively but the web
// (wasm) build must handle differently or skip.
/// Open a URL in the user's browser. The desktop launches the default handler;
/// the web opens a new tab (the button click is a user gesture, so it isn't
/// caught by the pop-up blocker). Focus of the opened page is left to the
/// OS / browser.
#[cfg(not(target_arch = "wasm32"))]
pub fn open_url(url: &str) {
let _ = open::that(url);
/// Open a URL in the user's browser.
///
/// Wayland requires an xdg-activation token from the source window before it
/// will let an existing browser window take focus. Linux obtains that token
/// from the OCS surface and passes it to xdg-open.
#[cfg(target_os = "linux")]
pub fn open_url<Message: Send + 'static>(
url: &str,
parent: Option<iced::window::Id>,
) -> iced::Task<Message> {
let url = url.to_string();
match parent {
Some(parent) => iced::window::run(parent, move |window| {
open_url_linux(&url, linux_activation_token(window));
})
.discard(),
None => {
let _ = open::that_detached(url);
iced::Task::none()
}
}
}
#[cfg(target_os = "linux")]
fn linux_activation_token(window: &dyn iced::window::Window) -> Option<String> {
use iced::window::raw_window_handle::{RawDisplayHandle, RawWindowHandle};
let RawWindowHandle::Wayland(window_handle) =
window.window_handle().ok()?.as_raw()
else {
return None;
};
let RawDisplayHandle::Wayland(display_handle) =
window.display_handle().ok()?.as_raw()
else {
return None;
};
// Keep the iced window borrowed while ashpd uses its raw Wayland surface.
// The returned token is an owned string and is immediately handed to the
// child process.
unsafe {
iced::futures::executor::block_on(
ashpd::ActivationToken::from_wayland_raw(
None,
window_handle.surface.as_ptr(),
display_handle.display.as_ptr(),
),
)
}
.map(String::from)
}
#[cfg(target_os = "linux")]
fn open_url_linux(url: &str, activation_token: Option<String>) {
let Some(token) = activation_token else {
let _ = open::that_detached(url);
return;
};
let mut command = std::process::Command::new("xdg-open");
command
.arg(url)
.env("XDG_ACTIVATION_TOKEN", &token)
.env("DESKTOP_STARTUP_ID", token);
if let Ok(mut child) = command.spawn() {
std::thread::spawn(move || {
let _ = child.wait();
});
} else {
let _ = open::that_detached(url);
}
}
#[cfg(all(not(target_arch = "wasm32"), not(target_os = "linux")))]
pub fn open_url<Message>(
url: &str,
_parent: Option<iced::window::Id>,
) -> iced::Task<Message> {
let _ = open::that_detached(url);
iced::Task::none()
}
/// Web opens the tab synchronously so the browser still sees the click as a
/// user gesture and does not block it as a pop-up.
#[cfg(target_arch = "wasm32")]
pub fn open_url(url: &str) {
pub fn open_url<Message>(
url: &str,
_parent: Option<iced::window::Id>,
) -> iced::Task<Message> {
if let Some(window) = web_sys::window() {
let _ = window.open_with_url_and_target(url, "_blank");
}
iced::Task::none()
}
#[cfg(target_arch = "wasm32")]