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.
148 lines
4.1 KiB
Rust
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();
|
|
}
|
|
}
|