1445 lines
50 KiB
Rust
1445 lines
50 KiB
Rust
use {
|
|
crate::{
|
|
allocator::{Allocator, AllocatorError, BO_USE_RENDERING, BufferObject},
|
|
async_engine::SpawnedFuture,
|
|
client::{Client, ClientCaps, ClientError},
|
|
cursor::KnownCursor,
|
|
egui_adapter::egui_vulkan::{
|
|
EGV_FORMAT, EgvContext, EgvError, EgvFramebuffer, EgvRenderer,
|
|
},
|
|
fixed::Fixed,
|
|
fontconfig::match_font,
|
|
gfx_api::SyncFile,
|
|
globals::{GlobalName, Singleton},
|
|
ifs::wl_seat::{
|
|
BTN_EXTRA, BTN_LEFT, BTN_MIDDLE, BTN_RIGHT, BTN_SIDE,
|
|
wl_pointer::{self, HORIZONTAL_SCROLL, PendingScroll, VERTICAL_SCROLL},
|
|
},
|
|
object::Version,
|
|
scale::Scale,
|
|
security_context_acceptor::AcceptorMetadata,
|
|
state::State,
|
|
utils::{
|
|
asyncevent::AsyncEvent,
|
|
buf::Buf,
|
|
clonecell::CloneCell,
|
|
copyhashmap::CopyHashMap,
|
|
double_buffered::DoubleBuffered,
|
|
errorfmt::ErrorFmt,
|
|
object_drop_queue::ObjectDropQueue,
|
|
oserror::{OsError, OsErrorExt2},
|
|
pipe::{Pipe, pipe},
|
|
rc_eq::rc_eq,
|
|
},
|
|
video::{dmabuf::DMA_BUF_SYNC_WRITE, drm::DrmError},
|
|
wire::{
|
|
WlSurfaceId,
|
|
wl_pointer::{Button, Enter, Leave, Motion},
|
|
wp_fractional_scale_v1::PreferredScale,
|
|
},
|
|
wl_usr::{
|
|
UsrCon, UsrConOwner,
|
|
usr_ifs::{
|
|
usr_jay_compositor::UsrJayCompositor,
|
|
usr_jay_sync_file_release::UsrJaySyncFileReleaseOwner,
|
|
usr_jay_sync_file_surface::UsrJaySyncFileSurface,
|
|
usr_wl_buffer::UsrWlBuffer,
|
|
usr_wl_callback::UsrWlCallbackOwner,
|
|
usr_wl_compositor::UsrWlCompositor,
|
|
usr_wl_data_device::UsrWlDataDevice,
|
|
usr_wl_data_device_manager::UsrWlDataDeviceManager,
|
|
usr_wl_data_source::{UsrWlDataSource, UsrWlDataSourceOwner},
|
|
usr_wl_keyboard::{UsrWlKeyboard, UsrWlKeyboardOwner},
|
|
usr_wl_pointer::{UsrWlPointer, UsrWlPointerOwner},
|
|
usr_wl_registry::UsrWlRegistry,
|
|
usr_wl_seat::UsrWlSeat,
|
|
usr_wl_surface::UsrWlSurface,
|
|
usr_wp_cursor_shape_device_v1::UsrWpCursorShapeDeviceV1,
|
|
usr_wp_cursor_shape_manager_v1::UsrWpCursorShapeManagerV1,
|
|
usr_wp_fractional_scale::{UsrWpFractionalScale, UsrWpFractionalScaleOwner},
|
|
usr_wp_fractional_scale_manager::UsrWpFractionalScaleManager,
|
|
usr_wp_viewport::UsrWpViewport,
|
|
usr_wp_viewporter::UsrWpViewporter,
|
|
usr_xdg_surface::{UsrXdgSurface, UsrXdgSurfaceOwner},
|
|
usr_xdg_toplevel::{UsrXdgToplevel, UsrXdgToplevelOwner},
|
|
usr_xdg_wm_base::UsrXdgWmBase,
|
|
usr_zwp_linux_dmabuf_v1::UsrZwpLinuxDmabufV1,
|
|
usr_zwp_primary_selection_device_manager::UsrZwpPrimarySelectionDeviceManagerV1,
|
|
},
|
|
},
|
|
},
|
|
egui::{
|
|
CursorIcon, Event, FontData, FontDefinitions, FontFamily, FullOutput, Key, Modifiers,
|
|
MouseWheelUnit, OutputCommand, PlatformOutput, PointerButton, Pos2, RawInput, TouchPhase,
|
|
Vec2, ViewportCommand, ViewportEvent, ViewportId, ViewportInfo, pos2, vec2,
|
|
},
|
|
futures_util::{FutureExt, select},
|
|
isnt::std_1::primitive::{IsntCharExt, IsntSliceExt, IsntStrExt},
|
|
kbvm::{Keysym, ModifierMask, lookup::Lookup},
|
|
std::{
|
|
cell::{Cell, RefCell},
|
|
collections::btree_map::Entry,
|
|
fs, mem,
|
|
rc::{Rc, Weak},
|
|
sync::Arc,
|
|
},
|
|
thiserror::Error,
|
|
uapi::{OwnedFd, c},
|
|
};
|
|
|
|
#[derive(Debug, Error)]
|
|
pub enum EggError {
|
|
#[error("Could not create a socket pair")]
|
|
CreateSocketPair(#[source] OsError),
|
|
#[error("Could not spawn a client")]
|
|
SpawnClient(#[source] ClientError),
|
|
#[error("Could not create a renderer")]
|
|
CreateRenderer(#[source] EgvError),
|
|
#[error("There is no render context")]
|
|
NoRenderContext,
|
|
#[error("Could not allocate a buffer")]
|
|
AllocateBuffer(#[source] AllocatorError),
|
|
#[error("Could not import a framebuffer")]
|
|
ImportFramebuffer(#[source] EgvError),
|
|
#[error("Could not render")]
|
|
Render(#[source] EgvError),
|
|
#[error("No viewport output")]
|
|
NoViewportOutput,
|
|
#[error("Could not export initial dmabuf sync file")]
|
|
ExportBoSyncFile(#[source] DrmError),
|
|
}
|
|
|
|
pub mod icons {
|
|
pub const ICON_ADD: &str = "\u{e145}";
|
|
pub const ICON_CLOSE: &str = "\u{e5cd}";
|
|
pub const ICON_DRAG_INDICATOR: &str = "\u{e945}";
|
|
pub const ICON_INFO: &str = "\u{e88e}";
|
|
pub const ICON_OPEN_IN_NEW: &str = "\u{e89e}";
|
|
pub const ICON_PENDING: &str = "\u{ef64}";
|
|
pub const ICON_REMOVE: &str = "\u{e15b}";
|
|
}
|
|
|
|
linear_ids!(EggContextIds, EggContextId, u64);
|
|
|
|
pub struct EggState {
|
|
fonts: RefCell<EggFonts>,
|
|
ctx: CloneCell<Option<Rc<EggContext>>>,
|
|
context_ids: EggContextIds,
|
|
cxts: CopyHashMap<EggContextId, Rc<EggContextInner>>,
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct EggFonts {
|
|
definitions: Option<FontDefinitions>,
|
|
proportional: Vec<String>,
|
|
monospace: Vec<String>,
|
|
}
|
|
|
|
pub struct EggContext {
|
|
inner: Rc<EggContextInner>,
|
|
}
|
|
|
|
struct EggContextInner {
|
|
id: EggContextId,
|
|
renderer: Rc<EgvRenderer>,
|
|
allocator: Rc<dyn Allocator>,
|
|
state: Rc<State>,
|
|
_client: Rc<Client>,
|
|
con: Rc<UsrCon>,
|
|
jay_compositor: Rc<UsrJayCompositor>,
|
|
wl_compositor: Rc<UsrWlCompositor>,
|
|
xdg_wm_base: Rc<UsrXdgWmBase>,
|
|
wl_data_device_manager: Rc<UsrWlDataDeviceManager>,
|
|
_zwp_primary_selection_device_manager_v1: Rc<UsrZwpPrimarySelectionDeviceManagerV1>,
|
|
wp_viewporter: Rc<UsrWpViewporter>,
|
|
wp_cursor_shape_manager_v1: Rc<UsrWpCursorShapeManagerV1>,
|
|
wp_fractional_scale_manager: Rc<UsrWpFractionalScaleManager>,
|
|
zwp_linux_dmabuf_v1: Rc<UsrZwpLinuxDmabufV1>,
|
|
registry: Rc<UsrWlRegistry>,
|
|
windows: CopyHashMap<WlSurfaceId, Rc<EggWindowInner>>,
|
|
seats: CopyHashMap<GlobalName, EggSeat>,
|
|
}
|
|
|
|
struct EggSeat {
|
|
inner: Rc<EggSeatInner>,
|
|
}
|
|
|
|
pub struct EggSeatInner {
|
|
ctx: Rc<EggContextInner>,
|
|
global_name: GlobalName,
|
|
wl_seat: Rc<UsrWlSeat>,
|
|
wl_pointer: Rc<UsrWlPointer>,
|
|
wl_data_device: Rc<UsrWlDataDevice>,
|
|
pointer_window: CloneCell<Option<Rc<EggWindowInner>>>,
|
|
pointer_enter_serial: Cell<u32>,
|
|
pointer_serial: Cell<u32>,
|
|
pointer_pos: Cell<Pos2>,
|
|
kb_modifiers: Cell<Modifiers>,
|
|
wp_cursor_shape_device_v1: Rc<UsrWpCursorShapeDeviceV1>,
|
|
wl_keyboard: Rc<UsrWlKeyboard>,
|
|
kb_window: CloneCell<Option<Rc<EggWindowInner>>>,
|
|
kb_serial: Cell<u32>,
|
|
serial: Cell<u32>,
|
|
wl_data_source: CloneCell<Option<Rc<UsrWlDataSource>>>,
|
|
copy_text: RefCell<Option<Buf>>,
|
|
copy_task: Cell<Option<SpawnedFuture<()>>>,
|
|
paste_task: Cell<Option<SpawnedFuture<()>>>,
|
|
}
|
|
|
|
pub struct EggWindow {
|
|
_ctx: Rc<EggContext>,
|
|
inner: Rc<EggWindowInner>,
|
|
_render_task: SpawnedFuture<()>,
|
|
_timer_task: SpawnedFuture<()>,
|
|
}
|
|
|
|
pub trait EggWindowOwner {
|
|
fn close(&self);
|
|
fn render(self: Rc<Self>, ui: &mut egui::Ui);
|
|
}
|
|
|
|
struct EggWindowInner {
|
|
ctx: Rc<EggContextInner>,
|
|
egv: Rc<EgvContext>,
|
|
egui: egui::Context,
|
|
wl_surface: Rc<UsrWlSurface>,
|
|
wp_viewport: Rc<UsrWpViewport>,
|
|
wp_fractional_scale: Rc<UsrWpFractionalScale>,
|
|
xdg_surface: Rc<UsrXdgSurface>,
|
|
xdg_toplevel: Rc<UsrXdgToplevel>,
|
|
jay_sync_file_surface: Rc<UsrJaySyncFileSurface>,
|
|
frame_task: AsyncEvent,
|
|
want_frame: Cell<bool>,
|
|
have_frame: Cell<bool>,
|
|
initial_commit_pending: Cell<bool>,
|
|
owner: CloneCell<Option<Rc<dyn EggWindowOwner>>>,
|
|
active_seat: CloneCell<Option<Rc<EggSeatInner>>>,
|
|
raw_input: RefCell<Option<RawInput>>,
|
|
close: Cell<bool>,
|
|
repaint_timeout: Cell<u64>,
|
|
repaint_timeout_changed: AsyncEvent,
|
|
fonts_changed: Cell<bool>,
|
|
|
|
buffers: DoubleBuffered<CloneCell<Option<Rc<EggFramebuffer>>>>,
|
|
|
|
surface_pending: RefCell<PendingWindowState>,
|
|
logical_size: Cell<[i32; 2]>,
|
|
physical_size: Cell<[i32; 2]>,
|
|
scale: Cell<Scale>,
|
|
}
|
|
|
|
struct EggFramebuffer {
|
|
client_acquire_fence: CloneCell<Option<Option<SyncFile>>>,
|
|
size: Cell<[i32; 2]>,
|
|
bo: Rc<dyn BufferObject>,
|
|
egv: Rc<EgvFramebuffer>,
|
|
wl_buffer: Rc<UsrWlBuffer>,
|
|
window: Weak<EggWindowInner>,
|
|
drop_queue: Rc<ObjectDropQueue<Rc<dyn BufferObject>>>,
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct PendingWindowState {
|
|
size: Option<(i32, i32)>,
|
|
}
|
|
|
|
const PROPORTIONAL_FONTS: &[&str] = &["sans-serif", "Noto Sans", "Noto Color Emoji"];
|
|
|
|
const MONOSPACE_FONTS: &[&str] = &["monospace", "Noto Sans Mono", "Noto Color Emoji"];
|
|
|
|
impl Default for EggState {
|
|
fn default() -> Self {
|
|
let slf = Self {
|
|
fonts: Default::default(),
|
|
ctx: Default::default(),
|
|
context_ids: Default::default(),
|
|
cxts: Default::default(),
|
|
};
|
|
slf.reset_fonts();
|
|
slf
|
|
}
|
|
}
|
|
|
|
impl EggState {
|
|
pub fn reset_fonts(&self) {
|
|
self.set_proportional_fonts(PROPORTIONAL_FONTS);
|
|
self.set_monospace_fonts(MONOSPACE_FONTS);
|
|
}
|
|
|
|
pub fn set_proportional_fonts(&self, fonts: &[&str]) {
|
|
self.change_fonts(fonts, |f| &mut f.proportional)
|
|
}
|
|
|
|
pub fn set_monospace_fonts(&self, fonts: &[&str]) {
|
|
self.change_fonts(fonts, |f| &mut f.monospace)
|
|
}
|
|
|
|
fn change_fonts(&self, fonts: &[&str], field: impl Fn(&mut EggFonts) -> &mut Vec<String>) {
|
|
let f = &mut *self.fonts.borrow_mut();
|
|
let field = field(f);
|
|
if *field == fonts {
|
|
return;
|
|
}
|
|
*field = fonts.iter().map(|s| s.to_string()).collect();
|
|
f.definitions.take();
|
|
for ctx in self.cxts.lock().values() {
|
|
for window in ctx.windows.lock().values() {
|
|
window.fonts_changed.set(true);
|
|
window.want_frame();
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn clear(&self) {
|
|
self.ctx.take();
|
|
}
|
|
|
|
fn font_definitions(&self) -> FontDefinitions {
|
|
let f = &mut self.fonts.borrow_mut();
|
|
if let Some(d) = &f.definitions {
|
|
return d.clone();
|
|
}
|
|
let mut d = FontDefinitions::empty();
|
|
for (ff, list) in [
|
|
(FontFamily::Proportional, &f.proportional),
|
|
(FontFamily::Monospace, &f.monospace),
|
|
] {
|
|
for family in list {
|
|
let font = match match_font(family) {
|
|
Ok(f) => f,
|
|
Err(e) => {
|
|
log::warn!("Could not find font family {family}: {}", ErrorFmt(e));
|
|
continue;
|
|
}
|
|
};
|
|
if let Entry::Vacant(e) = d.font_data.entry(font.fullname.clone()) {
|
|
let data = match fs::read(&font.file) {
|
|
Ok(f) => f,
|
|
Err(e) => {
|
|
log::error!("Could not read {}: {}", font.file.display(), ErrorFmt(e));
|
|
continue;
|
|
}
|
|
};
|
|
let data = Arc::new(FontData {
|
|
font: data.into(),
|
|
index: font.index.unwrap_or(0) as u32,
|
|
tweak: Default::default(),
|
|
});
|
|
e.insert(data);
|
|
}
|
|
let list = d.families.entry(ff.clone()).or_default();
|
|
if list.not_contains(&font.fullname) {
|
|
list.push(font.fullname);
|
|
}
|
|
}
|
|
}
|
|
{
|
|
let name = "material-icons";
|
|
let list = d.families.entry(FontFamily::Proportional).or_default();
|
|
if list.iter().all(|n| n != name) {
|
|
if let Entry::Vacant(e) = d.font_data.entry(name.to_string()) {
|
|
let data = Arc::new(FontData {
|
|
font: include_bytes!("icons.ttf").into(),
|
|
index: 0,
|
|
tweak: Default::default(),
|
|
});
|
|
e.insert(data);
|
|
}
|
|
list.push(name.to_string());
|
|
}
|
|
}
|
|
f.definitions = Some(d.clone());
|
|
d
|
|
}
|
|
}
|
|
|
|
impl State {
|
|
pub fn get_egg_context(self: &Rc<Self>) -> Result<Rc<EggContext>, EggError> {
|
|
if let Some(ctx) = self.egg_state.ctx.get() {
|
|
return Ok(ctx);
|
|
}
|
|
let Some(ctx) = self.render_ctx.get() else {
|
|
return Err(EggError::NoRenderContext);
|
|
};
|
|
let (client1, client2) = uapi::socketpair(c::AF_UNIX, c::SOCK_STREAM | c::SOCK_CLOEXEC, 0)
|
|
.map_os_err(EggError::CreateSocketPair)?;
|
|
let allocator = ctx.allocator();
|
|
let dev = allocator.drm().map(|d| d.dev());
|
|
let renderer = EgvRenderer::new(&self.eng, &self.ring, &self.eventfd_cache, dev)
|
|
.map_err(EggError::CreateRenderer)?;
|
|
let con = UsrCon::from_socket(
|
|
&self.ring,
|
|
&self.wheel,
|
|
&self.eng,
|
|
&self.dma_buf_ids,
|
|
&Rc::new(client1),
|
|
0,
|
|
);
|
|
let client = self
|
|
.clients
|
|
.spawn2(
|
|
self.clients.id(),
|
|
self,
|
|
Rc::new(client2),
|
|
uapi::getuid(),
|
|
uapi::getpid(),
|
|
ClientCaps::all(),
|
|
true,
|
|
false,
|
|
&Rc::new(AcceptorMetadata::secure()),
|
|
)
|
|
.map_err(EggError::SpawnClient)?;
|
|
let registry = con.get_registry();
|
|
let jay_compositor = {
|
|
let obj = Rc::new(UsrJayCompositor {
|
|
id: con.id(),
|
|
con: con.clone(),
|
|
owner: Default::default(),
|
|
caps: Default::default(),
|
|
version: Version(27),
|
|
});
|
|
registry.bind(self.globals.singletons[Singleton::JayCompositor], &*obj);
|
|
con.add_object(obj.clone());
|
|
obj
|
|
};
|
|
macro_rules! add_singletons {
|
|
($($name:ident, $global:ident, $ty:ident, $version:expr;)*) => {
|
|
$(
|
|
let $name = Rc::new($ty {
|
|
id: con.id(),
|
|
con: con.clone(),
|
|
version: Version($version),
|
|
});
|
|
registry.bind(self.globals.singletons[Singleton::$global], &*$name);
|
|
con.add_object($name.clone());
|
|
)*
|
|
};
|
|
}
|
|
add_singletons! {
|
|
wl_compositor, WlCompositor, UsrWlCompositor, 6;
|
|
xdg_wm_base, XdgWmBase, UsrXdgWmBase, 7;
|
|
wl_data_device_manager, WlDataDeviceManager, UsrWlDataDeviceManager, 3;
|
|
zwp_primary_selection_device_manager_v1, ZwpPrimarySelectionDeviceManagerV1, UsrZwpPrimarySelectionDeviceManagerV1, 1;
|
|
wp_viewporter, WpViewporter, UsrWpViewporter, 1;
|
|
wp_cursor_shape_manager_v1, WpCursorShapeManagerV1, UsrWpCursorShapeManagerV1, 2;
|
|
wp_fractional_scale_manager, WpFractionalScaleManagerV1, UsrWpFractionalScaleManager, 1;
|
|
zwp_linux_dmabuf_v1, ZwpLinuxDmabufV1, UsrZwpLinuxDmabufV1, 5;
|
|
}
|
|
let ctx = Rc::new(EggContext {
|
|
inner: Rc::new(EggContextInner {
|
|
id: self.egg_state.context_ids.next(),
|
|
renderer,
|
|
allocator,
|
|
state: self.clone(),
|
|
_client: client.clone(),
|
|
con,
|
|
jay_compositor,
|
|
wl_compositor,
|
|
xdg_wm_base,
|
|
wl_data_device_manager,
|
|
_zwp_primary_selection_device_manager_v1: zwp_primary_selection_device_manager_v1,
|
|
wp_viewporter,
|
|
wp_cursor_shape_manager_v1,
|
|
wp_fractional_scale_manager,
|
|
zwp_linux_dmabuf_v1,
|
|
registry,
|
|
windows: Default::default(),
|
|
seats: Default::default(),
|
|
}),
|
|
});
|
|
ctx.inner.con.owner.set(Some(ctx.inner.clone()));
|
|
self.egg_state.cxts.set(ctx.inner.id, ctx.inner.clone());
|
|
for &global_name in self.globals.seats.lock().keys() {
|
|
ctx.inner.add_seat(global_name);
|
|
}
|
|
self.egg_state.ctx.set(Some(ctx.clone()));
|
|
Ok(ctx)
|
|
}
|
|
}
|
|
|
|
impl EggContext {
|
|
pub fn create_window(self: &Rc<Self>, title: &str) -> Rc<EggWindow> {
|
|
let i = &self.inner;
|
|
let wl_surface = i.wl_compositor.create_surface();
|
|
let jay_sync_file_surface = i.jay_compositor.get_sync_file_surface(&wl_surface);
|
|
let xdg_surface = i.xdg_wm_base.get_xdg_surface(&wl_surface);
|
|
let xdg_toplevel = xdg_surface.get_toplevel();
|
|
xdg_toplevel.set_title(title);
|
|
let wp_fractional_scale = i
|
|
.wp_fractional_scale_manager
|
|
.get_fractional_scale(&wl_surface);
|
|
let wp_viewport = i.wp_viewporter.get_viewport(&wl_surface);
|
|
wl_surface.commit();
|
|
let window = Rc::new(EggWindowInner {
|
|
ctx: self.inner.clone(),
|
|
egv: i.renderer.create_context(),
|
|
egui: egui::Context::default(),
|
|
wl_surface,
|
|
wp_viewport,
|
|
wp_fractional_scale,
|
|
xdg_surface,
|
|
xdg_toplevel,
|
|
jay_sync_file_surface,
|
|
frame_task: Default::default(),
|
|
want_frame: Default::default(),
|
|
have_frame: Cell::new(true),
|
|
initial_commit_pending: Cell::new(true),
|
|
owner: Default::default(),
|
|
active_seat: Default::default(),
|
|
raw_input: RefCell::new(None),
|
|
close: Default::default(),
|
|
repaint_timeout: Cell::new(u64::MAX),
|
|
repaint_timeout_changed: Default::default(),
|
|
fonts_changed: Cell::new(true),
|
|
buffers: Default::default(),
|
|
surface_pending: Default::default(),
|
|
logical_size: Cell::new([800, 600]),
|
|
physical_size: Cell::new([800, 600]),
|
|
scale: Default::default(),
|
|
});
|
|
window
|
|
.egui
|
|
.all_styles_mut(|s| s.spacing.item_spacing.y = 5.0);
|
|
window.xdg_surface.owner.set(Some(window.clone()));
|
|
window.xdg_toplevel.owner.set(Some(window.clone()));
|
|
window.wp_fractional_scale.owner.set(Some(window.clone()));
|
|
i.windows.set(window.wl_surface.id, window.clone());
|
|
let eng = &i.state.eng;
|
|
let render_task = eng.spawn("egui-render", window.clone().render_frames());
|
|
let timer_task = eng.spawn("egui-timer", window.clone().handle_timer());
|
|
let window = EggWindow {
|
|
_ctx: self.clone(),
|
|
inner: window,
|
|
_render_task: render_task,
|
|
_timer_task: timer_task,
|
|
};
|
|
Rc::new(window)
|
|
}
|
|
}
|
|
|
|
impl EggContextInner {
|
|
pub fn add_seat(self: &Rc<Self>, global_name: GlobalName) {
|
|
let wl_seat = Rc::new(UsrWlSeat {
|
|
id: self.con.id(),
|
|
con: self.con.clone(),
|
|
owner: Default::default(),
|
|
version: Version(10),
|
|
});
|
|
self.registry.bind(global_name, &*wl_seat);
|
|
self.con.add_object(wl_seat.clone());
|
|
let wl_pointer = wl_seat.get_pointer();
|
|
let wl_keyboard = wl_seat.get_keyboard();
|
|
let wp_cursor_shape_device_v1 = self.wp_cursor_shape_manager_v1.get_pointer(&wl_pointer);
|
|
let wl_data_device = self.wl_data_device_manager.get_data_device(&wl_seat);
|
|
let seat = Rc::new(EggSeatInner {
|
|
ctx: self.clone(),
|
|
global_name,
|
|
wl_seat,
|
|
wl_pointer,
|
|
wl_data_device,
|
|
pointer_window: Default::default(),
|
|
pointer_enter_serial: Default::default(),
|
|
pointer_serial: Default::default(),
|
|
pointer_pos: Default::default(),
|
|
kb_modifiers: Default::default(),
|
|
wp_cursor_shape_device_v1,
|
|
wl_keyboard,
|
|
kb_window: Default::default(),
|
|
kb_serial: Default::default(),
|
|
serial: Default::default(),
|
|
wl_data_source: Default::default(),
|
|
copy_text: Default::default(),
|
|
copy_task: Default::default(),
|
|
paste_task: Default::default(),
|
|
});
|
|
seat.wl_pointer.owner.set(Some(seat.clone()));
|
|
seat.wl_keyboard.owner.set(Some(seat.clone()));
|
|
let seat = EggSeat { inner: seat };
|
|
self.seats.set(global_name, seat);
|
|
}
|
|
}
|
|
|
|
const TEXT_PLAIN: &str = "text/plain;charset=utf-8";
|
|
|
|
impl EggSeatInner {
|
|
pub fn request_paste(self: &Rc<Self>) {
|
|
let Some(offer) = self.wl_data_device.selection.get() else {
|
|
return;
|
|
};
|
|
if !offer.mime_types.borrow().contains(TEXT_PLAIN) {
|
|
return;
|
|
}
|
|
let Some(window) = self.kb_window.get() else {
|
|
return;
|
|
};
|
|
let Pipe { read, write } = match pipe() {
|
|
Ok(p) => p.map_read(Rc::new).map_write(Rc::new),
|
|
Err(e) => {
|
|
log::error!("Could not create pipe: {}", ErrorFmt(e));
|
|
return;
|
|
}
|
|
};
|
|
offer.receive(TEXT_PLAIN, &write);
|
|
let window = Rc::downgrade(&window);
|
|
let ring = self.ctx.state.ring.clone();
|
|
let mut buf = Buf::new(1024);
|
|
let mut out = Vec::new();
|
|
let task = self.ctx.state.eng.spawn("egui-paste", async move {
|
|
loop {
|
|
let n = match ring.read(&read, buf.clone()).await {
|
|
Ok(n) => n,
|
|
Err(e) => {
|
|
log::error!("Could not read from peer: {}", ErrorFmt(e));
|
|
return;
|
|
}
|
|
};
|
|
if n == 0 {
|
|
if let Some(window) = window.upgrade() {
|
|
let s = match String::from_utf8(out) {
|
|
Ok(s) => s,
|
|
Err(e) => {
|
|
log::error!("Peer did not send UTF-8: {}", ErrorFmt(e));
|
|
return;
|
|
}
|
|
};
|
|
window.event(Event::Paste(s));
|
|
}
|
|
return;
|
|
}
|
|
out.extend_from_slice(&buf[..n]);
|
|
if out.len() >= 1024 * buf.len() {
|
|
log::error!("Paste buffer is too large");
|
|
return;
|
|
}
|
|
}
|
|
});
|
|
self.paste_task.set(Some(task));
|
|
}
|
|
}
|
|
|
|
impl EggWindow {
|
|
pub fn request_redraw(&self) {
|
|
self.inner.want_frame();
|
|
}
|
|
|
|
pub fn set_owner(&self, owner: Option<Rc<dyn EggWindowOwner>>) {
|
|
self.inner.owner.set(owner);
|
|
}
|
|
}
|
|
|
|
impl EggWindowInner {
|
|
fn update_physical_size(&self) {
|
|
let size = self.logical_size.get();
|
|
let scale = self.scale.get();
|
|
let physical_size = scale.pixel_size(size);
|
|
if self.physical_size.replace(physical_size) != physical_size {
|
|
self.want_frame();
|
|
}
|
|
}
|
|
|
|
fn want_frame(&self) {
|
|
self.want_frame.set(true);
|
|
self.maybe_trigger_frame();
|
|
}
|
|
|
|
fn maybe_trigger_frame(&self) {
|
|
if self.want_frame.get() && self.have_frame.get() {
|
|
self.frame_task.trigger();
|
|
}
|
|
}
|
|
|
|
async fn handle_timer(self: Rc<Self>) {
|
|
loop {
|
|
let timeout = self.ctx.state.ring.timeout(self.repaint_timeout.get());
|
|
let triggered = || self.repaint_timeout_changed.triggered();
|
|
let timeout = select! {
|
|
_ = timeout.fuse() => true,
|
|
_ = triggered().fuse() => false,
|
|
};
|
|
if timeout {
|
|
self.want_frame();
|
|
triggered().await;
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn render_frames(self: Rc<Self>) {
|
|
loop {
|
|
self.frame_task.triggered().await;
|
|
if let Err(e) = self.render_frame() {
|
|
log::error!("Could not render frame: {}", ErrorFmt(e));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
fn render_frame(self: &Rc<Self>) -> Result<(), EggError> {
|
|
if self.fonts_changed.take() {
|
|
self.egui
|
|
.set_fonts(self.ctx.state.egg_state.font_definitions());
|
|
}
|
|
if self.initial_commit_pending.get() {
|
|
return Ok(());
|
|
}
|
|
if !self.have_frame.get() {
|
|
return Ok(());
|
|
}
|
|
if !self.want_frame.get() {
|
|
return Ok(());
|
|
}
|
|
let Some(owner) = self.owner.get() else {
|
|
return Ok(());
|
|
};
|
|
let Some(render_ctx) = self.ctx.state.render_ctx.get() else {
|
|
return Ok(());
|
|
};
|
|
let Some(format) = render_ctx.formats().get(&EGV_FORMAT.drm) else {
|
|
return Ok(());
|
|
};
|
|
let logical_size = self.logical_size.get();
|
|
let physical_size = self.physical_size.get();
|
|
let mut fb_opt = self.buffers.back().get();
|
|
'check: {
|
|
if let Some(fb) = &fb_opt {
|
|
if fb.size.get() != physical_size {
|
|
fb_opt = None;
|
|
break 'check;
|
|
}
|
|
if !format.read_modifiers.contains(&fb.bo.dmabuf().modifier) {
|
|
fb_opt = None;
|
|
break 'check;
|
|
}
|
|
}
|
|
}
|
|
let fb = match fb_opt {
|
|
Some(fb) => fb,
|
|
_ => {
|
|
let modifiers: Vec<_> = self
|
|
.ctx
|
|
.renderer
|
|
.support()
|
|
.iter()
|
|
.filter(|s| {
|
|
s.max_width >= physical_size[0] as u32
|
|
&& s.max_height >= physical_size[1] as u32
|
|
&& format.read_modifiers.contains(&s.modifier)
|
|
})
|
|
.map(|s| s.modifier)
|
|
.collect();
|
|
let bo = self
|
|
.ctx
|
|
.allocator
|
|
.create_bo(
|
|
&self.ctx.state.dma_buf_ids,
|
|
physical_size[0],
|
|
physical_size[1],
|
|
EGV_FORMAT,
|
|
&modifiers,
|
|
BO_USE_RENDERING,
|
|
)
|
|
.map_err(EggError::AllocateBuffer)?;
|
|
let egv = self
|
|
.egv
|
|
.import_framebuffer(&bo)
|
|
.map_err(EggError::ImportFramebuffer)?;
|
|
let dmabuf = bo.dmabuf();
|
|
let sync_file = dmabuf
|
|
.export_sync_file(DMA_BUF_SYNC_WRITE)
|
|
.map_err(EggError::ExportBoSyncFile)?;
|
|
let wl_buffer = self.ctx.zwp_linux_dmabuf_v1.create_buffer(dmabuf);
|
|
let fb = Rc::new(EggFramebuffer {
|
|
client_acquire_fence: CloneCell::new(Some(sync_file)),
|
|
size: Cell::new(physical_size),
|
|
bo,
|
|
egv,
|
|
wl_buffer,
|
|
window: Rc::downgrade(self),
|
|
drop_queue: self.ctx.state.bo_drop_queue.clone(),
|
|
});
|
|
self.buffers.back().set(Some(fb.clone()));
|
|
fb
|
|
}
|
|
};
|
|
let Some(sync_file) = fb.client_acquire_fence.get() else {
|
|
return Ok(());
|
|
};
|
|
let raw_input = self
|
|
.raw_input
|
|
.take()
|
|
.unwrap_or_else(|| self.default_raw_input());
|
|
let full_output = self.egui.run_ui(raw_input, |ui| {
|
|
owner.clone().render(ui);
|
|
});
|
|
let FullOutput {
|
|
platform_output,
|
|
textures_delta,
|
|
shapes,
|
|
pixels_per_point,
|
|
viewport_output,
|
|
} = full_output;
|
|
let primitives = self.egui.tessellate(shapes, pixels_per_point);
|
|
let sync = fb
|
|
.egv
|
|
.render(
|
|
textures_delta,
|
|
pixels_per_point,
|
|
&primitives,
|
|
(0.0, 0.0),
|
|
sync_file.as_ref(),
|
|
)
|
|
.map_err(EggError::Render)?;
|
|
let PlatformOutput {
|
|
commands,
|
|
cursor_icon,
|
|
..
|
|
} = platform_output;
|
|
if let Some(seat) = self.active_seat.get() {
|
|
'set_icon: {
|
|
let cursor = match cursor_icon {
|
|
CursorIcon::None => {
|
|
seat.wl_pointer
|
|
.set_cursor(seat.pointer_serial.get(), None, 0, 0);
|
|
break 'set_icon;
|
|
}
|
|
CursorIcon::Default => KnownCursor::Default,
|
|
CursorIcon::ContextMenu => KnownCursor::ContextMenu,
|
|
CursorIcon::Help => KnownCursor::Help,
|
|
CursorIcon::PointingHand => KnownCursor::Pointer,
|
|
CursorIcon::Progress => KnownCursor::Progress,
|
|
CursorIcon::Wait => KnownCursor::Wait,
|
|
CursorIcon::Cell => KnownCursor::Cell,
|
|
CursorIcon::Crosshair => KnownCursor::Crosshair,
|
|
CursorIcon::Text => KnownCursor::Text,
|
|
CursorIcon::VerticalText => KnownCursor::VerticalText,
|
|
CursorIcon::Alias => KnownCursor::Alias,
|
|
CursorIcon::Copy => KnownCursor::Copy,
|
|
CursorIcon::Move => KnownCursor::Move,
|
|
CursorIcon::NoDrop => KnownCursor::NoDrop,
|
|
CursorIcon::NotAllowed => KnownCursor::NotAllowed,
|
|
CursorIcon::Grab => KnownCursor::Grab,
|
|
CursorIcon::Grabbing => KnownCursor::Grabbing,
|
|
CursorIcon::AllScroll => KnownCursor::AllScroll,
|
|
CursorIcon::ResizeHorizontal => KnownCursor::EwResize,
|
|
CursorIcon::ResizeNeSw => KnownCursor::NeswResize,
|
|
CursorIcon::ResizeNwSe => KnownCursor::NwseResize,
|
|
CursorIcon::ResizeVertical => KnownCursor::NsResize,
|
|
CursorIcon::ResizeEast => KnownCursor::EResize,
|
|
CursorIcon::ResizeSouthEast => KnownCursor::SeResize,
|
|
CursorIcon::ResizeSouth => KnownCursor::SResize,
|
|
CursorIcon::ResizeSouthWest => KnownCursor::SwResize,
|
|
CursorIcon::ResizeWest => KnownCursor::WResize,
|
|
CursorIcon::ResizeNorthWest => KnownCursor::NwResize,
|
|
CursorIcon::ResizeNorth => KnownCursor::NResize,
|
|
CursorIcon::ResizeNorthEast => KnownCursor::NeResize,
|
|
CursorIcon::ResizeColumn => KnownCursor::ColResize,
|
|
CursorIcon::ResizeRow => KnownCursor::RowResize,
|
|
CursorIcon::ZoomIn => KnownCursor::ZoomIn,
|
|
CursorIcon::ZoomOut => KnownCursor::ZoomOut,
|
|
};
|
|
seat.wp_cursor_shape_device_v1
|
|
.set_shape(seat.pointer_serial.get(), cursor);
|
|
}
|
|
}
|
|
for command in commands {
|
|
match command {
|
|
OutputCommand::CopyText(t) => {
|
|
if let Some(seat) = self.active_seat.get() {
|
|
let data_src = self.ctx.wl_data_device_manager.create_data_source();
|
|
data_src.offer(TEXT_PLAIN);
|
|
data_src.owner.set(Some(seat.clone()));
|
|
seat.wl_data_device
|
|
.set_selection(seat.serial.get(), &data_src);
|
|
if let Some(old) = seat.wl_data_source.set(Some(data_src)) {
|
|
old.con.remove_obj(&*old);
|
|
}
|
|
seat.copy_text.replace(Some(Buf::from_slice(t.as_bytes())));
|
|
}
|
|
}
|
|
OutputCommand::CopyImage(_) => {}
|
|
OutputCommand::OpenUrl(url) => {
|
|
if let Some(forker) = self.ctx.state.forker.get() {
|
|
forker.spawn("xdg-open".to_string(), vec![url.url], vec![], vec![]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
let Some(viewport) = viewport_output.get(&ViewportId::ROOT) else {
|
|
return Err(EggError::NoViewportOutput);
|
|
};
|
|
for command in &viewport.commands {
|
|
match command {
|
|
ViewportCommand::Close => self.close.set(true),
|
|
ViewportCommand::CancelClose => self.close.set(false),
|
|
ViewportCommand::Title(s) => self.xdg_toplevel.set_title(s),
|
|
ViewportCommand::Fullscreen(b) => self.xdg_toplevel.set_fullscreen(*b),
|
|
ViewportCommand::RequestPaste => {
|
|
if let Some(seat) = self.active_seat.get() {
|
|
seat.request_paste();
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
let repaint_delay = u64::try_from(viewport.repaint_delay.as_nanos()).unwrap_or(u64::MAX);
|
|
let repaint_timeout = self.ctx.state.now_nsec().saturating_add(repaint_delay);
|
|
self.repaint_timeout.set(repaint_timeout);
|
|
if repaint_timeout != u64::MAX {
|
|
self.repaint_timeout_changed.trigger();
|
|
}
|
|
self.wl_surface.attach(&fb.wl_buffer);
|
|
self.wl_surface.damage();
|
|
self.jay_sync_file_surface.set_acquire(sync.as_ref());
|
|
self.jay_sync_file_surface
|
|
.get_release()
|
|
.owner
|
|
.set(Some(fb.clone()));
|
|
self.wl_surface.frame().owner.set(Some(self.clone()));
|
|
self.wp_viewport
|
|
.set_destination(logical_size[0], logical_size[1]);
|
|
self.wl_surface.commit();
|
|
fb.client_acquire_fence.take();
|
|
self.buffers.flip();
|
|
self.have_frame.set(false);
|
|
self.want_frame.set(false);
|
|
if self.close.get() {
|
|
owner.close();
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl UsrXdgSurfaceOwner for EggWindowInner {
|
|
fn configure(&self) {
|
|
let pending = mem::take(&mut *self.surface_pending.borrow_mut());
|
|
if let Some((mut w, mut h)) = pending.size {
|
|
let [old_w, old_h] = self.logical_size.get();
|
|
w = if w > 0 { w } else { old_w };
|
|
h = if h > 0 { h } else { old_h };
|
|
let size = [w, h];
|
|
if self.logical_size.replace(size) != size {
|
|
self.update_physical_size();
|
|
}
|
|
}
|
|
if self.initial_commit_pending.take() {
|
|
self.want_frame();
|
|
}
|
|
}
|
|
}
|
|
|
|
impl UsrXdgToplevelOwner for EggWindowInner {
|
|
fn configure(&self, width: i32, height: i32) {
|
|
self.surface_pending.borrow_mut().size = Some((width, height));
|
|
}
|
|
|
|
fn close(&self) {
|
|
let raw_input = &mut *self.raw_input.borrow_mut();
|
|
let raw_input = raw_input.get_or_insert_with(|| self.default_raw_input());
|
|
raw_input
|
|
.viewports
|
|
.get_mut(&ViewportId::ROOT)
|
|
.unwrap()
|
|
.events
|
|
.push(ViewportEvent::Close);
|
|
self.close.set(true);
|
|
self.want_frame();
|
|
}
|
|
}
|
|
|
|
impl UsrWpFractionalScaleOwner for EggWindowInner {
|
|
fn preferred_scale(self: Rc<Self>, ev: &PreferredScale) {
|
|
let scale = Scale::from_wl(ev.scale);
|
|
if self.scale.replace(scale) != scale {
|
|
self.update_physical_size();
|
|
}
|
|
}
|
|
}
|
|
|
|
impl EggWindowInner {
|
|
fn event(&self, event: Event) {
|
|
let raw_input = &mut *self.raw_input.borrow_mut();
|
|
let raw_input = raw_input.get_or_insert_with(|| self.default_raw_input());
|
|
raw_input.events.push(event);
|
|
self.want_frame();
|
|
}
|
|
|
|
fn default_raw_input(&self) -> RawInput {
|
|
let viewport_info = ViewportInfo {
|
|
native_pixels_per_point: Some(self.scale.get().to_f64() as _),
|
|
..Default::default()
|
|
};
|
|
let size = self.logical_size.get();
|
|
let size =
|
|
egui::Rect::from_min_size(Pos2::default(), Vec2::new(size[0] as f32, size[1] as f32));
|
|
let mut modifiers = Modifiers::default();
|
|
if let Some(seat) = self.active_seat.get() {
|
|
modifiers = seat.kb_modifiers.get();
|
|
}
|
|
RawInput {
|
|
viewport_id: ViewportId::ROOT,
|
|
viewports: std::iter::once((ViewportId::ROOT, viewport_info)).collect(),
|
|
screen_rect: Some(size),
|
|
max_texture_side: Some(self.ctx.renderer.max_texture_side()),
|
|
time: Some(self.ctx.state.now_nsec() as f64 / 1_000_000_000.0),
|
|
modifiers,
|
|
..Default::default()
|
|
}
|
|
}
|
|
}
|
|
|
|
impl EggSeatInner {
|
|
fn activate_pointer_window(self: &Rc<Self>) -> Option<Rc<EggWindowInner>> {
|
|
let window = self.pointer_window.get()?;
|
|
window.active_seat.set(Some(self.clone()));
|
|
Some(window)
|
|
}
|
|
|
|
fn activate_kb_window(self: &Rc<Self>) -> Option<Rc<EggWindowInner>> {
|
|
let window = self.kb_window.get()?;
|
|
window.active_seat.set(Some(self.clone()));
|
|
Some(window)
|
|
}
|
|
|
|
fn leave(self: &Rc<Self>) {
|
|
if let Some(window) = self.pointer_window.take()
|
|
&& let Some(active_seat) = window.active_seat.get()
|
|
&& rc_eq(&active_seat, &self)
|
|
{
|
|
window.active_seat.take();
|
|
}
|
|
}
|
|
|
|
fn unfocus(self: &Rc<Self>) {
|
|
if let Some(window) = self.kb_window.take()
|
|
&& let Some(active_seat) = window.active_seat.get()
|
|
&& rc_eq(&active_seat, &self)
|
|
{
|
|
window.active_seat.take();
|
|
}
|
|
}
|
|
|
|
fn motion(self: &Rc<Self>, surface_x: Fixed, surface_y: Fixed) {
|
|
let Some(window) = self.activate_pointer_window() else {
|
|
return;
|
|
};
|
|
let pos = pos2(surface_x.to_f32(), surface_y.to_f32());
|
|
self.pointer_pos.set(pos);
|
|
window.event(Event::PointerMoved(pos));
|
|
}
|
|
}
|
|
|
|
impl UsrWlPointerOwner for EggSeatInner {
|
|
fn enter(self: Rc<Self>, ev: &Enter) {
|
|
let Some(window) = self.ctx.windows.get(&ev.surface) else {
|
|
return;
|
|
};
|
|
self.pointer_window.set(Some(window.clone()));
|
|
self.pointer_enter_serial.set(ev.serial);
|
|
self.pointer_serial.set(ev.serial);
|
|
self.serial.set(ev.serial);
|
|
(&self).motion(ev.surface_x, ev.surface_y);
|
|
}
|
|
|
|
fn leave(self: Rc<Self>, _ev: &Leave) {
|
|
(&self).leave();
|
|
}
|
|
|
|
fn motion(self: Rc<Self>, ev: &Motion) {
|
|
(&self).motion(ev.surface_x, ev.surface_y);
|
|
}
|
|
|
|
fn button(self: Rc<Self>, ev: &Button) {
|
|
let Some(window) = self.activate_pointer_window() else {
|
|
return;
|
|
};
|
|
self.pointer_serial.set(ev.serial);
|
|
self.serial.set(ev.serial);
|
|
let button = match ev.button {
|
|
BTN_LEFT => PointerButton::Primary,
|
|
BTN_RIGHT => PointerButton::Secondary,
|
|
BTN_MIDDLE => PointerButton::Middle,
|
|
BTN_SIDE => PointerButton::Extra1,
|
|
BTN_EXTRA => PointerButton::Extra2,
|
|
_ => return,
|
|
};
|
|
window.event(Event::PointerButton {
|
|
pos: self.pointer_pos.get(),
|
|
button,
|
|
pressed: ev.state == wl_pointer::PRESSED,
|
|
modifiers: self.kb_modifiers.get(),
|
|
});
|
|
}
|
|
|
|
fn scroll(self: Rc<Self>, ps: &PendingScroll) {
|
|
let Some(window) = self.activate_pointer_window() else {
|
|
return;
|
|
};
|
|
let v120_x = ps.v120[HORIZONTAL_SCROLL].get();
|
|
let v120_y = ps.v120[VERTICAL_SCROLL].get();
|
|
let px_x = ps.px[HORIZONTAL_SCROLL].get();
|
|
let px_y = ps.px[VERTICAL_SCROLL].get();
|
|
let unit;
|
|
let delta;
|
|
if v120_x.is_some() || v120_y.is_some() {
|
|
unit = MouseWheelUnit::Line;
|
|
delta = vec2(
|
|
-v120_x.unwrap_or_default() as f32 / 120.0,
|
|
-v120_y.unwrap_or_default() as f32 / 120.0,
|
|
);
|
|
} else if px_x.is_some() || px_y.is_some() {
|
|
unit = MouseWheelUnit::Point;
|
|
delta = vec2(
|
|
-px_x.unwrap_or_default().to_f32(),
|
|
-px_y.unwrap_or_default().to_f32(),
|
|
);
|
|
} else {
|
|
return;
|
|
}
|
|
window.event(Event::MouseWheel {
|
|
unit,
|
|
delta,
|
|
phase: TouchPhase::Move,
|
|
modifiers: self.kb_modifiers.get(),
|
|
});
|
|
}
|
|
}
|
|
|
|
impl EggSeatInner {
|
|
fn handle_key(self: &Rc<Self>, lookup: Lookup<'_>, serial: u32, down: bool) {
|
|
let Some(window) = self.activate_kb_window() else {
|
|
return;
|
|
};
|
|
self.kb_serial.set(serial);
|
|
self.serial.set(serial);
|
|
if down {
|
|
let mut text = String::new();
|
|
for key in lookup {
|
|
if let Some(c) = key.char()
|
|
&& c.is_not_control()
|
|
{
|
|
text.push(c);
|
|
}
|
|
}
|
|
if text.is_not_empty() {
|
|
window.event(Event::Text(text));
|
|
}
|
|
}
|
|
for key in lookup {
|
|
let mut modifiers = map_mods(lookup.remaining_mods());
|
|
let Some(key) = map_key(key.keysym(), &mut modifiers) else {
|
|
continue;
|
|
};
|
|
if down && modifiers.ctrl {
|
|
match key {
|
|
Key::V => self.request_paste(),
|
|
Key::C => window.event(Event::Copy),
|
|
Key::X => window.event(Event::Cut),
|
|
_ => {}
|
|
}
|
|
}
|
|
window.event(Event::Key {
|
|
key,
|
|
physical_key: None,
|
|
pressed: down,
|
|
repeat: false,
|
|
modifiers,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
impl UsrWlKeyboardOwner for EggSeatInner {
|
|
fn focus(self: Rc<Self>, surface: WlSurfaceId, serial: u32) {
|
|
let Some(window) = self.ctx.windows.get(&surface) else {
|
|
return;
|
|
};
|
|
self.kb_window.set(Some(window.clone()));
|
|
self.kb_serial.set(serial);
|
|
self.serial.set(serial);
|
|
window.active_seat.set(Some(self.clone()));
|
|
}
|
|
|
|
fn unfocus(self: Rc<Self>) {
|
|
(&self).unfocus();
|
|
}
|
|
|
|
fn modifiers(self: Rc<Self>, mods: ModifierMask) {
|
|
self.kb_modifiers.set(map_mods(mods));
|
|
}
|
|
|
|
fn down(self: Rc<Self>, lookup: Lookup<'_>, serial: u32) {
|
|
self.handle_key(lookup, serial, true);
|
|
}
|
|
|
|
fn repeat(self: Rc<Self>, lookup: Lookup<'_>, serial: u32) {
|
|
self.handle_key(lookup, serial, true);
|
|
}
|
|
|
|
fn up(self: Rc<Self>, lookup: Lookup<'_>, serial: u32) {
|
|
self.handle_key(lookup, serial, false);
|
|
}
|
|
}
|
|
|
|
fn map_mods(mods: ModifierMask) -> Modifiers {
|
|
Modifiers {
|
|
alt: mods.contains(ModifierMask::ALT),
|
|
ctrl: mods.contains(ModifierMask::CONTROL),
|
|
shift: mods.contains(ModifierMask::SHIFT),
|
|
mac_cmd: false,
|
|
command: mods.contains(ModifierMask::CONTROL),
|
|
}
|
|
}
|
|
|
|
impl UsrJaySyncFileReleaseOwner for EggFramebuffer {
|
|
fn release(&self, sync_file: Option<SyncFile>) {
|
|
self.client_acquire_fence.set(Some(sync_file));
|
|
if let Some(window) = self.window.upgrade() {
|
|
window.maybe_trigger_frame();
|
|
}
|
|
}
|
|
}
|
|
|
|
impl UsrWlCallbackOwner for EggWindowInner {
|
|
fn done(self: Rc<Self>) {
|
|
self.have_frame.set(true);
|
|
self.maybe_trigger_frame();
|
|
}
|
|
}
|
|
|
|
fn map_key(kc: Keysym, mods: &mut Modifiers) -> Option<Key> {
|
|
use {Key as K, kbvm::syms as s};
|
|
let mut with_shift = |k| {
|
|
mods.shift = true;
|
|
k
|
|
};
|
|
let key = match kc {
|
|
s::Down | s::KP_Down => K::ArrowDown,
|
|
s::Left | s::KP_Left => K::ArrowLeft,
|
|
s::Right | s::KP_Right => K::ArrowRight,
|
|
s::Up | s::KP_Up => K::ArrowUp,
|
|
s::Escape => K::Escape,
|
|
s::Tab | s::KP_Tab => K::Tab,
|
|
s::ISO_Left_Tab => with_shift(K::Tab),
|
|
s::BackSpace => K::Backspace,
|
|
s::Return | s::KP_Enter => K::Enter,
|
|
s::space | s::KP_Space => K::Space,
|
|
s::Insert | s::KP_Insert => K::Insert,
|
|
s::Delete | s::KP_Delete => K::Delete,
|
|
s::Home | s::KP_Home | s::KP_Begin => K::Home,
|
|
s::End | s::KP_End => K::End,
|
|
s::Page_Up | s::KP_Page_Up => K::PageUp,
|
|
s::Page_Down | s::KP_Page_Down => K::PageDown,
|
|
s::XF86Copy => K::Copy,
|
|
s::XF86Cut => K::Cut,
|
|
s::XF86Paste => K::Paste,
|
|
s::colon => K::Colon,
|
|
s::comma => K::Comma,
|
|
s::backslash => K::Backslash,
|
|
s::slash | s::KP_Divide => K::Slash,
|
|
s::bar => K::Pipe,
|
|
s::question => K::Questionmark,
|
|
s::exclam => K::Exclamationmark,
|
|
s::bracketleft => K::OpenBracket,
|
|
s::bracketright => K::CloseBracket,
|
|
s::braceleft => K::OpenCurlyBracket,
|
|
s::braceright => K::CloseCurlyBracket,
|
|
s::grave => K::Backtick,
|
|
s::minus | s::KP_Subtract => K::Minus,
|
|
s::period | s::KP_Decimal => K::Period,
|
|
s::plus | s::KP_Add => K::Plus,
|
|
s::equal | s::KP_Equal => K::Equals,
|
|
s::semicolon => K::Semicolon,
|
|
s::quotedbl => K::Quote,
|
|
s::KP_0 | s::_0 => K::Num0,
|
|
s::KP_1 | s::_1 => K::Num1,
|
|
s::KP_2 | s::_2 => K::Num2,
|
|
s::KP_3 | s::_3 => K::Num3,
|
|
s::KP_4 | s::_4 => K::Num4,
|
|
s::KP_5 | s::_5 => K::Num5,
|
|
s::KP_6 | s::_6 => K::Num6,
|
|
s::KP_7 | s::_7 => K::Num7,
|
|
s::KP_8 | s::_8 => K::Num8,
|
|
s::KP_9 | s::_9 => K::Num9,
|
|
s::a => K::A,
|
|
s::b => K::B,
|
|
s::c => K::C,
|
|
s::d => K::D,
|
|
s::e => K::E,
|
|
s::f => K::F,
|
|
s::g => K::G,
|
|
s::h => K::H,
|
|
s::i => K::I,
|
|
s::j => K::J,
|
|
s::k => K::K,
|
|
s::l => K::L,
|
|
s::m => K::M,
|
|
s::n => K::N,
|
|
s::o => K::O,
|
|
s::p => K::P,
|
|
s::q => K::Q,
|
|
s::r => K::R,
|
|
s::s => K::S,
|
|
s::t => K::T,
|
|
s::u => K::U,
|
|
s::v => K::V,
|
|
s::w => K::W,
|
|
s::x => K::X,
|
|
s::y => K::Y,
|
|
s::z => K::Z,
|
|
s::A => with_shift(K::A),
|
|
s::B => with_shift(K::B),
|
|
s::C => with_shift(K::C),
|
|
s::D => with_shift(K::D),
|
|
s::E => with_shift(K::E),
|
|
s::F => with_shift(K::F),
|
|
s::G => with_shift(K::G),
|
|
s::H => with_shift(K::H),
|
|
s::I => with_shift(K::I),
|
|
s::J => with_shift(K::J),
|
|
s::K => with_shift(K::K),
|
|
s::L => with_shift(K::L),
|
|
s::M => with_shift(K::M),
|
|
s::N => with_shift(K::N),
|
|
s::O => with_shift(K::O),
|
|
s::P => with_shift(K::P),
|
|
s::Q => with_shift(K::Q),
|
|
s::R => with_shift(K::R),
|
|
s::S => with_shift(K::S),
|
|
s::T => with_shift(K::T),
|
|
s::U => with_shift(K::U),
|
|
s::V => with_shift(K::V),
|
|
s::W => with_shift(K::W),
|
|
s::X => with_shift(K::X),
|
|
s::Y => with_shift(K::Y),
|
|
s::Z => with_shift(K::Z),
|
|
s::F1 | s::KP_F1 => K::F1,
|
|
s::F2 | s::KP_F2 => K::F2,
|
|
s::F3 | s::KP_F3 => K::F3,
|
|
s::F4 | s::KP_F4 => K::F4,
|
|
s::F5 => K::F5,
|
|
s::F6 => K::F6,
|
|
s::F7 => K::F7,
|
|
s::F8 => K::F8,
|
|
s::F9 => K::F9,
|
|
s::F10 => K::F10,
|
|
s::F11 => K::F11,
|
|
s::F12 => K::F12,
|
|
s::F13 => K::F13,
|
|
s::F14 => K::F14,
|
|
s::F15 => K::F15,
|
|
s::F16 => K::F16,
|
|
s::F17 => K::F17,
|
|
s::F18 => K::F18,
|
|
s::F19 => K::F19,
|
|
s::F20 => K::F20,
|
|
s::F21 => K::F21,
|
|
s::F22 => K::F22,
|
|
s::F23 => K::F23,
|
|
s::F24 => K::F24,
|
|
s::F25 => K::F25,
|
|
s::F26 => K::F26,
|
|
s::F27 => K::F27,
|
|
s::F28 => K::F28,
|
|
s::F29 => K::F29,
|
|
s::F30 => K::F30,
|
|
s::F31 => K::F31,
|
|
s::F32 => K::F32,
|
|
s::F33 => K::F33,
|
|
s::F34 => K::F34,
|
|
s::F35 => K::F35,
|
|
s::XF86Back => K::BrowserBack,
|
|
_ => return None,
|
|
};
|
|
Some(key)
|
|
}
|
|
|
|
impl Drop for EggSeat {
|
|
fn drop(&mut self) {
|
|
let s = &self.inner;
|
|
s.copy_task.take();
|
|
s.paste_task.take();
|
|
s.leave();
|
|
s.unfocus();
|
|
if let Some(v) = s.wl_data_source.take() {
|
|
s.ctx.con.remove_obj(&*v);
|
|
}
|
|
s.ctx.seats.remove(&s.global_name);
|
|
s.ctx.con.remove_obj(&*s.wl_data_device);
|
|
s.ctx.con.remove_obj(&*s.wl_keyboard);
|
|
s.ctx.con.remove_obj(&*s.wp_cursor_shape_device_v1);
|
|
s.ctx.con.remove_obj(&*s.wl_pointer);
|
|
s.ctx.con.remove_obj(&*s.wl_seat);
|
|
}
|
|
}
|
|
|
|
impl Drop for EggContext {
|
|
fn drop(&mut self) {
|
|
let i = &self.inner;
|
|
i.state.egg_state.cxts.remove(&self.inner.id);
|
|
i.seats.clear();
|
|
i.windows.clear();
|
|
i.con.owner.take();
|
|
i.con.kill();
|
|
}
|
|
}
|
|
|
|
impl Drop for EggWindow {
|
|
fn drop(&mut self) {
|
|
let i = &self.inner;
|
|
i.ctx.windows.remove(&i.wl_surface.id);
|
|
if let Some(seat) = i.active_seat.take() {
|
|
for field in [&seat.kb_window, &seat.pointer_window] {
|
|
if let Some(w) = field.get()
|
|
&& rc_eq(&w, i)
|
|
{
|
|
field.take();
|
|
}
|
|
}
|
|
}
|
|
i.owner.take();
|
|
i.ctx.con.remove_obj(&*i.jay_sync_file_surface);
|
|
i.ctx.con.remove_obj(&*i.xdg_toplevel);
|
|
i.ctx.con.remove_obj(&*i.xdg_surface);
|
|
i.ctx.con.remove_obj(&*i.wp_fractional_scale);
|
|
i.ctx.con.remove_obj(&*i.wp_viewport);
|
|
i.ctx.con.remove_obj(&*i.wl_surface);
|
|
}
|
|
}
|
|
|
|
impl UsrWlDataSourceOwner for EggSeatInner {
|
|
fn send(&self, _mime_type: &str, fd: Rc<OwnedFd>) {
|
|
let Some(buf) = self.copy_text.borrow_mut().as_mut().map(|b| b.clone()) else {
|
|
return;
|
|
};
|
|
let ring = self.ctx.state.ring.clone();
|
|
let task = self.ctx.state.eng.spawn("egg-copy-text", async move {
|
|
if let Err(e) = ring.write(&fd, buf, None).await {
|
|
log::error!("Could not send text to client: {}", e);
|
|
}
|
|
});
|
|
self.copy_task.set(Some(task));
|
|
}
|
|
}
|
|
|
|
impl UsrConOwner for EggContextInner {
|
|
fn killed(&self) {
|
|
if let Some(ctx) = self.state.egg_state.ctx.get()
|
|
&& ctx.inner.id == self.id
|
|
{
|
|
self.state.egg_state.ctx.take();
|
|
}
|
|
for window in self.windows.clear().values() {
|
|
if let Some(owner) = window.owner.take() {
|
|
owner.close();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Drop for EggFramebuffer {
|
|
fn drop(&mut self) {
|
|
if let Some(Some(fence)) = self.client_acquire_fence.take() {
|
|
self.drop_queue.push(&fence.0, self.bo.clone());
|
|
}
|
|
self.wl_buffer.con.remove_obj(&*self.wl_buffer);
|
|
}
|
|
}
|