-
Notifications
You must be signed in to change notification settings - Fork 909
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add
platform::startup_notify
for Wayland/X11
The utils in this module should help the users to activate the windows they create, as well as manage activation tokens environment variables. The API is essential for Wayland in the first place, since some compositors may decide initial focus of the window based on whether the activation token was during the window creation. Fixes #2279.
- Loading branch information
Showing
18 changed files
with
775 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
//! Demonstrates the use of startup notifications on Linux. | ||
|
||
fn main() { | ||
example::main(); | ||
} | ||
|
||
#[cfg(any(x11_platform, wayland_platform))] | ||
#[path = "./util/fill.rs"] | ||
mod fill; | ||
|
||
#[cfg(any(x11_platform, wayland_platform))] | ||
mod example { | ||
use std::collections::HashMap; | ||
use std::rc::Rc; | ||
|
||
use winit::event::{ElementState, Event, KeyEvent, WindowEvent}; | ||
use winit::event_loop::EventLoop; | ||
use winit::keyboard::Key; | ||
use winit::platform::startup_notify::{ | ||
EventLoopExtStartupNotify, WindowBuilderExtStartupNotify, WindowExtStartupNotify, | ||
}; | ||
use winit::window::{Window, WindowBuilder, WindowId}; | ||
|
||
pub(super) fn main() { | ||
// Create the event loop and get the activation token. | ||
let event_loop = EventLoop::new(); | ||
let mut current_token = match event_loop.read_token_from_env() { | ||
Some(token) => Some(token), | ||
None => { | ||
println!("No startup notification token found in environment."); | ||
None | ||
} | ||
}; | ||
|
||
let mut windows: HashMap<WindowId, Rc<Window>> = HashMap::new(); | ||
let mut counter = 0; | ||
let mut create_first_window = false; | ||
|
||
event_loop.run(move |event, elwt, flow| { | ||
match event { | ||
Event::Resumed => create_first_window = true, | ||
|
||
Event::WindowEvent { | ||
window_id, | ||
event: | ||
WindowEvent::KeyboardInput { | ||
event: | ||
KeyEvent { | ||
logical_key, | ||
state: ElementState::Pressed, | ||
.. | ||
}, | ||
.. | ||
}, | ||
} => { | ||
if logical_key == Key::Character("n".into()) { | ||
if let Some(window) = windows.get(&window_id) { | ||
// Request a new activation token on this window. | ||
// Once we get it we will use it to create a window. | ||
window | ||
.request_activation_token() | ||
.expect("Failed to request activation token."); | ||
} | ||
} | ||
} | ||
|
||
Event::WindowEvent { | ||
window_id, | ||
event: WindowEvent::CloseRequested, | ||
} => { | ||
// Remove the window from the map. | ||
windows.remove(&window_id); | ||
if windows.is_empty() { | ||
flow.set_exit(); | ||
return; | ||
} | ||
} | ||
|
||
Event::WindowEvent { | ||
event: WindowEvent::ActivationTokenDone { token, .. }, | ||
.. | ||
} => { | ||
current_token = Some(token); | ||
} | ||
|
||
Event::RedrawRequested(id) => { | ||
if let Some(window) = windows.get(&id) { | ||
super::fill::fill_window(window); | ||
} | ||
} | ||
|
||
_ => {} | ||
} | ||
|
||
// See if we've passed the deadline. | ||
if current_token.is_some() || create_first_window { | ||
// Create the initial window. | ||
let window = { | ||
let mut builder = | ||
WindowBuilder::new().with_title(format!("Window {}", counter)); | ||
|
||
if let Some(token) = current_token.take() { | ||
println!("Creating a window with token {token:?}"); | ||
builder = builder.with_activation_token(token); | ||
} | ||
|
||
Rc::new(builder.build(elwt).unwrap()) | ||
}; | ||
|
||
// Add the window to the map. | ||
windows.insert(window.id(), window.clone()); | ||
|
||
counter += 1; | ||
create_first_window = false; | ||
} | ||
|
||
flow.set_wait(); | ||
}); | ||
} | ||
} | ||
|
||
#[cfg(not(any(x11_platform, wayland_platform)))] | ||
mod example { | ||
pub(super) fn main() { | ||
println!("This example is only supported on X11 and Wayland platforms."); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
//! Window startup notification to handle window raising. | ||
//! | ||
//! The [`ActivationToken`] is essential to ensure that your newly | ||
//! created window will obtain the focus, otherwise the user could | ||
//! be requered to click on the window. | ||
//! | ||
//! Such token is usually delivered via the environment variable and | ||
//! could be read from it with the [`EventLoopExtStartupNotify::read_token_from_env`]. | ||
//! | ||
//! Such token must also be reset after reading it from your environment with | ||
//! [`reset_activation_token_env`] otherwise child processes could inherit it. | ||
//! | ||
//! When starting a new child process with a newly obtained [`ActivationToken`] from | ||
//! [`WindowExtStartupNotify::request_activation_token`] the [`set_activation_token_env`] | ||
//! must be used to propagate it to the child | ||
//! | ||
//! To ensure the delivery of such token by other processes to you, the user should | ||
//! set `StartupNotify=true` inside the `.desktop` file of their application. | ||
//! | ||
//! The specification could be found [`here`]. | ||
//! | ||
//! [`here`]: https://specifications.freedesktop.org/startup-notification-spec/startup-notification-latest.txt | ||
|
||
use std::env; | ||
|
||
use crate::error::NotSupportedError; | ||
use crate::event_loop::{AsyncRequestSerial, EventLoopWindowTarget}; | ||
use crate::window::{ActivationToken, Window, WindowBuilder}; | ||
|
||
/// The variable which is used mostly on X11. | ||
const X11_VAR: &str = "DESKTOP_STARTUP_ID"; | ||
|
||
/// The variable which is used mostly on Wayland. | ||
const WAYLAND_VAR: &str = "XDG_ACTIVATION_TOKEN"; | ||
|
||
pub trait EventLoopExtStartupNotify { | ||
/// Read the token from the environment. | ||
/// | ||
/// It's recommended **to unset** this environment variable for child processes. | ||
fn read_token_from_env(&self) -> Option<ActivationToken>; | ||
} | ||
|
||
pub trait WindowExtStartupNotify { | ||
/// Request a new activation token. | ||
/// | ||
/// The token will be delivered inside | ||
fn request_activation_token(&self) -> Result<AsyncRequestSerial, NotSupportedError>; | ||
} | ||
|
||
pub trait WindowBuilderExtStartupNotify { | ||
/// Use this [`ActivationToken`] during window creation. | ||
/// | ||
/// Not using such a token upon a window could make your window not gaining | ||
/// focus until the user clicks on the window. | ||
fn with_activation_token(self, token: ActivationToken) -> Self; | ||
} | ||
|
||
impl<T> EventLoopExtStartupNotify for EventLoopWindowTarget<T> { | ||
fn read_token_from_env(&self) -> Option<ActivationToken> { | ||
match self.p { | ||
#[cfg(wayland_platform)] | ||
crate::platform_impl::EventLoopWindowTarget::Wayland(_) => env::var(WAYLAND_VAR), | ||
#[cfg(x11_platform)] | ||
crate::platform_impl::EventLoopWindowTarget::X(_) => env::var(X11_VAR), | ||
} | ||
.ok() | ||
.map(ActivationToken::_new) | ||
} | ||
} | ||
|
||
impl WindowExtStartupNotify for Window { | ||
fn request_activation_token(&self) -> Result<AsyncRequestSerial, NotSupportedError> { | ||
self.window.request_activation_token() | ||
} | ||
} | ||
|
||
impl WindowBuilderExtStartupNotify for WindowBuilder { | ||
fn with_activation_token(mut self, token: ActivationToken) -> Self { | ||
self.platform_specific.activation_token = Some(token); | ||
self | ||
} | ||
} | ||
|
||
/// Remove the activation environment variables from the current process. | ||
/// | ||
/// This is wise to do before running child processes, | ||
/// which may not to support the activation token. | ||
/// | ||
/// # Safety | ||
/// | ||
/// While the function is safe internally, it mutates the global environment | ||
/// state for the process, hence unsafe. | ||
pub unsafe fn reset_activation_token_env() { | ||
env::remove_var(X11_VAR); | ||
env::remove_var(WAYLAND_VAR); | ||
} | ||
|
||
/// Set environment variables responsible for activation token. | ||
/// | ||
/// This could be used before running daemon processes. | ||
/// | ||
/// # Safety | ||
/// | ||
/// While the function is safe internally, it mutates the global environment | ||
/// state for the process, hence unsafe. | ||
pub unsafe fn set_activation_token_env(token: ActivationToken) { | ||
env::set_var(X11_VAR, &token._token); | ||
env::set_var(WAYLAND_VAR, token._token); | ||
} |
Oops, something went wrong.