Compare commits

..

4 Commits

Author SHA1 Message Date
Kevin Yue
ea9819d161 fix: correct the Gentoo package name, fix #71 2025-05-05 17:14:23 +08:00
Kevin Yue
dc9480fd71 Release 2.4.4 2025-02-09 14:30:40 +00:00
Kevin Yue
fa2849a080 chore: update dependencies 2025-02-09 14:25:35 +00:00
Kevin Yue
c70c7ee5b9 fix: multiple tray icon related: #464 2025-02-09 08:23:40 +00:00
26 changed files with 1416 additions and 1276 deletions

View File

@@ -152,11 +152,13 @@ jobs:
- name: Build ${{ matrix.package }} package in Docker - name: Build ${{ matrix.package }} package in Docker
run: | run: |
docker run --pull=always --rm \ docker run --pull=always --rm \
-e COREPACK_INTEGRITY_KEYS=0 \
-v $(pwd)/build-gp-${{ matrix.package }}:/${{ matrix.package }} \ -v $(pwd)/build-gp-${{ matrix.package }}:/${{ matrix.package }} \
yuezk/gpdev:${{ matrix.package }}-builder-tauri2 yuezk/gpdev:${{ matrix.package }}-builder-tauri2
- name: Install ${{ matrix.package }} package in Docker - name: Install ${{ matrix.package }} package in Docker
run: | run: |
docker run --pull=always --rm \ docker run --pull=always --rm \
-e COREPACK_INTEGRITY_KEYS=0 \
-e GPGUI_INSTALLED=0 \ -e GPGUI_INSTALLED=0 \
-v $(pwd)/build-gp-${{ matrix.package }}:/${{ matrix.package }} \ -v $(pwd)/build-gp-${{ matrix.package }}:/${{ matrix.package }} \
yuezk/gpdev:${{ matrix.package }}-builder-tauri2 \ yuezk/gpdev:${{ matrix.package }}-builder-tauri2 \
@@ -205,12 +207,16 @@ jobs:
run: echo ${{ secrets.DOCKER_HUB_TOKEN }} | docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} --password-stdin run: echo ${{ secrets.DOCKER_HUB_TOKEN }} | docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} --password-stdin
- name: Build gpgui in Docker - name: Build gpgui in Docker
run: | run: |
docker run --pull=always --rm -v $(pwd)/gpgui-source:/gpgui yuezk/gpdev:gpgui-builder-tauri2 docker run --pull=always --rm \
-e COREPACK_INTEGRITY_KEYS=0 \
-v $(pwd)/gpgui-source:/gpgui yuezk/gpdev:gpgui-builder-tauri2
- name: Install gpgui in Docker - name: Install gpgui in Docker
run: | run: |
cd gpgui-source cd gpgui-source
tar -xJf *.bin.tar.xz tar -xJf *.bin.tar.xz
docker run --pull=always --rm -v $(pwd):/gpgui yuezk/gpdev:gpgui-builder-tauri2 \ docker run --pull=always --rm \
-e COREPACK_INTEGRITY_KEYS=0 \
-v $(pwd):/gpgui yuezk/gpdev:gpgui-builder-tauri2 \
bash -c "cd /gpgui/gpgui_*/ && ./gpgui --version" bash -c "cd /gpgui/gpgui_*/ && ./gpgui --version"
- name: Upload gpgui - name: Upload gpgui
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4

View File

@@ -112,12 +112,14 @@ jobs:
run: | run: |
docker run --pull=always --rm \ docker run --pull=always --rm \
-v $(pwd)/build-${{ matrix.package }}:/${{ matrix.package }} \ -v $(pwd)/build-${{ matrix.package }}:/${{ matrix.package }} \
-e COREPACK_INTEGRITY_KEYS=0 \
-e INCLUDE_GUI=1 \ -e INCLUDE_GUI=1 \
yuezk/gpdev:${{ matrix.package }}-builder-tauri2 yuezk/gpdev:${{ matrix.package }}-builder-tauri2
- name: Install ${{ matrix.package }} package in Docker - name: Install ${{ matrix.package }} package in Docker
run: | run: |
docker run --pull=always --rm \ docker run --pull=always --rm \
-e COREPACK_INTEGRITY_KEYS=0 \
-v $(pwd)/build-${{ matrix.package }}:/${{ matrix.package }} \ -v $(pwd)/build-${{ matrix.package }}:/${{ matrix.package }} \
yuezk/gpdev:${{ matrix.package }}-builder-tauri2 \ yuezk/gpdev:${{ matrix.package }}-builder-tauri2 \
bash install.sh bash install.sh

457
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,7 @@ members = [
[workspace.package] [workspace.package]
rust-version = "1.80" rust-version = "1.80"
version = "2.4.3" version = "2.4.4"
authors = ["Kevin Yue <k3vinyue@gmail.com>"] authors = ["Kevin Yue <k3vinyue@gmail.com>"]
homepage = "https://github.com/yuezk/GlobalProtect-openconnect" homepage = "https://github.com/yuezk/GlobalProtect-openconnect"
edition = "2021" edition = "2021"

View File

@@ -141,7 +141,7 @@ It is available via `guru` and `lamdness` overlays.
```bash ```bash
sudo eselect repository enable guru sudo eselect repository enable guru
sudo emerge -r guru sync sudo emerge -r guru sync
sudo emerge -av net-vpn/globalprotect-openconnect sudo emerge -av net-vpn/GlobalProtect-openconnect
``` ```
### Other distributions ### Other distributions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -5,7 +5,7 @@
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>GlobalProtect</title> <title>GlobalProtect</title>
<script type="module" crossorigin src="/assets/main-CQPVXkdn.js"></script> <script type="module" crossorigin src="/assets/main-sEPcTvJX.js"></script>
<link rel="stylesheet" crossorigin href="/assets/main-B3YRsHQ2.css"> <link rel="stylesheet" crossorigin href="/assets/main-B3YRsHQ2.css">
</head> </head>
<body> <body>

View File

@@ -11,27 +11,27 @@
"dependencies": { "dependencies": {
"@emotion/react": "^11.14.0", "@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0", "@emotion/styled": "^11.14.0",
"@mui/icons-material": "^6.3.0", "@mui/icons-material": "^6.4.3",
"@mui/material": "^6.3.0", "@mui/material": "^6.4.3",
"@tauri-apps/api": "^2.1.1", "@tauri-apps/api": "^2.2.0",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0" "react-dom": "^19.0.0"
}, },
"devDependencies": { "devDependencies": {
"@tauri-apps/cli": "^2.1.0", "@tauri-apps/cli": "^2.2.7",
"@types/node": "^22.10.2", "@types/node": "^22.13.1",
"@types/react": "^19.0.2", "@types/react": "^19.0.8",
"@types/react-dom": "^19.0.2", "@types/react-dom": "^19.0.3",
"@typescript-eslint/eslint-plugin": "^8.18.2", "@typescript-eslint/eslint-plugin": "^8.23.0",
"@typescript-eslint/parser": "^8.18.2", "@typescript-eslint/parser": "^8.23.0",
"@vitejs/plugin-react": "^4.3.4", "@vitejs/plugin-react": "^4.3.4",
"eslint": "^9.17.0", "eslint": "^9.20.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
"eslint-plugin-react": "^7.37.3", "eslint-plugin-react": "^7.37.4",
"eslint-plugin-react-hooks": "^5.1.0", "eslint-plugin-react-hooks": "^5.1.0",
"prettier": "3.4.2", "prettier": "3.4.2",
"typescript": "^5.7.2", "typescript": "^5.7.3",
"vite": "^6.0.5" "vite": "^6.1.0"
}, },
"packageManager": "pnpm@9.15.1" "packageManager": "pnpm@9.15.1"
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,10 @@
# Changelog # Changelog
## 2.4.4 - 2025-02-09
- GUI: fix multiple tray icons issue (fix [#464](https://github.com/yuezk/GlobalProtect-openconnect/issues/464))
- CLI: check the cli running state before running the `gpclient` command (fix [#447](https://github.com/yuezk/GlobalProtect-openconnect/issues/447))
## 2.4.3 - 2025-01-21 ## 2.4.3 - 2025-01-21
- Do not use static default value for `--os-version` option. - Do not use static default value for `--os-version` option.

View File

@@ -28,21 +28,15 @@ regex = { workspace = true, optional = true }
tokio-util = { workspace = true, optional = true } tokio-util = { workspace = true, optional = true }
html-escape = { version = "0.2.13", optional = true } html-escape = { version = "0.2.13", optional = true }
[target.'cfg(not(any(target_os="macos", target_os="windows")))'.dependencies] [target.'cfg(not(target_os = "macos"))'.dependencies]
webkit2gtk = { version = "2", optional = true } webkit2gtk = { version = "2", optional = true }
[target.'cfg(target_os="macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]
block2 = { version = "0.5", optional = true } block2 = { version = "0.5", optional = true }
objc2 = { version = "0.5", optional = true } objc2 = { version = "0.5", optional = true }
objc2-foundation = { version = "0.2", optional = true } objc2-foundation = { version = "0.2", optional = true }
objc2-web-kit = { version = "0.2", optional = true } objc2-web-kit = { version = "0.2", optional = true }
[target.'cfg(target_os="windows")'.dependencies]
webview2-com = { version = "0.34", optional = true }
windows-core = { version = "0.58", optional = true }
windows = { version = "0.58", optional = true }
serde_json = { workspace = true, optional = true }
[features] [features]
browser-auth = [ browser-auth = [
"dep:webbrowser", "dep:webbrowser",
@@ -62,8 +56,4 @@ webview-auth = [
"dep:objc2", "dep:objc2",
"dep:objc2-foundation", "dep:objc2-foundation",
"dep:objc2-web-kit", "dep:objc2-web-kit",
"dep:webview2-com",
"dep:windows-core",
"dep:windows",
"dep:serde_json",
] ]

View File

@@ -1,4 +1,4 @@
use std::{env::temp_dir, fs}; use std::{env::temp_dir, fs, os::unix::fs::PermissionsExt};
use gpapi::{auth::SamlAuthData, GP_CALLBACK_PORT_FILENAME}; use gpapi::{auth::SamlAuthData, GP_CALLBACK_PORT_FILENAME};
use log::info; use log::info;
@@ -96,11 +96,7 @@ async fn wait_auth_data() -> anyhow::Result<SamlAuthData> {
// Write the port to a file // Write the port to a file
fs::write(&port_file, port.to_string())?; fs::write(&port_file, port.to_string())?;
#[cfg(unix)] fs::set_permissions(&port_file, fs::Permissions::from_mode(0o600))?;
{
use os::unix::fs::PermissionsExt;
fs::set_permissions(&port_file, fs::Permissions::from_mode(0o600))?;
}
// Remove the previous log file // Remove the previous log file
let callback_log = temp_dir().join("gpcallback.log"); let callback_log = temp_dir().join("gpcallback.log");

View File

@@ -1,9 +1,8 @@
mod auth_messenger; mod auth_messenger;
mod webview_auth; mod webview_auth;
#[cfg_attr(not(any(target_os = "macos", target_os = "windows")), path = "webview/unix.rs")] #[cfg_attr(not(target_os = "macos"), path = "webview/unix.rs")]
#[cfg_attr(target_os = "macos", path = "webview/macos.rs")] #[cfg_attr(target_os = "macos", path = "webview/macos.rs")]
#[cfg_attr(windows, path = "webview/windows.rs")]
mod platform_impl; mod platform_impl;
pub use webview_auth::WebviewAuthenticator; pub use webview_auth::WebviewAuthenticator;

View File

@@ -15,7 +15,7 @@ pub(crate) enum AuthDataLocation {
#[derive(Debug)] #[derive(Debug)]
pub(crate) enum AuthError { pub(crate) enum AuthError {
/// Failed to load page due to TLS error /// Failed to load page due to TLS error
#[cfg(not(any(target_os = "macos", target_os = "windows")))] #[cfg(not(target_os = "macos"))]
TlsError, TlsError,
/// 1. Found auth data in headers/body but it's invalid /// 1. Found auth data in headers/body but it's invalid
/// 2. Loaded an empty page, failed to load page. etc. /// 2. Loaded an empty page, failed to load page. etc.

View File

@@ -115,7 +115,7 @@ impl<'a> WebviewAuthenticator<'a> {
match auth_messenger.subscribe().await? { match auth_messenger.subscribe().await? {
AuthEvent::Close => bail!("Authentication cancelled"), AuthEvent::Close => bail!("Authentication cancelled"),
AuthEvent::RaiseWindow => self.raise_window(&auth_window), AuthEvent::RaiseWindow => self.raise_window(&auth_window),
#[cfg(not(any(target_os = "macos", target_os = "windows")))] #[cfg(not(target_os = "macos"))]
AuthEvent::Error(AuthError::TlsError) => bail!(gpapi::error::PortalError::TlsError), AuthEvent::Error(AuthError::TlsError) => bail!(gpapi::error::PortalError::TlsError),
AuthEvent::Error(AuthError::NotFound(location)) => { AuthEvent::Error(AuthError::NotFound(location)) => {
info!( info!(
@@ -261,10 +261,10 @@ impl<'a> WebviewAuthenticator<'a> {
info!("Raising auth window..."); info!("Raising auth window...");
#[cfg(any(target_os = "macos", target_os = "windows"))] #[cfg(target_os = "macos")]
let result = auth_window.show(); let result = auth_window.show();
#[cfg(not(any(target_os = "macos", target_os = "windows")))] #[cfg(not(target_os = "macos"))]
let result = { let result = {
use gpapi::utils::window::WindowExt; use gpapi::utils::window::WindowExt;
auth_window.raise() auth_window.raise()

View File

@@ -1,142 +0,0 @@
use log::warn;
use tauri::webview::PlatformWebview;
use webview2_com::{
pwstr_from_str, take_pwstr, ExecuteScriptCompletedHandler,
Microsoft::Web::WebView2::Win32::{
ICoreWebView2WebResourceResponseView, ICoreWebView2_14, ICoreWebView2_2,
COREWEBVIEW2_SERVER_CERTIFICATE_ERROR_ACTION_ALWAYS_ALLOW,
},
ServerCertificateErrorDetectedEventHandler, WebResourceResponseReceivedEventHandler,
};
use windows_core::{Interface, PWSTR};
use super::{
auth_messenger::AuthError,
webview_auth::{GetHeader, PlatformWebviewExt},
};
impl PlatformWebviewExt for PlatformWebview {
fn ignore_tls_errors(&self) -> anyhow::Result<()> {
unsafe {
let wv = self.controller().CoreWebView2()?.cast::<ICoreWebView2_14>()?;
let handler = ServerCertificateErrorDetectedEventHandler::create(Box::new(|_, e| {
if let Some(e) = e {
let _ = e.SetAction(COREWEBVIEW2_SERVER_CERTIFICATE_ERROR_ACTION_ALWAYS_ALLOW);
}
Ok(())
}));
wv.add_ServerCertificateErrorDetected(&handler, &mut Default::default())?;
}
Ok(())
}
fn load_url(&self, url: &str) -> anyhow::Result<()> {
let url = pwstr_from_str(url);
unsafe { self.controller().CoreWebView2()?.Navigate(url)? }
Ok(())
}
fn load_html(&self, html: &str) -> anyhow::Result<()> {
let html = pwstr_from_str(html);
unsafe { self.controller().CoreWebView2()?.NavigateToString(html)? }
Ok(())
}
fn get_html(&self, callback: Box<dyn Fn(anyhow::Result<String>) + 'static>) {
unsafe {
match self.controller().CoreWebView2() {
Ok(wv) => {
let js = "document.documentElement.outerHTML";
let js = pwstr_from_str(js);
let handler = ExecuteScriptCompletedHandler::create(Box::new(move |err, html| {
if let Err(err) = err {
callback(Err(anyhow::anyhow!(err)));
return Ok(());
}
// The returned HTML is JSON.stringify'd string, so we need to parse it
let res = match serde_json::from_str(&html) {
Ok(Some(html)) => Ok(html),
Ok(None) => Err(anyhow::anyhow!("No HTML returned")),
Err(err) => Err(anyhow::anyhow!(err)),
};
callback(res);
Ok(())
}));
if let Err(err) = wv.ExecuteScript(js, &handler) {
warn!("Failed to execute script: {}", err);
}
}
Err(err) => callback(Err(anyhow::anyhow!(err))),
}
}
}
}
impl GetHeader for ICoreWebView2WebResourceResponseView {
fn get_header(&self, key: &str) -> Option<String> {
unsafe {
let headers = self.Headers().ok()?;
let key = pwstr_from_str(key);
let mut contains = Default::default();
headers.Contains(key, &mut contains).ok()?;
if contains.as_bool() {
let mut value = PWSTR::null();
headers.GetHeader(key, &mut value).ok()?;
let value = take_pwstr(value);
Some(value)
} else {
None
}
}
}
}
pub trait PlatformWebviewOnResponse {
fn on_response(
&self,
callback: Box<dyn Fn(anyhow::Result<ICoreWebView2WebResourceResponseView, AuthError>) + 'static>,
);
}
impl PlatformWebviewOnResponse for PlatformWebview {
fn on_response(
&self,
callback: Box<dyn Fn(anyhow::Result<ICoreWebView2WebResourceResponseView, AuthError>) + 'static>,
) {
unsafe {
let _ = self
.controller()
.CoreWebView2()
.and_then(|wv| wv.cast::<ICoreWebView2_2>())
.map(|wv| {
let handler = WebResourceResponseReceivedEventHandler::create(Box::new(move |_, e| {
let Some(e) = e else {
return Ok(());
};
match e.Response() {
Ok(res) => callback(Ok(res)),
Err(err) => warn!("Failed to get response: {}", err),
}
Ok(())
}));
let _ = wv.add_WebResourceResponseReceived(&handler, &mut Default::default());
});
}
}
}

View File

@@ -27,7 +27,7 @@ chacha20poly1305 = { version = "0.10", features = ["std"] }
redact-engine.workspace = true redact-engine.workspace = true
url.workspace = true url.workspace = true
regex.workspace = true regex.workspace = true
uzers.workspace = true
serde_urlencoded.workspace = true serde_urlencoded.workspace = true
md5.workspace = true md5.workspace = true
sha256.workspace = true sha256.workspace = true
@@ -39,8 +39,8 @@ clap-verbosity-flag = { workspace = true, optional = true }
env_logger = { workspace = true, optional = true } env_logger = { workspace = true, optional = true }
log-reload = { version = "0.1", optional = true } log-reload = { version = "0.1", optional = true }
[target.'cfg(target_family="unix")'.dependencies] [target.'cfg(not(any(target_os="macos", target_os="windows")))'.dependencies]
uzers.workspace = true gtk = "0.18"
[features] [features]
tauri = ["dep:tauri"] tauri = ["dep:tauri"]

View File

@@ -104,7 +104,7 @@ impl SamlAuthData {
} }
let auth_data = decode_to_string(auth_data).map_err(|e| { let auth_data = decode_to_string(auth_data).map_err(|e| {
warn!("Failed to decode SAML auth data: {}", auth_data); warn!("Failed to decode SAML auth data: {}", e);
AuthDataParseError::Invalid(anyhow::anyhow!(e)) AuthDataParseError::Invalid(anyhow::anyhow!(e))
})?; })?;
let auth_data = Self::from_html(&auth_data)?; let auth_data = Self::from_html(&auth_data)?;

View File

@@ -1,7 +1,5 @@
mod login; mod login;
mod parse_gateways; mod parse_gateways;
#[cfg(unix)]
pub mod hip; pub mod hip;
pub use login::*; pub use login::*;

View File

@@ -4,10 +4,7 @@ pub mod error;
pub mod gateway; pub mod gateway;
pub mod gp_params; pub mod gp_params;
pub mod portal; pub mod portal;
#[cfg(unix)]
pub mod process; pub mod process;
pub mod service; pub mod service;
pub mod utils; pub mod utils;

View File

@@ -1,11 +1,8 @@
pub(crate) mod command_traits; pub(crate) mod command_traits;
pub(crate) mod gui_helper_launcher; pub(crate) mod gui_helper_launcher;
pub mod auth_launcher; pub mod auth_launcher;
pub mod gui_launcher; pub mod gui_launcher;
pub mod hip_launcher; pub mod hip_launcher;
pub mod service_launcher; pub mod service_launcher;
#[cfg(unix)]
pub mod users; pub mod users;

View File

@@ -41,12 +41,6 @@ pub fn patch_gui_runtime_env(hidpi: bool) {
// This is to avoid blank screen on some systems // This is to avoid blank screen on some systems
std::env::set_var("WEBKIT_DISABLE_COMPOSITING_MODE", "1"); std::env::set_var("WEBKIT_DISABLE_COMPOSITING_MODE", "1");
// Workaround for https://github.com/tauri-apps/tao/issues/929
let is_wayland = std::env::var("XDG_SESSION_TYPE").unwrap_or_default() == "wayland";
if is_wayland {
env::set_var("GDK_BACKEND", "x11");
}
if hidpi { if hidpi {
info!("Setting GDK_SCALE=2 and GDK_DPI_SCALE=0.5"); info!("Setting GDK_SCALE=2 and GDK_DPI_SCALE=0.5");
std::env::set_var("GDK_SCALE", "2"); std::env::set_var("GDK_SCALE", "2");

View File

@@ -9,7 +9,7 @@ pub mod lock_file;
pub mod openssl; pub mod openssl;
pub mod redact; pub mod redact;
pub mod request; pub mod request;
#[cfg(all(feature = "tauri", not(any(target_os = "macos", target_os = "windows"))))] #[cfg(feature = "tauri")]
pub mod window; pub mod window;
mod shutdown_signal; mod shutdown_signal;

View File

@@ -6,21 +6,15 @@ pub async fn shutdown_signal() {
}; };
#[cfg(unix)] #[cfg(unix)]
{ let terminate = async {
let terminate = async { signal::unix::signal(signal::unix::SignalKind::terminate())
signal::unix::signal(signal::unix::SignalKind::terminate()) .expect("failed to install signal handler")
.expect("failed to install signal handler") .recv()
.recv() .await;
.await; };
};
tokio::select! {
_ = ctrl_c => {},
_ = terminate => {},
}
}
#[cfg(not(unix))] tokio::select! {
{ _ = ctrl_c => {},
ctrl_c.await; _ = terminate => {},
} }
} }

View File

@@ -1,73 +1,97 @@
use std::{process::ExitStatus, time::Duration};
use anyhow::bail;
use log::info;
use tauri::WebviewWindow; use tauri::WebviewWindow;
use tokio::process::Command;
pub trait WindowExt { pub trait WindowExt {
fn raise(&self) -> anyhow::Result<()>; fn raise(&self) -> anyhow::Result<()>;
} }
impl WindowExt for WebviewWindow { impl WindowExt for WebviewWindow {
#[cfg(any(target_os = "macos", target_os = "windows"))]
fn raise(&self) -> anyhow::Result<()> { fn raise(&self) -> anyhow::Result<()> {
raise_window(self) self.show()?;
Ok(())
}
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
fn raise(&self) -> anyhow::Result<()> {
unix::raise_window(self)
} }
} }
pub fn raise_window(win: &WebviewWindow) -> anyhow::Result<()> { #[cfg(not(any(target_os = "macos", target_os = "windows")))]
let is_wayland = std::env::var("XDG_SESSION_TYPE").unwrap_or_default() == "wayland"; mod unix {
use std::{process::ExitStatus, time::Duration};
if is_wayland { use anyhow::bail;
win.hide()?; use gtk::{
win.show()?; glib::Cast,
} else { traits::{EventBoxExt, GtkWindowExt, WidgetExt},
if !win.is_visible()? { EventBox,
win.show()?; };
} use log::info;
let title = win.title()?; use tauri::WebviewWindow;
tokio::spawn(async move { use tokio::process::Command;
if let Err(err) = wmctrl_raise_window(&title).await {
info!("Window not raised: {}", err); pub fn raise_window(win: &WebviewWindow) -> anyhow::Result<()> {
let is_wayland = std::env::var("XDG_SESSION_TYPE").unwrap_or_default() == "wayland";
if is_wayland {
let gtk_win = win.gtk_window()?;
if let Some(header) = gtk_win.titlebar() {
let _ = header.downcast::<EventBox>().map(|event_box| {
event_box.set_above_child(false);
});
} }
});
}
// Calling window.show() on Windows will cause the menu to be shown. gtk_win.hide();
// We need to hide it again. gtk_win.show_all();
win.hide_menu()?; } else {
if !win.is_visible()? {
Ok(()) win.show()?;
}
async fn wmctrl_raise_window(title: &str) -> anyhow::Result<()> {
let mut counter = 0;
loop {
if let Ok(exit_status) = wmctrl_try_raise_window(title).await {
if exit_status.success() {
info!("Window raised after {} attempts", counter + 1);
return Ok(());
} }
let title = win.title()?;
tokio::spawn(async move {
if let Err(err) = wmctrl_raise_window(&title).await {
info!("Window not raised: {}", err);
}
});
} }
if counter >= 10 { // Calling window.show() on window object will cause the menu to be shown.
bail!("Failed to raise window: {}", title) // We need to hide it again.
} win.hide_menu()?;
counter += 1; Ok(())
tokio::time::sleep(Duration::from_millis(100)).await; }
async fn wmctrl_raise_window(title: &str) -> anyhow::Result<()> {
let mut counter = 0;
loop {
if let Ok(exit_status) = wmctrl_try_raise_window(title).await {
if exit_status.success() {
info!("Window raised after {} attempts", counter + 1);
return Ok(());
}
}
if counter >= 10 {
bail!("Failed to raise window: {}", title)
}
counter += 1;
tokio::time::sleep(Duration::from_millis(100)).await;
}
}
async fn wmctrl_try_raise_window(title: &str) -> anyhow::Result<ExitStatus> {
let exit_status = Command::new("wmctrl")
.arg("-F")
.arg("-a")
.arg(title)
.spawn()?
.wait()
.await?;
Ok(exit_status)
} }
} }
async fn wmctrl_try_raise_window(title: &str) -> anyhow::Result<ExitStatus> {
let exit_status = Command::new("wmctrl")
.arg("-F")
.arg("-a")
.arg(title)
.spawn()?
.wait()
.await?;
Ok(exit_status)
}