Files
zed/crates/gpui_linux/src/linux/headless/client.rs
T
yenru0 a7b8190c24 gpui: Track in-app drags outside of every window
Platforms that can query the global cursor position (X11 via
QueryPointer, tests via an injected state) now report it through
Platform::global_cursor_state. While a drag is active, App samples that
state to keep drag_hover_window current: the drag ghost paints only in
the window under the cursor and in no window while the cursor is
outside all of them, fixing stale ghosts left behind in the source
window.

When the button is released outside every window, no window receives
the mouse-up, so the drag used to stick until the next click. The poll
now detects that release and hands the drag to a handler registered
with App::set_drag_dropped_outside_handler, letting the workspace layer
decide what a tear-off means.
2026-10-14 05:09:54 +09:00

148 lines
4.1 KiB
Rust

use std::cell::RefCell;
use std::rc::Rc;
use calloop::{EventLoop, LoopHandle};
use gpui_util::ResultExt;
use crate::linux::headless::window::{HeadlessDisplay, HeadlessWindow};
use crate::linux::{LinuxClient, LinuxCommon, LinuxKeyboardLayout};
use gpui::{
AnyWindowHandle, CursorStyle, DisplayId, GlobalCursorState, PlatformDisplay,
PlatformKeyboardLayout, PlatformWindow, WindowParams,
};
pub struct HeadlessClientState {
pub(crate) _loop_handle: LoopHandle<'static, HeadlessClient>,
pub(crate) event_loop: Option<calloop::EventLoop<'static, HeadlessClient>>,
pub(crate) common: LinuxCommon,
pub(crate) display: Rc<dyn PlatformDisplay>,
}
#[derive(Clone)]
pub(crate) struct HeadlessClient(Rc<RefCell<HeadlessClientState>>);
impl HeadlessClient {
pub(crate) fn new() -> Self {
let event_loop = EventLoop::try_new().unwrap();
let (common, main_receiver, power_receiver) = LinuxCommon::new(event_loop.get_signal());
let handle = event_loop.handle();
handle
.insert_source(main_receiver, |event, _, _: &mut HeadlessClient| {
if let calloop::channel::Event::Msg(runnable) = event {
runnable.run();
}
})
.ok();
handle
.insert_source(power_receiver, |event, _, client: &mut HeadlessClient| {
if let calloop::channel::Event::Msg(event) = event {
client.with_common(|common| common.handle_system_power_event(event));
}
})
.ok();
HeadlessClient(Rc::new(RefCell::new(HeadlessClientState {
event_loop: Some(event_loop),
_loop_handle: handle,
common,
display: Rc::new(HeadlessDisplay::new()),
})))
}
}
impl LinuxClient for HeadlessClient {
fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R {
f(&mut self.0.borrow_mut().common)
}
fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout> {
Box::new(LinuxKeyboardLayout::new("unknown".into()))
}
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
vec![self.0.borrow().display.clone()]
}
fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> {
Some(self.0.borrow().display.clone())
}
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
let display = self.0.borrow().display.clone();
(display.id() == id).then_some(display)
}
#[cfg(feature = "screen-capture")]
fn screen_capture_sources(
&self,
) -> futures::channel::oneshot::Receiver<anyhow::Result<Vec<Rc<dyn gpui::ScreenCaptureSource>>>>
{
let (tx, rx) = futures::channel::oneshot::channel();
tx.send(Err(anyhow::anyhow!(
"Headless mode does not support screen capture."
)))
.ok();
rx
}
fn active_window(&self) -> Option<AnyWindowHandle> {
None
}
fn window_stack(&self) -> Option<Vec<AnyWindowHandle>> {
None
}
fn global_cursor_state(&self) -> Option<GlobalCursorState> {
None
}
fn open_window(
&self,
_handle: AnyWindowHandle,
params: WindowParams,
) -> anyhow::Result<Box<dyn PlatformWindow>> {
Ok(Box::new(HeadlessWindow::new(
params,
self.0.borrow().display.clone(),
)))
}
fn compositor_name(&self) -> &'static str {
"headless"
}
fn set_cursor_style(&self, _style: CursorStyle) {}
fn open_uri(&self, _uri: &str) {}
fn reveal_path(&self, _path: std::path::PathBuf) {}
fn write_to_primary(&self, _item: gpui::ClipboardItem) {}
fn write_to_clipboard(&self, _item: gpui::ClipboardItem) {}
fn read_from_primary(&self) -> Option<gpui::ClipboardItem> {
None
}
fn read_from_clipboard(&self) -> Option<gpui::ClipboardItem> {
None
}
fn run(&self) {
let mut event_loop = self
.0
.borrow_mut()
.event_loop
.take()
.expect("App is already running");
event_loop.run(None, &mut self.clone(), |_| {}).log_err();
}
}