Compare commits

...

25 Commits

Author SHA1 Message Date
Kirill Chibisov
340202f842 Winit version 0.28.5 2023-05-06 20:42:05 +03:00
Kirill Chibisov
870e6d1cb2 On macOS, fix key_up being ignored without IME
Fixes: d15feb5cfa (On macOS, fix empty marked text)
2023-05-06 20:42:05 +03:00
Kirill Chibisov
b455f88f40 Winit version 0.28.4 2023-05-04 17:00:41 +03:00
Kirill Chibisov
30678cbcef On Wayland, fix nightly warnings
The new analysis suggests that we can remove mut.
2023-05-04 17:00:41 +03:00
Kirill Chibisov
bb26b0355c On macOS, fix empty marked text blocking input
Fixes #2775.
2023-05-04 17:00:41 +03:00
Kirill Chibisov
6f97ff174b On X11, fix nightly warnings
The new analysis suggests that we can remove mut.
2023-05-04 17:00:41 +03:00
Amandus Søve Thorsrud
0881a28302 Run Window::set_ime_position on main thread on macOS
Fixes #2756.
2023-05-04 17:00:41 +03:00
Xiaopeng Li
d37dac8f39 Fix potential panic (#2755)
* Fix potential panic

* Update CHANGELOG.md

* Use checked_div

---------

Co-authored-by: Xiaopeng Li <lixiaopeng.jetspark@bytedance.com>
2023-05-04 17:00:41 +03:00
Kirill Chibisov
64a97c16c7 Winit version 0.28.3 2023-03-16 22:03:19 +03:00
Mads Marquart
dec45bb09f Fix macos memory leaks (#2739)
* Use a weak reference from WinitView to WinitWindow

* Allow patched objc2 version

* Add changelog entry
2023-03-16 22:03:19 +03:00
Kirill Chibisov
d102c21792 Winit version 0.28.2 2023-03-02 14:35:02 +03:00
Dylan Scott
68ed564def On macOS, resize simple fullscreen on window move
Fixes #1118.
2023-03-02 14:35:02 +03:00
Nicolas Mazzon
339d57b646 On Windows, check whether CoCreateInstance succeeds 2023-03-02 14:35:02 +03:00
Kirill Chibisov
2e4dafc9fe On macOS, fix initial focused state
The synthetic focused event was queued after the real event was send
leading to focused issues on startup.

Fixes #2695.
2023-03-02 14:35:02 +03:00
John Nunley
0fbba02318 Update FEATURES.md 2023-03-02 14:35:02 +03:00
Kirill Chibisov
41e524f12c On Wayland, fix rounding issue in resizes 2023-03-02 14:35:02 +03:00
Simon Hausmann
644c47a6f8 Add support for Window::theme on the web (#2687) 2023-03-02 14:35:02 +03:00
Kirill Chibisov
0be08e574d On Wayland, fix rare crash on DPI change
While I don't understand the root cause for this issue, we can
dirty fix like that for now.
2023-03-02 14:35:02 +03:00
Kirill Chibisov
811cc5cdb7 On macOS, set resize increments only for live resize
Closes #2684 for macOS.
2023-03-02 14:35:02 +03:00
Sludge
230b37df56 Implement HasRawDisplayHandle for EventLoop (#2677)
* Implement `HasRawDisplayHandle` for `EventLoop`

* Add changelog entry
2023-03-02 14:35:02 +03:00
John Nunley
09bca59cf3 On Windows, name the waiter thread (#2672) 2023-03-02 14:35:02 +03:00
John Nunley
9fb8aaa6f4 Replace lazy window message ids with a slimmer version (#2598) 2023-03-02 14:35:02 +03:00
Kirill Chibisov
546ab7575e Winit version 0.28.1 2023-02-02 14:49:31 +03:00
Kirill Chibisov
3e258a377f Fix window drop on Wayland
In some scenarious of window dropping the callback for keyboard
may run after the window was dropped.
2023-02-02 14:45:38 +03:00
Kirill Chibisov
e5260da95b Winit version 0.28.0 2023-02-02 06:52:53 +03:00
21 changed files with 335 additions and 116 deletions

View File

@@ -8,6 +8,37 @@ And please only add new entries to the top of this list, right below the `# Unre
# Unreleased
# 0.28.5
- On macOS, fix `key_up` beind ignored when `Ime` is disabled.
# 0.28.4
- On macOS, fix empty marked text blocking regular input.
- On macOS, fixed potential panic when getting refresh rate.
- On macOS, fix crash when calling `Window::set_ime_position` from another thread.
# 0.28.3
- Fix macOS memory leaks.
# 0.28.2
- Implement `HasRawDisplayHandle` for `EventLoop`.
- On macOS, set resize increments only for live resizes.
- On Wayland, fix rare crash on DPI change
- Web: Added support for `Window::theme`.
- On Wayland, fix rounding issues when doing resize.
- On macOS, fix wrong focused state on startup.
- On Windows, fix crash on setting taskbar when using Visual Studio debugger.
- On macOS, resize simple fullscreen windows on windowDidChangeScreen events.
# 0.28.1
- On Wayland, fix crash when dropping a window in multi-window setup.
# 0.28.0
- On macOS, fixed `Ime::Commit` persisting for all input after interacting with `Ime`.
- On macOS, added `WindowExtMacOS::option_as_alt` and `WindowExtMacOS::set_option_as_alt`.
- On Windows, fix window size for maximized, undecorated windows.

View File

@@ -1,6 +1,6 @@
[package]
name = "winit"
version = "0.27.5"
version = "0.28.5"
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
description = "Cross-platform window creation library."
edition = "2021"
@@ -68,7 +68,7 @@ ndk = "0.7.0"
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
core-foundation = "0.9.3"
objc2 = "=0.3.0-beta.3"
objc2 = ">=0.3.0-beta.3, <0.3.0-beta.4" # Allow `0.3.0-beta.3.patch-leaks`
[target.'cfg(target_os = "macos")'.dependencies]
core-graphics = "0.22.3"

View File

@@ -179,7 +179,7 @@ Legend:
|Providing pointer to init Vulkan |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |**N/A**|**N/A** |
|Window decorations |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|✔️ |
|Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|Window resizing |✔️ |▢[#219]|✔️ |▢[#306] |**N/A**|**N/A**|✔️ |✔️ |
|Window resizing |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |✔️ |
|Window resize increments |❌ |✔️ |✔️ |❌ |**N/A**|**N/A**|**N/A**|**N/A** |
|Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|N/A |✔️ |
|Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
@@ -210,7 +210,7 @@ Legend:
|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |✔️ |**N/A** |
|Multitouch |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ |**N/A** |
|Keyboard events |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |✔️ |
|Drag & Drop |▢[#720] |▢[#720] |▢[#720] |[#306] |**N/A**|**N/A**|❓ |**N/A** |
|Drag & Drop |▢[#720] |▢[#720] |▢[#720] |[#720] |**N/A**|**N/A**|❓ |**N/A** |
|Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❓ |**N/A** |
|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❓ |**N/A** |
|Device movement events |❓ |❓ |❓ |❓ |❌ |❌ |❓ |**N/A** |
@@ -223,7 +223,7 @@ Changes in the API that have been agreed upon but aren't implemented across all
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |Redox OS|
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|New API for HiDPI ([#315] [#319]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |❓ |
|Event Loop 2.0 ([#459]) |✔️ |✔️ | |✔️ |✔️ |✔️ |❓ |❓ |
|Event Loop 2.0 ([#459]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |❓ |
|Keyboard Input ([#812]) |❌ |❌ |❌ |❌ |❌ |❌ |❓ |❓ |
### Completed API Reworks

View File

@@ -6,7 +6,7 @@
```toml
[dependencies]
winit = "0.27.5"
winit = "0.28.5"
```
## [Documentation](https://docs.rs/winit)

View File

@@ -5,6 +5,9 @@ use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEve
use winit::event_loop::EventLoop;
use winit::window::{Fullscreen, WindowBuilder};
#[cfg(target_os = "macos")]
use winit::platform::macos::WindowExtMacOS;
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
@@ -32,6 +35,8 @@ fn main() {
println!("- Esc\tExit");
println!("- F\tToggle exclusive fullscreen mode");
println!("- B\tToggle borderless mode");
#[cfg(target_os = "macos")]
println!("- C\tToggle simple fullscreen mode");
println!("- S\tNext screen");
println!("- M\tNext mode for this screen");
println!("- D\tToggle window decorations");
@@ -67,6 +72,10 @@ fn main() {
println!("Setting mode: {fullscreen:?}");
window.set_fullscreen(fullscreen);
}
#[cfg(target_os = "macos")]
VirtualKeyCode::C => {
window.set_simple_fullscreen(!window.simple_fullscreen());
}
VirtualKeyCode::S => {
monitor_index += 1;
if let Some(mon) = elwt.available_monitors().nth(monitor_index) {

View File

@@ -313,6 +313,13 @@ impl<T> EventLoop<T> {
}
}
unsafe impl<T> HasRawDisplayHandle for EventLoop<T> {
/// Returns a [`raw_window_handle::RawDisplayHandle`] for the event loop.
fn raw_display_handle(&self) -> RawDisplayHandle {
self.event_loop.window_target().p.raw_display_handle()
}
}
impl<T> Deref for EventLoop<T> {
type Target = EventLoopWindowTarget<T>;
fn deref(&self) -> &EventLoopWindowTarget<T> {

View File

@@ -19,6 +19,7 @@ use sctk::environment::Environment;
use sctk::seat::pointer::{ThemeManager, ThemeSpec};
use sctk::WaylandSource;
use crate::dpi::{LogicalSize, PhysicalSize};
use crate::event::{Event, StartCause, WindowEvent};
use crate::event_loop::{ControlFlow, EventLoopWindowTarget as RootEventLoopWindowTarget};
use crate::platform_impl::platform::sticky_exit_callback;
@@ -385,7 +386,7 @@ impl<T: 'static> EventLoop<T> {
let window_size = window_compositor_update.size.unwrap_or(*size);
*size = window_size;
window_size.to_physical(scale_factor)
logical_to_physical_rounded(window_size, scale_factor)
});
sticky_exit_callback(
@@ -430,7 +431,7 @@ impl<T: 'static> EventLoop<T> {
} else {
*window_size = size;
let scale_factor = window_handle.scale_factor();
let physical_size = size.to_physical(scale_factor);
let physical_size = logical_to_physical_rounded(size, scale_factor);
Some(physical_size)
};
@@ -599,3 +600,10 @@ impl<T: 'static> EventLoop<T> {
.map_err(|error| error.into())
}
}
// The default routine does floor, but we need round on Wayland.
fn logical_to_physical_rounded(size: LogicalSize<u32>, scale_factor: f64) -> PhysicalSize<u32> {
let width = size.width as f64 * scale_factor;
let height = size.height as f64 * scale_factor;
(width.round(), height.round()).into()
}

View File

@@ -24,7 +24,10 @@ pub(super) fn handle_keyboard(
KeyboardEvent::Enter { surface, .. } => {
let window_id = wayland::make_wid(&surface);
let window_handle = winit_state.window_map.get_mut(&window_id).unwrap();
let window_handle = match winit_state.window_map.get_mut(&window_id) {
Some(window_handle) => window_handle,
None => return,
};
window_handle.has_focus.store(true, Ordering::Relaxed);
// Window gained focus.
@@ -39,7 +42,14 @@ pub(super) fn handle_keyboard(
inner.target_window_id = Some(window_id);
}
KeyboardEvent::Leave { surface, .. } => {
// Reset the id.
inner.target_window_id = None;
let window_id = wayland::make_wid(&surface);
let window_handle = match winit_state.window_map.get_mut(&window_id) {
Some(window_handle) => window_handle,
None => return,
};
// Notify that no modifiers are being pressed.
if !inner.modifiers_state.borrow().is_empty() {
@@ -49,14 +59,10 @@ pub(super) fn handle_keyboard(
);
}
let window_handle = winit_state.window_map.get_mut(&window_id).unwrap();
window_handle.has_focus.store(false, Ordering::Relaxed);
// Window lost focus.
event_sink.push_window_event(WindowEvent::Focused(false), window_id);
// Reset the id.
inner.target_window_id = None;
}
KeyboardEvent::Key {
rawkey,

View File

@@ -45,9 +45,6 @@ pub(super) fn handle_pointer(
pointer_data.latest_enter_serial.replace(serial);
let window_id = wayland::make_wid(&surface);
if !winit_state.window_map.contains_key(&window_id) {
return;
}
let window_handle = match winit_state.window_map.get_mut(&window_id) {
Some(window_handle) => window_handle,
None => return,

View File

@@ -128,6 +128,12 @@ impl Window {
let surface = event_loop_window_target
.env
.create_surface_with_scale_callback(move |scale, surface, mut dispatch_data| {
// While I'm not sure how this could happen, we can safely ignore it
// for now as a quickfix.
if !surface.as_ref().is_alive() {
return;
}
let winit_state = dispatch_data.get::<WinitState>().unwrap();
// Get the window that received the event.
@@ -164,12 +170,12 @@ impl Window {
use sctk::window::{Event, State};
let winit_state = dispatch_data.get::<WinitState>().unwrap();
let mut window_compositor_update = winit_state
let window_compositor_update = winit_state
.window_compositor_updates
.get_mut(&window_id)
.unwrap();
let mut window_user_requests = winit_state
let window_user_requests = winit_state
.window_user_requests
.get_mut(&window_id)
.unwrap();

View File

@@ -384,7 +384,7 @@ impl UnownedWindow {
}
}
let mut shared_state = window.shared_state.get_mut().unwrap();
let shared_state = window.shared_state.get_mut().unwrap();
shared_state.min_inner_size = min_inner_size.map(Into::into);
shared_state.max_inner_size = max_inner_size.map(Into::into);
shared_state.resize_increments = window_attrs.resize_increments;

View File

@@ -230,7 +230,9 @@ impl MonitorHandle {
return None;
}
Some((time.time_scale as i64 / time.time_value * 1000) as u32)
(time.time_scale as i64)
.checked_div(time.time_value)
.map(|v| (v * 1000) as u32)
}
}

View File

@@ -2,10 +2,10 @@ use std::ops::Deref;
use dispatch::Queue;
use objc2::foundation::{is_main_thread, CGFloat, NSPoint, NSSize, NSString};
use objc2::rc::autoreleasepool;
use objc2::rc::{autoreleasepool, Id};
use crate::{
dpi::LogicalSize,
dpi::{LogicalPosition, LogicalSize},
platform_impl::platform::{
appkit::{NSScreen, NSWindow, NSWindowLevel, NSWindowStyleMask},
ffi,
@@ -201,3 +201,11 @@ pub(crate) fn close_sync(window: &NSWindow) {
});
});
}
pub(crate) fn set_ime_position_sync(window: &WinitWindow, logical_spot: LogicalPosition<f64>) {
let window = MainThreadSafe(window);
run_on_main(move || {
// TODO(madsmtm): Remove the need for this
unsafe { Id::from_shared(window.view()) }.set_ime_position(logical_spot);
});
}

View File

@@ -7,7 +7,7 @@ use objc2::foundation::{
NSArray, NSAttributedString, NSAttributedStringKey, NSCopying, NSMutableAttributedString,
NSObject, NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger,
};
use objc2::rc::{Id, Owned, Shared};
use objc2::rc::{Id, Owned, Shared, WeakId};
use objc2::runtime::{Object, Sel};
use objc2::{class, declare_class, msg_send, msg_send_id, sel, ClassType};
@@ -55,8 +55,9 @@ enum ImeState {
/// The IME events are disabled, so only `ReceivedCharacter` is being sent to the user.
Disabled,
/// The IME events are enabled.
Enabled,
/// The ground state of enabled IME input. It means that both Preedit and regular keyboard
/// input could be start from it.
Ground,
/// The IME is in preedit.
Preedit,
@@ -136,7 +137,8 @@ declare_class!(
#[derive(Debug)]
#[allow(non_snake_case)]
pub(super) struct WinitView {
_ns_window: IvarDrop<Id<WinitWindow, Shared>>,
// Weak reference because the window keeps a strong reference to the view
_ns_window: IvarDrop<Box<WeakId<WinitWindow>>>,
pub(super) state: IvarDrop<Box<ViewState>>,
marked_text: IvarDrop<Id<NSMutableAttributedString, Owned>>,
accepts_first_mouse: bool,
@@ -167,7 +169,10 @@ declare_class!(
forward_key_to_app: false,
};
Ivar::write(&mut this._ns_window, window.retain());
Ivar::write(
&mut this._ns_window,
Box::new(WeakId::new(&window.retain())),
);
Ivar::write(&mut this.state, Box::new(state));
Ivar::write(&mut this.marked_text, NSMutableAttributedString::new());
Ivar::write(&mut this.accepts_first_mouse, accepts_first_mouse);
@@ -321,7 +326,7 @@ declare_class!(
)
};
// Update marked text
// Update marked text.
*self.marked_text = marked_text;
// Notify IME is active if application still doesn't know it.
@@ -330,10 +335,11 @@ declare_class!(
self.queue_event(WindowEvent::Ime(Ime::Enabled));
}
// Don't update self.state to preedit when we've just commited a string, since the following
// preedit string will be None anyway.
if self.state.ime_state != ImeState::Commited {
if self.hasMarkedText() {
self.state.ime_state = ImeState::Preedit;
} else {
// In case the preedit was cleared, set IME into the Ground state.
self.state.ime_state = ImeState::Ground;
}
// Empty string basically means that there's no preedit, so indicate that by sending
@@ -359,7 +365,7 @@ declare_class!(
self.queue_event(WindowEvent::Ime(Ime::Preedit(String::new(), None)));
if self.is_ime_enabled() {
// Leave the Preedit self.state
self.state.ime_state = ImeState::Enabled;
self.state.ime_state = ImeState::Ground;
} else {
warn!("Expected to have IME enabled when receiving unmarkText");
}
@@ -447,8 +453,8 @@ declare_class!(
self.state.forward_key_to_app = true;
if self.hasMarkedText() && self.state.ime_state == ImeState::Preedit {
// Leave preedit so that we also report the keyup for this key
self.state.ime_state = ImeState::Enabled;
// Leave preedit so that we also report the key-up for this key.
self.state.ime_state = ImeState::Ground;
}
}
}
@@ -463,7 +469,6 @@ declare_class!(
self.state.input_source = input_source;
self.queue_event(WindowEvent::Ime(Ime::Disabled));
}
let was_in_preedit = self.state.ime_state == ImeState::Preedit;
// Get the characters from the event.
let ev_mods = event_mods(event);
@@ -484,7 +489,6 @@ declare_class!(
// we must send the `KeyboardInput` event during IME if it triggered
// `doCommandBySelector`. (doCommandBySelector means that the keyboard input
// is not handled by IME and should be handled by the application)
let mut text_commited = false;
if self.state.ime_allowed {
let new_event = if ignore_alt_characters {
replace_event_chars(event, &characters)
@@ -499,21 +503,25 @@ declare_class!(
if self.state.ime_state == ImeState::Commited {
// Remove any marked text, so normal input can continue.
*self.marked_text = NSMutableAttributedString::new();
self.state.ime_state = ImeState::Enabled;
text_commited = true;
}
}
let now_in_preedit = self.state.ime_state == ImeState::Preedit;
let scancode = event.scancode() as u32;
let virtual_keycode = retrieve_keycode(event);
self.update_potentially_stale_modifiers(event);
let ime_related = was_in_preedit || now_in_preedit || text_commited;
let had_ime_input = match self.state.ime_state {
ImeState::Commited => {
// Allow normal input after the commit.
self.state.ime_state = ImeState::Ground;
true
}
ImeState::Preedit => true,
_ => false,
};
if !ime_related || self.state.forward_key_to_app || !self.state.ime_allowed {
if !had_ime_input || self.state.forward_key_to_app {
#[allow(deprecated)]
self.queue_event(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
@@ -540,8 +548,8 @@ declare_class!(
self.update_potentially_stale_modifiers(event);
// We want to send keyboard input when we are not currently in preedit
if self.state.ime_state != ImeState::Preedit {
// We want to send keyboard input when we are currently in the ground state.
if matches!(self.state.ime_state, ImeState::Ground | ImeState::Disabled) {
#[allow(deprecated)]
self.queue_event(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
@@ -873,11 +881,11 @@ impl WinitView {
// (which is incompatible with `frameDidChange:`)
//
// unsafe { msg_send_id![self, window] }
(*self._ns_window).clone()
self._ns_window.load().expect("view to have a window")
}
fn window_id(&self) -> WindowId {
WindowId(self._ns_window.id())
WindowId(self.window().id())
}
fn queue_event(&self, event: WindowEvent<'static>) {

View File

@@ -150,7 +150,7 @@ pub struct SharedState {
pub(crate) target_fullscreen: Option<Option<Fullscreen>>,
pub maximized: bool,
pub standard_frame: Option<NSRect>,
is_simple_fullscreen: bool,
pub(crate) is_simple_fullscreen: bool,
pub saved_style: Option<NSWindowStyleMask>,
/// Presentation options saved before entering `set_simple_fullscreen`, and
/// restored upon exiting it. Also used when transitioning from Borderless to
@@ -160,6 +160,9 @@ pub struct SharedState {
save_presentation_opts: Option<NSApplicationPresentationOptions>,
pub current_theme: Option<Theme>,
/// The current resize incerments for the window content.
pub(crate) resize_increments: NSSize,
/// The state of the `Option` as `Alt`.
pub(crate) option_as_alt: OptionAsAlt,
}
@@ -301,6 +304,16 @@ impl WinitWindow {
};
this.map(|mut this| {
let resize_increments = match attrs
.resize_increments
.map(|i| i.to_logical::<f64>(this.scale_factor()))
{
Some(LogicalSize { width, height }) if width >= 1. && height >= 1. => {
NSSize::new(width, height)
}
_ => NSSize::new(1., 1.),
};
// Properly initialize the window's variables
//
// Ideally this should be done in an `init` method,
@@ -308,6 +321,7 @@ impl WinitWindow {
let state = SharedState {
resizable: attrs.resizable,
maximized: attrs.maximized,
resize_increments,
..Default::default()
};
Ivar::write(&mut this.shared_state, Box::new(Mutex::new(state)));
@@ -353,19 +367,6 @@ impl WinitWindow {
}
}
if let Some(increments) = attrs.resize_increments {
let increments = increments.to_logical(this.scale_factor());
let (w, h) = (increments.width, increments.height);
if w >= 1.0 && h >= 1.0 {
let size = NSSize::new(w, h);
// It was concluded (#2411) that there is never a use-case for
// "outer" resize increments, hence we set "inner" ones here.
// ("outer" in macOS being just resizeIncrements, and "inner" - contentResizeIncrements)
// This is consistent with X11 size hints behavior
this.setContentResizeIncrements(size);
}
}
if !pl_attrs.has_shadow {
this.setHasShadow(false);
}
@@ -465,6 +466,10 @@ impl WinitWindow {
let delegate = WinitWindowDelegate::new(&this, attrs.fullscreen.is_some());
// XXX Send `Focused(false)` right after creating the window delegate, so we won't
// obscure the real focused events on the startup.
delegate.queue_event(WindowEvent::Focused(false));
// Set fullscreen mode after we setup everything
this.set_fullscreen(attrs.fullscreen.map(Into::into));
@@ -484,8 +489,6 @@ impl WinitWindow {
this.set_maximized(attrs.maximized);
}
delegate.queue_event(WindowEvent::Focused(false));
Ok((this, delegate))
}
@@ -648,7 +651,9 @@ impl WinitWindow {
}
pub fn resize_increments(&self) -> Option<PhysicalSize<u32>> {
let increments = self.contentResizeIncrements();
let increments = self
.lock_shared_state("set_resize_increments")
.resize_increments;
let (w, h) = (increments.width, increments.height);
if w > 1.0 || h > 1.0 {
Some(LogicalSize::new(w, h).to_physical(self.scale_factor()))
@@ -658,12 +663,21 @@ impl WinitWindow {
}
pub fn set_resize_increments(&self, increments: Option<Size>) {
let size = increments
// XXX the resize increments are only used during live resizes.
let mut shared_state_lock = self.lock_shared_state("set_resize_increments");
shared_state_lock.resize_increments = increments
.map(|increments| {
let logical = increments.to_logical::<f64>(self.scale_factor());
NSSize::new(logical.width.max(1.0), logical.height.max(1.0))
})
.unwrap_or_else(|| NSSize::new(1.0, 1.0));
}
pub(crate) fn set_resize_increments_inner(&self, size: NSSize) {
// It was concluded (#2411) that there is never a use-case for
// "outer" resize increments, hence we set "inner" ones here.
// ("outer" in macOS being just resizeIncrements, and "inner" - contentResizeIncrements)
// This is consistent with X11 size hints behavior
self.setContentResizeIncrements(size);
}
@@ -1151,8 +1165,7 @@ impl WinitWindow {
pub fn set_ime_position(&self, spot: Position) {
let scale_factor = self.scale_factor();
let logical_spot = spot.to_logical(scale_factor);
// TODO(madsmtm): Remove the need for this
unsafe { Id::from_shared(self.view()) }.set_ime_position(logical_spot);
util::set_ime_position_sync(self, logical_spot);
}
#[inline]
@@ -1303,6 +1316,10 @@ impl WindowExtMacOS for WinitWindow {
// Tell our window's state that we're in fullscreen
shared_state_lock.is_simple_fullscreen = true;
// Drop shared state lock before calling app.setPresentationOptions, because
// it will call our windowDidChangeScreen listener which reacquires the lock
drop(shared_state_lock);
// Simulate pre-Lion fullscreen by hiding the dock and menu bar
let presentation_options =
NSApplicationPresentationOptions::NSApplicationPresentationAutoHideDock
@@ -1327,11 +1344,17 @@ impl WindowExtMacOS for WinitWindow {
self.set_style_mask_sync(new_mask);
shared_state_lock.is_simple_fullscreen = false;
if let Some(presentation_opts) = shared_state_lock.save_presentation_opts {
let save_presentation_opts = shared_state_lock.save_presentation_opts;
let frame = shared_state_lock.saved_standard_frame();
// Drop shared state lock before calling app.setPresentationOptions, because
// it will call our windowDidChangeScreen listener which reacquires the lock
drop(shared_state_lock);
if let Some(presentation_opts) = save_presentation_opts {
app.setPresentationOptions(presentation_opts);
}
let frame = shared_state_lock.saved_standard_frame();
self.setFrame_display(frame, true);
self.setMovable(true);

View File

@@ -3,7 +3,7 @@
use std::ptr;
use objc2::declare::{Ivar, IvarDrop};
use objc2::foundation::{NSArray, NSObject, NSString};
use objc2::foundation::{NSArray, NSObject, NSSize, NSString};
use objc2::rc::{autoreleasepool, Id, Shared};
use objc2::runtime::Object;
use objc2::{class, declare_class, msg_send, msg_send_id, sel, ClassType};
@@ -117,6 +117,23 @@ declare_class!(
self.emit_move_event();
}
#[sel(windowWillStartLiveResize:)]
fn window_will_start_live_resize(&mut self, _: Option<&Object>) {
trace_scope!("windowWillStartLiveResize:");
let increments = self
.window
.lock_shared_state("window_will_enter_fullscreen")
.resize_increments;
self.window.set_resize_increments_inner(increments);
}
#[sel(windowDidEndLiveResize:)]
fn window_did_end_live_resize(&mut self, _: Option<&Object>) {
trace_scope!("windowDidEndLiveResize:");
self.window.set_resize_increments_inner(NSSize::new(1., 1.));
}
// This won't be triggered if the move was part of a resize.
#[sel(windowDidMove:)]
fn window_did_move(&mut self, _: Option<&Object>) {
@@ -392,6 +409,20 @@ declare_class!(
self.queue_event(WindowEvent::ThemeChanged(theme));
}
}
#[sel(windowDidChangeScreen:)]
fn window_did_change_screen(&self, _: Option<&Object>) {
trace_scope!("windowDidChangeScreen:");
let is_simple_fullscreen = self
.window
.lock_shared_state("window_did_change_screen")
.is_simple_fullscreen;
if is_simple_fullscreen {
if let Some(screen) = self.window.screen() {
self.window.setFrame_display(screen.frame(), true);
}
}
}
}
);

View File

@@ -409,7 +409,20 @@ impl Window {
#[inline]
pub fn theme(&self) -> Option<Theme> {
None
web_sys::window()
.and_then(|window| {
window
.match_media("(prefers-color-scheme: dark)")
.ok()
.flatten()
})
.map(|media_query_list| {
if media_query_list.matches() {
Theme::Dark
} else {
Theme::Light
}
})
}
#[inline]

View File

@@ -10,6 +10,7 @@ use std::{
mem, panic, ptr,
rc::Rc,
sync::{
atomic::{AtomicU32, Ordering},
mpsc::{self, Receiver, Sender},
Arc, Mutex, MutexGuard,
},
@@ -204,7 +205,10 @@ impl<T: 'static> EventLoop<T> {
let thread_msg_target = create_event_target_window::<T>();
thread::spawn(move || wait_thread(thread_id, thread_msg_target));
thread::Builder::new()
.name("winit wait thread".to_string())
.spawn(move || wait_thread(thread_id, thread_msg_target))
.expect("Failed to spawn winit wait thread");
let wait_thread_id = get_wait_thread_id();
let runner_shared = Rc::new(EventLoopRunner::new(thread_msg_target, wait_thread_id));
@@ -382,11 +386,12 @@ fn get_wait_thread_id() -> u32 {
let result = GetMessageW(
&mut msg,
-1,
*SEND_WAIT_THREAD_ID_MSG_ID,
*SEND_WAIT_THREAD_ID_MSG_ID,
SEND_WAIT_THREAD_ID_MSG_ID.get(),
SEND_WAIT_THREAD_ID_MSG_ID.get(),
);
assert_eq!(
msg.message, *SEND_WAIT_THREAD_ID_MSG_ID,
msg.message,
SEND_WAIT_THREAD_ID_MSG_ID.get(),
"this shouldn't be possible. please open an issue with Winit. error code: {result}"
);
msg.lParam as u32
@@ -412,7 +417,7 @@ fn wait_thread(parent_thread_id: u32, msg_window_id: HWND) {
let cur_thread_id = GetCurrentThreadId();
PostThreadMessageW(
parent_thread_id,
*SEND_WAIT_THREAD_ID_MSG_ID,
SEND_WAIT_THREAD_ID_MSG_ID.get(),
0,
cur_thread_id as LPARAM,
);
@@ -436,9 +441,9 @@ fn wait_thread(parent_thread_id: u32, msg_window_id: HWND) {
DispatchMessageW(&msg);
}
if msg.message == *WAIT_UNTIL_MSG_ID {
if msg.message == WAIT_UNTIL_MSG_ID.get() {
wait_until_opt = Some(*WaitUntilInstantBox::from_raw(msg.lParam as *mut _));
} else if msg.message == *CANCEL_WAIT_UNTIL_MSG_ID {
} else if msg.message == CANCEL_WAIT_UNTIL_MSG_ID.get() {
wait_until_opt = None;
}
@@ -466,11 +471,11 @@ fn wait_thread(parent_thread_id: u32, msg_window_id: HWND) {
timeEndPeriod(period);
}
if resume_reason == WAIT_TIMEOUT {
PostMessageW(msg_window_id, *PROCESS_NEW_EVENTS_MSG_ID, 0, 0);
PostMessageW(msg_window_id, PROCESS_NEW_EVENTS_MSG_ID.get(), 0, 0);
wait_until_opt = None;
}
} else {
PostMessageW(msg_window_id, *PROCESS_NEW_EVENTS_MSG_ID, 0, 0);
PostMessageW(msg_window_id, PROCESS_NEW_EVENTS_MSG_ID.get(), 0, 0);
wait_until_opt = None;
}
}
@@ -556,7 +561,7 @@ impl EventLoopThreadExecutor {
let raw = Box::into_raw(boxed2);
let res = PostMessageW(self.target_window, *EXEC_MSG_ID, raw as usize, 0);
let res = PostMessageW(self.target_window, EXEC_MSG_ID.get(), raw as usize, 0);
assert!(
res != false.into(),
"PostMessage failed; is the messages queue full?"
@@ -586,7 +591,7 @@ impl<T: 'static> Clone for EventLoopProxy<T> {
impl<T: 'static> EventLoopProxy<T> {
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
unsafe {
if PostMessageW(self.target_window, *USER_EVENT_MSG_ID, 0, 0) != false.into() {
if PostMessageW(self.target_window, USER_EVENT_MSG_ID.get(), 0, 0) != false.into() {
self.event_send.send(event).ok();
Ok(())
} else {
@@ -598,40 +603,84 @@ impl<T: 'static> EventLoopProxy<T> {
type WaitUntilInstantBox = Box<Instant>;
/// A lazily-initialized window message ID.
pub struct LazyMessageId {
/// The ID.
id: AtomicU32,
/// The name of the message.
name: &'static str,
}
/// An invalid custom window ID.
const INVALID_ID: u32 = 0x0;
impl LazyMessageId {
/// Create a new `LazyId`.
const fn new(name: &'static str) -> Self {
Self {
id: AtomicU32::new(INVALID_ID),
name,
}
}
/// Get the message ID.
pub fn get(&self) -> u32 {
// Load the ID.
let id = self.id.load(Ordering::Relaxed);
if id != INVALID_ID {
return id;
}
// Register the message.
// SAFETY: We are sure that the pointer is a valid C string ending with '\0'.
assert!(self.name.ends_with('\0'));
let new_id = unsafe { RegisterWindowMessageA(self.name.as_ptr()) };
assert_ne!(
new_id,
0,
"RegisterWindowMessageA returned zero for '{}': {}",
self.name,
std::io::Error::last_os_error()
);
// Store the new ID. Since `RegisterWindowMessageA` returns the same value for any given string,
// the target value will always either be a). `INVALID_ID` or b). the correct ID. Therefore a
// compare-and-swap operation here (or really any consideration) is never necessary.
self.id.store(new_id, Ordering::Relaxed);
new_id
}
}
// Message sent by the `EventLoopProxy` when we want to wake up the thread.
// WPARAM and LPARAM are unused.
static USER_EVENT_MSG_ID: Lazy<u32> =
Lazy::new(|| unsafe { RegisterWindowMessageA("Winit::WakeupMsg\0".as_ptr()) });
static USER_EVENT_MSG_ID: LazyMessageId = LazyMessageId::new("Winit::WakeupMsg\0");
// Message sent when we want to execute a closure in the thread.
// WPARAM contains a Box<Box<dyn FnMut()>> that must be retrieved with `Box::from_raw`,
// and LPARAM is unused.
static EXEC_MSG_ID: Lazy<u32> =
Lazy::new(|| unsafe { RegisterWindowMessageA("Winit::ExecMsg\0".as_ptr()) });
static PROCESS_NEW_EVENTS_MSG_ID: Lazy<u32> =
Lazy::new(|| unsafe { RegisterWindowMessageA("Winit::ProcessNewEvents\0".as_ptr()) });
static EXEC_MSG_ID: LazyMessageId = LazyMessageId::new("Winit::ExecMsg\0");
static PROCESS_NEW_EVENTS_MSG_ID: LazyMessageId = LazyMessageId::new("Winit::ProcessNewEvents\0");
/// lparam is the wait thread's message id.
static SEND_WAIT_THREAD_ID_MSG_ID: Lazy<u32> =
Lazy::new(|| unsafe { RegisterWindowMessageA("Winit::SendWaitThreadId\0".as_ptr()) });
static SEND_WAIT_THREAD_ID_MSG_ID: LazyMessageId = LazyMessageId::new("Winit::SendWaitThreadId\0");
/// lparam points to a `Box<Instant>` signifying the time `PROCESS_NEW_EVENTS_MSG_ID` should
/// be sent.
static WAIT_UNTIL_MSG_ID: Lazy<u32> =
Lazy::new(|| unsafe { RegisterWindowMessageA("Winit::WaitUntil\0".as_ptr()) });
static CANCEL_WAIT_UNTIL_MSG_ID: Lazy<u32> =
Lazy::new(|| unsafe { RegisterWindowMessageA("Winit::CancelWaitUntil\0".as_ptr()) });
static WAIT_UNTIL_MSG_ID: LazyMessageId = LazyMessageId::new("Winit::WaitUntil\0");
static CANCEL_WAIT_UNTIL_MSG_ID: LazyMessageId = LazyMessageId::new("Winit::CancelWaitUntil\0");
// Message sent by a `Window` when it wants to be destroyed by the main thread.
// WPARAM and LPARAM are unused.
pub static DESTROY_MSG_ID: Lazy<u32> =
Lazy::new(|| unsafe { RegisterWindowMessageA("Winit::DestroyMsg\0".as_ptr()) });
pub static DESTROY_MSG_ID: LazyMessageId = LazyMessageId::new("Winit::DestroyMsg\0");
// WPARAM is a bool specifying the `WindowFlags::MARKER_RETAIN_STATE_ON_SIZE` flag. See the
// documentation in the `window_state` module for more information.
pub static SET_RETAIN_STATE_ON_SIZE_MSG_ID: Lazy<u32> =
Lazy::new(|| unsafe { RegisterWindowMessageA("Winit::SetRetainMaximized\0".as_ptr()) });
pub static SET_RETAIN_STATE_ON_SIZE_MSG_ID: LazyMessageId =
LazyMessageId::new("Winit::SetRetainMaximized\0");
static THREAD_EVENT_TARGET_WINDOW_CLASS: Lazy<Vec<u16>> =
Lazy::new(|| util::encode_wide("Winit Thread Event Target"));
/// When the taskbar is created, it registers a message with the "TaskbarCreated" string and then broadcasts this message to all top-level windows
/// <https://docs.microsoft.com/en-us/windows/win32/shell/taskbar#taskbar-creation-notification>
pub static TASKBAR_CREATED: Lazy<u32> =
Lazy::new(|| unsafe { RegisterWindowMessageA("TaskbarCreated\0".as_ptr()) });
pub static TASKBAR_CREATED: LazyMessageId = LazyMessageId::new("TaskbarCreated\0");
fn create_event_target_window<T: 'static>() -> HWND {
use windows_sys::Win32::UI::WindowsAndMessaging::CS_HREDRAW;
@@ -783,13 +832,18 @@ unsafe fn flush_paint_messages<T: 'static>(
unsafe fn process_control_flow<T: 'static>(runner: &EventLoopRunner<T>) {
match runner.control_flow() {
ControlFlow::Poll => {
PostMessageW(runner.thread_msg_target(), *PROCESS_NEW_EVENTS_MSG_ID, 0, 0);
PostMessageW(
runner.thread_msg_target(),
PROCESS_NEW_EVENTS_MSG_ID.get(),
0,
0,
);
}
ControlFlow::Wait => (),
ControlFlow::WaitUntil(until) => {
PostThreadMessageW(
runner.wait_thread_id(),
*WAIT_UNTIL_MSG_ID,
WAIT_UNTIL_MSG_ID.get(),
0,
Box::into_raw(WaitUntilInstantBox::new(until)) as isize,
);
@@ -2244,16 +2298,16 @@ unsafe fn public_window_callback_inner<T: 'static>(
}
_ => {
if msg == *DESTROY_MSG_ID {
if msg == DESTROY_MSG_ID.get() {
DestroyWindow(window);
0
} else if msg == *SET_RETAIN_STATE_ON_SIZE_MSG_ID {
} else if msg == SET_RETAIN_STATE_ON_SIZE_MSG_ID.get() {
let mut window_state = userdata.window_state_lock();
window_state.set_window_flags_in_place(|f| {
f.set(WindowFlags::MARKER_RETAIN_STATE_ON_SIZE, wparam != 0)
});
0
} else if msg == *TASKBAR_CREATED {
} else if msg == TASKBAR_CREATED.get() {
let window_state = userdata.window_state_lock();
set_skip_taskbar(window, window_state.skip_taskbar);
DefWindowProcW(window, msg, wparam, lparam)
@@ -2442,21 +2496,21 @@ unsafe extern "system" fn thread_event_target_callback<T: 'static>(
DefWindowProcW(window, msg, wparam, lparam)
}
_ if msg == *USER_EVENT_MSG_ID => {
_ if msg == USER_EVENT_MSG_ID.get() => {
if let Ok(event) = userdata.user_event_receiver.recv() {
userdata.send_event(Event::UserEvent(event));
}
0
}
_ if msg == *EXEC_MSG_ID => {
_ if msg == EXEC_MSG_ID.get() => {
let mut function: ThreadExecFn = Box::from_raw(wparam as *mut _);
function();
0
}
_ if msg == *PROCESS_NEW_EVENTS_MSG_ID => {
_ if msg == PROCESS_NEW_EVENTS_MSG_ID.get() => {
PostThreadMessageW(
userdata.event_loop_runner.wait_thread_id(),
*CANCEL_WAIT_UNTIL_MSG_ID,
CANCEL_WAIT_UNTIL_MSG_ID.get(),
0,
0,
);

View File

@@ -841,7 +841,7 @@ impl Drop for Window {
unsafe {
// The window must be destroyed from the same thread that created it, so we send a
// custom message to be handled by our callback to do the actual work.
PostMessageW(self.hwnd(), *DESTROY_MSG_ID, 0, 0);
PostMessageW(self.hwnd(), DESTROY_MSG_ID.get(), 0, 0);
}
}
}
@@ -1218,10 +1218,13 @@ unsafe fn taskbar_mark_fullscreen(handle: HWND, fullscreen: bool) {
&IID_ITaskbarList2,
&mut task_bar_list2 as *mut _ as *mut _,
);
if hr != S_OK {
// In visual studio retrieving the taskbar list fails
return;
}
let hr_init = (*(*task_bar_list2).lpVtbl).parent.HrInit;
if hr != S_OK || hr_init(task_bar_list2.cast()) != S_OK {
if hr_init(task_bar_list2.cast()) != S_OK {
// In some old windows, the taskbar object could not be created, we just ignore it
return;
}
@@ -1247,10 +1250,13 @@ pub(crate) unsafe fn set_skip_taskbar(hwnd: HWND, skip: bool) {
&IID_ITaskbarList,
&mut task_bar_list as *mut _ as *mut _,
);
if hr != S_OK {
// In visual studio retrieving the taskbar list fails
return;
}
let hr_init = (*(*task_bar_list).lpVtbl).HrInit;
if hr != S_OK || hr_init(task_bar_list.cast()) != S_OK {
if hr_init(task_bar_list.cast()) != S_OK {
// In some old windows, the taskbar object could not be created, we just ignore it
return;
}

View File

@@ -391,7 +391,12 @@ impl WindowFlags {
let (style, style_ex) = new.to_window_styles();
unsafe {
SendMessageW(window, *event_loop::SET_RETAIN_STATE_ON_SIZE_MSG_ID, 1, 0);
SendMessageW(
window,
event_loop::SET_RETAIN_STATE_ON_SIZE_MSG_ID.get(),
1,
0,
);
// This condition is necessary to avoid having an unrestorable window
if !new.contains(WindowFlags::MINIMIZED) {
@@ -412,7 +417,12 @@ impl WindowFlags {
// Refresh the window frame
SetWindowPos(window, 0, 0, 0, 0, 0, flags);
SendMessageW(window, *event_loop::SET_RETAIN_STATE_ON_SIZE_MSG_ID, 0, 0);
SendMessageW(
window,
event_loop::SET_RETAIN_STATE_ON_SIZE_MSG_ID.get(),
0,
0,
);
}
}
}

View File

@@ -1126,7 +1126,7 @@ impl Window {
/// ## Platform-specific
///
/// - **macOS:** This is an app-wide setting.
/// - **iOS / Android / Web / Wayland / x11 / Orbital:** Unsupported.
/// - **iOS / Android / Wayland / x11 / Orbital:** Unsupported.
#[inline]
pub fn theme(&self) -> Option<Theme> {
self.window.theme()