Compare commits

..

1 Commits

Author SHA1 Message Date
Mads Marquart
12e2d24e10 macOS: Feature-gate CGSSetWindowBackgroundBlurRadius
Behind the Cargo feature `private-apple-apis`.
2026-03-24 15:34:09 +01:00
47 changed files with 453 additions and 758 deletions

View File

@@ -136,7 +136,7 @@ jobs:
- name: Generate lockfile
# Also updates the crates.io index
run: cargo generate-lockfile && cargo update -p smol_str --precise 0.3.2 && cargo update -p unicode-segmentation --precise 1.12.0 && cargo update -p wayland-protocols --precise 0.32.12
run: cargo generate-lockfile && cargo update -p smol_str --precise 0.3.2
- name: Install GCC Multilib
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')
@@ -290,14 +290,31 @@ jobs:
key: cargo-${{ matrix.toolchain }}-${{ matrix.platform.name }}-${{ hashFiles('Cargo.lock') }}
cargo-deny:
name: Run cargo-deny
name: Run cargo-deny on ${{ matrix.platform.name }}
runs-on: ubuntu-latest
# TODO: remove this matrix when https://github.com/EmbarkStudios/cargo-deny/issues/324 is resolved
strategy:
fail-fast: false
matrix:
platform:
- { name: 'Android', target: aarch64-linux-android }
- { name: 'iOS', target: aarch64-apple-ios }
- { name: 'Linux', target: x86_64-unknown-linux-gnu }
- { name: 'macOS', target: aarch64-apple-darwin }
- { name: 'Redox OS', target: x86_64-unknown-redox }
- { name: 'Web', target: wasm32-unknown-unknown }
- { name: 'Windows GNU', target: x86_64-pc-windows-gnu }
- { name: 'Windows MSVC', target: x86_64-pc-windows-msvc }
steps:
- uses: taiki-e/checkout-action@v1
- uses: EmbarkStudios/cargo-deny-action@v2
with:
command: check
log-level: error
manifest-path: winit/Cargo.toml
arguments: --all-features --target ${{ matrix.platform.target }}
eslint:
name: ESLint

View File

@@ -32,7 +32,7 @@ jobs:
cargo doc --no-deps -Z rustdoc-map -Z rustdoc-scrape-examples --features=serde,mint,android-native-activity
- name: Setup Pages
uses: actions/configure-pages@v6
uses: actions/configure-pages@v5
- name: Fix permissions
run: |
@@ -47,4 +47,4 @@ jobs:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v5
uses: actions/deploy-pages@v4

View File

@@ -1,25 +1,27 @@
# https://embarkstudios.github.io/cargo-deny
# cargo install cargo-deny
# cargo update && cargo deny check
# cargo update && cargo deny --target aarch64-apple-ios check
# Note: running just `cargo deny check` without a `--target` will result in
# false positives due to https://github.com/EmbarkStudios/cargo-deny/issues/324
[graph]
all-features = true
exclude-dev = true
targets = [
"aarch64-apple-darwin",
"aarch64-apple-ios",
"aarch64-linux-android",
"i686-pc-windows-gnu",
"i686-pc-windows-msvc",
"i686-unknown-linux-gnu",
{ triple = "aarch64-apple-darwin" },
{ triple = "aarch64-apple-ios" },
{ triple = "aarch64-linux-android" },
{ triple = "i686-pc-windows-gnu" },
{ triple = "i686-pc-windows-msvc" },
{ triple = "i686-unknown-linux-gnu" },
{ triple = "wasm32-unknown-unknown", features = [
"atomics",
] },
"x86_64-apple-darwin",
"x86_64-apple-ios",
"x86_64-pc-windows-gnu",
"x86_64-pc-windows-msvc",
"x86_64-unknown-linux-gnu",
"x86_64-unknown-redox",
{ triple = "x86_64-apple-darwin" },
{ triple = "x86_64-apple-ios" },
{ triple = "x86_64-pc-windows-gnu" },
{ triple = "x86_64-pc-windows-msvc" },
{ triple = "x86_64-unknown-linux-gnu" },
{ triple = "x86_64-unknown-redox" },
]
[licenses]
@@ -31,7 +33,6 @@ allow = [
"MIT", # https://tldrlegal.com/license/mit-license
"Unicode-3.0", # https://spdx.org/licenses/Unicode-3.0.html
"Zlib", # https://spdx.org/licenses/Zlib.html
"MPL-2.0", # https://www.mozilla.org/en-US/MPL/2.0/
]
confidence-threshold = 1.0
private = { ignore = true }
@@ -39,23 +40,46 @@ private = { ignore = true }
[bans]
multiple-versions = "deny"
skip = [
{ crate = "jni-sys@0.3", reason = "uses the semver trick to depend on v0.4, but `ndk` hasn't been updated to v0.4 yet" },
{ crate = "thiserror@1.0", reason = "dep of `ndk` crate, yet to be updated" },
{ crate = "thiserror-impl@1.0", reason = "dep of `thiserror`" },
{ crate = "objc2@0.5", reason = "used by crossfont" },
{ crate = "objc2-foundation@0.2", reason = "used by crossfont" },
]
skip-tree = [
{ crate = "windows-sys", reason = "foundational but bumps fairly often, nothing we can do about it not having a shared version" },
{ crate = "bitflags@1", reason = "the ecosystem is in the process of migrating" },
{ crate = "rustix@0.38", reason = "the ecosystem is in the process of migrating" },
{ crate = "linux-raw-sys@0.4", reason = "the ecosystem is in the process of migrating" },
]
wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed
[bans.build]
bypass = [
{ crate = "android-activity", allow-globs = ["android-games-sdk/import-games-sdk.sh"] },
{ crate = "freetype-sys", allow-globs = ["freetype2/*"] },
# `crossfont` still depends (partially transitively) on `winapi`.
{ crate = "winapi-i686-pc-windows-gnu", allow-globs = ["lib/lib*.a"] },
{ crate = "winapi-x86_64-pc-windows-gnu", allow-globs = ["lib/lib*.a"] },
]
include-archives = true
interpreted = "deny"
[[bans.build.bypass]]
allow = [
{ path = "generate-bindings.sh", checksum = "268ec23248218d779e33853cdc60e2985e70214ff004716cd734270de1f6b561" },
]
crate = "android-activity"
[[bans.build.bypass]]
allow-globs = ["ci/*", "githooks/*"]
crate = "zerocopy"
[[bans.build.bypass]]
allow-globs = ["cherry-pick-stable.sh"]
crate = "libc"
[[bans.build.bypass]]
allow-globs = ["freetype2/*"]
crate = "freetype-sys"
[[bans.build.bypass]]
allow-globs = ["lib/*.a"]
crate = "windows_i686_gnu"
[[bans.build.bypass]]
allow-globs = ["lib/*.lib"]
crate = "windows_i686_msvc"
[[bans.build.bypass]]
allow-globs = ["lib/*.a"]
crate = "windows_x86_64_gnu"
[[bans.build.bypass]]
allow-globs = ["lib/*.lib"]
crate = "windows_x86_64_msvc"

View File

@@ -9,6 +9,7 @@ rust-version.workspace = true
version.workspace = true
[features]
private-apple-apis = []
serde = ["dep:serde", "bitflags/serde", "smol_str/serde", "dpi/serde"]
[dependencies]
@@ -107,7 +108,7 @@ objc2-foundation = { workspace = true, features = [
"NSThread",
"NSValue",
] }
winit-common = { workspace = true, features = ["core-foundation", "event-handler", "foundation"] }
winit-common = { workspace = true, features = ["core-foundation", "event-handler"] }
[dev-dependencies]
winit.workspace = true

View File

@@ -1,8 +1,8 @@
#![allow(clippy::unnecessary_cast)]
use std::cell::Cell;
use std::mem;
use std::rc::Rc;
use std::{mem, ptr};
use dispatch2::MainThreadBound;
use objc2::runtime::{Imp, Sel};
@@ -80,7 +80,9 @@ pub(crate) fn override_send_event(global_app: &NSApplication) {
let overridden = unsafe { mem::transmute::<SendEvent, Imp>(send_event) };
// If we've already overridden the method, don't do anything.
if ptr::fn_addr_eq(overridden, method.implementation()) {
// FIXME(madsmtm): Use `std::ptr::fn_addr_eq` (Rust 1.85) once available in MSRV.
#[allow(unknown_lints, unpredictable_function_pointer_comparisons)]
if overridden == method.implementation() {
return;
}

View File

@@ -14,7 +14,6 @@ use objc2_foundation::{NSNotificationCenter, NSObjectProtocol};
use rwh_06::HasDisplayHandle;
use tracing::debug_span;
use winit_common::core_foundation::{MainRunLoop, MainRunLoopObserver, tracing_observers};
use winit_common::foundation::create_observer;
use winit_core::application::ApplicationHandler;
use winit_core::cursor::{CustomCursor as CoreCustomCursor, CustomCursorSource};
use winit_core::error::{EventLoopError, RequestError};
@@ -31,6 +30,7 @@ use super::app_state::AppState;
use super::cursor::CustomCursor;
use super::event::dummy_event;
use super::monitor;
use super::notification_center::create_observer;
use crate::ActivationPolicy;
use crate::window::Window;

View File

@@ -4,8 +4,6 @@
use std::ffi::c_void;
use objc2::ffi::NSInteger;
use objc2::runtime::AnyObject;
use objc2_core_foundation::{CFString, CFUUID, cf_type};
use objc2_core_graphics::CGDirectDisplayID;
@@ -28,17 +26,6 @@ unsafe extern "C" {
pub fn CGDisplayGetDisplayIDFromUUID(uuid: &CFUUID) -> CGDirectDisplayID;
}
#[link(name = "CoreGraphics", kind = "framework")]
unsafe extern "C" {
// Wildly used private APIs; Apple uses them for their Terminal.app.
pub fn CGSMainConnectionID() -> *mut AnyObject;
pub fn CGSSetWindowBackgroundBlurRadius(
connection_id: *mut AnyObject,
window_id: NSInteger,
radius: i64,
) -> i32;
}
#[repr(transparent)]
pub struct TISInputSource(std::ffi::c_void);

View File

@@ -76,6 +76,7 @@ mod event_loop;
mod ffi;
mod menu;
mod monitor;
mod notification_center;
mod observer;
mod view;
mod window;

View File

@@ -1,3 +1,4 @@
// NOTE: This is symlinked to be contained in both the AppKit and UIKit implementations.
use std::ptr::NonNull;
use block2::RcBlock;
@@ -11,7 +12,7 @@ use objc2_foundation::{
///
/// This is used in Winit as an alternative to declaring an application delegate, as we want to
/// give the user full control over those.
pub fn create_observer(
pub(crate) fn create_observer(
center: &NSNotificationCenter,
name: &NSNotificationName,
handler: impl Fn(&NSNotification) + 'static,

View File

@@ -3,13 +3,13 @@ use std::cell::{Cell, RefCell};
use std::collections::{HashMap, VecDeque};
use std::rc::Rc;
use dpi::{LogicalPosition, PhysicalSize};
use dpi::{LogicalPosition, LogicalSize};
use objc2::rc::Retained;
use objc2::runtime::{AnyObject, Sel};
use objc2::{AnyThread, DefinedClass, MainThreadMarker, define_class, msg_send};
use objc2_app_kit::{
NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient, NSTrackingArea,
NSTrackingAreaOptions, NSView, NSViewLayerContentsRedrawPolicy, NSWindow,
NSTrackingAreaOptions, NSView, NSWindow,
};
use objc2_core_foundation::CGRect;
use objc2_foundation::{
@@ -161,20 +161,10 @@ define_class!(
// resize occurring.
// 2. Even when a window resize does occur on a new tabbed window, it contains the wrong
// size (includes tab height).
self.surface_resized();
// During live resize, AppKit may not let the normal event loop reach its next redraw
// point before stretching the current layer contents. Redraw immediately after the
// app has observed the new surface size.
self.redraw_during_live_resize();
}
#[unsafe(method(viewDidChangeBackingProperties))]
fn view_did_change_backing_properties(&self) {
let _entered = debug_span!("viewDidChangeBackingProperties").entered();
// Moving between displays or changing scale can alter the drawable backing size
// without a matching frame-size change.
self.surface_resized();
self.redraw_during_live_resize();
let rect = self.frame();
let logical_size = LogicalSize::new(rect.size.width as f64, rect.size.height as f64);
let size = logical_size.to_physical::<u32>(self.scale_factor());
self.queue_event(WindowEvent::SurfaceResized(size));
}
#[unsafe(method(drawRect:))]
@@ -283,26 +273,27 @@ define_class!(
self.ivars().ime_state.set(ImeState::Ground);
}
let string = string.to_string();
let cursor_range = if string.is_empty() {
// An empty string basically means that there's no preedit, so indicate that by
// sending a `None` cursor range.
None
} else {
// Convert the selected range from UTF-16 code unit indices to UTF-8 byte
// offsets. `utf16_to_utf8_offset` is defensive: it snaps an offset that would
// split a surrogate pair down to the character boundary and clamps an
// out-of-bounds offset to the string length, so no `NSRangeException` is
// possible and the resulting range can never be inverted (`lower <= upper`).
// IMEs are known to send both mid-surrogate and out-of-bounds offsets (e.g.
// native Pinyin, see https://github.com/alacritty/alacritty/issues/8791).
let lowerbound_utf8 = utf16_to_utf8_offset(&string, selected_range.location);
let upperbound_utf8 = utf16_to_utf8_offset(&string, selected_range.end());
// Clamp to string length to avoid NSRangeException from out-of-bounds
// indices sent by macOS IME (e.g. native Pinyin, see
// https://github.com/alacritty/alacritty/issues/8791).
let len = string.length();
let location = selected_range.location.min(len);
let end = selected_range.end().min(len);
// Convert the selected range from UTF-16 indices to UTF-8 indices.
let sub_string_a = string.substringToIndex(location);
let sub_string_b = string.substringToIndex(end);
let lowerbound_utf8 = sub_string_a.len();
let upperbound_utf8 = sub_string_b.len();
Some((lowerbound_utf8, upperbound_utf8))
};
// Send WindowEvent for updating marked text
self.queue_event(WindowEvent::Ime(Ime::Preedit(string, cursor_range)));
self.queue_event(WindowEvent::Ime(Ime::Preedit(string.to_string(), cursor_range)));
}
#[unsafe(method(unmarkText))]
@@ -805,10 +796,6 @@ impl WinitView {
let this: Retained<Self> = unsafe { msg_send![super(this), init] };
*this.ivars().input_source.borrow_mut() = this.current_input_source();
// Ask AppKit to redisplay the layer while the view is being resized so layer-backed
// surfaces keep painting.
this.setLayerContentsRedrawPolicy(NSViewLayerContentsRedrawPolicy::DuringViewResize);
// `MouseEnteredAndExited` enables receiving events through `mouseEntered:` and
// `mouseExited:`.
//
@@ -867,37 +854,6 @@ impl WinitView {
});
}
fn surface_resized(&self) {
let Some(window) = (**self).window() else {
return;
};
let size = self.surface_size();
let window_id = window_id(&window);
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
app.window_event(event_loop, window_id, WindowEvent::SurfaceResized(size));
});
}
/// Returns the drawable size from the view's backing-coordinate bounds.
pub(super) fn surface_size(&self) -> PhysicalSize<u32> {
// The view bounds are authoritative for full-size content views and during live resize.
// Deriving this from the window frame can exclude custom titlebar content or be stale.
let backing_bounds = self.convertRectToBacking(self.bounds());
PhysicalSize::new(
backing_bounds.size.width.round().max(0.0) as u32,
backing_bounds.size.height.round().max(0.0) as u32,
)
}
fn redraw_during_live_resize(&self) {
let Some(window) = (**self).window() else {
return;
};
if window.inLiveResize() {
self.ivars().app_state.handle_redraw(window_id(&window));
}
}
fn scale_factor(&self) -> f64 {
self.window().backingScaleFactor() as f64
}
@@ -1214,92 +1170,3 @@ fn replace_event(event: &NSEvent, option_as_alt: OptionAsAlt) -> Retained<NSEven
event.copy()
}
}
/// Convert a UTF-16 code unit offset into the corresponding UTF-8 byte offset within `s`.
///
/// IMEs are not required to send well-formed offsets, so this is defensive: an offset that
/// would split a surrogate pair is snapped down to the start of that character, and an
/// out-of-bounds offset is clamped to the end of the string (e.g. native Pinyin sends
/// out-of-bounds indices, see <https://github.com/alacritty/alacritty/issues/8791>).
///
/// The mapping is monotone non-decreasing, so applying it to the location and end of an
/// `NSRange` (where `location <= end`) can never produce an inverted byte range.
fn utf16_to_utf8_offset(s: &str, utf16_offset: usize) -> usize {
let mut utf16_pos = 0;
for (utf8_pos, ch) in s.char_indices() {
if utf16_pos >= utf16_offset {
return utf8_pos;
}
utf16_pos += ch.len_utf16();
// The target offset lands strictly inside this character's UTF-16 representation,
// i.e. it splits a surrogate pair: snap down to the character boundary.
if utf16_pos > utf16_offset {
return utf8_pos;
}
}
s.len()
}
#[cfg(test)]
mod tests {
use super::*;
/// Apply the UTF-16 -> UTF-8 conversion to both ends of a `selectedRange {loc, len}`,
/// mirroring what `set_marked_text` does for the emitted `Ime::Preedit` cursor range.
fn convert(s: &str, loc: usize, len: usize) -> (usize, usize) {
(utf16_to_utf8_offset(s, loc), utf16_to_utf8_offset(s, loc + len))
}
#[test]
fn mid_surrogate_offset_snaps_down() {
// "😀a": 😀 is one char = 2 UTF-16 units = 4 UTF-8 bytes; offset 1 is mid-pair.
assert_eq!(utf16_to_utf8_offset("\u{1F600}a", 1), 0);
// Offset 2 is the boundary just after the pair.
assert_eq!(utf16_to_utf8_offset("\u{1F600}a", 2), 4);
}
#[test]
fn no_longer_inverted() {
// "a😀b" with selectedRange {1,1}: previously emitted (1, 0) -- lower > upper, a
// slice-panic vector. The boundary-snapping conversion keeps lower <= upper.
assert_eq!(convert("a\u{1F600}b", 1, 1), (1, 1));
}
#[test]
fn prefix_preserved_on_mid_pair_collapse() {
// "a😀b" with selectedRange {2,0}: previously collapsed to (0, 0), discarding the
// valid "a" prefix; now snaps to the char boundary after "a".
assert_eq!(convert("a\u{1F600}b", 2, 0), (1, 1));
}
#[test]
fn out_of_bounds_clamps_to_len() {
// Subsumes the #4494 `.min(len)` clamp: an out-of-bounds offset maps to the string
// length instead of triggering an NSRangeException.
assert_eq!(convert("\u{1F600}a", 99, 0), (5, 5));
}
#[test]
fn well_formed_inputs_are_identity() {
// The common case (well-formed boundary indices) must be byte-for-byte unchanged.
assert_eq!(convert("a\u{1F600}b", 3, 0), (5, 5));
assert_eq!(convert("a\u{1F600}b", 4, 0), (6, 6));
// BMP multi-byte (Japanese): each char is 1 UTF-16 unit and 3 UTF-8 bytes.
assert_eq!(convert("\u{3053}\u{3093}", 1, 1), (3, 6));
}
#[test]
fn monotone_non_decreasing() {
// Sweep every UTF-16 offset (including out-of-bounds) over a string mixing BMP and
// non-BMP characters and assert the conversion never goes backwards, which is what
// guarantees `lower <= upper` for any `NSRange`.
let s = "a\u{1F600}b\u{3053}\u{1F4A9}c";
let mut prev = 0;
for off in 0..=20 {
let cur = utf16_to_utf8_offset(s, off);
assert!(cur >= prev, "non-monotone at offset {off}: {cur} < {prev}");
assert!(cur <= s.len(), "offset {off} mapped past end: {cur} > {}", s.len());
prev = cur;
}
}
}

View File

@@ -55,7 +55,6 @@ use winit_core::window::{
use super::app_state::AppState;
use super::cursor::{CustomCursor, cursor_from_icon};
use super::ffi;
use super::monitor::{self, MonitorHandle, flip_window_screen_coordinates, get_display_id};
use super::util::cgerr;
use super::view::WinitView;
@@ -912,7 +911,10 @@ impl WindowDelegate {
fn handle_scale_factor_changed(&self, scale_factor: CGFloat) {
let window = self.window();
let suggested_size = self.view().surface_size();
let content_size = window.contentRectForFrameRect(window.frame()).size;
let content_size = LogicalSize::new(content_size.width, content_size.height);
let suggested_size = content_size.to_physical(scale_factor);
let new_surface_size = Arc::new(Mutex::new(suggested_size));
self.queue_event(WindowEvent::ScaleFactorChanged {
scale_factor,
@@ -970,17 +972,30 @@ impl WindowDelegate {
}
pub fn set_blur(&self, blur: bool) {
// NOTE: in general we want to specify the blur radius, but the choice of 80
// should be a reasonable default.
let radius = if blur { 80 } else { 0 };
let window_number = self.window().windowNumber();
unsafe {
ffi::CGSSetWindowBackgroundBlurRadius(
ffi::CGSMainConnectionID(),
window_number,
radius,
);
#[cfg(feature = "private-apple-apis")]
{
#[link(name = "CoreGraphics", kind = "framework")]
unsafe extern "C" {
// Wildly used private APIs; Apple uses them for their Terminal.app.
pub fn CGSMainConnectionID() -> *mut objc2::runtime::AnyObject;
pub fn CGSSetWindowBackgroundBlurRadius(
connection_id: *mut objc2::runtime::AnyObject,
window_id: objc2_foundation::NSInteger,
radius: i64,
) -> i32;
}
// NOTE: in general we want to specify the blur radius, but the choice of 80
// should be a reasonable default.
let radius = if blur { 80 } else { 0 };
let window_number = self.window().windowNumber();
unsafe {
CGSSetWindowBackgroundBlurRadius(CGSMainConnectionID(), window_number, radius)
};
}
// TODO: Implement blur using public methods somehow?
let _ = blur;
}
pub fn set_visible(&self, visible: bool) {
@@ -1037,7 +1052,9 @@ impl WindowDelegate {
#[inline]
pub fn surface_size(&self) -> PhysicalSize<u32> {
self.view().surface_size()
let content_rect = self.window().contentRectForFrameRect(self.window().frame());
let logical = LogicalSize::new(content_rect.size.width, content_rect.size.height);
logical.to_physical(self.scale_factor())
}
#[inline]

View File

@@ -20,9 +20,6 @@ xkb = ["dep:xkbcommon-dl", "dep:smol_str"]
# CoreFoundation
core-foundation = ["dep:block2", "dep:objc2", "dep:objc2-core-foundation"]
# Foundation
foundation = ["dep:block2", "dep:objc2", "dep:objc2-foundation"]
[dependencies]
smol_str = { workspace = true, optional = true }
tracing.workspace = true
@@ -33,7 +30,7 @@ memmap2 = { workspace = true, optional = true }
x11-dl = { workspace = true, optional = true }
xkbcommon-dl = { workspace = true, optional = true }
# Foundation / CoreFoundation
# CoreFoundation
block2 = { workspace = true, optional = true }
objc2 = { workspace = true, optional = true }
objc2-core-foundation = { workspace = true, optional = true, features = [
@@ -43,13 +40,6 @@ objc2-core-foundation = { workspace = true, optional = true, features = [
"CFRunLoop",
"CFString",
] }
objc2-foundation = { workspace = true, optional = true, features = [
"std",
"block2",
"NSNotification",
"NSString",
"NSOperation",
] }
[package.metadata.docs.rs]
all-features = true

View File

@@ -1,3 +0,0 @@
mod notification_center;
pub use self::notification_center::*;

View File

@@ -4,7 +4,5 @@
pub mod core_foundation;
#[cfg(feature = "event-handler")]
pub mod event_handler;
#[cfg(feature = "foundation")]
pub mod foundation;
#[cfg(feature = "xkb")]
pub mod xkb;

View File

@@ -269,20 +269,6 @@ pub enum WindowEvent {
button: ButtonSource,
},
/// Multi-finger hold gesture on the touchpad or touchscreen without movement.
///
/// The `phase` field indicates the lifecycle of the hold gesture:
/// - `Started`: One or more fingers are in contact with the touchpad/touchscreen.
/// - `Ended`: All fingers have been lifted from the touchpad/touchscreen.
/// - `Cancelled`: The hold gesture was interrupted, for example when another finger touches the
/// touchpad (causing a new `Started` event with more fingers), or when movement begins and
/// transitions to other gestures like pinch, pan, or rotation.
///
/// ## Platform-specific
///
/// - Only available on **Wayland**.
HoldGesture { device_id: Option<DeviceId>, phase: TouchPhase },
/// Two-finger pinch gesture, often used for magnification.
///
/// ## Platform-specific
@@ -1020,17 +1006,9 @@ pub enum Ime {
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum TouchPhase {
/// Initial touch contact or gesture start, for example when one or more fingers touch the
/// screen or touchpad.
Started,
/// The touch contact point changed, for example without lifting the finger.
Moved,
/// All touch contact points have been lifted from the touchscreen or touchpad.
///
/// This event is important as it should clear any state or event in flight that was
/// generated by the preceding `Started` and `Moved` events.
Ended,
/// The event was cancelled and should cancel any event in flight and clear state.
Cancelled,
}

View File

@@ -879,9 +879,9 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug {
///
/// ## Platform-specific
///
/// - **macOS**: Must enable the `private-apple-apis` Cargo feature.
/// - **Android / iOS / X11 / Web / Windows:** Unsupported.
/// - **Wayland:** Only works with `org_kde_kwin_blur_manager` or
/// `ext_background_effect_manager_v1` protocol.
/// - **Wayland:** Only works with org_kde_kwin_blur_manager protocol.
fn set_blur(&self, blur: bool);
/// Modifies the window's visibility.

View File

@@ -69,7 +69,7 @@ objc2-ui-kit = { workspace = true, features = [
"UIViewController",
"UIWindow",
] }
winit-common = { workspace = true, features = ["core-foundation", "event-handler", "foundation"] }
winit-common = { workspace = true, features = ["core-foundation", "event-handler"] }
[package.metadata.docs.rs]
all-features = true

View File

@@ -14,7 +14,6 @@ use objc2_ui_kit::{
use rwh_06::HasDisplayHandle;
use tracing::debug_span;
use winit_common::core_foundation::{MainRunLoop, MainRunLoopObserver, tracing_observers};
use winit_common::foundation::create_observer;
use winit_core::application::ApplicationHandler;
use winit_core::cursor::{CustomCursor, CustomCursorSource};
use winit_core::error::{EventLoopError, NotSupportedError, RequestError};
@@ -26,6 +25,7 @@ use winit_core::monitor::MonitorHandle as CoreMonitorHandle;
use winit_core::window::{Theme, Window as CoreWindow};
use super::app_state::{AppState, send_occluded_event_for_all_windows};
use super::notification_center::create_observer;
use crate::monitor::MonitorHandle;
use crate::window::Window;
use crate::{app_state, monitor};

View File

@@ -103,6 +103,7 @@
mod app_state;
mod event_loop;
mod monitor;
mod notification_center;
mod view;
mod view_controller;
mod window;

View File

@@ -0,0 +1 @@
../../winit-appkit/src/notification_center.rs

View File

@@ -40,7 +40,7 @@ sctk = { package = "smithay-client-toolkit", version = "0.20.0", default-feature
sctk-adwaita = { version = "0.11.0", default-features = false, optional = true }
wayland-backend = { version = "0.3.10", default-features = false, features = ["client_system"] }
wayland-client = "0.31.10"
wayland-protocols = { version = "0.32.11", features = ["staging", "unstable"] }
wayland-protocols = { version = "0.32.8", features = ["staging"] }
wayland-protocols-plasma = { version = "0.3.8", features = ["client"] }
winit-common = { workspace = true, features = ["xkb", "wayland"] }

View File

@@ -12,7 +12,6 @@ use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::
use sctk::seat::pointer::{ThemeSpec, ThemedPointer};
use sctk::seat::{Capability as SeatCapability, SeatHandler, SeatState};
use tracing::warn;
use wayland_protocols::wp::pointer_gestures::zv1::client::zwp_pointer_gesture_hold_v1::ZwpPointerGestureHoldV1;
use wayland_protocols::wp::pointer_gestures::zv1::client::zwp_pointer_gesture_pinch_v1::ZwpPointerGesturePinchV1;
use wayland_protocols::wp::tablet::zv2::client::zwp_tablet_seat_v2::ZwpTabletSeatV2;
use winit_core::event::WindowEvent;
@@ -61,9 +60,6 @@ pub struct WinitSeatState {
/// The pinch pointer gesture bound on the seat.
pointer_gesture_pinch: Option<ZwpPointerGesturePinchV1>,
/// The hold pointer gesture bound on the seat.
pointer_gesture_hold: Option<ZwpPointerGestureHoldV1>,
/// The keyboard bound on the seat.
keyboard_state: Option<KeyboardState>,
@@ -145,14 +141,6 @@ impl SeatHandler for WinitState {
)
});
seat_state.pointer_gesture_hold = self.pointer_gestures.as_ref().map(|manager| {
manager.get_hold_gesture(
themed_pointer.pointer(),
queue_handle,
PointerGestureData::default(),
)
});
let themed_pointer = Arc::new(themed_pointer);
// Register cursor surface.
@@ -221,10 +209,6 @@ impl SeatHandler for WinitState {
pointer_gesture_pinch.destroy();
}
if let Some(pointer_gesture_hold) = seat_state.pointer_gesture_hold.take() {
pointer_gesture_hold.destroy();
}
if let Some(pointer) = seat_state.pointer.take() {
let pointer_data = pointer.pointer().winit_data();

View File

@@ -7,12 +7,9 @@ use sctk::globals::GlobalData;
use sctk::reexports::client::globals::{BindError, GlobalList};
use sctk::reexports::client::{Connection, Dispatch, Proxy, QueueHandle, delegate_dispatch};
use sctk::reexports::protocols::wp::pointer_gestures::zv1::client::zwp_pointer_gesture_pinch_v1::{
Event as PinchEvent, ZwpPointerGesturePinchV1,
Event, ZwpPointerGesturePinchV1,
};
use sctk::reexports::protocols::wp::pointer_gestures::zv1::client::zwp_pointer_gestures_v1::ZwpPointerGesturesV1;
use wayland_protocols::wp::pointer_gestures::zv1::client::zwp_pointer_gesture_hold_v1::{
Event as HoldEvent, ZwpPointerGestureHoldV1,
};
use winit_core::event::{TouchPhase, WindowEvent};
use winit_core::window::WindowId;
@@ -30,7 +27,7 @@ impl PointerGesturesState {
globals: &GlobalList,
queue_handle: &QueueHandle<WinitState>,
) -> Result<Self, BindError> {
let pointer_gestures = globals.bind(queue_handle, 3..=3, GlobalData)?;
let pointer_gestures = globals.bind(queue_handle, 1..=1, GlobalData)?;
Ok(Self { pointer_gestures })
}
}
@@ -73,49 +70,6 @@ impl Dispatch<ZwpPointerGesturesV1, GlobalData, WinitState> for PointerGesturesS
}
}
impl Dispatch<ZwpPointerGestureHoldV1, PointerGestureData, WinitState> for PointerGesturesState {
fn event(
state: &mut WinitState,
_proxy: &ZwpPointerGestureHoldV1,
event: <ZwpPointerGestureHoldV1 as wayland_client::Proxy>::Event,
data: &PointerGestureData,
_conn: &Connection,
_qhandle: &QueueHandle<WinitState>,
) {
let mut pointer_gesture_data = data.inner.lock().unwrap();
let (window_id, phase) = match event {
HoldEvent::Begin { surface, fingers, .. } => {
if fingers < 2 {
return;
}
let window_id = crate::make_wid(&surface);
pointer_gesture_data.window_id = Some(window_id);
(window_id, TouchPhase::Started)
},
HoldEvent::End { cancelled, .. } => {
let window_id = match pointer_gesture_data.window_id {
Some(window_id) => window_id,
_ => return,
};
// Reset the state.
*pointer_gesture_data = Default::default();
let phase = if cancelled == 0 { TouchPhase::Ended } else { TouchPhase::Cancelled };
(window_id, phase)
},
_ => return,
};
state
.events_sink
.push_window_event(WindowEvent::HoldGesture { device_id: None, phase }, window_id);
}
}
impl Dispatch<ZwpPointerGesturePinchV1, PointerGestureData, WinitState> for PointerGesturesState {
fn event(
state: &mut WinitState,
@@ -127,7 +81,7 @@ impl Dispatch<ZwpPointerGesturePinchV1, PointerGestureData, WinitState> for Poin
) {
let mut pointer_gesture_data = data.inner.lock().unwrap();
let (window_id, phase, pan_delta, pinch_delta, rotation_delta) = match event {
PinchEvent::Begin { surface, fingers, .. } => {
Event::Begin { surface, fingers, .. } => {
// We only support two fingers for now.
if fingers != 2 {
return;
@@ -146,7 +100,7 @@ impl Dispatch<ZwpPointerGesturePinchV1, PointerGestureData, WinitState> for Poin
(window_id, TouchPhase::Started, PhysicalPosition::new(0., 0.), 0., 0.)
},
PinchEvent::Update { dx, dy, scale: pinch, rotation, .. } => {
Event::Update { dx, dy, scale: pinch, rotation, .. } => {
let window_id = match pointer_gesture_data.window_id {
Some(window_id) => window_id,
_ => return,
@@ -167,7 +121,7 @@ impl Dispatch<ZwpPointerGesturePinchV1, PointerGestureData, WinitState> for Poin
let rotation_delta = -rotation as f32;
(window_id, TouchPhase::Moved, pan_delta, pinch_delta, rotation_delta)
},
PinchEvent::End { cancelled, .. } => {
Event::End { cancelled, .. } => {
let window_id = match pointer_gesture_data.window_id {
Some(window_id) => window_id,
_ => return,
@@ -201,4 +155,3 @@ impl Dispatch<ZwpPointerGesturePinchV1, PointerGestureData, WinitState> for Poin
delegate_dispatch!(WinitState: [ZwpPointerGesturesV1: GlobalData] => PointerGesturesState);
delegate_dispatch!(WinitState: [ZwpPointerGesturePinchV1: PointerGestureData] => PointerGesturesState);
delegate_dispatch!(WinitState: [ZwpPointerGestureHoldV1: PointerGestureData] => PointerGesturesState);

View File

@@ -29,7 +29,7 @@ use crate::seat::{
PointerConstraintsState, PointerGesturesState, RelativePointerState, TextInputState,
WinitPointerData, WinitPointerDataExt, WinitSeatState,
};
use crate::types::bgr_effects::BgrEffectManager;
use crate::types::kwin_blur::KWinBlurManager;
use crate::types::wp_fractional_scaling::FractionalScalingManager;
use crate::types::wp_tablet_input_v2::TabletManager;
use crate::types::wp_viewporter::ViewporterState;
@@ -116,8 +116,8 @@ pub struct WinitState {
/// Fractional scaling manager.
pub fractional_scaling_manager: Option<FractionalScalingManager>,
/// Blur manager.
pub blur_manager: Option<BgrEffectManager>,
/// KWin blur manager.
pub kwin_blur_manager: Option<KWinBlurManager>,
/// Loop handle to re-register event sources, such as keyboard repeat.
pub loop_handle: LoopHandle<'static, Self>,
@@ -192,7 +192,7 @@ impl WinitState {
window_events_sink: Default::default(),
viewporter_state,
fractional_scaling_manager,
blur_manager: BgrEffectManager::new(globals, queue_handle).ok(),
kwin_blur_manager: KWinBlurManager::new(globals, queue_handle).ok(),
seats,
text_input_state: TextInputState::new(globals, queue_handle).ok(),

View File

@@ -1,85 +0,0 @@
use sctk::compositor::Region;
use sctk::reexports::client::QueueHandle;
use sctk::reexports::client::globals::{BindError, GlobalList};
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::protocols::ext::background_effect::v1::client::ext_background_effect_surface_v1::ExtBackgroundEffectSurfaceV1;
use wayland_protocols_plasma::blur::client::org_kde_kwin_blur::OrgKdeKwinBlur;
use crate::state::WinitState;
use crate::types::ext_background_effect::ExtBackgroundEffectManager;
use crate::types::kwin_blur::KWinBlurManager;
/// Wrapper around various background effects for [`WlSurface`].
#[derive(Debug, Clone)]
pub enum BgrEffectManager {
Ext(ExtBackgroundEffectManager),
KWin(KWinBlurManager),
}
impl BgrEffectManager {
pub fn new(
globals: &GlobalList,
queue_handle: &QueueHandle<WinitState>,
) -> Result<Self, BindError> {
ExtBackgroundEffectManager::new(globals, queue_handle)
.map(Self::Ext)
.or_else(|_| KWinBlurManager::new(globals, queue_handle).map(Self::KWin))
}
/// Creates a new blur effect for the surface.
pub fn new_blur_effect(
&mut self,
surface: &WlSurface,
queue_handle: &QueueHandle<WinitState>,
) -> SurfaceBlurEffect {
match self {
BgrEffectManager::Ext(mgr) => SurfaceBlurEffect::Ext(mgr.blur(surface, queue_handle)),
BgrEffectManager::KWin(mgr) => SurfaceBlurEffect::Kwin(
mgr.blur(surface, queue_handle),
mgr.clone(),
surface.clone(),
),
}
}
}
#[derive(Debug)]
pub enum SurfaceBlurEffect {
Ext(ExtBackgroundEffectSurfaceV1),
Kwin(OrgKdeKwinBlur, KWinBlurManager, WlSurface),
}
impl SurfaceBlurEffect {
/// Returns `true` if the main surface commit is required.
///
/// `None` clears the blur.
#[must_use]
pub fn set_blur(&self, region: Option<&Region>) -> bool {
let region = region.map(|region| region.wl_region());
match self {
SurfaceBlurEffect::Ext(surface) => {
surface.set_blur_region(region);
true
},
SurfaceBlurEffect::Kwin(blur, ..) => {
blur.set_region(region);
blur.commit();
true
},
}
}
}
impl Drop for SurfaceBlurEffect {
fn drop(&mut self) {
match self {
SurfaceBlurEffect::Ext(surface) => surface.destroy(),
SurfaceBlurEffect::Kwin(blur, mgr, wl_surface) => {
blur.set_region(None);
blur.commit();
blur.release();
mgr.unset(wl_surface);
},
}
}
}

View File

@@ -1,59 +0,0 @@
use sctk::globals::GlobalData;
use sctk::reexports::client::globals::{BindError, GlobalList};
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::{Connection, Dispatch, Proxy, QueueHandle, delegate_dispatch};
use wayland_protocols::ext::background_effect::v1::client::ext_background_effect_manager_v1::ExtBackgroundEffectManagerV1;
use wayland_protocols::ext::background_effect::v1::client::ext_background_effect_surface_v1::ExtBackgroundEffectSurfaceV1;
use crate::state::WinitState;
#[derive(Debug, Clone)]
pub struct ExtBackgroundEffectManager {
manager: ExtBackgroundEffectManagerV1,
}
impl ExtBackgroundEffectManager {
pub fn new(
globals: &GlobalList,
queue_handle: &QueueHandle<WinitState>,
) -> Result<Self, BindError> {
let manager = globals.bind(queue_handle, 1..=1, GlobalData)?;
Ok(Self { manager })
}
pub fn blur(
&mut self,
surface: &WlSurface,
queue_handle: &QueueHandle<WinitState>,
) -> ExtBackgroundEffectSurfaceV1 {
self.manager.get_background_effect(surface, queue_handle, ())
}
}
impl Dispatch<ExtBackgroundEffectManagerV1, GlobalData, WinitState> for ExtBackgroundEffectManager {
fn event(
_: &mut WinitState,
_: &ExtBackgroundEffectManagerV1,
_: <ExtBackgroundEffectManagerV1 as Proxy>::Event,
_: &GlobalData,
_: &Connection,
_: &QueueHandle<WinitState>,
) {
}
}
impl Dispatch<ExtBackgroundEffectSurfaceV1, (), WinitState> for ExtBackgroundEffectManager {
fn event(
_: &mut WinitState,
_: &ExtBackgroundEffectSurfaceV1,
_: <ExtBackgroundEffectSurfaceV1 as Proxy>::Event,
_: &(),
_: &Connection,
_: &QueueHandle<WinitState>,
) {
// There is no event
}
}
delegate_dispatch!(WinitState: [ExtBackgroundEffectManagerV1: GlobalData] => ExtBackgroundEffectManager);
delegate_dispatch!(WinitState: [ExtBackgroundEffectSurfaceV1: ()] => ExtBackgroundEffectManager);

View File

@@ -1,8 +1,6 @@
//! Wayland protocol implementation boilerplate.
pub mod bgr_effects;
pub mod cursor;
pub mod ext_background_effect;
pub mod kwin_blur;
pub mod wp_fractional_scaling;
pub mod wp_tablet_input_v2;

View File

@@ -127,8 +127,7 @@ impl Window {
// Set transparency hint.
window_state.set_transparent(attributes.transparent);
// Set blur.
let _ = window_state.set_blur(attributes.blur);
window_state.set_blur(attributes.blur);
// Set the decorations hint.
window_state.set_decorate(attributes.decorations);
@@ -499,9 +498,7 @@ impl CoreWindow for Window {
#[inline]
fn set_blur(&self, blur: bool) {
if self.window_state.lock().unwrap().set_blur(blur) {
self.request_redraw();
}
self.window_state.lock().unwrap().set_blur(blur);
}
#[inline]

View File

@@ -29,6 +29,7 @@ use sctk::shm::slot::SlotPool;
use sctk::subcompositor::SubcompositorState;
use tracing::{info, warn};
use wayland_protocols::xdg::toplevel_icon::v1::client::xdg_toplevel_icon_manager_v1::XdgToplevelIconManagerV1;
use wayland_protocols_plasma::blur::client::org_kde_kwin_blur::OrgKdeKwinBlur;
use winit_core::cursor::{CursorIcon, CustomCursor as CoreCustomCursor};
use winit_core::error::{NotSupportedError, RequestError};
use winit_core::window::{
@@ -42,8 +43,8 @@ use crate::seat::{
ZwpTextInputV3Ext,
};
use crate::state::{WindowCompositorUpdate, WinitState};
use crate::types::bgr_effects::{BgrEffectManager, SurfaceBlurEffect};
use crate::types::cursor::{CustomCursor, SelectedCursor, WaylandCustomCursor};
use crate::types::kwin_blur::KWinBlurManager;
use crate::types::xdg_toplevel_icon_manager::ToplevelIcon;
#[cfg(feature = "sctk-adwaita")]
@@ -155,8 +156,8 @@ pub struct WindowState {
viewport: Option<WpViewport>,
fractional_scale: Option<WpFractionalScaleV1>,
blur: Option<SurfaceBlurEffect>,
blur_manager: Option<BgrEffectManager>,
blur: Option<OrgKdeKwinBlur>,
blur_manager: Option<KWinBlurManager>,
/// Whether the client side decorations have pending move operations.
///
@@ -205,7 +206,7 @@ impl WindowState {
toplevel_icon: None,
xdg_toplevel_icon_manager,
blur: None,
blur_manager: winit_state.blur_manager.clone(),
blur_manager: winit_state.kwin_blur_manager.clone(),
compositor,
handle,
csd_fails: false,
@@ -741,13 +742,6 @@ impl WindowState {
// Set surface size without the borders.
viewport.set_destination(self.size.width as _, self.size.height as _);
}
// Update blur region with new size.
if self.blur.is_some() {
// NOTE: either user resized or configure, in both cases
// the redraw scheduling is done on the caller side.
let _ = self.set_blur(true);
}
}
/// Get the scale factor of the window.
@@ -1119,37 +1113,20 @@ impl WindowState {
}
}
/// Make window background blurred.
///
/// Returns `true` if redraw is required.
#[must_use]
pub fn set_blur(&mut self, blurred: bool) -> bool {
if !blurred {
self.blur = None;
return true;
}
let mgr = match self.blur_manager.as_mut() {
Some(mgr) => mgr,
None => {
info!("Blur manager unavailable, unable to change blur");
return false;
},
};
let blur = match self.blur.as_ref() {
Some(blur) => blur,
None => {
self.blur = Some(mgr.new_blur_effect(self.window.wl_surface(), &self.queue_handle));
self.blur.as_ref().unwrap()
},
};
if let Ok(region) = Region::new(&*self.compositor) {
region.add(0, 0, i32::MAX, i32::MAX);
blur.set_blur(Some(&region))
} else {
false
/// Make window background blurred
#[inline]
pub fn set_blur(&mut self, blurred: bool) {
if blurred && self.blur.is_none() {
if let Some(blur_manager) = self.blur_manager.as_ref() {
let blur = blur_manager.blur(self.window.wl_surface(), &self.queue_handle);
blur.commit();
self.blur = Some(blur);
} else {
info!("Blur manager unavailable, unable to change blur")
}
} else if !blurred && self.blur.is_some() {
self.blur_manager.as_ref().unwrap().unset(self.window.wl_surface());
self.blur.take().unwrap().release();
}
}
@@ -1247,6 +1224,10 @@ impl WindowState {
impl Drop for WindowState {
fn drop(&mut self) {
if let Some(blur) = self.blur.take() {
blur.release();
}
if let Some(fs) = self.fractional_scale.take() {
fs.destroy();
}

View File

@@ -526,7 +526,7 @@ impl MonitorHandler {
},
};
// Notifying `Future`s is not dependent on the lifetime of the runner,
// Notifying `Future`s is not dependant on the lifetime of the runner,
// because they can outlive it.
if let Some(runner) = runner.upgrade() {
if let Some(details) = details {
@@ -761,7 +761,7 @@ impl MonitorPermissionFuture {
wasm_bindgen_futures::spawn_local(async move {
match future.await {
Ok(details) => {
// Notifying `Future`s is not dependent on the lifetime of the runner, because
// Notifying `Future`s is not dependant on the lifetime of the runner, because
// they can outlive it.
notifier.notify(Ok(()));

View File

@@ -51,12 +51,12 @@ use windows_sys::Win32::UI::WindowsAndMessaging::{
SystemParametersInfoW, TranslateMessage, WHEEL_DELTA, WINDOWPOS, WM_CAPTURECHANGED, WM_CLOSE,
WM_CREATE, WM_DESTROY, WM_DPICHANGED, WM_ENTERSIZEMOVE, WM_EXITSIZEMOVE, WM_GETMINMAXINFO,
WM_IME_COMPOSITION, WM_IME_ENDCOMPOSITION, WM_IME_SETCONTEXT, WM_IME_STARTCOMPOSITION,
WM_INPUT, WM_INPUTLANGCHANGE, WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS, WM_LBUTTONDOWN, WM_LBUTTONUP,
WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MENUCHAR, WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL,
WM_NCACTIVATE, WM_NCCALCSIZE, WM_NCCREATE, WM_NCDESTROY, WM_NCLBUTTONDOWN, WM_PAINT,
WM_POINTERDOWN, WM_POINTERUP, WM_POINTERUPDATE, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR,
WM_SETFOCUS, WM_SETTINGCHANGE, WM_SIZE, WM_SIZING, WM_SYSCOMMAND, WM_SYSKEYDOWN, WM_SYSKEYUP,
WM_TOUCH, WM_WINDOWPOSCHANGED, WM_WINDOWPOSCHANGING, WM_XBUTTONDOWN, WM_XBUTTONUP, WMSZ_BOTTOM,
WM_INPUT, WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN,
WM_MBUTTONUP, WM_MENUCHAR, WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCACTIVATE,
WM_NCCALCSIZE, WM_NCCREATE, WM_NCDESTROY, WM_NCLBUTTONDOWN, WM_PAINT, WM_POINTERDOWN,
WM_POINTERUP, WM_POINTERUPDATE, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR, WM_SETFOCUS,
WM_SETTINGCHANGE, WM_SIZE, WM_SIZING, WM_SYSCOMMAND, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_TOUCH,
WM_WINDOWPOSCHANGED, WM_WINDOWPOSCHANGING, WM_XBUTTONDOWN, WM_XBUTTONUP, WMSZ_BOTTOM,
WMSZ_BOTTOMLEFT, WMSZ_BOTTOMRIGHT, WMSZ_LEFT, WMSZ_RIGHT, WMSZ_TOP, WMSZ_TOPLEFT,
WMSZ_TOPRIGHT, WNDCLASSEXW, WS_EX_LAYERED, WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW,
WS_EX_TRANSPARENT, WS_OVERLAPPED, WS_POPUP, WS_VISIBLE,
@@ -1508,21 +1508,6 @@ unsafe fn public_window_callback_inner(
result = ProcResult::Value(unsafe { DefWindowProcW(window, msg, wparam, lparam) });
},
WM_INPUTLANGCHANGE => {
// Refresh the cached keyboard layout for the newly activated input
// language. This message is sent (by Windows or by layout switchers
// such as Punto Switcher) after the layout changes. Refreshing the
// cache here prevents a freeze that otherwise occurs when switching
// layout via such tools. We still defer to `DefWindowProc` so the
// message keeps propagating to first-level child windows, as the
// Win32 documentation requires.
{
let mut layouts = LAYOUT_CACHE.lock().unwrap();
layouts.get_current_layout();
}
result = ProcResult::DefWindowProc(wparam);
},
// this is necessary for us to maintain minimize/restore state
WM_SYSCOMMAND => {
if wparam == SC_RESTORE as usize {

View File

@@ -1757,7 +1757,7 @@ impl EventProcessor {
.find(|prev_monitor| prev_monitor.name == new_monitor.name)
.map(|prev_monitor| prev_monitor.scale_factor);
if Some(new_monitor.scale_factor) != maybe_prev_scale_factor {
for window in self.target.windows.borrow().values().filter_map(|w| w.upgrade()) {
for window in self.target.windows.borrow().iter().filter_map(|(_, w)| w.upgrade()) {
window.refresh_dpi_for_monitor(
&new_monitor,
maybe_prev_scale_factor,

View File

@@ -44,6 +44,7 @@ default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
android-game-activity = ["winit-android/game-activity"]
android-native-activity = ["winit-android/native-activity"]
mint = ["dpi/mint"]
private-apple-apis = ["winit-appkit/private-apple-apis"]
serde = [
"dep:serde",
"cursor-icon/serde",
@@ -76,13 +77,9 @@ winit-core.workspace = true
[dev-dependencies]
image = { workspace = true, features = ["png"] }
softbuffer.workspace = true
tracing = { workspace = true, features = ["log"] }
tracing-subscriber = { workspace = true, features = ["env-filter"] }
# Launching a window without drawing to it has unpredictable results varying from platform to
# platform. We use the `softbuffer` crate in our examples because of its ease of use to avoid
# confusion around this. `glutin` or `wgpu` could also be used to fill the window buffer, but they
# are more complicated to set up.
softbuffer.workspace = true
[target.'cfg(target_os = "android")'.dependencies]
winit-android.workspace = true

View File

@@ -15,6 +15,7 @@ use std::time::Instant;
use std::{fmt, mem};
use cursor_icon::CursorIcon;
use rwh_06::{DisplayHandle, HasDisplayHandle};
use softbuffer::{Context, Surface};
use tracing::{error, info};
#[cfg(web_platform)]
@@ -24,7 +25,7 @@ use winit::cursor::{Cursor, CustomCursor, CustomCursorSource};
use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize};
use winit::error::RequestError;
use winit::event::{DeviceEvent, DeviceId, MouseButton, MouseScrollDelta, WindowEvent};
use winit::event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle};
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::icon::{Icon, RgbaIcon};
use winit::keyboard::{Key, ModifiersState};
use winit::monitor::Fullscreen;
@@ -44,6 +45,9 @@ use winit_core::application::macos::ApplicationHandlerExtMacOS;
#[path = "util/tracing.rs"]
mod tracing_init;
#[path = "util/fill.rs"]
mod fill;
/// The amount of points to around the window for drag resize direction calculations.
const BORDER_SIZE: f64 = 20.;
@@ -90,12 +94,20 @@ struct Application {
/// Drawing context.
///
/// With OpenGL it could be EGLDisplay.
context: Context<OwnedDisplayHandle>,
context: Option<Context<DisplayHandle<'static>>>,
}
impl Application {
fn new(event_loop: &EventLoop, receiver: Receiver<Action>, sender: Sender<Action>) -> Self {
let context = Context::new(event_loop.owned_display_handle()).unwrap();
// SAFETY: we drop the context right before the event loop is stopped, thus making it safe.
let context = Some(
Context::new(unsafe {
std::mem::transmute::<DisplayHandle<'_>, DisplayHandle<'static>>(
event_loop.display_handle().unwrap(),
)
})
.unwrap(),
);
// You'll have to choose an icon size at your own discretion. On X11, the desired size
// varies by WM, and on Windows, you still have to account for screen scaling. Here
@@ -529,7 +541,16 @@ impl ApplicationHandler for Application {
WindowEvent::DoubleTapGesture { .. } => {
info!("Smart zoom");
},
_ => (),
WindowEvent::TouchpadPressure { .. }
| WindowEvent::DragLeft { .. }
| WindowEvent::KeyboardInput { .. }
| WindowEvent::PointerEntered { .. }
| WindowEvent::DragEntered { .. }
| WindowEvent::DragMoved { .. }
| WindowEvent::DragDropped { .. }
| WindowEvent::Destroyed
| WindowEvent::Ime(_)
| WindowEvent::Moved(_) => (),
}
}
@@ -584,7 +605,9 @@ impl ApplicationHandlerExtMacOS for Application {
/// State of the window.
struct WindowState {
/// Render surface.
surface: Surface<OwnedDisplayHandle, Arc<dyn Window>>,
///
/// NOTE: This surface must be dropped before the `Window`.
surface: Surface<DisplayHandle<'static>, Arc<dyn Window>>,
/// The actual winit Window.
window: Arc<dyn Window>,
/// The window theme we're drawing with.
@@ -625,7 +648,9 @@ impl WindowState {
fn new(app: &Application, window: Box<dyn Window>) -> Result<Self, Box<dyn Error>> {
let window: Arc<dyn Window> = Arc::from(window);
let surface = Surface::new(&app.context, Arc::clone(&window))?;
// SAFETY: the surface is dropped before the `window` which provided it with handle, thus
// it doesn't outlive it.
let surface = Surface::new(app.context.as_ref().unwrap(), Arc::clone(&window))?;
let theme = window.theme().unwrap_or(Theme::Dark);
info!("Theme: {theme:?}");
@@ -912,40 +937,35 @@ impl WindowState {
return Ok(());
}
if self.animated_fill_color {
fill::fill_window_with_animated_color(&*self.window, self.start_time);
return Ok(());
}
let mut buffer = self.surface.buffer_mut()?;
if self.animated_fill_color {
// Fill the entire buffer with a single color.
let time = self.start_time.elapsed().as_secs_f32() * 1.5;
let blue = (time.sin() * 255.0) as u32;
let green = ((time.cos() * 255.0) as u32) << 8;
let red = ((1.0 - time.sin() * 255.0) as u32) << 16;
let color = red | green | blue;
buffer.fill(color);
} else {
// Draw a different color inside the safe area
let surface_size = self.window.surface_size();
let insets = self.window.safe_area();
for y in 0..surface_size.height {
for x in 0..surface_size.width {
let index = y as usize * surface_size.width as usize + x as usize;
if insets.left <= x
&& x <= (surface_size.width - insets.right)
&& insets.top <= y
&& y <= (surface_size.height - insets.bottom)
{
// In safe area
buffer[index] = match self.theme {
Theme::Light => 0xffe8e8e8, // Light gray
Theme::Dark => 0xff525252, // Medium gray
};
} else {
// Outside safe area
buffer[index] = match self.theme {
Theme::Light => 0xffffffff, // White
Theme::Dark => 0xff181818, // Dark gray
};
}
// Draw a different color inside the safe area
let surface_size = self.window.surface_size();
let insets = self.window.safe_area();
for y in 0..surface_size.height {
for x in 0..surface_size.width {
let index = y as usize * surface_size.width as usize + x as usize;
if insets.left <= x
&& x <= (surface_size.width - insets.right)
&& insets.top <= y
&& y <= (surface_size.height - insets.bottom)
{
// In safe area
buffer[index] = match self.theme {
Theme::Light => 0xffe8e8e8, // Light gray
Theme::Dark => 0xff525252, // Medium gray
};
} else {
// Outside safe area
buffer[index] = match self.theme {
Theme::Light => 0xffffffff, // White
Theme::Dark => 0xff181818, // Dark gray
};
}
}
}

View File

@@ -3,12 +3,11 @@
fn main() -> Result<(), impl std::error::Error> {
use std::collections::HashMap;
use softbuffer::{Context, Surface};
use tracing::info;
use winit::application::ApplicationHandler;
use winit::dpi::{LogicalPosition, LogicalSize, Position};
use winit::event::{ElementState, KeyEvent, WindowEvent};
use winit::event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle};
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::raw_window_handle::HasRawWindowHandle;
use winit::window::{Window, WindowAttributes, WindowId};
@@ -17,20 +16,18 @@ fn main() -> Result<(), impl std::error::Error> {
#[derive(Debug)]
struct WindowData {
surface: Surface<OwnedDisplayHandle, Box<dyn Window>>,
window: Box<dyn Window>,
color: u32,
}
impl WindowData {
fn new(context: &Context<OwnedDisplayHandle>, window: Box<dyn Window>, color: u32) -> Self {
let surface = Surface::new(context, window).unwrap();
Self { surface, color }
fn new(window: Box<dyn Window>, color: u32) -> Self {
Self { window, color }
}
}
#[derive(Debug)]
#[derive(Default, Debug)]
struct Application {
context: Context<OwnedDisplayHandle>,
parent_window_id: Option<WindowId>,
windows: HashMap<WindowId, WindowData>,
}
@@ -45,7 +42,7 @@ fn main() -> Result<(), impl std::error::Error> {
info!("Parent window id: {:?})", window.id());
self.parent_window_id = Some(window.id());
self.windows.insert(window.id(), WindowData::new(&self.context, window, 0xffbbbbbb));
self.windows.insert(window.id(), WindowData::new(window, 0xffbbbbbb));
}
fn window_event(
@@ -76,24 +73,18 @@ fn main() -> Result<(), impl std::error::Error> {
0xff000000 + 3_u32.pow((child_index + 2).rem_euclid(16) as u32);
let parent_window = self.windows.get(&self.parent_window_id.unwrap()).unwrap();
let child_window = spawn_child_window(
parent_window.surface.window().as_ref(),
event_loop,
child_index,
);
let child_window =
spawn_child_window(parent_window.window.as_ref(), event_loop, child_index);
let child_id = child_window.id();
info!("Child window created with id: {child_id:?}");
self.windows.insert(
child_id,
WindowData::new(&self.context, child_window, child_color),
);
self.windows.insert(child_id, WindowData::new(child_window, child_color));
},
WindowEvent::RedrawRequested => {
if let Some(window) = self.windows.get_mut(&window_id) {
if let Some(window) = self.windows.get(&window_id) {
if window_id == self.parent_window_id.unwrap() {
fill::fill(&mut window.surface);
fill::fill_window(window.window.as_ref());
} else {
fill::fill_with_color(&mut window.surface, window.color);
fill::fill_window_with_color(window.window.as_ref(), window.color);
}
}
},
@@ -127,8 +118,7 @@ fn main() -> Result<(), impl std::error::Error> {
}
let event_loop = EventLoop::new().unwrap();
let context = Context::new(event_loop.owned_display_handle()).unwrap();
event_loop.run_app(Application { context, parent_window_id: None, windows: HashMap::new() })
event_loop.run_app(Application::default())
}
#[cfg(not(any(x11_platform, macos_platform, windows_platform)))]

View File

@@ -4,13 +4,12 @@ use std::thread;
#[cfg(not(web_platform))]
use std::time;
use softbuffer::{Context, Surface};
use tracing::{info, warn};
#[cfg(web_platform)]
use web_time as time;
use winit::application::ApplicationHandler;
use winit::event::{ElementState, KeyEvent, StartCause, WindowEvent};
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop, OwnedDisplayHandle};
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
use winit::keyboard::{Key, NamedKey};
use winit::window::{Window, WindowAttributes, WindowId};
@@ -53,7 +52,7 @@ struct ControlFlowDemo {
request_redraw: bool,
wait_cancelled: bool,
close_requested: bool,
surface: Option<Surface<OwnedDisplayHandle, Box<dyn Window>>>,
window: Option<Box<dyn Window>>,
}
impl ApplicationHandler for ControlFlowDemo {
@@ -70,10 +69,7 @@ impl ApplicationHandler for ControlFlowDemo {
let window_attributes = WindowAttributes::default().with_title(
"Press 1, 2, 3 to change control flow mode. Press R to toggle redraw requests.",
);
let window = event_loop.create_window(window_attributes).unwrap();
let context = Context::new(event_loop.owned_display_handle()).unwrap();
let surface = Surface::new(&context, window).unwrap();
self.surface = Some(surface);
self.window = Some(event_loop.create_window(window_attributes).unwrap());
}
fn window_event(
@@ -116,9 +112,9 @@ impl ApplicationHandler for ControlFlowDemo {
_ => (),
},
WindowEvent::RedrawRequested => {
let surface = self.surface.as_mut().unwrap();
surface.window().pre_present_notify();
fill::fill(surface);
let window = self.window.as_ref().unwrap();
window.pre_present_notify();
fill::fill_window(window.as_ref());
},
_ => (),
}
@@ -126,7 +122,7 @@ impl ApplicationHandler for ControlFlowDemo {
fn about_to_wait(&mut self, event_loop: &dyn ActiveEventLoop) {
if self.request_redraw && !self.wait_cancelled && !self.close_requested {
self.surface.as_ref().unwrap().window().request_redraw();
self.window.as_ref().unwrap().request_redraw();
}
match self.mode {

View File

@@ -1,10 +1,9 @@
use std::error::Error;
use softbuffer::{Context, Surface};
use tracing::info;
use winit::application::ApplicationHandler;
use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle};
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::window::{Window, WindowAttributes, WindowId};
#[path = "util/fill.rs"]
@@ -17,24 +16,27 @@ fn main() -> Result<(), Box<dyn Error>> {
let event_loop = EventLoop::new()?;
let app = Application::default();
let app = Application::new();
Ok(event_loop.run_app(app)?)
}
/// Application state and event handling.
#[derive(Default, Debug)]
#[derive(Debug)]
struct Application {
surface: Option<Surface<OwnedDisplayHandle, Box<dyn Window>>>,
window: Option<Box<dyn Window>>,
}
impl Application {
fn new() -> Self {
Self { window: None }
}
}
impl ApplicationHandler for Application {
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
let window_attributes =
WindowAttributes::default().with_title("Drag and drop files on me!");
let window = event_loop.create_window(window_attributes).unwrap();
let context = Context::new(event_loop.owned_display_handle()).unwrap();
let surface = Surface::new(&context, window).unwrap();
self.surface = Some(surface);
self.window = Some(event_loop.create_window(window_attributes).unwrap());
}
fn window_event(
@@ -51,9 +53,9 @@ impl ApplicationHandler for Application {
info!("{event:?}");
},
WindowEvent::RedrawRequested => {
let surface = self.surface.as_mut().unwrap();
surface.window().pre_present_notify();
fill::fill(surface);
let window = self.window.as_ref().unwrap();
window.pre_present_notify();
fill::fill_window(window.as_ref());
},
WindowEvent::CloseRequested => {
event_loop.exit();

View File

@@ -9,11 +9,10 @@ use std::cmp;
use std::error::Error;
use dpi::{LogicalPosition, PhysicalSize};
use softbuffer::{Context, Surface};
use tracing::{error, info};
use winit::application::ApplicationHandler;
use winit::event::{Ime, WindowEvent};
use winit::event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle};
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::keyboard::{Key, ModifiersState, NamedKey};
#[cfg(web_platform)]
use winit::platform::web::WindowAttributesWeb;
@@ -31,7 +30,7 @@ const IME_CURSOR_SIZE: PhysicalSize<u32> = PhysicalSize::new(20, 20);
#[derive(Debug)]
struct App {
surface: Option<Surface<OwnedDisplayHandle, Box<dyn Window>>>,
window: Option<Box<dyn Window>>,
input_state: TextInputState,
modifiers: ModifiersState,
}
@@ -74,12 +73,14 @@ impl ApplicationHandler for App {
#[cfg(web_platform)]
let window_attributes = WindowAttributes::default()
.with_platform_attributes(Box::new(WindowAttributesWeb::default().with_append(true)));
let window = event_loop.create_window(window_attributes).expect("failed creating window");
let context =
Context::new(event_loop.owned_display_handle()).expect("failed creating context");
let surface = Surface::new(&context, window).expect("failed creating surface");
self.surface = Some(surface);
self.window = match event_loop.create_window(window_attributes) {
Ok(window) => Some(window),
Err(err) => {
error!("error creating window: {err}");
event_loop.exit();
return;
},
};
// Allow IME out of the box.
let enable_request = ImeEnableRequest::new(
@@ -100,13 +101,11 @@ impl ApplicationHandler for App {
match event {
WindowEvent::CloseRequested => {
info!("Close was requested; stopping");
self.surface = None;
self.window = None;
event_loop.exit();
},
WindowEvent::SurfaceResized(surface_size) => {
let surface = self.surface.as_mut().expect("resize event without a surface");
fill::resize(surface, surface_size);
surface.window().request_redraw();
WindowEvent::SurfaceResized(_) => {
self.window.as_ref().expect("resize event without a window").request_redraw();
},
WindowEvent::RedrawRequested => {
// Redraw the application.
@@ -115,13 +114,13 @@ impl ApplicationHandler for App {
// this event rather than in AboutToWait, since rendering in here allows
// the program to gracefully handle redraws requested by the OS.
let surface = self.surface.as_mut().expect("redraw event without a surface");
let window = self.window.as_ref().expect("redraw request without a window");
// Notify that you're about to draw.
surface.window().pre_present_notify();
window.pre_present_notify();
// Draw.
fill::fill(surface);
fill::fill_window(window.as_ref());
// For contiguous redraw loop you can request a redraw from here.
// window.request_redraw();
@@ -215,14 +214,14 @@ impl App {
}
fn handle_ime_event(&mut self, event: Ime) {
let surface = self.surface.as_ref().expect("IME request without a window");
let window = self.window.as_ref().expect("IME request without a window");
match event {
Ime::Enabled => info!("IME enabled for Window={:?}", surface.window().id()),
Ime::Enabled => info!("IME enabled for Window={:?}", window.id()),
Ime::Preedit(text, caret_pos) => info!("Preedit: {text}, with caret at {caret_pos:?}"),
Ime::Commit(text) => {
self.input_state.append_text(&text);
let request_data = self.get_ime_update();
surface.window().request_ime_update(ImeRequest::Update(request_data)).unwrap();
window.request_ime_update(ImeRequest::Update(request_data)).unwrap();
self.print_input_state();
},
Ime::DeleteSurrounding { before_bytes, after_bytes } => {
@@ -247,7 +246,7 @@ impl App {
error!("Buggy IME tried to delete with indices not on char boundary.");
}
},
Ime::Disabled => info!("IME disabled for Window={:?}", surface.window().id()),
Ime::Disabled => info!("IME disabled for Window={:?}", window.id()),
}
}
@@ -313,7 +312,7 @@ impl App {
}
fn window(&self) -> &dyn Window {
self.surface.as_ref().unwrap().window().as_ref()
self.window.as_ref().unwrap().as_ref()
}
}
@@ -356,7 +355,7 @@ Use CTRL+h to cycle content hint permutations.
);
let app = App {
surface: None,
window: None,
input_state: TextInputState {
ime_enabled: true,
contents: String::new(),

View File

@@ -7,12 +7,11 @@ fn main() -> std::process::ExitCode {
use std::thread::sleep;
use std::time::Duration;
use softbuffer::{Context, Surface};
use tracing::info;
use winit::application::ApplicationHandler;
use winit::event::WindowEvent;
use winit::event_loop::pump_events::{EventLoopExtPumpEvents, PumpStatus};
use winit::event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle};
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::window::{Window, WindowAttributes, WindowId};
#[path = "util/fill.rs"]
@@ -22,16 +21,13 @@ fn main() -> std::process::ExitCode {
#[derive(Default, Debug)]
struct PumpDemo {
surface: Option<Surface<OwnedDisplayHandle, Box<dyn Window>>>,
window: Option<Box<dyn Window>>,
}
impl ApplicationHandler for PumpDemo {
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
let window_attributes = WindowAttributes::default().with_title("A fantastic window!");
let window = event_loop.create_window(window_attributes).unwrap();
let context = Context::new(event_loop.owned_display_handle()).unwrap();
self.surface = Some(Surface::new(&context, window).unwrap());
self.window = Some(event_loop.create_window(window_attributes).unwrap());
}
fn window_event(
@@ -42,16 +38,16 @@ fn main() -> std::process::ExitCode {
) {
info!("{event:?}");
let surface = match self.surface.as_mut() {
Some(surface) => surface,
let window = match self.window.as_ref() {
Some(window) => window,
None => return,
};
match event {
WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::RedrawRequested => {
fill::fill(surface);
surface.window().request_redraw();
fill::fill_window(window.as_ref());
window.request_redraw();
},
_ => (),
}

View File

@@ -5,12 +5,11 @@
fn main() -> Result<(), Box<dyn std::error::Error>> {
use std::time::Duration;
use softbuffer::{Context, Surface};
use tracing::info;
use winit::application::ApplicationHandler;
use winit::event::WindowEvent;
use winit::event_loop::run_on_demand::EventLoopExtRunOnDemand;
use winit::event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle};
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::window::{Window, WindowAttributes, WindowId};
#[path = "util/fill.rs"]
@@ -18,30 +17,27 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
#[path = "util/tracing.rs"]
mod tracing;
#[derive(Debug)]
#[derive(Default, Debug)]
struct App {
context: Context<OwnedDisplayHandle>,
idx: usize,
surface: Option<Surface<OwnedDisplayHandle, Box<dyn Window>>>,
window_id: Option<WindowId>,
window: Option<Box<dyn Window>>,
}
impl ApplicationHandler for App {
fn about_to_wait(&mut self, _event_loop: &dyn ActiveEventLoop) {
if let Some(surface) = self.surface.as_ref() {
surface.window().request_redraw();
if let Some(window) = self.window.as_ref() {
window.request_redraw();
}
}
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
let window_attributes = WindowAttributes::default()
.with_title(format!("Fantastic window number {}!", self.idx))
.with_title("Fantastic window number one!")
.with_surface_size(winit::dpi::LogicalSize::new(128.0, 128.0));
let window = event_loop.create_window(window_attributes).unwrap();
self.window_id = Some(window.id());
let surface = Surface::new(&self.context, window).unwrap();
self.surface = Some(surface);
self.window = Some(window);
}
fn window_event(
@@ -57,17 +53,19 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
return;
}
let Some(surface) = self.surface.as_mut() else {
return;
let window = match self.window.as_mut() {
Some(window) => window,
None => return,
};
match event {
WindowEvent::CloseRequested => {
info!("Window {} CloseRequested", self.idx);
self.surface = None;
fill::cleanup_window(window.as_ref());
self.window = None;
},
WindowEvent::RedrawRequested => {
fill::fill(surface);
fill::fill_window(window.as_ref());
},
_ => (),
}
@@ -78,8 +76,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut event_loop = EventLoop::new().unwrap();
let context = Context::new(event_loop.owned_display_handle()).unwrap();
let mut app = App { context, idx: 1, surface: None, window_id: None };
let mut app = App { idx: 1, ..Default::default() };
event_loop.run_app_on_demand(&mut app)?;
info!("Finished first loop");

View File

@@ -1,43 +1,115 @@
//! Fill the window buffer with a solid color.
//!
//! Launching a window without drawing to it has unpredictable results varying from platform to
//! platform. In order to have well-defined examples, this module provides an easy way to
//! fill the window buffer with a solid color.
//!
//! The `softbuffer` crate is used, largely because of its ease of use. `glutin` or `wgpu` could
//! also be used to fill the window buffer, but they are more complicated to use.
use std::cell::RefCell;
use std::collections::HashMap;
use std::mem;
use std::mem::ManuallyDrop;
use std::num::NonZeroU32;
#[cfg(not(web_platform))]
use std::time::Instant;
use rwh_06::{HasDisplayHandle, HasWindowHandle};
use softbuffer::Surface;
use winit::window::Window;
use softbuffer::{Context, Surface};
#[cfg(web_platform)]
use web_time::Instant;
use winit::window::{Window, WindowId};
/// Resize the surface.
pub fn resize(
surface: &mut Surface<impl HasDisplayHandle, impl HasWindowHandle>,
surface_size: dpi::PhysicalSize<u32>,
) {
// Handle zero-sized buffers.
thread_local! {
// NOTE: You should never do things like that, create context and drop it before
// you drop the event loop. We do this for brevity to not blow up examples. We use
// ManuallyDrop to prevent destructors from running.
//
// FIXME(madsmtm): This should be done by softbuffer internally in the future:
// https://github.com/rust-windowing/softbuffer/issues/238
let (Some(width), Some(height)) =
(NonZeroU32::new(surface_size.width), NonZeroU32::new(surface_size.height))
else {
return;
};
surface.resize(width, height).expect("Failed to resize the softbuffer surface");
// A static, thread-local map of graphics contexts to open windows.
static GC: ManuallyDrop<RefCell<Option<GraphicsContext>>> = const { ManuallyDrop::new(RefCell::new(None)) };
}
/// Fill the window buffer with a solid color.
pub fn fill_with_color(
surface: &mut Surface<impl HasDisplayHandle, impl HasWindowHandle + AsRef<dyn Window>>,
color: u32,
) {
let surface_size = surface.window().as_ref().surface_size();
resize(surface, surface_size);
/// The graphics context used to draw to a window.
struct GraphicsContext {
/// The global softbuffer context.
context: RefCell<Context<&'static dyn Window>>,
let mut buffer = surface.buffer_mut().expect("Failed to get the softbuffer buffer");
buffer.fill(color);
buffer.present().expect("Failed to present the softbuffer buffer");
/// The hash map of window IDs to surfaces.
surfaces: HashMap<WindowId, Surface<&'static dyn Window, &'static dyn Window>>,
}
impl GraphicsContext {
fn new(w: &dyn Window) -> Self {
Self {
context: RefCell::new(
Context::new(unsafe { mem::transmute::<&'_ dyn Window, &'static dyn Window>(w) })
.expect("Failed to create a softbuffer context"),
),
surfaces: HashMap::new(),
}
}
fn create_surface(
&mut self,
window: &dyn Window,
) -> &mut Surface<&'static dyn Window, &'static dyn Window> {
self.surfaces.entry(window.id()).or_insert_with(|| {
Surface::new(&self.context.borrow(), unsafe {
mem::transmute::<&'_ dyn Window, &'static dyn Window>(window)
})
.expect("Failed to create a softbuffer surface")
})
}
fn destroy_surface(&mut self, window: &dyn Window) {
self.surfaces.remove(&window.id());
}
}
pub fn fill_window_with_color(window: &dyn Window, color: u32) {
GC.with(|gc| {
let size = window.surface_size();
let (Some(width), Some(height)) =
(NonZeroU32::new(size.width), NonZeroU32::new(size.height))
else {
return;
};
// Either get the last context used or create a new one.
let mut gc = gc.borrow_mut();
let surface = gc.get_or_insert_with(|| GraphicsContext::new(window)).create_surface(window);
// Fill a buffer with a solid color
surface.resize(width, height).expect("Failed to resize the softbuffer surface");
let mut buffer = surface.buffer_mut().expect("Failed to get the softbuffer buffer");
buffer.fill(color);
buffer.present().expect("Failed to present the softbuffer buffer");
})
}
#[allow(dead_code)]
pub fn fill(
surface: &mut Surface<impl HasDisplayHandle, impl HasWindowHandle + AsRef<dyn Window>>,
) {
fill_with_color(surface, 0xff181818);
pub fn fill_window(window: &dyn Window) {
fill_window_with_color(window, 0xff181818);
}
#[allow(dead_code)]
pub fn fill_window_with_animated_color(window: &dyn Window, start: Instant) {
let time = start.elapsed().as_secs_f32() * 1.5;
let blue = (time.sin() * 255.0) as u32;
let green = ((time.cos() * 255.0) as u32) << 8;
let red = ((1.0 - time.sin() * 255.0) as u32) << 16;
let color = red | green | blue;
fill_window_with_color(window, color);
}
#[allow(dead_code)]
pub fn cleanup_window(window: &dyn Window) {
GC.with(|gc| {
let mut gc = gc.borrow_mut();
if let Some(context) = gc.as_mut() {
context.destroy_surface(window);
}
});
}

View File

@@ -2,11 +2,10 @@
use std::error::Error;
use softbuffer::{Context, Surface};
use tracing::info;
use tracing::{error, info};
use winit::application::ApplicationHandler;
use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle};
use winit::event_loop::{ActiveEventLoop, EventLoop};
#[cfg(web_platform)]
use winit::platform::web::WindowAttributesWeb;
use winit::window::{Window, WindowAttributes, WindowId};
@@ -18,7 +17,7 @@ mod tracing;
#[derive(Default, Debug)]
struct App {
surface: Option<Surface<OwnedDisplayHandle, Box<dyn Window>>>,
window: Option<Box<dyn Window>>,
}
impl ApplicationHandler for App {
@@ -28,12 +27,14 @@ impl ApplicationHandler for App {
#[cfg(web_platform)]
let window_attributes = WindowAttributes::default()
.with_platform_attributes(Box::new(WindowAttributesWeb::default().with_append(true)));
let window = event_loop.create_window(window_attributes).expect("failed creating window");
let context =
Context::new(event_loop.owned_display_handle()).expect("failed creating context");
let surface = Surface::new(&context, window).expect("failed creating surface");
self.surface = Some(surface);
self.window = match event_loop.create_window(window_attributes) {
Ok(window) => Some(window),
Err(err) => {
error!("error creating window: {err}");
event_loop.exit();
return;
},
}
}
fn window_event(&mut self, event_loop: &dyn ActiveEventLoop, _: WindowId, event: WindowEvent) {
@@ -43,10 +44,8 @@ impl ApplicationHandler for App {
info!("Close was requested; stopping");
event_loop.exit();
},
WindowEvent::SurfaceResized(surface_size) => {
let surface = self.surface.as_mut().expect("resize event without a surface");
fill::resize(surface, surface_size);
surface.window().request_redraw();
WindowEvent::SurfaceResized(_) => {
self.window.as_ref().expect("resize event without a window").request_redraw();
},
WindowEvent::RedrawRequested => {
// Redraw the application.
@@ -55,15 +54,13 @@ impl ApplicationHandler for App {
// this event rather than in AboutToWait, since rendering in here allows
// the program to gracefully handle redraws requested by the OS.
let surface = self.surface.as_mut().expect("redraw event without a surface");
let window = self.window.as_ref().expect("redraw request without a window");
// Notify that you're about to draw.
surface.window().pre_present_notify();
window.pre_present_notify();
// Draw.
let mut buffer = surface.buffer_mut().expect("Failed to get the softbuffer buffer");
buffer.fill(0xff181818);
buffer.present().expect("Failed to present the softbuffer buffer");
fill::fill_window(window.as_ref());
// For contiguous redraw loop you can request a redraw from here.
// window.request_redraw();

View File

@@ -3,10 +3,9 @@ use std::error::Error;
#[cfg(x11_platform)]
fn main() -> Result<(), Box<dyn Error>> {
use softbuffer::{Context, Surface};
use winit::application::ApplicationHandler;
use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle};
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::platform::x11::WindowAttributesX11;
use winit::window::{Window, WindowAttributes, WindowId};
@@ -18,7 +17,7 @@ fn main() -> Result<(), Box<dyn Error>> {
#[derive(Debug)]
pub struct XEmbedDemo {
parent_window_id: u32,
surface: Option<Surface<OwnedDisplayHandle, Box<dyn Window>>>,
window: Option<Box<dyn Window>>,
}
impl ApplicationHandler for XEmbedDemo {
@@ -30,10 +29,7 @@ fn main() -> Result<(), Box<dyn Error>> {
WindowAttributesX11::default().with_embed_parent_window(self.parent_window_id);
window_attributes = window_attributes.with_platform_attributes(Box::new(x11_attrs));
let window = event_loop.create_window(window_attributes).unwrap();
let context = Context::new(event_loop.owned_display_handle()).unwrap();
let surface = Surface::new(&context, window).unwrap();
self.surface = Some(surface);
self.window = Some(event_loop.create_window(window_attributes).unwrap());
}
fn window_event(
@@ -42,19 +38,19 @@ fn main() -> Result<(), Box<dyn Error>> {
_window_id: WindowId,
event: WindowEvent,
) {
let window = self.window.as_ref().unwrap();
match event {
WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::RedrawRequested => {
let surface = self.surface.as_mut().unwrap();
surface.window().pre_present_notify();
fill::fill(surface);
window.pre_present_notify();
fill::fill_window(window.as_ref());
},
_ => (),
}
}
fn about_to_wait(&mut self, _event_loop: &dyn ActiveEventLoop) {
self.surface.as_ref().unwrap().window().request_redraw();
self.window.as_ref().unwrap().request_redraw();
}
}
@@ -67,7 +63,7 @@ fn main() -> Result<(), Box<dyn Error>> {
tracing::init();
let event_loop = EventLoop::new()?;
Ok(event_loop.run_app(XEmbedDemo { parent_window_id, surface: None })?)
Ok(event_loop.run_app(XEmbedDemo { parent_window_id, window: None })?)
}
#[cfg(not(x11_platform))]

View File

@@ -48,22 +48,16 @@ changelog entry.
- Implement `Send` and `Sync` for `OwnedDisplayHandle`.
- Use new macOS 15 cursors for resize icons.
- On Android, added scancode conversions for more obscure key codes.
- On Wayland, added `HoldGesture` event for multi-finger hold gestures
- On Wayland, added ext-background-effect-v1 support.
### Changed
- Updated `windows-sys` to `v0.61`.
- On older macOS versions (tested up to 12.7.6), applications now receive mouse movement events for unfocused windows, matching the behavior on other platforms.
- On macOS, using the private API `CGSSetWindowBackgroundBlurRadius` for `Window::set_blur` is now disabled by default. It can be re-enabled using the Cargo feature `private-apple-apis`.
### Fixed
- On Windows, fix a freeze that occurs when the keyboard layout is switched by
tools such as Punto Switcher. The `WM_INPUTLANGCHANGE` message is now handled
to refresh the cached keyboard layout, while still deferring to
`DefWindowProc` for normal propagation.
- On Redox, handle `EINTR` when reading from `event_socket` instead of panicking.
- On Wayland, switch from using the `ahash` hashing algorithm to `foldhash`.
- On macOS, fix borderless game presentation options not sticking after switching spaces.
- On macOS, fix IME being locked on (regardless of requests to disable) after being enabled once.
- On macOS, fix a panic and incorrect cursor position in Ime::Preedit when the preedit string contains special characters (ie. emojis) caused by incorrect UTF-16 to UTF-8 offset conversion.

View File

@@ -29,7 +29,7 @@ use crate::platform_impl;
///
/// To wake up an `EventLoop` from a another thread, see the [`EventLoopProxy`] docs.
///
/// Note that this cannot be shared across threads (due to platform-dependent logic
/// Note that this cannot be shared across threads (due to platform-dependant logic
/// forbidding it), as such it is neither [`Send`] nor [`Sync`]. If you need cross-thread access,
/// the [`Window`] created from this _can_ be sent to an other thread, and the
/// [`EventLoopProxy`] allows you to wake up an `EventLoop` from another thread.

View File

@@ -195,6 +195,9 @@
//! * `rwh_06`: Implement `raw-window-handle v0.6` traits.
//! * `serde`: Enables serialization/deserialization of certain types with [Serde](https://crates.io/crates/serde).
//! * `mint`: Enables mint (math interoperability standard types) conversions.
//! * `private-apple-apis`: Enables private APIs whose usage might cause rejections from the App
//! Store. Currently enables the use of `CGSSetWindowBackgroundBlurRadius`, commonly used for
//! terminal emulators.
//!
//! See the [`platform`] module for documentation on platform-specific cargo
//! features.