x11 calloop 2 (#13955)

Release Notes:

- N/A

---------

Co-authored-by: Max <max@zed.dev>
This commit is contained in:
Conrad Irwin 2024-07-08 18:38:36 -06:00 committed by GitHub
parent efc2336be5
commit b0ecda6370
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 287 additions and 442 deletions

20
Cargo.lock generated
View file

@ -4888,7 +4888,6 @@ dependencies = [
"log",
"media",
"metal",
"mio 1.0.0",
"num_cpus",
"objc",
"oo7",
@ -5689,7 +5688,7 @@ dependencies = [
"fnv",
"lazy_static",
"libc",
"mio 0.8.11",
"mio",
"rand 0.8.5",
"serde",
"tempfile",
@ -6619,19 +6618,6 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "mio"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4929e1f84c5e54c3ec6141cd5d8b5a5c055f031f80cf78f2072920173cb4d880"
dependencies = [
"hermit-abi 0.3.9",
"libc",
"log",
"wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys 0.52.0",
]
[[package]]
name = "miow"
version = "0.6.0"
@ -6870,7 +6856,7 @@ dependencies = [
"kqueue",
"libc",
"log",
"mio 0.8.11",
"mio",
"walkdir",
"windows-sys 0.48.0",
]
@ -11081,7 +11067,7 @@ dependencies = [
"backtrace",
"bytes 1.5.0",
"libc",
"mio 0.8.11",
"mio",
"num_cpus",
"parking_lot",
"pin-project-lite",

View file

@ -141,7 +141,6 @@ xim = { git = "https://github.com/npmania/xim-rs", rev = "27132caffc5b9bc9c432ca
] }
font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "5a5c4d4", features = ["source-fontconfig-dlopen"] }
x11-clipboard = "0.9.2"
mio = { version = "1.0.0", features = ["os-poll", "os-ext"] }
[target.'cfg(windows)'.dependencies]
windows.workspace = true

View file

@ -5,11 +5,9 @@ use calloop::{
timer::TimeoutAction,
EventLoop,
};
use mio::Waker;
use parking::{Parker, Unparker};
use parking_lot::Mutex;
use std::{
sync::Arc,
thread,
time::{Duration, Instant},
};
@ -23,7 +21,6 @@ struct TimerAfter {
pub(crate) struct LinuxDispatcher {
parker: Mutex<Parker>,
main_sender: Sender<Runnable>,
main_waker: Option<Arc<Waker>>,
timer_sender: Sender<TimerAfter>,
background_sender: flume::Sender<Runnable>,
_background_threads: Vec<thread::JoinHandle<()>>,
@ -31,7 +28,7 @@ pub(crate) struct LinuxDispatcher {
}
impl LinuxDispatcher {
pub fn new(main_sender: Sender<Runnable>, main_waker: Option<Arc<Waker>>) -> Self {
pub fn new(main_sender: Sender<Runnable>) -> Self {
let (background_sender, background_receiver) = flume::unbounded::<Runnable>();
let thread_count = std::thread::available_parallelism()
.map(|i| i.get())
@ -91,7 +88,6 @@ impl LinuxDispatcher {
Self {
parker: Mutex::new(Parker::new()),
main_sender,
main_waker,
timer_sender,
background_sender,
_background_threads: background_threads,
@ -111,9 +107,6 @@ impl PlatformDispatcher for LinuxDispatcher {
fn dispatch_on_main_thread(&self, runnable: Runnable) {
self.main_sender.send(runnable).ok();
if let Some(main_waker) = self.main_waker.as_ref() {
main_waker.wake().ok();
}
}
fn dispatch_after(&self, duration: Duration, runnable: Runnable) {

View file

@ -22,7 +22,7 @@ impl HeadlessClient {
pub(crate) fn new() -> Self {
let event_loop = EventLoop::try_new().unwrap();
let (common, main_receiver) = LinuxCommon::new(Box::new(event_loop.get_signal()), None);
let (common, main_receiver) = LinuxCommon::new(event_loop.get_signal());
let handle = event_loop.handle();

View file

@ -28,7 +28,6 @@ use calloop::{EventLoop, LoopHandle, LoopSignal};
use filedescriptor::FileDescriptor;
use flume::{Receiver, Sender};
use futures::channel::oneshot;
use mio::Waker;
use parking_lot::Mutex;
use time::UtcOffset;
use util::ResultExt;
@ -88,16 +87,6 @@ pub(crate) struct PlatformHandlers {
pub(crate) validate_app_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
}
pub trait QuitSignal {
fn quit(&mut self);
}
impl QuitSignal for LoopSignal {
fn quit(&mut self) {
self.stop();
}
}
pub(crate) struct LinuxCommon {
pub(crate) background_executor: BackgroundExecutor,
pub(crate) foreground_executor: ForegroundExecutor,
@ -105,20 +94,17 @@ pub(crate) struct LinuxCommon {
pub(crate) appearance: WindowAppearance,
pub(crate) auto_hide_scrollbars: bool,
pub(crate) callbacks: PlatformHandlers,
pub(crate) quit_signal: Box<dyn QuitSignal>,
pub(crate) signal: LoopSignal,
pub(crate) menus: Vec<OwnedMenu>,
}
impl LinuxCommon {
pub fn new(
quit_signal: Box<dyn QuitSignal>,
main_waker: Option<Arc<Waker>>,
) -> (Self, Channel<Runnable>) {
pub fn new(signal: LoopSignal) -> (Self, Channel<Runnable>) {
let (main_sender, main_receiver) = calloop::channel::channel::<Runnable>();
let text_system = Arc::new(CosmicTextSystem::new());
let callbacks = PlatformHandlers::default();
let dispatcher = Arc::new(LinuxDispatcher::new(main_sender.clone(), main_waker));
let dispatcher = Arc::new(LinuxDispatcher::new(main_sender.clone()));
let background_executor = BackgroundExecutor::new(dispatcher.clone());
@ -129,7 +115,7 @@ impl LinuxCommon {
appearance: WindowAppearance::Light,
auto_hide_scrollbars: false,
callbacks,
quit_signal,
signal,
menus: Vec::new(),
};
@ -163,7 +149,7 @@ impl<P: LinuxClient + 'static> Platform for P {
}
fn quit(&self) {
self.with_common(|common| common.quit_signal.quit());
self.with_common(|common| common.signal.stop());
}
fn compositor_name(&self) -> &'static str {

View file

@ -326,7 +326,7 @@ impl WaylandClientStatePtr {
}
}
if state.windows.is_empty() {
state.common.quit_signal.quit();
state.common.signal.stop();
}
}
}
@ -422,7 +422,7 @@ impl WaylandClient {
let event_loop = EventLoop::<WaylandClientStatePtr>::try_new().unwrap();
let (common, main_receiver) = LinuxCommon::new(Box::new(event_loop.get_signal()), None);
let (common, main_receiver) = LinuxCommon::new(event_loop.get_signal());
let handle = event_loop.handle();
handle
@ -459,7 +459,7 @@ impl WaylandClient {
let mut cursor = Cursor::new(&conn, &globals, 24);
handle
.insert_source(XDPEventSource::new(&common.background_executor, None), {
.insert_source(XDPEventSource::new(&common.background_executor), {
move |event, _, client| match event {
XDPEvent::WindowAppearance(appearance) => {
if let Some(client) = client.0.upgrade() {

View file

@ -1,27 +1,23 @@
use std::cell::RefCell;
use std::collections::HashSet;
use std::ops::Deref;
use std::os::fd::AsRawFd;
use std::path::PathBuf;
use std::rc::{Rc, Weak};
use std::sync::Arc;
use std::time::{Duration, Instant};
use anyhow::Context;
use async_task::Runnable;
use calloop::channel::Channel;
use calloop::generic::{FdWrapper, Generic};
use calloop::{EventLoop, LoopHandle, RegistrationToken};
use collections::HashMap;
use futures::channel::oneshot;
use mio::{Interest, Token, Waker};
use util::ResultExt;
use x11rb::connection::{Connection, RequestConnection};
use x11rb::cursor;
use x11rb::errors::ConnectionError;
use x11rb::protocol::randr::ConnectionExt as _;
use x11rb::protocol::xinput::ConnectionExt;
use x11rb::protocol::xkb::ConnectionExt as _;
use x11rb::protocol::xproto::{ChangeWindowAttributesAux, ConnectionExt as _};
use x11rb::protocol::xproto::{ChangeWindowAttributesAux, ConnectionExt as _, KeyPressEvent};
use x11rb::protocol::{randr, render, xinput, xkb, xproto, Event};
use x11rb::resource_manager::Database;
use x11rb::xcb_ffi::XCBConnection;
@ -35,7 +31,7 @@ use crate::platform::{LinuxCommon, PlatformWindow};
use crate::{
modifiers_from_xinput_info, point, px, AnyWindowHandle, Bounds, ClipboardItem, CursorStyle,
DisplayId, Keystroke, Modifiers, ModifiersChangedEvent, Pixels, Platform, PlatformDisplay,
PlatformInput, Point, QuitSignal, ScrollDelta, Size, TouchPhase, WindowParams, X11Window,
PlatformInput, Point, ScrollDelta, Size, TouchPhase, WindowParams, X11Window,
};
use super::{button_of_key, modifiers_from_state, pressed_button_from_mask};
@ -51,6 +47,7 @@ pub(super) const XINPUT_MASTER_DEVICE: u16 = 1;
pub(crate) struct WindowRef {
window: X11WindowStatePtr,
refresh_event_token: RegistrationToken,
}
impl WindowRef {
@ -98,18 +95,15 @@ impl From<xim::ClientError> for EventHandlerError {
}
pub struct X11ClientState {
/// poll is in an Option so we can take it out in `run()` without
/// mutating self.
poll: Option<mio::Poll>,
quit_signal_rx: oneshot::Receiver<()>,
runnables: Channel<Runnable>,
xdp_event_source: XDPEventSource,
pub(crate) loop_handle: LoopHandle<'static, X11Client>,
pub(crate) event_loop: Option<calloop::EventLoop<'static, X11Client>>,
pub(crate) last_click: Instant,
pub(crate) last_location: Point<Pixels>,
pub(crate) current_count: usize,
pub(crate) scale_factor: f32,
pub(crate) xcb_connection: Rc<XCBConnection>,
pub(crate) x_root_index: usize,
pub(crate) _resource_database: Database,
@ -146,11 +140,8 @@ impl X11ClientStatePtr {
let client = X11Client(self.0.upgrade().expect("client already dropped"));
let mut state = client.0.borrow_mut();
if state.windows.remove(&x_window).is_none() {
log::warn!(
"failed to remove X window {} from client state, does not exist",
x_window
);
if let Some(window_ref) = state.windows.remove(&x_window) {
state.loop_handle.remove(window_ref.refresh_event_token);
}
if state.mouse_focused_window == Some(x_window) {
state.mouse_focused_window = None;
@ -161,36 +152,7 @@ impl X11ClientStatePtr {
state.cursor_styles.remove(&x_window);
if state.windows.is_empty() {
state.common.quit_signal.quit();
}
}
}
struct ChannelQuitSignal {
tx: Option<oneshot::Sender<()>>,
waker: Option<Arc<Waker>>,
}
impl ChannelQuitSignal {
fn new(waker: Option<Arc<Waker>>) -> (Self, oneshot::Receiver<()>) {
let (tx, rx) = oneshot::channel::<()>();
let quit_signal = ChannelQuitSignal {
tx: Some(tx),
waker,
};
(quit_signal, rx)
}
}
impl QuitSignal for ChannelQuitSignal {
fn quit(&mut self) {
if let Some(tx) = self.tx.take() {
tx.send(()).log_err();
if let Some(waker) = self.waker.as_ref() {
waker.wake().ok();
}
state.common.signal.stop();
}
}
}
@ -200,12 +162,27 @@ pub(crate) struct X11Client(Rc<RefCell<X11ClientState>>);
impl X11Client {
pub(crate) fn new() -> Self {
let mut poll = mio::Poll::new().unwrap();
let event_loop = EventLoop::try_new().unwrap();
let waker = Arc::new(Waker::new(poll.registry(), WAKER_TOKEN).unwrap());
let (common, main_receiver) = LinuxCommon::new(event_loop.get_signal());
let (quit_signal, quit_signal_rx) = ChannelQuitSignal::new(Some(waker.clone()));
let (common, runnables) = LinuxCommon::new(Box::new(quit_signal), Some(waker.clone()));
let handle = event_loop.handle();
handle
.insert_source(main_receiver, {
let handle = handle.clone();
move |event, _, _: &mut X11Client| {
if let calloop::channel::Event::Msg(runnable) = event {
// Insert the runnables as idle callbacks, so we make sure that user-input and X11
// events have higher priority and runnables are only worked off after the event
// callbacks.
handle.insert_idle(|_| {
runnable.run();
});
}
}
})
.unwrap();
let (xcb_connection, x_root_index) = XCBConnection::connect(None).unwrap();
xcb_connection
@ -304,18 +281,47 @@ impl X11Client {
None
};
let xdp_event_source =
XDPEventSource::new(&common.background_executor, Some(waker.clone()));
// Safety: Safe if xcb::Connection always returns a valid fd
let fd = unsafe { FdWrapper::new(Rc::clone(&xcb_connection)) };
handle
.insert_source(
Generic::new_with_error::<EventHandlerError>(
fd,
calloop::Interest::READ,
calloop::Mode::Level,
),
{
let xcb_connection = xcb_connection.clone();
move |_readiness, _, client| {
client.process_x11_events(&xcb_connection)?;
Ok(calloop::PostAction::Continue)
}
},
)
.expect("Failed to initialize x11 event source");
handle
.insert_source(XDPEventSource::new(&common.background_executor), {
move |event, _, client| match event {
XDPEvent::WindowAppearance(appearance) => {
client.with_common(|common| common.appearance = appearance);
for (_, window) in &mut client.0.borrow_mut().windows {
window.window.set_appearance(appearance);
}
}
XDPEvent::CursorTheme(_) | XDPEvent::CursorSize(_) => {
// noop, X11 manages this for us.
}
}
})
.unwrap();
X11Client(Rc::new(RefCell::new(X11ClientState {
poll: Some(poll),
runnables,
xdp_event_source,
quit_signal_rx,
common,
modifiers: Modifiers::default(),
event_loop: Some(event_loop),
loop_handle: handle,
common,
last_click: Instant::now(),
last_location: Point::new(px(0.0), px(0.0)),
current_count: 0,
@ -349,6 +355,125 @@ impl X11Client {
})))
}
pub fn process_x11_events(
&self,
xcb_connection: &XCBConnection,
) -> Result<(), EventHandlerError> {
loop {
let mut events = Vec::new();
let mut windows_to_refresh = HashSet::new();
let mut last_key_release = None;
let mut last_key_press: Option<KeyPressEvent> = None;
loop {
match xcb_connection.poll_for_event() {
Ok(Some(event)) => {
match event {
Event::Expose(expose_event) => {
windows_to_refresh.insert(expose_event.window);
}
Event::KeyRelease(_) => {
last_key_release = Some(event);
}
Event::KeyPress(key_press) => {
if let Some(last_press) = last_key_press.as_ref() {
if last_press.detail == key_press.detail {
continue;
}
}
if let Some(Event::KeyRelease(key_release)) =
last_key_release.take()
{
// We ignore that last KeyRelease if it's too close to this KeyPress,
// suggesting that it's auto-generated by X11 as a key-repeat event.
if key_release.detail != key_press.detail
|| key_press.time.saturating_sub(key_release.time) > 20
{
events.push(Event::KeyRelease(key_release));
}
}
events.push(Event::KeyPress(key_press));
last_key_press = Some(key_press);
}
_ => {
if let Some(release_event) = last_key_release.take() {
events.push(release_event);
}
events.push(event);
}
}
}
Ok(None) => {
// Add any remaining stored KeyRelease event
if let Some(release_event) = last_key_release.take() {
events.push(release_event);
}
break;
}
Err(e) => {
log::warn!("error polling for X11 events: {e:?}");
break;
}
}
}
if events.is_empty() && windows_to_refresh.is_empty() {
break;
}
for window in windows_to_refresh.into_iter() {
if let Some(window) = self.get_window(window) {
window.refresh();
}
}
for event in events.into_iter() {
let mut state = self.0.borrow_mut();
if state.ximc.is_none() || state.xim_handler.is_none() {
drop(state);
self.handle_event(event);
continue;
}
let mut ximc = state.ximc.take().unwrap();
let mut xim_handler = state.xim_handler.take().unwrap();
let xim_connected = xim_handler.connected;
drop(state);
let xim_filtered = match ximc.filter_event(&event, &mut xim_handler) {
Ok(handled) => handled,
Err(err) => {
log::error!("XIMClientError: {}", err);
false
}
};
let xim_callback_event = xim_handler.last_callback_event.take();
let mut state = self.0.borrow_mut();
state.ximc = Some(ximc);
state.xim_handler = Some(xim_handler);
drop(state);
if let Some(event) = xim_callback_event {
self.handle_xim_callback_event(event);
}
if xim_filtered {
continue;
}
if xim_connected {
self.xim_handle_event(event);
} else {
self.handle_event(event);
}
}
}
Ok(())
}
pub fn enable_ime(&self) {
let mut state = self.0.borrow_mut();
if state.ximc.is_none() {
@ -411,117 +536,6 @@ impl X11Client {
.map(|window_reference| window_reference.window.clone())
}
fn read_x11_events(&self) -> (HashSet<u32>, Vec<Event>) {
let mut events = Vec::new();
let mut windows_to_refresh = HashSet::new();
let mut state = self.0.borrow_mut();
let mut last_key_release: Option<Event> = None;
loop {
match state.xcb_connection.poll_for_event() {
Ok(Some(event)) => {
if let Event::Expose(expose_event) = event {
windows_to_refresh.insert(expose_event.window);
} else {
match event {
Event::KeyRelease(_) => {
last_key_release = Some(event);
}
Event::KeyPress(key_press) => {
if let Some(Event::KeyRelease(key_release)) =
last_key_release.take()
{
// We ignore that last KeyRelease if it's too close to this KeyPress,
// suggesting that it's auto-generated by X11 as a key-repeat event.
if key_release.detail != key_press.detail
|| key_press.time.wrapping_sub(key_release.time) > 20
{
events.push(Event::KeyRelease(key_release));
}
}
events.push(Event::KeyPress(key_press));
}
_ => {
if let Some(release_event) = last_key_release.take() {
events.push(release_event);
}
events.push(event);
}
}
}
}
Ok(None) => {
// Add any remaining stored KeyRelease event
if let Some(release_event) = last_key_release.take() {
events.push(release_event);
}
break;
}
Err(e) => {
log::warn!("error polling for X11 events: {e:?}");
break;
}
}
}
(windows_to_refresh, events)
}
fn process_x11_events(&self, events: Vec<Event>) {
log::trace!(
"main thread: processing X11 events. events: {}",
events.len()
);
for event in events.into_iter() {
log::trace!("main thread: processing X11 event: {:?}", event);
let mut state = self.0.borrow_mut();
if state.ximc.is_none() || state.xim_handler.is_none() {
drop(state);
self.handle_event(event);
continue;
}
let mut ximc = state.ximc.take().unwrap();
let mut xim_handler = state.xim_handler.take().unwrap();
let xim_connected = xim_handler.connected;
drop(state);
// let xim_filtered = false;
let xim_filtered = match ximc.filter_event(&event, &mut xim_handler) {
Ok(handled) => handled,
Err(err) => {
log::error!("XIMClientError: {}", err);
false
}
};
let xim_callback_event = xim_handler.last_callback_event.take();
let mut state = self.0.borrow_mut();
state.ximc = Some(ximc);
state.xim_handler = Some(xim_handler);
if let Some(event) = xim_callback_event {
drop(state);
self.handle_xim_callback_event(event);
} else {
drop(state);
}
if xim_filtered {
continue;
}
if xim_connected {
self.xim_handle_event(event);
} else {
self.handle_event(event);
}
}
}
fn handle_event(&self, event: Event) -> Option<()> {
match event {
Event::ClientMessage(event) => {
@ -561,10 +575,6 @@ impl X11Client {
let window = self.get_window(event.window)?;
window.property_notify(event);
}
Event::Expose(event) => {
let window = self.get_window(event.window)?;
window.refresh();
}
Event::FocusIn(event) if event.mode == xproto::NotifyMode::NORMAL => {
let window = self.get_window(event.event)?;
window.set_focused(true);
@ -979,13 +989,11 @@ impl X11Client {
}
}
const XCB_CONNECTION_TOKEN: Token = Token(0);
const WAKER_TOKEN: Token = Token(1);
impl LinuxClient for X11Client {
fn compositor_name(&self) -> &'static str {
"X11"
}
fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R {
f(&mut self.0.borrow_mut().common)
}
@ -1051,8 +1059,61 @@ impl LinuxClient for X11Client {
state.common.appearance,
)?;
let screen_resources = state
.xcb_connection
.randr_get_screen_resources(x_window)
.unwrap()
.reply()
.expect("Could not find available screens");
let mode = screen_resources
.crtcs
.iter()
.find_map(|crtc| {
let crtc_info = state
.xcb_connection
.randr_get_crtc_info(*crtc, x11rb::CURRENT_TIME)
.ok()?
.reply()
.ok()?;
screen_resources
.modes
.iter()
.find(|m| m.id == crtc_info.mode)
})
.expect("Unable to find screen refresh rate");
let refresh_event_token = state
.loop_handle
.insert_source(calloop::timer::Timer::immediate(), {
let refresh_duration = mode_refresh_rate(mode);
move |mut instant, (), client| {
let xcb_connection = {
let state = client.0.borrow_mut();
let xcb_connection = state.xcb_connection.clone();
if let Some(window) = state.windows.get(&x_window) {
let window = window.window.clone();
drop(state);
window.refresh();
}
xcb_connection
};
client.process_x11_events(&xcb_connection).log_err();
// Take into account that some frames have been skipped
let now = Instant::now();
while instant < now {
instant += refresh_duration;
}
calloop::timer::TimeoutAction::ToInstant(instant)
}
})
.expect("Failed to initialize refresh timer");
let window_ref = WindowRef {
window: window.0.clone(),
refresh_event_token,
};
state.windows.insert(x_window, window_ref);
@ -1181,134 +1242,14 @@ impl LinuxClient for X11Client {
}
fn run(&self) {
let mut poll = self
let mut event_loop = self
.0
.borrow_mut()
.poll
.event_loop
.take()
.context("no poll set on X11Client. calling run more than once is not possible")
.unwrap();
.expect("App is already running");
let xcb_fd = self.0.borrow().xcb_connection.as_raw_fd();
let mut xcb_source = mio::unix::SourceFd(&xcb_fd);
poll.registry()
.register(&mut xcb_source, XCB_CONNECTION_TOKEN, Interest::READABLE)
.unwrap();
let mut events = mio::Events::with_capacity(1024);
let mut next_refresh_needed = Instant::now();
'run_loop: loop {
let poll_timeout = next_refresh_needed - Instant::now();
// We rounding the poll_timeout down so `mio` doesn't round it up to the next higher milliseconds
let poll_timeout = Duration::from_millis(poll_timeout.as_millis() as u64);
if poll_timeout >= Duration::from_millis(1) {
let _ = poll.poll(&mut events, Some(poll_timeout));
};
let mut state = self.0.borrow_mut();
// Check if we need to quit
if let Ok(Some(())) = state.quit_signal_rx.try_recv() {
return;
}
// Redraw windows
let now = Instant::now();
if now > next_refresh_needed {
// This will be pulled down to 16ms (or less) if a window is open
let mut frame_length = Duration::from_millis(100);
let mut windows = vec![];
for (_, window_ref) in state.windows.iter() {
if !window_ref.window.state.borrow().destroyed {
frame_length = frame_length.min(window_ref.window.refresh_rate());
windows.push(window_ref.window.clone());
}
}
drop(state);
for window in windows {
window.refresh();
}
state = self.0.borrow_mut();
// In the case that we're looping a bit too fast, slow down
next_refresh_needed = now.max(next_refresh_needed) + frame_length;
}
// X11 events
drop(state);
loop {
let (x_windows, events) = self.read_x11_events();
for x_window in x_windows {
if let Some(window) = self.get_window(x_window) {
log::trace!(
"main thread: refreshing window {} due expose event",
window.x_window
);
window.refresh();
}
}
if events.len() == 0 {
break;
}
self.process_x11_events(events);
// When X11 is sending us events faster than we can handle we'll
// let the frame rate drop to 10fps to try and avoid getting too behind.
if Instant::now() > next_refresh_needed + Duration::from_millis(80) {
continue 'run_loop;
}
}
state = self.0.borrow_mut();
// Runnables
while let Ok(runnable) = state.runnables.try_recv() {
drop(state);
let start = Instant::now();
runnable.run();
log::trace!("main thread: ran runnable. took: {:?}", start.elapsed());
state = self.0.borrow_mut();
if Instant::now() + Duration::from_millis(1) >= next_refresh_needed {
continue 'run_loop;
}
}
// XDG events
if let Ok(event) = state.xdp_event_source.try_recv() {
log::trace!("main thread: XDG event");
match event {
XDPEvent::WindowAppearance(appearance) => {
let mut windows = state
.windows
.values()
.map(|window| window.window.clone())
.collect::<Vec<_>>();
drop(state);
self.with_common(|common| common.appearance = appearance);
for mut window in windows {
window.set_appearance(appearance);
}
}
XDPEvent::CursorTheme(_) | XDPEvent::CursorSize(_) => {
// noop, X11 manages this for us.
}
};
};
}
event_loop.run(None, &mut self.clone(), |_| {}).log_err();
}
fn active_window(&self) -> Option<AnyWindowHandle> {
@ -1322,6 +1263,19 @@ impl LinuxClient for X11Client {
}
}
// Adatpted from:
// https://docs.rs/winit/0.29.11/src/winit/platform_impl/linux/x11/monitor.rs.html#103-111
pub fn mode_refresh_rate(mode: &randr::ModeInfo) -> Duration {
if mode.dot_clock == 0 || mode.htotal == 0 || mode.vtotal == 0 {
return Duration::from_millis(16);
}
let millihertz = mode.dot_clock as u64 * 1_000 / (mode.htotal as u64 * mode.vtotal as u64);
let micros = 1_000_000_000 / millihertz;
log::info!("Refreshing at {} micros", micros);
Duration::from_micros(micros)
}
fn fp3232_to_f32(value: xinput::Fp3232) -> f32 {
value.integral as f32 + value.frac as f32 / u32::MAX as f32
}

View file

@ -15,7 +15,6 @@ use util::{maybe, ResultExt};
use x11rb::{
connection::Connection,
protocol::{
randr::{self, ConnectionExt as _},
sync,
xinput::{self, ConnectionExt as _},
xproto::{self, ClientMessageEvent, ConnectionExt, EventMask, TranslateCoordinatesReply},
@ -26,7 +25,7 @@ use x11rb::{
use std::{
cell::RefCell, ffi::c_void, mem::size_of, num::NonZeroU32, ops::Div, ptr::NonNull, rc::Rc,
sync::Arc, time::Duration,
sync::Arc,
};
use super::{X11Display, XINPUT_MASTER_DEVICE};
@ -220,7 +219,6 @@ pub struct Callbacks {
pub struct X11WindowState {
pub destroyed: bool,
refresh_rate: Duration,
client: X11ClientStatePtr,
executor: ForegroundExecutor,
atoms: XcbAtoms,
@ -257,7 +255,7 @@ pub(crate) struct X11WindowStatePtr {
pub state: Rc<RefCell<X11WindowState>>,
pub(crate) callbacks: Rc<RefCell<Callbacks>>,
xcb_connection: Rc<XCBConnection>,
pub x_window: xproto::Window,
x_window: xproto::Window,
}
impl rwh::HasWindowHandle for RawWindow {
@ -493,31 +491,6 @@ impl X11WindowState {
};
xcb_connection.map_window(x_window).unwrap();
let screen_resources = xcb_connection
.randr_get_screen_resources(x_window)
.unwrap()
.reply()
.expect("Could not find available screens");
let mode = screen_resources
.crtcs
.iter()
.find_map(|crtc| {
let crtc_info = xcb_connection
.randr_get_crtc_info(*crtc, x11rb::CURRENT_TIME)
.ok()?
.reply()
.ok()?;
screen_resources
.modes
.iter()
.find(|m| m.id == crtc_info.mode)
})
.expect("Unable to find screen refresh rate");
let refresh_rate = mode_refresh_rate(&mode);
Ok(Self {
client,
executor,
@ -545,7 +518,6 @@ impl X11WindowState {
edge_constraints: None,
counter_id: sync_request_counter,
last_sync_counter: None,
refresh_rate,
})
}
@ -953,10 +925,6 @@ impl X11WindowStatePtr {
(fun)()
}
}
pub fn refresh_rate(&self) -> Duration {
self.state.borrow().refresh_rate
}
}
impl PlatformWindow for X11Window {
@ -1369,16 +1337,3 @@ impl PlatformWindow for X11Window {
}
}
}
// Adapted from:
// https://docs.rs/winit/0.29.11/src/winit/platform_impl/linux/x11/monitor.rs.html#103-111
pub fn mode_refresh_rate(mode: &randr::ModeInfo) -> Duration {
if mode.dot_clock == 0 || mode.htotal == 0 || mode.vtotal == 0 {
return Duration::from_millis(16);
}
let millihertz = mode.dot_clock as u64 * 1_000 / (mode.htotal as u64 * mode.vtotal as u64);
let micros = 1_000_000_000 / millihertz;
log::info!("Refreshing at {} micros", micros);
Duration::from_micros(micros)
}

View file

@ -2,13 +2,9 @@
//!
//! This module uses the [ashpd] crate
use std::sync::Arc;
use anyhow::anyhow;
use ashpd::desktop::settings::{ColorScheme, Settings};
use calloop::channel::{Channel, Sender};
use calloop::channel::Channel;
use calloop::{EventSource, Poll, PostAction, Readiness, Token, TokenFactory};
use mio::Waker;
use smol::stream::StreamExt;
use crate::{BackgroundExecutor, WindowAppearance};
@ -24,45 +20,31 @@ pub struct XDPEventSource {
}
impl XDPEventSource {
pub fn new(executor: &BackgroundExecutor, waker: Option<Arc<Waker>>) -> Self {
pub fn new(executor: &BackgroundExecutor) -> Self {
let (sender, channel) = calloop::channel::channel();
let background = executor.clone();
executor
.spawn(async move {
fn send_event<T>(
sender: &Sender<T>,
waker: &Option<Arc<Waker>>,
event: T,
) -> Result<(), std::sync::mpsc::SendError<T>> {
sender.send(event)?;
if let Some(waker) = waker {
waker.wake().ok();
};
Ok(())
}
let settings = Settings::new().await?;
if let Ok(initial_appearance) = settings.color_scheme().await {
send_event(
&sender,
&waker,
Event::WindowAppearance(WindowAppearance::from_native(initial_appearance)),
)?;
sender.send(Event::WindowAppearance(WindowAppearance::from_native(
initial_appearance,
)))?;
}
if let Ok(initial_theme) = settings
.read::<String>("org.gnome.desktop.interface", "cursor-theme")
.await
{
send_event(&sender, &waker, Event::CursorTheme(initial_theme))?;
sender.send(Event::CursorTheme(initial_theme))?;
}
if let Ok(initial_size) = settings
.read::<u32>("org.gnome.desktop.interface", "cursor-size")
.await
{
send_event(&sender, &waker, Event::CursorSize(initial_size))?;
sender.send(Event::CursorSize(initial_size))?;
}
if let Ok(mut cursor_theme_changed) = settings
@ -73,12 +55,11 @@ impl XDPEventSource {
.await
{
let sender = sender.clone();
let waker = waker.clone();
background
.spawn(async move {
while let Some(theme) = cursor_theme_changed.next().await {
let theme = theme?;
send_event(&sender, &waker, Event::CursorTheme(theme))?;
sender.send(Event::CursorTheme(theme))?;
}
anyhow::Ok(())
})
@ -93,12 +74,11 @@ impl XDPEventSource {
.await
{
let sender = sender.clone();
let waker = waker.clone();
background
.spawn(async move {
while let Some(size) = cursor_size_changed.next().await {
let size = size?;
send_event(&sender, &waker, Event::CursorSize(size))?;
sender.send(Event::CursorSize(size))?;
}
anyhow::Ok(())
})
@ -107,11 +87,9 @@ impl XDPEventSource {
let mut appearance_changed = settings.receive_color_scheme_changed().await?;
while let Some(scheme) = appearance_changed.next().await {
send_event(
&sender,
&waker,
Event::WindowAppearance(WindowAppearance::from_native(scheme)),
)?;
sender.send(Event::WindowAppearance(WindowAppearance::from_native(
scheme,
)))?;
}
anyhow::Ok(())
@ -120,12 +98,6 @@ impl XDPEventSource {
Self { channel }
}
pub fn try_recv(&self) -> anyhow::Result<Event> {
self.channel
.try_recv()
.map_err(|error| anyhow!("{}", error))
}
}
impl EventSource for XDPEventSource {