Compare commits

..

9 Commits

Author SHA1 Message Date
Mads Marquart
1850131b97 Document that multiple windows are not supported on Android 2024-02-20 15:32:08 +01:00
Mads Marquart
2bc1943188 Pin ahash in CI (#3498)
Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
2024-02-16 17:51:48 +01:00
Kirill Chibisov
385c4b3c88 On Wayland, update title from AboutToWait
Fixes #3472.
2024-02-14 00:48:52 +04:00
Kirill Chibisov
ea70f773d3 On X11, don't require XIM to be present
In general, we may want to use xinput v2 for keyboard input in such
cases, so we have compose going, but for now just don't crash if
there's no XIM.
2024-02-13 07:49:58 +04:00
AryaveerSR
83012f4c1c Fix Documentation for ScaleFactorChanged in WindowEvent. (#3489) 2024-02-12 18:20:03 +01:00
Jeremy Soller
6825fed073 On Orbital, implement various Window methods
Implement the following methods on the `Window`:
  - `Window::set_cursor_grab`.
  - `Window::set_cursor_visible`.
  - `Window::drag_window`.
  - `Window::drag_resize_window`.
  - `Window::set_transparent`.
  - `Window::set_visible`.
  - `Window::is_visible`.
  - `Window::set_resizable`.
  - `Window::is_resizable`.
  - `Window::set_maximized`.
  - `Window::is_maximized`.
  - `Window::set_decorations`.
  - `Window::is_decorated`.
  - `Window::set_window_level`.

To make locked pointer useful, the `DeviceEvent::MouseMotion`
event was also implemented.
2024-02-11 04:40:06 +04:00
Kirill Chibisov
273984a385 On X11, extract event handlers
Make code more clear wrt explicit returns during event handling,
which may lead to skipped IME event handling.
2024-02-11 03:31:47 +04:00
Kirill Chibisov
dbe0f852da On X11, store window target on EventProcessor
Remove the redundant `Rc` to access the window target.
2024-02-11 03:31:47 +04:00
Kirill Chibisov
d1902aa15a On X11, don't require XSETTINGS
We could fail to setup property watcher and fail to start, thus
don't require XSETTINGS to work.

Fixes: df8805c0 (On X11, reload DPI on _XSETTINGS_SETTINGS)
2024-02-10 00:24:03 +04:00
18 changed files with 1899 additions and 1607 deletions

View File

@@ -89,7 +89,7 @@ jobs:
- name: Generate lockfile
# Also updates the crates.io index
run: cargo generate-lockfile
run: cargo generate-lockfile && cargo update -p ahash --precise 0.8.7
- name: Install GCC Multilib
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')

View File

@@ -11,7 +11,7 @@ Unreleased` header.
# Unreleased
- **Breaking:** Use `RequestIgnored` as the error type in `InnerSizeWriter::request_inner_size`.
- On X11, don't require XIM to run.
- Fix compatibility with 32-bit platforms without 64-bit atomics.
- On X11, fix swapped instance and general class names.
- **Breaking:** Removed unnecessary generic parameter `T` from `EventLoopWindowTarget`.
@@ -54,6 +54,22 @@ Unreleased` header.
- On Orbital, fix `logical_key` and `text` not reported in `KeyEvent`.
- On Orbital, implement `KeyEventExtModifierSupplement`.
- On Orbital, map keys to `NamedKey` when possible.
- On Orbital, implement `set_cursor_grab`.
- On Orbital, implement `set_cursor_visible`.
- On Orbital, implement `drag_window`.
- On Orbital, implement `drag_resize_window`.
- On Orbital, implement `set_transparent`.
- On Orbital, implement `set_visible`.
- On Orbital, implement `is_visible`.
- On Orbital, implement `set_resizable`.
- On Orbital, implement `is_resizable`.
- On Orbital, implement `set_maximized`.
- On Orbital, implement `is_maximized`.
- On Orbital, implement `set_decorations`.
- On Orbital, implement `is_decorated`.
- On Orbital, implement `set_window_level`.
- On Orbital, emit `DeviceEvent::MouseMotion`.
- On Wayland, fix title in CSD not updated from `AboutToWait`.
# 0.29.10

View File

@@ -32,8 +32,6 @@
//!
//! [`EventLoop::run(...)`]: crate::event_loop::EventLoop::run
//! [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
use std::error::Error;
use std::fmt;
use std::path::PathBuf;
use std::sync::{Mutex, Weak};
#[cfg(not(web_platform))]
@@ -45,6 +43,7 @@ use smol_str::SmolStr;
#[cfg(web_platform)]
use web_time::Instant;
use crate::error::ExternalError;
#[cfg(doc)]
use crate::window::Window;
use crate::{
@@ -537,9 +536,8 @@ pub enum WindowEvent {
/// * Changing the display's scale factor (e.g. in Control Panel on Windows).
/// * Moving the window to a display with a different scale factor.
///
/// After this event callback has been processed, the window will be resized to whatever value
/// is pointed to by the `new_inner_size` reference. By default, this will contain the size suggested
/// by the OS, but it can be changed to any value.
/// To update the window size, use the provided [`InnerSizeWriter`] handle. By default, the window is
/// resized to the value suggested by the OS, but it can be changed to any value.
///
/// For more information about DPI in general, see the [`dpi`](crate::dpi) module.
ScaleFactorChanged {
@@ -1127,23 +1125,16 @@ impl InnerSizeWriter {
Self { new_inner_size }
}
/// Try to request a new inner size which will be set synchronously on the
/// window.
///
///
/// # Errors
///
/// This method returns an error when the request was ignored because it
/// was done asynchronously, outside the event loop callback.
/// Try to request inner size which will be set synchroniously on the window.
pub fn request_inner_size(
&mut self,
new_inner_size: PhysicalSize<u32>,
) -> Result<(), RequestIgnored> {
) -> Result<(), ExternalError> {
if let Some(inner) = self.new_inner_size.upgrade() {
*inner.lock().unwrap() = new_inner_size;
Ok(())
} else {
Err(RequestIgnored { _priv: () })
Err(ExternalError::Ignored)
}
}
}
@@ -1154,22 +1145,6 @@ impl PartialEq for InnerSizeWriter {
}
}
/// The request to change the inner size synchronously was ignored.
///
/// See [`InnerSizeWriter::request_inner_size`] for details.
#[derive(Debug, Clone)] // Explicitly not other traits, in case we want to extend it in the future
pub struct RequestIgnored {
_priv: (),
}
impl fmt::Display for RequestIgnored {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
f.write_str("the request to change the inner size was ignored")
}
}
impl Error for RequestIgnored {}
#[cfg(test)]
mod tests {
use crate::event;

View File

@@ -498,7 +498,7 @@ impl<'a> KeyEventResults<'a> {
}
}
fn physical_key(&mut self) -> PhysicalKey {
fn physical_key(&self) -> PhysicalKey {
keymap::raw_keycode_to_physicalkey(self.keycode)
}
@@ -553,6 +553,7 @@ impl<'a> KeyEventResults<'a> {
} else {
0
};
self.keysym_to_key(keysym)
.unwrap_or_else(|(key, location)| {
(
@@ -565,7 +566,7 @@ impl<'a> KeyEventResults<'a> {
})
}
fn keysym_to_key(&mut self, keysym: u32) -> Result<(Key, KeyLocation), (Key, KeyLocation)> {
fn keysym_to_key(&self, keysym: u32) -> Result<(Key, KeyLocation), (Key, KeyLocation)> {
let location = super::keymap::keysym_location(keysym);
let key = super::keymap::keysym_to_key(keysym);
if matches!(key, Key::Unidentified(_)) {

View File

@@ -940,10 +940,12 @@ impl EventLoopWindowTarget {
}
}
#[allow(dead_code)]
fn set_exit_code(&self, code: i32) {
x11_or_wayland!(match self; Self(evlp) => evlp.set_exit_code(code))
}
#[allow(dead_code)]
fn exit_code(&self) -> Option<i32> {
x11_or_wayland!(match self; Self(evlp) => evlp.exit_code())
}

View File

@@ -461,19 +461,19 @@ impl<T: 'static> EventLoop<T> {
window_ids.extend(state.window_requests.get_mut().keys());
});
for window_id in window_ids.drain(..) {
for window_id in window_ids.iter() {
let event = self.with_state(|state| {
let window_requests = state.window_requests.get_mut();
if window_requests.get(&window_id).unwrap().take_closed() {
mem::drop(window_requests.remove(&window_id));
mem::drop(state.windows.get_mut().remove(&window_id));
if window_requests.get(window_id).unwrap().take_closed() {
mem::drop(window_requests.remove(window_id));
mem::drop(state.windows.get_mut().remove(window_id));
return Some(WindowEvent::Destroyed);
}
let mut window = state
.windows
.get_mut()
.get_mut(&window_id)
.get_mut(window_id)
.unwrap()
.lock()
.unwrap();
@@ -485,7 +485,7 @@ impl<T: 'static> EventLoop<T> {
// Reset the frame callbacks state.
window.frame_callback_reset();
let mut redraw_requested = window_requests
.get(&window_id)
.get(window_id)
.unwrap()
.take_redraw_requested();
@@ -498,7 +498,7 @@ impl<T: 'static> EventLoop<T> {
if let Some(event) = event {
callback(
Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
window_id: crate::window::WindowId(*window_id),
event,
},
&self.window_target,
@@ -514,6 +514,42 @@ impl<T: 'static> EventLoop<T> {
// This is always the last event we dispatch before poll again
callback(Event::AboutToWait, &self.window_target);
// Update the window frames and schedule redraws.
let mut wake_up = false;
for window_id in window_ids.drain(..) {
wake_up |= self.with_state(|state| match state.windows.get_mut().get_mut(&window_id) {
Some(window) => {
let refresh = window.lock().unwrap().refresh_frame();
if refresh {
state
.window_requests
.get_mut()
.get_mut(&window_id)
.unwrap()
.redraw_requested
.store(true, Ordering::Relaxed);
}
refresh
}
None => false,
});
}
// Wakeup event loop if needed.
//
// If the user draws from the `AboutToWait` this is likely not required, however
// we can't do much about it.
if wake_up {
match &self.window_target.p {
PlatformEventLoopWindowTarget::Wayland(window_target) => {
window_target.event_loop_awakener.ping();
}
#[cfg(x11_platform)]
PlatformEventLoopWindowTarget::X(_) => unreachable!(),
}
}
std::mem::swap(&mut self.compositor_updates, &mut compositor_updates);
std::mem::swap(&mut self.buffer_sink, &mut buffer_sink);
std::mem::swap(&mut self.window_ids, &mut window_ids);

View File

@@ -6,7 +6,7 @@ macro_rules! atom_manager {
($($name:ident $(:$lit:literal)?),*) => {
x11rb::atom_manager! {
/// The atoms used by `winit`
pub(crate) Atoms: AtomsCookie {
pub Atoms: AtomsCookie {
$($name $(:$lit)?,)*
}
}
@@ -14,7 +14,7 @@ macro_rules! atom_manager {
/// Indices into the `Atoms` struct.
#[derive(Copy, Clone, Debug)]
#[allow(non_camel_case_types)]
pub(crate) enum AtomName {
pub enum AtomName {
$($name,)*
}

View File

@@ -41,7 +41,7 @@ impl From<io::Error> for DndDataParseError {
}
}
pub(crate) struct Dnd {
pub struct Dnd {
xconn: Arc<XConnection>,
// Populated by XdndEnter event handler
pub version: Option<c_long>,

File diff suppressed because it is too large Load Diff

View File

@@ -1,72 +1,33 @@
#![cfg(x11_platform)]
mod activation;
mod atoms;
mod dnd;
mod event_processor;
pub mod ffi;
mod ime;
mod monitor;
pub mod util;
mod window;
mod xdisplay;
mod xsettings;
pub(crate) use self::{
monitor::{MonitorHandle, VideoModeHandle},
window::UnownedWindow,
xdisplay::{XConnection, XError, XNotSupported},
};
use std::cell::{Cell, RefCell};
use std::collections::{HashMap, HashSet};
use std::ffi::CStr;
use std::fmt;
use std::marker::PhantomData;
use std::mem::MaybeUninit;
use std::ops::Deref;
use std::os::raw::*;
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
use std::sync::mpsc::{self, Receiver, Sender, TryRecvError};
use std::sync::{Arc, Weak};
use std::time::{Duration, Instant};
use std::{ptr, slice, str};
use calloop::generic::Generic;
use calloop::EventLoop as Loop;
use calloop::{ping::Ping, Readiness};
use libc::{self, setlocale, LC_CTYPE};
use log::warn;
use std::{
cell::{Cell, RefCell},
collections::{HashMap, HashSet},
ffi::CStr,
fmt,
marker::PhantomData,
mem::MaybeUninit,
ops::Deref,
os::{
raw::*,
unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd},
},
ptr,
rc::Rc,
slice, str,
sync::mpsc::{Receiver, Sender, TryRecvError},
sync::{mpsc, Arc, Weak},
time::{Duration, Instant},
};
use libc::{self, setlocale, LC_CTYPE};
use atoms::*;
use x11rb::connection::RequestConnection;
use x11rb::errors::{ConnectError, ConnectionError, IdsExhausted, ReplyError};
use x11rb::protocol::xinput::{self, ConnectionExt as _};
use x11rb::protocol::xkb;
use x11rb::protocol::xproto::{self, ConnectionExt as _};
use x11rb::x11_utils::X11Error as LogicalError;
use x11rb::{
connection::RequestConnection,
protocol::{
xinput::{self, ConnectionExt as _},
xkb,
xproto::{self, ConnectionExt as _},
},
};
use x11rb::{
errors::{ConnectError, ConnectionError, IdsExhausted, ReplyError},
xcb_ffi::ReplyOrIdError,
};
use x11rb::xcb_ffi::ReplyOrIdError;
pub(super) use self::util::CustomCursor;
use self::{
dnd::{Dnd, DndState},
event_processor::EventProcessor,
ime::{Ime, ImeCreationError, ImeReceiver, ImeRequest, ImeSender},
};
use super::{common::xkb_state::KbdState, ControlFlow, OsError};
use crate::{
error::{EventLoopError, OsError as RootOsError},
@@ -77,6 +38,28 @@ use crate::{
window::WindowAttributes,
};
mod activation;
mod atoms;
mod dnd;
mod event_processor;
pub mod ffi;
mod ime;
mod monitor;
mod util;
mod window;
mod xdisplay;
mod xsettings;
use atoms::*;
use dnd::{Dnd, DndState};
use event_processor::EventProcessor;
use ime::{Ime, ImeCreationError, ImeReceiver, ImeRequest, ImeSender};
pub(crate) use monitor::{MonitorHandle, VideoModeHandle};
use window::UnownedWindow;
pub(crate) use xdisplay::{XConnection, XError, XNotSupported};
pub use util::CustomCursor;
// Xinput constants not defined in x11rb
const ALL_DEVICES: u16 = 0;
const ALL_MASTER_DEVICES: u16 = 1;
@@ -150,7 +133,7 @@ pub struct EventLoopWindowTarget {
control_flow: Cell<ControlFlow>,
exit: Cell<Option<i32>>,
root: xproto::Window,
ime: RefCell<Ime>,
ime: Option<RefCell<Ime>>,
windows: RefCell<HashMap<WindowId, Weak<UnownedWindow>>>,
redraw_sender: WakeSender<WindowId>,
activation_sender: WakeSender<ActivationToken>,
@@ -166,7 +149,6 @@ pub struct EventLoop<T: 'static> {
user_receiver: PeekableReceiver<T>,
activation_receiver: PeekableReceiver<ActivationToken>,
user_sender: Sender<T>,
target: Rc<RootELW>,
/// The current state of the event loop.
state: EventLoopState,
@@ -227,13 +209,15 @@ impl<T: 'static> EventLoop<T> {
setlocale(LC_CTYPE, default_locale);
}
}
let ime = RefCell::new({
let result = Ime::new(Arc::clone(&xconn), ime_event_sender);
if let Err(ImeCreationError::OpenFailure(ref state)) = result {
panic!("Failed to open input method: {state:#?}");
}
result.expect("Failed to set input method destruction callback")
});
let ime = Ime::new(Arc::clone(&xconn), ime_event_sender);
if let Err(ImeCreationError::OpenFailure(state)) = ime.as_ref() {
warn!("Failed to open input method: {state:#?}");
} else if let Err(err) = ime.as_ref() {
warn!("Failed to set input method destruction callback: {err:?}");
}
let ime = ime.ok().map(RefCell::new);
let randr_event_offset = xconn
.select_xrandr_input(root)
@@ -324,13 +308,13 @@ impl<T: 'static> EventLoop<T> {
// Set initial device event filter.
window_target.update_listen_device_events(true);
let target = Rc::new(RootELW {
let root_window_target = RootELW {
p: super::EventLoopWindowTarget::X(window_target),
_marker: PhantomData,
});
};
let event_processor = EventProcessor {
target: target.clone(),
target: root_window_target,
dnd,
devices: Default::default(),
randr_event_offset,
@@ -349,8 +333,9 @@ impl<T: 'static> EventLoop<T> {
// Register for device hotplug events
// (The request buffer is flushed during `init_device`)
get_xtarget(&target)
.xconn
let xconn = &EventProcessor::window_target(&event_processor.target).xconn;
xconn
.select_xinput_events(
root,
ALL_DEVICES,
@@ -358,8 +343,7 @@ impl<T: 'static> EventLoop<T> {
)
.expect_then_ignore_error("Failed to register for XInput2 device hotplug events");
get_xtarget(&target)
.xconn
xconn
.select_xkb_events(
0x100, // Use the "core keyboard device"
xkb::EventType::NEW_KEYBOARD_NOTIFY
@@ -379,7 +363,6 @@ impl<T: 'static> EventLoop<T> {
activation_receiver: PeekableReceiver::from_recv(activation_token_channel),
user_receiver: PeekableReceiver::from_recv(user_channel),
user_sender,
target,
state: EventLoopState {
x11_readiness: Readiness::EMPTY,
},
@@ -396,7 +379,7 @@ impl<T: 'static> EventLoop<T> {
}
pub(crate) fn window_target(&self) -> &RootELW {
&self.target
&self.event_processor.target
}
pub fn run_on_demand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError>
@@ -421,7 +404,7 @@ impl<T: 'static> EventLoop<T> {
// `run_on_demand` calls but if they have only just dropped their
// windows we need to make sure those last requests are sent to the
// X Server.
let wt = get_xtarget(&self.target);
let wt = EventProcessor::window_target(&self.event_processor.target);
wt.x_connection().sync_with_server().map_err(|x_err| {
EventLoopError::Os(os_error!(OsError::XError(Arc::new(X11Error::Xlib(x_err)))))
})?;
@@ -544,12 +527,12 @@ impl<T: 'static> EventLoop<T> {
where
F: FnMut(Event<T>, &RootELW),
{
callback(crate::event::Event::NewEvents(cause), &self.target);
callback(Event::NewEvents(cause), &self.event_processor.target);
// NB: For consistency all platforms must emit a 'resumed' event even though X11
// applications don't themselves have a formal suspend/resume lifecycle.
if cause == StartCause::Init {
callback(crate::event::Event::Resumed, &self.target);
callback(Event::Resumed, &self.event_processor.target);
}
// Process all pending events
@@ -564,16 +547,16 @@ impl<T: 'static> EventLoop<T> {
});
match token {
Some(Ok(token)) => callback(
crate::event::Event::WindowEvent {
Some(Ok(token)) => {
let event = Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: crate::event::WindowEvent::ActivationTokenDone {
event: WindowEvent::ActivationTokenDone {
serial,
token: crate::window::ActivationToken::_new(token),
},
},
&self.target,
),
};
callback(event, &self.event_processor.target)
}
Some(Err(e)) => {
log::error!("Failed to get activation token: {}", e);
}
@@ -584,7 +567,7 @@ impl<T: 'static> EventLoop<T> {
// Empty the user event buffer
{
while let Ok(event) = self.user_receiver.try_recv() {
callback(crate::event::Event::UserEvent(event), &self.target);
callback(Event::UserEvent(event), &self.event_processor.target);
}
}
@@ -603,14 +586,14 @@ impl<T: 'static> EventLoop<T> {
window_id,
event: WindowEvent::RedrawRequested,
},
&self.target,
&self.event_processor.target,
);
}
}
// This is always the last event we dispatch before poll again
{
callback(crate::event::Event::AboutToWait, &self.target);
callback(Event::AboutToWait, &self.event_processor.target);
}
}
@@ -618,40 +601,44 @@ impl<T: 'static> EventLoop<T> {
where
F: FnMut(Event<T>, &RootELW),
{
let target = &self.target;
let mut xev = MaybeUninit::uninit();
let wt = get_xtarget(&self.target);
while unsafe { self.event_processor.poll_one_event(xev.as_mut_ptr()) } {
let mut xev = unsafe { xev.assume_init() };
self.event_processor.process_event(&mut xev, |event| {
if let Event::WindowEvent {
window_id: crate::window::WindowId(wid),
event: WindowEvent::RedrawRequested,
} = event
{
wt.redraw_sender.send(wid).unwrap();
} else {
callback(event, target);
}
});
self.event_processor
.process_event(&mut xev, |window_target, event| {
if let Event::WindowEvent {
window_id: crate::window::WindowId(wid),
event: WindowEvent::RedrawRequested,
} = event
{
let window_target = EventProcessor::window_target(window_target);
window_target.redraw_sender.send(wid).unwrap();
} else {
callback(event, window_target);
}
});
}
}
fn control_flow(&self) -> ControlFlow {
self.target.p.control_flow()
let window_target = EventProcessor::window_target(&self.event_processor.target);
window_target.control_flow()
}
fn exiting(&self) -> bool {
self.target.p.exiting()
let window_target = EventProcessor::window_target(&self.event_processor.target);
window_target.exiting()
}
fn set_exit_code(&self, code: i32) {
self.target.p.set_exit_code(code)
let window_target = EventProcessor::window_target(&self.event_processor.target);
window_target.set_exit_code(code);
}
fn exit_code(&self) -> Option<i32> {
self.target.p.exit_code()
let window_target = EventProcessor::window_target(&self.event_processor.target);
window_target.exit_code()
}
}
@@ -667,14 +654,6 @@ impl<T> AsRawFd for EventLoop<T> {
}
}
pub(crate) fn get_xtarget(target: &RootELW) -> &EventLoopWindowTarget {
match target.p {
super::EventLoopWindowTarget::X(ref target) => target,
#[cfg(wayland_platform)]
_ => unreachable!(),
}
}
impl EventLoopWindowTarget {
/// Returns the `XConnection` of this events loop.
#[inline]
@@ -1048,7 +1027,7 @@ fn mkdid(w: xinput::DeviceId) -> crate::event::DeviceId {
}
#[derive(Debug)]
struct Device {
pub struct Device {
_name: String,
scroll_axes: Vec<(i32, ScrollAxis)>,
// For master devices, this is the paired device (pointer <-> keyboard).

View File

@@ -324,7 +324,7 @@ impl XConnection {
}
}
pub(crate) struct ScreenResources {
pub struct ScreenResources {
/// List of attached modes.
modes: Vec<randr::ModeInfo>,

View File

@@ -39,11 +39,13 @@ impl XConnection {
// Retrieve DPI from Xft.dpi property
pub fn get_xft_dpi(&self) -> Option<f64> {
// Try to get it from XSETTINGS first.
match self.xsettings_dpi() {
Ok(Some(dpi)) => return Some(dpi),
Ok(None) => {}
Err(err) => {
log::warn!("failed to fetch XSettings: {err}");
if let Some(xsettings_screen) = self.xsettings_screen() {
match self.xsettings_dpi(xsettings_screen) {
Ok(Some(dpi)) => return Some(dpi),
Ok(None) => {}
Err(err) => {
log::warn!("failed to fetch XSettings: {err}");
}
}
}

View File

@@ -123,7 +123,7 @@ impl SharedState {
unsafe impl Send for UnownedWindow {}
unsafe impl Sync for UnownedWindow {}
pub(crate) struct UnownedWindow {
pub struct UnownedWindow {
pub(crate) xconn: Arc<XConnection>, // never changes
xwindow: xproto::Window, // never changes
#[allow(dead_code)]
@@ -555,9 +555,9 @@ impl UnownedWindow {
leap!(xconn.select_xinput_events(window.xwindow, super::ALL_MASTER_DEVICES, mask))
.ignore_error();
{
let result = event_loop
.ime
// Try to create input context for the window.
if let Some(ime) = event_loop.ime.as_ref() {
let result = ime
.borrow_mut()
.create_context(window.xwindow as ffi::Window, false);
leap!(result);

View File

@@ -22,7 +22,7 @@ use x11rb::{
};
/// A connection to an X server.
pub(crate) struct XConnection {
pub struct XConnection {
pub xlib: ffi::Xlib,
pub xcursor: ffi::Xcursor,
@@ -59,7 +59,7 @@ pub(crate) struct XConnection {
randr_version: (u32, u32),
/// Atom for the XSettings screen.
xsettings_screen: xproto::Atom,
xsettings_screen: Option<xproto::Atom>,
pub latest_error: Mutex<Option<XError>>,
pub cursor_cache: Mutex<HashMap<Option<CursorIcon>, ffi::Cursor>>,
@@ -108,21 +108,6 @@ impl XConnection {
// Get the default screen.
let default_screen = unsafe { (xlib.XDefaultScreen)(display) } as usize;
// Fetch the _XSETTINGS_S[screen number] atom.
let xsettings_screen = xcb
.intern_atom(false, format!("_XSETTINGS_S{}", default_screen).as_bytes())
.map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?;
// Fetch the other atoms.
let atoms = Atoms::new(&xcb)
.map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?
.reply()
.map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?;
let xsettings_screen = xsettings_screen
.reply()
.map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?
.atom;
// Load the database.
let database = resource_manager::new_from_default(&xcb)
.map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?;
@@ -134,23 +119,16 @@ impl XConnection {
.reply()
.expect("failed to query XRandR version");
// Get PropertyNotify events from the XSETTINGS window.
// TODO: The XSETTINGS window here can change. In the future, listen for DestroyNotify on this window
// in order to accomodate for a changed window here.
let selector_window = xcb
.get_selection_owner(xsettings_screen)
let xsettings_screen = Self::new_xsettings_screen(&xcb, default_screen);
if xsettings_screen.is_none() {
log::warn!("error setting XSETTINGS; Xft options won't reload automatically")
}
// Fetch atoms.
let atoms = Atoms::new(&xcb)
.map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?
.reply()
.map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?
.owner;
xcb.change_window_attributes(
selector_window,
&xproto::ChangeWindowAttributesAux::new()
.event_mask(xproto::EventMask::PROPERTY_CHANGE),
)
.map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?
.check()
.map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?;
.map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?;
Ok(XConnection {
xlib,
@@ -170,6 +148,37 @@ impl XConnection {
})
}
fn new_xsettings_screen(xcb: &XCBConnection, default_screen: usize) -> Option<xproto::Atom> {
// Fetch the _XSETTINGS_S[screen number] atom.
let xsettings_screen = xcb
.intern_atom(false, format!("_XSETTINGS_S{}", default_screen).as_bytes())
.ok()?
.reply()
.ok()?
.atom;
// Get PropertyNotify events from the XSETTINGS window.
// TODO: The XSETTINGS window here can change. In the future, listen for DestroyNotify on this window
// in order to accomodate for a changed window here.
let selector_window = xcb
.get_selection_owner(xsettings_screen)
.ok()?
.reply()
.ok()?
.owner;
xcb.change_window_attributes(
selector_window,
&xproto::ChangeWindowAttributesAux::new()
.event_mask(xproto::EventMask::PROPERTY_CHANGE),
)
.ok()?
.check()
.ok()?;
Some(xsettings_screen)
}
/// Checks whether an error has been triggered by the previous function calls.
#[inline]
pub fn check_errors(&self) -> Result<(), XError> {
@@ -258,7 +267,7 @@ impl XConnection {
/// Get the atom for Xsettings.
#[inline]
pub fn xsettings_screen(&self) -> u32 {
pub fn xsettings_screen(&self) -> Option<xproto::Atom> {
self.xsettings_screen
}
}
@@ -300,7 +309,7 @@ impl fmt::Display for XError {
/// Error returned if this system doesn't have XLib or can't create an X connection.
#[derive(Clone, Debug)]
pub(crate) enum XNotSupported {
pub enum XNotSupported {
/// Failed to load one or several shared libraries.
LibraryOpenError(ffi::OpenError),

View File

@@ -4,13 +4,13 @@
//!
//! [here]: https://github.com/derat/xsettingsd
use super::{atoms::*, XConnection};
use x11rb::protocol::xproto::ConnectionExt;
use std::iter;
use std::num::NonZeroUsize;
use x11rb::protocol::xproto::{self, ConnectionExt};
use super::{atoms::*, XConnection};
type Result<T> = core::result::Result<T, ParserError>;
const DPI_NAME: &[u8] = b"Xft/DPI";
@@ -20,13 +20,16 @@ const BIG_ENDIAN: u8 = b'B';
impl XConnection {
/// Get the DPI from XSettings.
pub(crate) fn xsettings_dpi(&self) -> core::result::Result<Option<f64>, super::X11Error> {
pub(crate) fn xsettings_dpi(
&self,
xsettings_screen: xproto::Atom,
) -> core::result::Result<Option<f64>, super::X11Error> {
let atoms = self.atoms();
// Get the current owner of the screen's settings.
let owner = self
.xcb_connection()
.get_selection_owner(self.xsettings_screen())?
.get_selection_owner(xsettings_screen)?
.reply()?;
// Read the _XSETTINGS_SETTINGS property.

View File

@@ -9,8 +9,8 @@ use std::{
use bitflags::bitflags;
use orbclient::{
ButtonEvent, EventOption, FocusEvent, HoverEvent, KeyEvent, MouseEvent, MoveEvent, QuitEvent,
ResizeEvent, ScrollEvent, TextInputEvent,
ButtonEvent, EventOption, FocusEvent, HoverEvent, KeyEvent, MouseEvent, MouseRelativeEvent,
MoveEvent, QuitEvent, ResizeEvent, ScrollEvent, TextInputEvent,
};
use smol_str::SmolStr;
@@ -457,6 +457,14 @@ impl<T: 'static> EventLoop<T> {
},
});
}
EventOption::MouseRelative(MouseRelativeEvent { dx, dy }) => {
event_handler(event::Event::DeviceEvent {
device_id: event::DeviceId(DeviceId),
event: event::DeviceEvent::MouseMotion {
delta: (dx as f64, dy as f64),
},
});
}
EventOption::Button(ButtonEvent {
left,
middle,
@@ -510,7 +518,7 @@ impl<T: 'static> EventLoop<T> {
// Acknowledge resize after event loop.
event_state.resize_opt = Some((width, height));
}
//TODO: Clipboard
//TODO: Screen, Clipboard, Drop
EventOption::Hover(HoverEvent { entered }) => {
if entered {
event_handler(event::Event::WindowEvent {

View File

@@ -13,7 +13,8 @@ use crate::{
};
use super::{
EventLoopWindowTarget, MonitorHandle, RedoxSocket, TimeSocket, WindowId, WindowProperties,
EventLoopWindowTarget, MonitorHandle, OsError, RedoxSocket, TimeSocket, WindowId,
WindowProperties,
};
// These values match the values uses in the `window_new` function in orbital:
@@ -21,7 +22,9 @@ use super::{
const ORBITAL_FLAG_ASYNC: char = 'a';
const ORBITAL_FLAG_BACK: char = 'b';
const ORBITAL_FLAG_FRONT: char = 'f';
const ORBITAL_FLAG_HIDDEN: char = 'h';
const ORBITAL_FLAG_BORDERLESS: char = 'l';
const ORBITAL_FLAG_MAXIMIZED: char = 'm';
const ORBITAL_FLAG_RESIZABLE: char = 'r';
const ORBITAL_FLAG_TRANSPARENT: char = 't';
@@ -57,11 +60,15 @@ impl Window {
// Async by default.
let mut flag_str = ORBITAL_FLAG_ASYNC.to_string();
if attrs.maximized {
flag_str.push(ORBITAL_FLAG_MAXIMIZED);
}
if attrs.resizable {
flag_str.push(ORBITAL_FLAG_RESIZABLE);
}
//TODO: maximized, fullscreen, visible
//TODO: fullscreen
if attrs.transparent {
flag_str.push(ORBITAL_FLAG_TRANSPARENT);
@@ -71,6 +78,10 @@ impl Window {
flag_str.push(ORBITAL_FLAG_BORDERLESS);
}
if !attrs.visible {
flag_str.push(ORBITAL_FLAG_HIDDEN);
}
match attrs.window_level {
window::WindowLevel::AlwaysOnBottom => {
flag_str.push(ORBITAL_FLAG_BACK);
@@ -129,6 +140,23 @@ impl Window {
f(self)
}
fn get_flag(&self, flag: char) -> Result<bool, error::ExternalError> {
let mut buf: [u8; 4096] = [0; 4096];
let path = self
.window_socket
.fpath(&mut buf)
.map_err(|err| error::ExternalError::Os(os_error!(OsError::new(err))))?;
let properties = WindowProperties::new(path);
Ok(properties.flags.contains(flag))
}
fn set_flag(&self, flag: char, value: bool) -> Result<(), error::ExternalError> {
self.window_socket
.write(format!("F,{flag},{}", if value { 1 } else { 0 }).as_bytes())
.map_err(|err| error::ExternalError::Os(os_error!(OsError::new(err))))?;
Ok(())
}
#[inline]
pub fn id(&self) -> WindowId {
WindowId {
@@ -254,17 +282,21 @@ impl Window {
}
#[inline]
pub fn set_transparent(&self, _transparent: bool) {}
pub fn set_transparent(&self, transparent: bool) {
let _ = self.set_flag(ORBITAL_FLAG_TRANSPARENT, transparent);
}
#[inline]
pub fn set_blur(&self, _blur: bool) {}
#[inline]
pub fn set_visible(&self, _visibility: bool) {}
pub fn set_visible(&self, visible: bool) {
let _ = self.set_flag(ORBITAL_FLAG_HIDDEN, !visible);
}
#[inline]
pub fn is_visible(&self) -> Option<bool> {
None
Some(!self.get_flag(ORBITAL_FLAG_HIDDEN).unwrap_or(false))
}
#[inline]
@@ -276,17 +308,13 @@ impl Window {
pub fn set_resize_increments(&self, _increments: Option<Size>) {}
#[inline]
pub fn set_resizable(&self, _resizeable: bool) {}
pub fn set_resizable(&self, resizeable: bool) {
let _ = self.set_flag(ORBITAL_FLAG_RESIZABLE, resizeable);
}
#[inline]
pub fn is_resizable(&self) -> bool {
let mut buf: [u8; 4096] = [0; 4096];
let path = self
.window_socket
.fpath(&mut buf)
.expect("failed to read properties");
let properties = WindowProperties::new(path);
properties.flags.contains(ORBITAL_FLAG_RESIZABLE)
self.get_flag(ORBITAL_FLAG_RESIZABLE).unwrap_or(false)
}
#[inline]
@@ -298,11 +326,13 @@ impl Window {
}
#[inline]
pub fn set_maximized(&self, _maximized: bool) {}
pub fn set_maximized(&self, maximized: bool) {
let _ = self.set_flag(ORBITAL_FLAG_MAXIMIZED, maximized);
}
#[inline]
pub fn is_maximized(&self) -> bool {
false
self.get_flag(ORBITAL_FLAG_MAXIMIZED).unwrap_or(false)
}
#[inline]
@@ -314,21 +344,30 @@ impl Window {
}
#[inline]
pub fn set_decorations(&self, _decorations: bool) {}
#[inline]
pub fn is_decorated(&self) -> bool {
let mut buf: [u8; 4096] = [0; 4096];
let path = self
.window_socket
.fpath(&mut buf)
.expect("failed to read properties");
let properties = WindowProperties::new(path);
!properties.flags.contains(ORBITAL_FLAG_BORDERLESS)
pub fn set_decorations(&self, decorations: bool) {
let _ = self.set_flag(ORBITAL_FLAG_BORDERLESS, !decorations);
}
#[inline]
pub fn set_window_level(&self, _level: window::WindowLevel) {}
pub fn is_decorated(&self) -> bool {
!self.get_flag(ORBITAL_FLAG_BORDERLESS).unwrap_or(false)
}
#[inline]
pub fn set_window_level(&self, level: window::WindowLevel) {
match level {
window::WindowLevel::AlwaysOnBottom => {
let _ = self.set_flag(ORBITAL_FLAG_BACK, true);
}
window::WindowLevel::Normal => {
let _ = self.set_flag(ORBITAL_FLAG_BACK, false);
let _ = self.set_flag(ORBITAL_FLAG_FRONT, false);
}
window::WindowLevel::AlwaysOnTop => {
let _ = self.set_flag(ORBITAL_FLAG_FRONT, true);
}
}
}
#[inline]
pub fn set_window_icon(&self, _window_icon: Option<crate::icon::Icon>) {}
@@ -359,30 +398,58 @@ impl Window {
}
#[inline]
pub fn set_cursor_grab(&self, _: window::CursorGrabMode) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported(
error::NotSupportedError::new(),
))
pub fn set_cursor_grab(
&self,
mode: window::CursorGrabMode,
) -> Result<(), error::ExternalError> {
let (grab, relative) = match mode {
window::CursorGrabMode::None => (false, false),
window::CursorGrabMode::Confined => (true, false),
window::CursorGrabMode::Locked => (true, true),
};
self.window_socket
.write(format!("M,G,{}", if grab { 1 } else { 0 }).as_bytes())
.map_err(|err| error::ExternalError::Os(os_error!(OsError::new(err))))?;
self.window_socket
.write(format!("M,R,{}", if relative { 1 } else { 0 }).as_bytes())
.map_err(|err| error::ExternalError::Os(os_error!(OsError::new(err))))?;
Ok(())
}
#[inline]
pub fn set_cursor_visible(&self, _: bool) {}
pub fn set_cursor_visible(&self, visible: bool) {
let _ = self
.window_socket
.write(format!("M,C,{}", if visible { 1 } else { 0 }).as_bytes());
}
#[inline]
pub fn drag_window(&self) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported(
error::NotSupportedError::new(),
))
self.window_socket
.write(b"D")
.map_err(|err| error::ExternalError::Os(os_error!(OsError::new(err))))?;
Ok(())
}
#[inline]
pub fn drag_resize_window(
&self,
_direction: window::ResizeDirection,
direction: window::ResizeDirection,
) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported(
error::NotSupportedError::new(),
))
let arg = match direction {
window::ResizeDirection::East => "R",
window::ResizeDirection::North => "T",
window::ResizeDirection::NorthEast => "T,R",
window::ResizeDirection::NorthWest => "T,L",
window::ResizeDirection::South => "B",
window::ResizeDirection::SouthEast => "B,R",
window::ResizeDirection::SouthWest => "B,L",
window::ResizeDirection::West => "L",
};
self.window_socket
.write(format!("D,{}", arg).as_bytes())
.map_err(|err| error::ExternalError::Os(os_error!(OsError::new(err))))?;
Ok(())
}
#[inline]

View File

@@ -57,8 +57,11 @@ use serde::{Deserialize, Serialize};
///
/// ## Platform-specific
///
/// **Web:** The [`Window`], which is represented by a `HTMLElementCanvas`, can
/// not be closed by dropping the [`Window`].
/// - **Web:** The window is represented by a `HTMLElementCanvas`, and cannot
/// currently be closed by dropping the [`Window`].
/// - **Android:** Each window is spawned inside its own process, and as such
/// Winit does not support multiple windows on Android. Create a new
/// activity instead.
pub struct Window {
pub(crate) window: platform_impl::Window,
}
@@ -946,7 +949,7 @@ impl Window {
///
/// ## Platform-specific
///
/// - **Web / iOS / Android / Orbital:** Unsupported.
/// - **Web / iOS / Android:** Unsupported.
/// - **X11:** Can only be set while building the window, with [`WindowBuilder::with_transparent`].
#[inline]
pub fn set_transparent(&self, transparent: bool) {
@@ -1079,7 +1082,7 @@ impl Window {
///
/// ## Platform-specific
///
/// - **iOS / Android / Web / Orbital:** Unsupported.
/// - **iOS / Android / Web:** Unsupported.
#[inline]
pub fn set_maximized(&self, maximized: bool) {
self.window
@@ -1090,7 +1093,7 @@ impl Window {
///
/// ## Platform-specific
///
/// - **iOS / Android / Web / Orbital:** Unsupported.
/// - **iOS / Android / Web:** Unsupported.
#[inline]
pub fn is_maximized(&self) -> bool {
self.window.maybe_wait_on_main(|w| w.is_maximized())
@@ -1450,7 +1453,7 @@ impl Window {
/// - **Wayland:** The cursor is only hidden within the confines of the window.
/// - **macOS:** The cursor is hidden as long as the window has input focus, even if the cursor is
/// outside of the window.
/// - **iOS / Android / Orbital:** Unsupported.
/// - **iOS / Android:** Unsupported.
#[inline]
pub fn set_cursor_visible(&self, visible: bool) {
self.window
@@ -1467,7 +1470,7 @@ impl Window {
/// - **X11:** Un-grabs the cursor.
/// - **Wayland:** Requires the cursor to be inside the window to be dragged.
/// - **macOS:** May prevent the button release event to be triggered.
/// - **iOS / Android / Web / Orbital:** Always returns an [`ExternalError::NotSupported`].
/// - **iOS / Android / Web:** Always returns an [`ExternalError::NotSupported`].
#[inline]
pub fn drag_window(&self) -> Result<(), ExternalError> {
self.window.maybe_wait_on_main(|w| w.drag_window())
@@ -1481,7 +1484,7 @@ impl Window {
/// ## Platform-specific
///
/// - **macOS:** Always returns an [`ExternalError::NotSupported`]
/// - **iOS / Android / Web / Orbital:** Always returns an [`ExternalError::NotSupported`].
/// - **iOS / Android / Web:** Always returns an [`ExternalError::NotSupported`].
#[inline]
pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> {
self.window
@@ -1642,7 +1645,7 @@ pub enum CursorGrabMode {
/// ## Platform-specific
///
/// - **macOS:** Not implemented. Always returns [`ExternalError::NotSupported`] for now.
/// - **iOS / Android / Web / Orbital:** Always returns an [`ExternalError::NotSupported`].
/// - **iOS / Android / Web:** Always returns an [`ExternalError::NotSupported`].
Confined,
/// The cursor is locked inside the window area to the certain position.
@@ -1653,7 +1656,7 @@ pub enum CursorGrabMode {
/// ## Platform-specific
///
/// - **X11 / Windows:** Not implemented. Always returns [`ExternalError::NotSupported`] for now.
/// - **iOS / Android / Orbital:** Always returns an [`ExternalError::NotSupported`].
/// - **iOS / Android:** Always returns an [`ExternalError::NotSupported`].
Locked,
}