Merge pull request #169 from mahkoh/jorth/select-toplevel
portal: implement window capture
This commit is contained in:
commit
670588fe4d
61 changed files with 1549 additions and 292 deletions
|
|
@ -257,6 +257,10 @@ pub mod colors {
|
||||||
///
|
///
|
||||||
/// Default: `#23092c`.
|
/// Default: `#23092c`.
|
||||||
const 14 => ATTENTION_REQUESTED_BACKGROUND_COLOR,
|
const 14 => ATTENTION_REQUESTED_BACKGROUND_COLOR,
|
||||||
|
/// Color used to highlight parts of the UI.
|
||||||
|
///
|
||||||
|
/// Default: `#9d28c67f`.
|
||||||
|
const 15 => HIGHLIGHT_COLOR,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the color of GUI element.
|
/// Sets the color of GUI element.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
# Unreleased
|
# Unreleased
|
||||||
|
|
||||||
|
- Screencasts now support window capture.
|
||||||
- Add support for wp-alpha-modifier.
|
- Add support for wp-alpha-modifier.
|
||||||
- Add support for per-device keymaps.
|
- Add support for per-device keymaps.
|
||||||
- Add support for virtual-keyboard-unstable-v1.
|
- Add support for virtual-keyboard-unstable-v1.
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use {
|
||||||
},
|
},
|
||||||
jay_output::JayOutput,
|
jay_output::JayOutput,
|
||||||
jay_screencast::JayScreencast,
|
jay_screencast::JayScreencast,
|
||||||
|
jay_toplevel::JayToplevel,
|
||||||
jay_workspace::JayWorkspace,
|
jay_workspace::JayWorkspace,
|
||||||
wl_buffer::WlBuffer,
|
wl_buffer::WlBuffer,
|
||||||
wl_display::WlDisplay,
|
wl_display::WlDisplay,
|
||||||
|
|
@ -29,10 +30,10 @@ use {
|
||||||
copyhashmap::{CopyHashMap, Locked},
|
copyhashmap::{CopyHashMap, Locked},
|
||||||
},
|
},
|
||||||
wire::{
|
wire::{
|
||||||
JayOutputId, JayScreencastId, JayWorkspaceId, WlBufferId, WlDataSourceId, WlOutputId,
|
JayOutputId, JayScreencastId, JayToplevelId, JayWorkspaceId, WlBufferId,
|
||||||
WlPointerId, WlRegionId, WlRegistryId, WlSeatId, WlSurfaceId,
|
WlDataSourceId, WlOutputId, WlPointerId, WlRegionId, WlRegistryId, WlSeatId,
|
||||||
WpLinuxDrmSyncobjTimelineV1Id, XdgPositionerId, XdgSurfaceId, XdgToplevelId,
|
WlSurfaceId, WpLinuxDrmSyncobjTimelineV1Id, XdgPositionerId, XdgSurfaceId,
|
||||||
XdgWmBaseId, ZwlrDataControlSourceV1Id, ZwpPrimarySelectionSourceV1Id,
|
XdgToplevelId, XdgWmBaseId, ZwlrDataControlSourceV1Id, ZwpPrimarySelectionSourceV1Id,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
std::{cell::RefCell, mem, rc::Rc},
|
std::{cell::RefCell, mem, rc::Rc},
|
||||||
|
|
@ -60,6 +61,7 @@ pub struct Objects {
|
||||||
pub screencasts: CopyHashMap<JayScreencastId, Rc<JayScreencast>>,
|
pub screencasts: CopyHashMap<JayScreencastId, Rc<JayScreencast>>,
|
||||||
pub timelines: CopyHashMap<WpLinuxDrmSyncobjTimelineV1Id, Rc<WpLinuxDrmSyncobjTimelineV1>>,
|
pub timelines: CopyHashMap<WpLinuxDrmSyncobjTimelineV1Id, Rc<WpLinuxDrmSyncobjTimelineV1>>,
|
||||||
pub zwlr_data_sources: CopyHashMap<ZwlrDataControlSourceV1Id, Rc<ZwlrDataControlSourceV1>>,
|
pub zwlr_data_sources: CopyHashMap<ZwlrDataControlSourceV1Id, Rc<ZwlrDataControlSourceV1>>,
|
||||||
|
pub jay_toplevels: CopyHashMap<JayToplevelId, Rc<JayToplevel>>,
|
||||||
ids: RefCell<Vec<usize>>,
|
ids: RefCell<Vec<usize>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -89,6 +91,7 @@ impl Objects {
|
||||||
screencasts: Default::default(),
|
screencasts: Default::default(),
|
||||||
timelines: Default::default(),
|
timelines: Default::default(),
|
||||||
zwlr_data_sources: Default::default(),
|
zwlr_data_sources: Default::default(),
|
||||||
|
jay_toplevels: Default::default(),
|
||||||
ids: RefCell::new(vec![]),
|
ids: RefCell::new(vec![]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -122,6 +125,7 @@ impl Objects {
|
||||||
self.screencasts.clear();
|
self.screencasts.clear();
|
||||||
self.timelines.clear();
|
self.timelines.clear();
|
||||||
self.zwlr_data_sources.clear();
|
self.zwlr_data_sources.clear();
|
||||||
|
self.jay_toplevels.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn id<T>(&self, client_data: &Client) -> Result<T, ClientError>
|
pub fn id<T>(&self, client_data: &Client) -> Result<T, ClientError>
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ use {
|
||||||
forker,
|
forker,
|
||||||
globals::Globals,
|
globals::Globals,
|
||||||
ifs::{
|
ifs::{
|
||||||
|
jay_screencast::{perform_screencast_realloc, perform_toplevel_screencasts},
|
||||||
wl_output::{OutputId, PersistentOutputState, WlOutputGlobal},
|
wl_output::{OutputId, PersistentOutputState, WlOutputGlobal},
|
||||||
wl_surface::{zwp_input_popup_surface_v2::input_popup_positioning, NoneSurfaceExt},
|
wl_surface::{zwp_input_popup_surface_v2::input_popup_positioning, NoneSurfaceExt},
|
||||||
},
|
},
|
||||||
|
|
@ -173,6 +174,8 @@ fn start_compositor2(
|
||||||
pending_float_layout: Default::default(),
|
pending_float_layout: Default::default(),
|
||||||
pending_float_titles: Default::default(),
|
pending_float_titles: Default::default(),
|
||||||
pending_input_popup_positioning: Default::default(),
|
pending_input_popup_positioning: Default::default(),
|
||||||
|
pending_toplevel_screencasts: Default::default(),
|
||||||
|
pending_toplevel_screencast_reallocs: Default::default(),
|
||||||
dbus: Dbus::new(&engine, &ring, &run_toplevel),
|
dbus: Dbus::new(&engine, &ring, &run_toplevel),
|
||||||
fdcloser: FdCloser::new(),
|
fdcloser: FdCloser::new(),
|
||||||
logger: logger.clone(),
|
logger: logger.clone(),
|
||||||
|
|
@ -329,6 +332,8 @@ fn start_global_event_handlers(
|
||||||
eng.spawn2(Phase::PostLayout, float_titles(state.clone())),
|
eng.spawn2(Phase::PostLayout, float_titles(state.clone())),
|
||||||
eng.spawn2(Phase::PostLayout, idle(state.clone(), backend.clone())),
|
eng.spawn2(Phase::PostLayout, idle(state.clone(), backend.clone())),
|
||||||
eng.spawn2(Phase::PostLayout, input_popup_positioning(state.clone())),
|
eng.spawn2(Phase::PostLayout, input_popup_positioning(state.clone())),
|
||||||
|
eng.spawn2(Phase::Present, perform_toplevel_screencasts(state.clone())),
|
||||||
|
eng.spawn2(Phase::PostLayout, perform_screencast_realloc(state.clone())),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1430,6 +1430,7 @@ impl ConfigProxyHandler {
|
||||||
FOCUSED_INACTIVE_TITLE_TEXT_COLOR => &colors.focused_inactive_title_text,
|
FOCUSED_INACTIVE_TITLE_TEXT_COLOR => &colors.focused_inactive_title_text,
|
||||||
BAR_STATUS_TEXT_COLOR => &colors.bar_text,
|
BAR_STATUS_TEXT_COLOR => &colors.bar_text,
|
||||||
ATTENTION_REQUESTED_BACKGROUND_COLOR => &colors.attention_requested_background,
|
ATTENTION_REQUESTED_BACKGROUND_COLOR => &colors.attention_requested_background,
|
||||||
|
HIGHLIGHT_COLOR => &colors.highlight,
|
||||||
_ => return Err(CphError::UnknownColor(colorable.0)),
|
_ => return Err(CphError::UnknownColor(colorable.0)),
|
||||||
};
|
};
|
||||||
Ok(colorable)
|
Ok(colorable)
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@ pub mod jay_render_ctx;
|
||||||
pub mod jay_screencast;
|
pub mod jay_screencast;
|
||||||
pub mod jay_screenshot;
|
pub mod jay_screenshot;
|
||||||
pub mod jay_seat_events;
|
pub mod jay_seat_events;
|
||||||
|
pub mod jay_select_toplevel;
|
||||||
|
pub mod jay_toplevel;
|
||||||
pub mod jay_workspace;
|
pub mod jay_workspace;
|
||||||
pub mod jay_workspace_watcher;
|
pub mod jay_workspace_watcher;
|
||||||
pub mod org_kde_kwin_server_decoration;
|
pub mod org_kde_kwin_server_decoration;
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,17 @@ use {
|
||||||
client::{Client, ClientError},
|
client::{Client, ClientError},
|
||||||
globals::{Global, GlobalName},
|
globals::{Global, GlobalName},
|
||||||
ifs::{
|
ifs::{
|
||||||
jay_idle::JayIdle, jay_input::JayInput, jay_log_file::JayLogFile,
|
jay_idle::JayIdle,
|
||||||
jay_output::JayOutput, jay_pointer::JayPointer, jay_randr::JayRandr,
|
jay_input::JayInput,
|
||||||
jay_render_ctx::JayRenderCtx, jay_screencast::JayScreencast,
|
jay_log_file::JayLogFile,
|
||||||
jay_screenshot::JayScreenshot, jay_seat_events::JaySeatEvents,
|
jay_output::JayOutput,
|
||||||
|
jay_pointer::JayPointer,
|
||||||
|
jay_randr::JayRandr,
|
||||||
|
jay_render_ctx::JayRenderCtx,
|
||||||
|
jay_screencast::JayScreencast,
|
||||||
|
jay_screenshot::JayScreenshot,
|
||||||
|
jay_seat_events::JaySeatEvents,
|
||||||
|
jay_select_toplevel::{JaySelectToplevel, JayToplevelSelector},
|
||||||
jay_workspace_watcher::JayWorkspaceWatcher,
|
jay_workspace_watcher::JayWorkspaceWatcher,
|
||||||
},
|
},
|
||||||
leaks::Tracker,
|
leaks::Tracker,
|
||||||
|
|
@ -18,7 +25,7 @@ use {
|
||||||
},
|
},
|
||||||
bstr::ByteSlice,
|
bstr::ByteSlice,
|
||||||
log::Level,
|
log::Level,
|
||||||
std::{ops::Deref, rc::Rc},
|
std::{cell::Cell, ops::Deref, rc::Rc},
|
||||||
thiserror::Error,
|
thiserror::Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -44,6 +51,7 @@ impl JayCompositorGlobal {
|
||||||
});
|
});
|
||||||
track!(client, obj);
|
track!(client, obj);
|
||||||
client.add_client_obj(&obj)?;
|
client.add_client_obj(&obj)?;
|
||||||
|
obj.send_capabilities();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -72,7 +80,21 @@ pub struct JayCompositor {
|
||||||
tracker: Tracker<Self>,
|
tracker: Tracker<Self>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct Cap;
|
||||||
|
|
||||||
|
impl Cap {
|
||||||
|
pub const NONE: u16 = 0;
|
||||||
|
pub const WINDOW_CAPTURE: u16 = 1;
|
||||||
|
}
|
||||||
|
|
||||||
impl JayCompositor {
|
impl JayCompositor {
|
||||||
|
fn send_capabilities(&self) {
|
||||||
|
self.client.event(Capabilities {
|
||||||
|
self_id: self.id,
|
||||||
|
cap: &[Cap::NONE, Cap::WINDOW_CAPTURE],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fn take_screenshot_impl(
|
fn take_screenshot_impl(
|
||||||
&self,
|
&self,
|
||||||
id: JayScreenshotId,
|
id: JayScreenshotId,
|
||||||
|
|
@ -322,6 +344,24 @@ impl JayCompositorRequestHandler for JayCompositor {
|
||||||
self.client.add_client_obj(&sc)?;
|
self.client.add_client_obj(&sc)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn select_toplevel(&self, req: SelectToplevel, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||||
|
let seat = self.client.lookup(req.seat)?;
|
||||||
|
let obj = Rc::new(JaySelectToplevel {
|
||||||
|
id: req.id,
|
||||||
|
client: self.client.clone(),
|
||||||
|
tracker: Default::default(),
|
||||||
|
destroyed: Cell::new(false),
|
||||||
|
});
|
||||||
|
track!(self.client, obj);
|
||||||
|
self.client.add_client_obj(&obj)?;
|
||||||
|
let selector = JayToplevelSelector {
|
||||||
|
tl: Default::default(),
|
||||||
|
jst: obj.clone(),
|
||||||
|
};
|
||||||
|
seat.global.select_toplevel(selector);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object_base! {
|
object_base! {
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,17 @@ use {
|
||||||
client::{Client, ClientError},
|
client::{Client, ClientError},
|
||||||
format::XRGB8888,
|
format::XRGB8888,
|
||||||
gfx_api::{GfxContext, GfxError, GfxFramebuffer, GfxTexture},
|
gfx_api::{GfxContext, GfxError, GfxFramebuffer, GfxTexture},
|
||||||
ifs::jay_output::JayOutput,
|
ifs::{jay_output::JayOutput, jay_toplevel::JayToplevel},
|
||||||
leaks::Tracker,
|
leaks::Tracker,
|
||||||
object::{Object, Version},
|
object::{Object, Version},
|
||||||
tree::{OutputNode, WorkspaceNodeId},
|
scale::Scale,
|
||||||
|
state::State,
|
||||||
|
tree::{OutputNode, ToplevelNode, WorkspaceNodeId},
|
||||||
utils::{
|
utils::{
|
||||||
clonecell::CloneCell, errorfmt::ErrorFmt, numcell::NumCell, option_ext::OptionExt,
|
clonecell::{CloneCell, UnsafeCellCloneSafe},
|
||||||
|
errorfmt::ErrorFmt,
|
||||||
|
numcell::NumCell,
|
||||||
|
option_ext::OptionExt,
|
||||||
},
|
},
|
||||||
video::{
|
video::{
|
||||||
dmabuf::DmaBuf,
|
dmabuf::DmaBuf,
|
||||||
|
|
@ -19,6 +24,7 @@ use {
|
||||||
},
|
},
|
||||||
ahash::AHashSet,
|
ahash::AHashSet,
|
||||||
indexmap::{indexset, IndexSet},
|
indexmap::{indexset, IndexSet},
|
||||||
|
jay_config::video::Transform,
|
||||||
once_cell::sync::Lazy,
|
once_cell::sync::Lazy,
|
||||||
std::{
|
std::{
|
||||||
cell::{Cell, RefCell},
|
cell::{Cell, RefCell},
|
||||||
|
|
@ -28,6 +34,28 @@ use {
|
||||||
thiserror::Error,
|
thiserror::Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub async fn perform_toplevel_screencasts(state: Rc<State>) {
|
||||||
|
loop {
|
||||||
|
let screencast = state.pending_toplevel_screencasts.pop().await;
|
||||||
|
screencast.perform_toplevel_screencast();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn perform_screencast_realloc(state: Rc<State>) {
|
||||||
|
loop {
|
||||||
|
let screencast = state.pending_toplevel_screencast_reallocs.pop().await;
|
||||||
|
screencast.realloc_scheduled.set(false);
|
||||||
|
match state.render_ctx.get() {
|
||||||
|
None => screencast.do_destroy(),
|
||||||
|
Some(ctx) => {
|
||||||
|
if let Err(e) = screencast.realloc(&ctx) {
|
||||||
|
screencast.client.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct JayScreencast {
|
pub struct JayScreencast {
|
||||||
pub id: JayScreencastId,
|
pub id: JayScreencastId,
|
||||||
pub client: Rc<Client>,
|
pub client: Rc<Client>,
|
||||||
|
|
@ -38,20 +66,35 @@ pub struct JayScreencast {
|
||||||
buffers_acked: Cell<bool>,
|
buffers_acked: Cell<bool>,
|
||||||
buffers: RefCell<Vec<ScreencastBuffer>>,
|
buffers: RefCell<Vec<ScreencastBuffer>>,
|
||||||
missed_frame: Cell<bool>,
|
missed_frame: Cell<bool>,
|
||||||
output: CloneCell<Option<Rc<OutputNode>>>,
|
target: CloneCell<Option<Target>>,
|
||||||
destroyed: Cell<bool>,
|
destroyed: Cell<bool>,
|
||||||
running: Cell<bool>,
|
running: Cell<bool>,
|
||||||
show_all: Cell<bool>,
|
show_all: Cell<bool>,
|
||||||
show_workspaces: RefCell<AHashSet<WorkspaceNodeId>>,
|
show_workspaces: RefCell<AHashSet<WorkspaceNodeId>>,
|
||||||
linear: Cell<bool>,
|
linear: Cell<bool>,
|
||||||
pending: Pending,
|
pending: Pending,
|
||||||
|
need_realloc: Cell<bool>,
|
||||||
|
realloc_scheduled: Cell<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
enum Target {
|
||||||
|
Output(Rc<OutputNode>),
|
||||||
|
Toplevel(Rc<dyn ToplevelNode>),
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl UnsafeCellCloneSafe for Target {}
|
||||||
|
|
||||||
|
enum PendingTarget {
|
||||||
|
Output(Rc<JayOutput>),
|
||||||
|
Toplevel(Rc<JayToplevel>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct Pending {
|
struct Pending {
|
||||||
linear: Cell<Option<bool>>,
|
linear: Cell<Option<bool>>,
|
||||||
running: Cell<Option<bool>>,
|
running: Cell<Option<bool>>,
|
||||||
output: Cell<Option<Option<Rc<JayOutput>>>>,
|
target: Cell<Option<Option<PendingTarget>>>,
|
||||||
show_all: Cell<Option<bool>>,
|
show_all: Cell<Option<bool>>,
|
||||||
show_workspaces: RefCell<Option<AHashSet<WorkspaceNodeId>>>,
|
show_workspaces: RefCell<Option<AHashSet<WorkspaceNodeId>>>,
|
||||||
}
|
}
|
||||||
|
|
@ -71,19 +114,80 @@ impl JayScreencast {
|
||||||
config_serial: Default::default(),
|
config_serial: Default::default(),
|
||||||
config_acked: Cell::new(true),
|
config_acked: Cell::new(true),
|
||||||
buffers_serial: Default::default(),
|
buffers_serial: Default::default(),
|
||||||
buffers_acked: Cell::new(false),
|
buffers_acked: Cell::new(true),
|
||||||
buffers: Default::default(),
|
buffers: Default::default(),
|
||||||
missed_frame: Cell::new(false),
|
missed_frame: Cell::new(false),
|
||||||
output: Default::default(),
|
target: Default::default(),
|
||||||
destroyed: Cell::new(false),
|
destroyed: Cell::new(false),
|
||||||
running: Cell::new(false),
|
running: Cell::new(false),
|
||||||
show_all: Cell::new(false),
|
show_all: Cell::new(false),
|
||||||
show_workspaces: Default::default(),
|
show_workspaces: Default::default(),
|
||||||
linear: Cell::new(false),
|
linear: Cell::new(false),
|
||||||
pending: Default::default(),
|
pending: Default::default(),
|
||||||
|
need_realloc: Cell::new(false),
|
||||||
|
realloc_scheduled: Cell::new(false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn schedule_toplevel_screencast(self: &Rc<Self>) {
|
||||||
|
if !self.running.get() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.client
|
||||||
|
.state
|
||||||
|
.pending_toplevel_screencasts
|
||||||
|
.push(self.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn perform_toplevel_screencast(&self) {
|
||||||
|
if self.destroyed.get() || !self.running.get() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let Some(target) = self.target.get() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Target::Toplevel(tl) = target else {
|
||||||
|
log::warn!("Tried to perform window screencast for output screencast");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let scale = match tl.tl_data().workspace.get() {
|
||||||
|
None => Scale::default(),
|
||||||
|
Some(w) => w.output.get().global.persistent.scale.get(),
|
||||||
|
};
|
||||||
|
let mut buffer = self.buffers.borrow_mut();
|
||||||
|
for (idx, buffer) in buffer.deref_mut().iter_mut().enumerate() {
|
||||||
|
if buffer.free {
|
||||||
|
let res = buffer.fb.render_node(
|
||||||
|
tl.tl_as_node(),
|
||||||
|
&self.client.state,
|
||||||
|
Some(tl.node_absolute_position()),
|
||||||
|
None,
|
||||||
|
scale,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
Transform::None,
|
||||||
|
);
|
||||||
|
match res {
|
||||||
|
Ok(_) => {
|
||||||
|
self.client.event(Ready {
|
||||||
|
self_id: self.id,
|
||||||
|
idx: idx as _,
|
||||||
|
});
|
||||||
|
buffer.free = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Could not perform window copy: {}", ErrorFmt(e));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.missed_frame.set(true);
|
||||||
|
self.client.event(MissedFrame { self_id: self.id })
|
||||||
|
}
|
||||||
|
|
||||||
fn send_buffers(&self) {
|
fn send_buffers(&self) {
|
||||||
self.buffers_acked.set(false);
|
self.buffers_acked.set(false);
|
||||||
let serial = self.buffers_serial.fetch_add(1) + 1;
|
let serial = self.buffers_serial.fetch_add(1) + 1;
|
||||||
|
|
@ -115,11 +219,13 @@ impl JayScreencast {
|
||||||
fn send_config(&self) {
|
fn send_config(&self) {
|
||||||
self.config_acked.set(false);
|
self.config_acked.set(false);
|
||||||
let serial = self.config_serial.fetch_add(1) + 1;
|
let serial = self.config_serial.fetch_add(1) + 1;
|
||||||
if let Some(output) = self.output.get() {
|
if let Some(target) = self.target.get() {
|
||||||
self.client.event(ConfigOutput {
|
if let Target::Output(output) = target {
|
||||||
self_id: self.id,
|
self.client.event(ConfigOutput {
|
||||||
linear_id: output.id.raw(),
|
self_id: self.id,
|
||||||
});
|
linear_id: output.id.raw(),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.client.event(ConfigAllowAllWorkspaces {
|
self.client.event(ConfigAllowAllWorkspaces {
|
||||||
self_id: self.id,
|
self_id: self.id,
|
||||||
|
|
@ -200,10 +306,21 @@ impl JayScreencast {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn detach(&self) {
|
fn detach(&self) {
|
||||||
if let Some(output) = self.output.take() {
|
if let Some(target) = self.target.take() {
|
||||||
output.screencasts.remove(&(self.client.id, self.id));
|
match target {
|
||||||
if output.screencasts.is_empty() {
|
Target::Output(output) => {
|
||||||
output.state.damage();
|
output.screencasts.remove(&(self.client.id, self.id));
|
||||||
|
if output.screencasts.is_empty() {
|
||||||
|
output.state.damage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Target::Toplevel(tl) => {
|
||||||
|
let data = tl.tl_data();
|
||||||
|
data.jay_screencasts.remove(&(self.client.id, self.id));
|
||||||
|
if data.jay_screencasts.is_empty() {
|
||||||
|
self.client.state.damage();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -214,17 +331,39 @@ impl JayScreencast {
|
||||||
self.client.event(Destroyed { self_id: self.id });
|
self.client.event(Destroyed { self_id: self.id });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn realloc(&self, ctx: &Rc<dyn GfxContext>) -> Result<(), JayScreencastError> {
|
pub fn schedule_realloc(self: &Rc<Self>) {
|
||||||
|
self.need_realloc.set(true);
|
||||||
|
if !self.realloc_scheduled.replace(true) {
|
||||||
|
self.client
|
||||||
|
.state
|
||||||
|
.pending_toplevel_screencast_reallocs
|
||||||
|
.push(self.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn realloc(&self, ctx: &Rc<dyn GfxContext>) -> Result<(), JayScreencastError> {
|
||||||
|
if !self.destroyed.get() && self.buffers_acked.get() {
|
||||||
|
self.do_realloc(ctx)
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn do_realloc(&self, ctx: &Rc<dyn GfxContext>) -> Result<(), JayScreencastError> {
|
||||||
|
self.need_realloc.set(false);
|
||||||
let mut buffers = vec![];
|
let mut buffers = vec![];
|
||||||
let formats = ctx.formats();
|
let formats = ctx.formats();
|
||||||
let format = match formats.get(&XRGB8888.drm) {
|
let format = match formats.get(&XRGB8888.drm) {
|
||||||
Some(f) => f,
|
Some(f) => f,
|
||||||
_ => return Err(JayScreencastError::XRGB8888),
|
_ => return Err(JayScreencastError::XRGB8888),
|
||||||
};
|
};
|
||||||
if let Some(output) = self.output.get() {
|
if let Some(target) = self.target.get() {
|
||||||
let (width, height) = output.global.pixel_size();
|
let (width, height) = target_size(Some(&target));
|
||||||
let num = 3;
|
let num = 3;
|
||||||
for _ in 0..num {
|
for _ in 0..num {
|
||||||
|
if width == 0 || height == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let mut usage = GBM_BO_USE_RENDERING;
|
let mut usage = GBM_BO_USE_RENDERING;
|
||||||
let modifiers = match self.linear.get() {
|
let modifiers = match self.linear.get() {
|
||||||
true if format.write_modifiers.contains(&LINEAR_MODIFIER) => {
|
true if format.write_modifiers.contains(&LINEAR_MODIFIER) => {
|
||||||
|
|
@ -267,8 +406,11 @@ impl JayScreencast {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn damage(&self) {
|
fn damage(&self) {
|
||||||
if let Some(output) = self.output.get() {
|
if let Some(target) = self.target.get() {
|
||||||
output.global.connector.connector.damage();
|
match target {
|
||||||
|
Target::Output(o) => o.global.connector.connector.damage(),
|
||||||
|
Target::Toplevel(_) => self.client.state.damage(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -284,14 +426,14 @@ impl JayScreencastRequestHandler for JayScreencast {
|
||||||
|
|
||||||
fn set_output(&self, req: SetOutput, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
fn set_output(&self, req: SetOutput, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||||
let output = if req.output.is_some() {
|
let output = if req.output.is_some() {
|
||||||
Some(self.client.lookup(req.output)?)
|
Some(PendingTarget::Output(self.client.lookup(req.output)?))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
if self.destroyed.get() || !self.config_acked.get() {
|
if self.destroyed.get() || !self.config_acked.get() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
self.pending.output.set(Some(output));
|
self.pending.target.set(Some(output));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -362,19 +504,42 @@ impl JayScreencastRequestHandler for JayScreencast {
|
||||||
|
|
||||||
let mut need_realloc = false;
|
let mut need_realloc = false;
|
||||||
|
|
||||||
if let Some(output) = self.pending.output.take() {
|
if let Some(target) = self.pending.target.take() {
|
||||||
let output = output.and_then(|o| o.output.get());
|
self.detach();
|
||||||
if output_size(&output) != output_size(&self.output.get()) {
|
let mut new_target = None;
|
||||||
|
if let Some(new) = target {
|
||||||
|
match new {
|
||||||
|
PendingTarget::Output(o) => {
|
||||||
|
let Some(o) = o.output.get() else {
|
||||||
|
self.do_destroy();
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
if o.screencasts.is_empty() {
|
||||||
|
o.state.damage();
|
||||||
|
}
|
||||||
|
o.screencasts.set((self.client.id, self.id), slf.clone());
|
||||||
|
new_target = Some(Target::Output(o));
|
||||||
|
}
|
||||||
|
PendingTarget::Toplevel(t) => {
|
||||||
|
if t.destroyed.get() {
|
||||||
|
self.do_destroy();
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let t = t.toplevel.clone();
|
||||||
|
let data = t.tl_data();
|
||||||
|
if data.jay_screencasts.is_empty() {
|
||||||
|
data.state.damage();
|
||||||
|
}
|
||||||
|
data.jay_screencasts
|
||||||
|
.set((self.client.id, self.id), slf.clone());
|
||||||
|
new_target = Some(Target::Toplevel(t));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if target_size(new_target.as_ref()) != target_size(self.target.get().as_ref()) {
|
||||||
need_realloc = true;
|
need_realloc = true;
|
||||||
}
|
}
|
||||||
self.detach();
|
self.target.set(new_target);
|
||||||
if let Some(new) = &output {
|
|
||||||
if new.screencasts.is_empty() {
|
|
||||||
new.state.damage();
|
|
||||||
}
|
|
||||||
new.screencasts.set((self.client.id, self.id), slf.clone());
|
|
||||||
}
|
|
||||||
self.output.set(output);
|
|
||||||
}
|
}
|
||||||
if let Some(linear) = self.pending.linear.take() {
|
if let Some(linear) = self.pending.linear.take() {
|
||||||
if self.linear.replace(linear) != linear {
|
if self.linear.replace(linear) != linear {
|
||||||
|
|
@ -392,29 +557,21 @@ impl JayScreencastRequestHandler for JayScreencast {
|
||||||
}
|
}
|
||||||
|
|
||||||
if need_realloc {
|
if need_realloc {
|
||||||
let ctx = match self.client.state.render_ctx.get() {
|
slf.schedule_realloc();
|
||||||
Some(ctx) => ctx,
|
|
||||||
_ => {
|
|
||||||
self.do_destroy();
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if let Err(e) = self.realloc(&ctx) {
|
|
||||||
log::error!("Could not allocate buffers: {}", ErrorFmt(e));
|
|
||||||
self.do_destroy();
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ack_buffers(&self, req: AckBuffers, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
fn ack_buffers(&self, req: AckBuffers, slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||||
if self.destroyed.get() {
|
if self.destroyed.get() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
if req.serial == self.buffers_serial.get() {
|
if req.serial == self.buffers_serial.get() {
|
||||||
self.buffers_acked.set(true);
|
self.buffers_acked.set(true);
|
||||||
|
if self.need_realloc.get() {
|
||||||
|
slf.schedule_realloc();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -443,6 +600,19 @@ impl JayScreencastRequestHandler for JayScreencast {
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_toplevel(&self, req: SetToplevel, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||||
|
let toplevel = if req.id.is_some() {
|
||||||
|
Some(PendingTarget::Toplevel(self.client.lookup(req.id)?))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
if self.destroyed.get() || !self.config_acked.get() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
self.pending.target.set(Some(toplevel));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object_base! {
|
object_base! {
|
||||||
|
|
@ -477,9 +647,19 @@ pub enum JayScreencastError {
|
||||||
}
|
}
|
||||||
efrom!(JayScreencastError, ClientError);
|
efrom!(JayScreencastError, ClientError);
|
||||||
|
|
||||||
fn output_size(output: &Option<Rc<OutputNode>>) -> (i32, i32) {
|
fn target_size(target: Option<&Target>) -> (i32, i32) {
|
||||||
match output {
|
if let Some(target) = target {
|
||||||
Some(o) => o.global.pixel_size(),
|
match target {
|
||||||
_ => (0, 0),
|
Target::Output(o) => return o.global.pixel_size(),
|
||||||
|
Target::Toplevel(t) => {
|
||||||
|
let data = t.tl_data();
|
||||||
|
let (dw, dh) = data.desired_extents.get().size();
|
||||||
|
if let Some(ws) = data.workspace.get() {
|
||||||
|
let scale = ws.output.get().global.persistent.scale.get();
|
||||||
|
return scale.pixel_size(dw, dh);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
(0, 0)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
100
src/ifs/jay_select_toplevel.rs
Normal file
100
src/ifs/jay_select_toplevel.rs
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
client::{Client, ClientError},
|
||||||
|
ifs::{jay_toplevel::JayToplevel, wl_seat::ToplevelSelector},
|
||||||
|
leaks::Tracker,
|
||||||
|
object::{Object, Version},
|
||||||
|
tree::ToplevelNode,
|
||||||
|
utils::clonecell::CloneCell,
|
||||||
|
wire::{jay_select_toplevel::*, JaySelectToplevelId, JayToplevelId},
|
||||||
|
},
|
||||||
|
std::{cell::Cell, rc::Rc},
|
||||||
|
thiserror::Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct JaySelectToplevel {
|
||||||
|
pub id: JaySelectToplevelId,
|
||||||
|
pub client: Rc<Client>,
|
||||||
|
pub tracker: Tracker<Self>,
|
||||||
|
pub destroyed: Cell<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct JayToplevelSelector {
|
||||||
|
pub tl: CloneCell<Option<Rc<dyn ToplevelNode>>>,
|
||||||
|
pub jst: Rc<JaySelectToplevel>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToplevelSelector for JayToplevelSelector {
|
||||||
|
fn set(&self, toplevel: Rc<dyn ToplevelNode>) {
|
||||||
|
self.tl.set(Some(toplevel));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for JayToplevelSelector {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if self.jst.destroyed.get() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let id = match self.tl.take() {
|
||||||
|
None => JayToplevelId::NONE,
|
||||||
|
Some(toplevel) => {
|
||||||
|
let id = match self.jst.client.new_id() {
|
||||||
|
Ok(id) => id,
|
||||||
|
Err(e) => {
|
||||||
|
self.jst.client.error(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let jtl = Rc::new(JayToplevel {
|
||||||
|
id,
|
||||||
|
client: self.jst.client.clone(),
|
||||||
|
tracker: Default::default(),
|
||||||
|
toplevel,
|
||||||
|
destroyed: Cell::new(false),
|
||||||
|
});
|
||||||
|
track!(self.jst.client, jtl);
|
||||||
|
self.jst.client.add_server_obj(&jtl);
|
||||||
|
jtl.toplevel
|
||||||
|
.tl_data()
|
||||||
|
.jay_toplevels
|
||||||
|
.set((jtl.client.id, jtl.id), jtl.clone());
|
||||||
|
jtl.id
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.jst.send_done(id);
|
||||||
|
let _ = self.jst.client.remove_obj(&*self.jst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JaySelectToplevel {
|
||||||
|
fn send_done(&self, id: JayToplevelId) {
|
||||||
|
self.client.event(Done {
|
||||||
|
self_id: self.id,
|
||||||
|
id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JaySelectToplevelRequestHandler for JaySelectToplevel {
|
||||||
|
type Error = JaySelectToplevelError;
|
||||||
|
}
|
||||||
|
|
||||||
|
object_base! {
|
||||||
|
self = JaySelectToplevel;
|
||||||
|
version = Version(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Object for JaySelectToplevel {
|
||||||
|
fn break_loops(&self) {
|
||||||
|
self.destroyed.set(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
simple_add_obj!(JaySelectToplevel);
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum JaySelectToplevelError {
|
||||||
|
#[error(transparent)]
|
||||||
|
ClientError(Box<ClientError>),
|
||||||
|
}
|
||||||
|
efrom!(JaySelectToplevelError, ClientError);
|
||||||
68
src/ifs/jay_toplevel.rs
Normal file
68
src/ifs/jay_toplevel.rs
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
client::{Client, ClientError},
|
||||||
|
leaks::Tracker,
|
||||||
|
object::{Object, Version},
|
||||||
|
tree::ToplevelNode,
|
||||||
|
wire::{jay_toplevel::*, JayToplevelId},
|
||||||
|
},
|
||||||
|
std::{cell::Cell, rc::Rc},
|
||||||
|
thiserror::Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct JayToplevel {
|
||||||
|
pub id: JayToplevelId,
|
||||||
|
pub client: Rc<Client>,
|
||||||
|
pub tracker: Tracker<Self>,
|
||||||
|
pub toplevel: Rc<dyn ToplevelNode>,
|
||||||
|
pub destroyed: Cell<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JayToplevel {
|
||||||
|
fn detach(&self) {
|
||||||
|
self.destroyed.set(true);
|
||||||
|
self.toplevel
|
||||||
|
.tl_data()
|
||||||
|
.jay_toplevels
|
||||||
|
.remove(&(self.client.id, self.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(&self) {
|
||||||
|
self.destroyed.set(true);
|
||||||
|
self.send_destroyed();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_destroyed(&self) {
|
||||||
|
self.client.event(Destroyed { self_id: self.id });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JayToplevelRequestHandler for JayToplevel {
|
||||||
|
type Error = JayToplevelError;
|
||||||
|
|
||||||
|
fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||||
|
self.detach();
|
||||||
|
self.client.remove_obj(self)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object_base! {
|
||||||
|
self = JayToplevel;
|
||||||
|
version = Version(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Object for JayToplevel {
|
||||||
|
fn break_loops(&self) {
|
||||||
|
self.detach();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dedicated_add_obj!(JayToplevel, JayToplevelId, jay_toplevels);
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum JayToplevelError {
|
||||||
|
#[error(transparent)]
|
||||||
|
ClientError(Box<ClientError>),
|
||||||
|
}
|
||||||
|
efrom!(JayToplevelError, ClientError);
|
||||||
|
|
@ -11,7 +11,6 @@ pub mod zwp_relative_pointer_v1;
|
||||||
pub mod zwp_virtual_keyboard_manager_v1;
|
pub mod zwp_virtual_keyboard_manager_v1;
|
||||||
pub mod zwp_virtual_keyboard_v1;
|
pub mod zwp_virtual_keyboard_v1;
|
||||||
|
|
||||||
pub use event_handling::NodeSeatState;
|
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
async_engine::SpawnedFuture,
|
async_engine::SpawnedFuture,
|
||||||
|
|
@ -84,6 +83,7 @@ use {
|
||||||
thiserror::Error,
|
thiserror::Error,
|
||||||
uapi::OwnedFd,
|
uapi::OwnedFd,
|
||||||
};
|
};
|
||||||
|
pub use {event_handling::NodeSeatState, pointer_owner::ToplevelSelector};
|
||||||
|
|
||||||
pub const POINTER: u32 = 1;
|
pub const POINTER: u32 = 1;
|
||||||
const KEYBOARD: u32 = 2;
|
const KEYBOARD: u32 = 2;
|
||||||
|
|
@ -94,6 +94,7 @@ const TOUCH: u32 = 4;
|
||||||
const MISSING_CAPABILITY: u32 = 0;
|
const MISSING_CAPABILITY: u32 = 0;
|
||||||
|
|
||||||
pub const BTN_LEFT: u32 = 0x110;
|
pub const BTN_LEFT: u32 = 0x110;
|
||||||
|
pub const BTN_RIGHT: u32 = 0x111;
|
||||||
|
|
||||||
pub const SEAT_NAME_SINCE: Version = Version(2);
|
pub const SEAT_NAME_SINCE: Version = Version(2);
|
||||||
|
|
||||||
|
|
@ -1151,6 +1152,11 @@ impl WlSeatGlobal {
|
||||||
pub fn set_forward(&self, forward: bool) {
|
pub fn set_forward(&self, forward: bool) {
|
||||||
self.forward.set(forward);
|
self.forward.set(forward);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn select_toplevel(self: &Rc<Self>, selector: impl ToplevelSelector) {
|
||||||
|
self.pointer_owner.select_toplevel(self, selector);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
global_base!(WlSeatGlobal, WlSeat, WlSeatError);
|
global_base!(WlSeatGlobal, WlSeat, WlSeatError);
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ use {
|
||||||
isnt::std_1::primitive::{IsntSlice2Ext, IsntSliceExt},
|
isnt::std_1::primitive::{IsntSlice2Ext, IsntSliceExt},
|
||||||
jay_config::keyboard::{
|
jay_config::keyboard::{
|
||||||
mods::{Modifiers, CAPS, NUM, RELEASE},
|
mods::{Modifiers, CAPS, NUM, RELEASE},
|
||||||
syms::KeySym,
|
syms::{KeySym, SYM_Escape},
|
||||||
},
|
},
|
||||||
smallvec::SmallVec,
|
smallvec::SmallVec,
|
||||||
std::{cell::RefCell, collections::hash_map::Entry, rc::Rc},
|
std::{cell::RefCell, collections::hash_map::Entry, rc::Rc},
|
||||||
|
|
@ -346,7 +346,7 @@ impl WlSeatGlobal {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn key_event<F>(
|
pub(super) fn key_event<F>(
|
||||||
&self,
|
self: &Rc<Self>,
|
||||||
time_usec: u64,
|
time_usec: u64,
|
||||||
key: u32,
|
key: u32,
|
||||||
key_state: KeyState,
|
key_state: KeyState,
|
||||||
|
|
@ -375,14 +375,17 @@ impl WlSeatGlobal {
|
||||||
let mut shortcuts = SmallVec::<[_; 1]>::new();
|
let mut shortcuts = SmallVec::<[_; 1]>::new();
|
||||||
let new_mods;
|
let new_mods;
|
||||||
{
|
{
|
||||||
if !self.state.lock.locked.get() {
|
let mut mods = xkb_state.mods().mods_effective & !(CAPS.0 | NUM.0);
|
||||||
let mut mods = xkb_state.mods().mods_effective & !(CAPS.0 | NUM.0);
|
if state == wl_keyboard::RELEASED {
|
||||||
if state == wl_keyboard::RELEASED {
|
mods |= RELEASE.0;
|
||||||
mods |= RELEASE.0;
|
}
|
||||||
|
let scs = &*self.shortcuts.borrow();
|
||||||
|
let keysyms = xkb_state.unmodified_keysyms(key);
|
||||||
|
for &sym in keysyms {
|
||||||
|
if sym == SYM_Escape.0 && mods == 0 {
|
||||||
|
self.pointer_owner.revert_to_default(self);
|
||||||
}
|
}
|
||||||
let scs = &*self.shortcuts.borrow();
|
if !self.state.lock.locked.get() {
|
||||||
let keysyms = xkb_state.unmodified_keysyms(key);
|
|
||||||
for &sym in keysyms {
|
|
||||||
if let Some(key_mods) = scs.get(&sym) {
|
if let Some(key_mods) = scs.get(&sym) {
|
||||||
for (key_mods, mask) in key_mods {
|
for (key_mods, mask) in key_mods {
|
||||||
if mods & mask == key_mods {
|
if mods & mask == key_mods {
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,46 @@
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
backend::{AxisSource, KeyState, ScrollAxis, AXIS_120},
|
backend::{AxisSource, KeyState, ScrollAxis, AXIS_120},
|
||||||
|
cursor::KnownCursor,
|
||||||
fixed::Fixed,
|
fixed::Fixed,
|
||||||
ifs::{
|
ifs::{
|
||||||
ipc,
|
ipc,
|
||||||
ipc::wl_data_source::WlDataSource,
|
ipc::wl_data_source::WlDataSource,
|
||||||
wl_seat::{
|
wl_seat::{
|
||||||
wl_pointer::PendingScroll, Dnd, DroppedDnd, WlSeatError, WlSeatGlobal,
|
wl_pointer::PendingScroll, Dnd, DroppedDnd, WlSeatError, WlSeatGlobal, BTN_LEFT,
|
||||||
CHANGE_CURSOR_MOVED,
|
BTN_RIGHT, CHANGE_CURSOR_MOVED,
|
||||||
},
|
},
|
||||||
wl_surface::WlSurface,
|
wl_surface::WlSurface,
|
||||||
xdg_toplevel_drag_v1::XdgToplevelDragV1,
|
xdg_toplevel_drag_v1::XdgToplevelDragV1,
|
||||||
},
|
},
|
||||||
state::DeviceHandlerData,
|
state::DeviceHandlerData,
|
||||||
tree::{FoundNode, Node},
|
tree::{FindTreeUsecase, FoundNode, Node, ToplevelNode},
|
||||||
utils::{clonecell::CloneCell, smallmap::SmallMap},
|
utils::{clonecell::CloneCell, smallmap::SmallMap},
|
||||||
},
|
},
|
||||||
std::{cell::Cell, rc::Rc},
|
std::{
|
||||||
|
cell::Cell,
|
||||||
|
rc::{Rc, Weak},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct PointerOwnerHolder {
|
pub struct PointerOwnerHolder {
|
||||||
default: Rc<DefaultPointerOwner>,
|
default: Rc<SimplePointerOwner<DefaultPointerUsecase>>,
|
||||||
owner: CloneCell<Rc<dyn PointerOwner>>,
|
owner: CloneCell<Rc<dyn PointerOwner>>,
|
||||||
pending_scroll: PendingScroll,
|
pending_scroll: PendingScroll,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait ToplevelSelector: 'static {
|
||||||
|
fn set(&self, toplevel: Rc<dyn ToplevelNode>);
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for PointerOwnerHolder {
|
impl Default for PointerOwnerHolder {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
let default = Rc::new(SimplePointerOwner {
|
||||||
|
usecase: DefaultPointerUsecase,
|
||||||
|
});
|
||||||
Self {
|
Self {
|
||||||
default: Rc::new(DefaultPointerOwner),
|
default: default.clone(),
|
||||||
owner: CloneCell::new(Rc::new(DefaultPointerOwner)),
|
owner: CloneCell::new(default.clone()),
|
||||||
pending_scroll: Default::default(),
|
pending_scroll: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -145,6 +156,20 @@ impl PointerOwnerHolder {
|
||||||
seat.pointer_owner.owner.set(self.default.clone());
|
seat.pointer_owner.owner.set(self.default.clone());
|
||||||
seat.changes.or_assign(CHANGE_CURSOR_MOVED);
|
seat.changes.or_assign(CHANGE_CURSOR_MOVED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn select_toplevel(&self, seat: &Rc<WlSeatGlobal>, selector: impl ToplevelSelector) {
|
||||||
|
self.revert_to_default(seat);
|
||||||
|
let usecase = Rc::new(SelectToplevelUsecase {
|
||||||
|
seat: Rc::downgrade(seat),
|
||||||
|
selector,
|
||||||
|
latest: Default::default(),
|
||||||
|
});
|
||||||
|
if let Some(node) = seat.pointer_stack.borrow().last() {
|
||||||
|
usecase.node_focus(seat, node);
|
||||||
|
}
|
||||||
|
self.owner.set(Rc::new(SimplePointerOwner { usecase }));
|
||||||
|
seat.trigger_tree_changed();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trait PointerOwner {
|
trait PointerOwner {
|
||||||
|
|
@ -167,9 +192,12 @@ trait PointerOwner {
|
||||||
fn remove_dnd_icon(&self);
|
fn remove_dnd_icon(&self);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DefaultPointerOwner;
|
struct SimplePointerOwner<T> {
|
||||||
|
usecase: T,
|
||||||
|
}
|
||||||
|
|
||||||
struct GrabPointerOwner {
|
struct SimpleGrabPointerOwner<T> {
|
||||||
|
usecase: T,
|
||||||
buttons: SmallMap<u32, (), 1>,
|
buttons: SmallMap<u32, (), 1>,
|
||||||
node: Rc<dyn Node>,
|
node: Rc<dyn Node>,
|
||||||
serial: u32,
|
serial: u32,
|
||||||
|
|
@ -184,7 +212,16 @@ struct DndPointerOwner {
|
||||||
pos_y: Cell<Fixed>,
|
pos_y: Cell<Fixed>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PointerOwner for DefaultPointerOwner {
|
#[derive(Copy, Clone)]
|
||||||
|
struct DefaultPointerUsecase;
|
||||||
|
|
||||||
|
struct SelectToplevelUsecase<S: ?Sized> {
|
||||||
|
seat: Weak<WlSeatGlobal>,
|
||||||
|
latest: CloneCell<Option<Rc<dyn ToplevelNode>>>,
|
||||||
|
selector: S,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: SimplePointerOwnerUsecase> PointerOwner for SimplePointerOwner<T> {
|
||||||
fn button(&self, seat: &Rc<WlSeatGlobal>, time_usec: u64, button: u32, state: KeyState) {
|
fn button(&self, seat: &Rc<WlSeatGlobal>, time_usec: u64, button: u32, state: KeyState) {
|
||||||
if state != KeyState::Pressed {
|
if state != KeyState::Pressed {
|
||||||
return;
|
return;
|
||||||
|
|
@ -193,12 +230,18 @@ impl PointerOwner for DefaultPointerOwner {
|
||||||
Some(n) => n,
|
Some(n) => n,
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
|
if self.usecase.default_button(self, seat, button, &pn) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let serial = seat.state.next_serial(pn.node_client().as_deref());
|
let serial = seat.state.next_serial(pn.node_client().as_deref());
|
||||||
seat.pointer_owner.owner.set(Rc::new(GrabPointerOwner {
|
seat.pointer_owner
|
||||||
buttons: SmallMap::new_with(button, ()),
|
.owner
|
||||||
node: pn.clone(),
|
.set(Rc::new(SimpleGrabPointerOwner {
|
||||||
serial,
|
usecase: self.usecase.clone(),
|
||||||
}));
|
buttons: SmallMap::new_with(button, ()),
|
||||||
|
node: pn.clone(),
|
||||||
|
serial,
|
||||||
|
}));
|
||||||
pn.node_seat_state().add_pointer_grab(seat);
|
pn.node_seat_state().add_pointer_grab(seat);
|
||||||
pn.node_on_button(seat, time_usec, button, state, serial);
|
pn.node_on_button(seat, time_usec, button, state, serial);
|
||||||
}
|
}
|
||||||
|
|
@ -220,7 +263,7 @@ impl PointerOwner for DefaultPointerOwner {
|
||||||
});
|
});
|
||||||
seat.state
|
seat.state
|
||||||
.root
|
.root
|
||||||
.node_find_tree_at(x_int, y_int, &mut found_tree);
|
.node_find_tree_at(x_int, y_int, &mut found_tree, T::FIND_TREE_USECASE);
|
||||||
let mut divergence = found_tree.len().min(stack.len());
|
let mut divergence = found_tree.len().min(stack.len());
|
||||||
for (i, (found, stack)) in found_tree.iter().zip(stack.iter()).enumerate() {
|
for (i, (found, stack)) in found_tree.iter().zip(stack.iter()).enumerate() {
|
||||||
if found.node.node_id() != stack.node_id() {
|
if found.node.node_id() != stack.node_id() {
|
||||||
|
|
@ -266,6 +309,7 @@ impl PointerOwner for DefaultPointerOwner {
|
||||||
}
|
}
|
||||||
if let Some(node) = stack.last() {
|
if let Some(node) = stack.last() {
|
||||||
node.node_on_pointer_focus(seat);
|
node.node_on_pointer_focus(seat);
|
||||||
|
self.usecase.node_focus(seat, node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
found_tree.clear();
|
found_tree.clear();
|
||||||
|
|
@ -289,8 +333,12 @@ impl PointerOwner for DefaultPointerOwner {
|
||||||
seat.dropped_dnd.borrow_mut().take();
|
seat.dropped_dnd.borrow_mut().take();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn revert_to_default(&self, _seat: &Rc<WlSeatGlobal>) {
|
fn revert_to_default(&self, seat: &Rc<WlSeatGlobal>) {
|
||||||
// nothing
|
if !T::IS_DEFAULT {
|
||||||
|
seat.pointer_owner.set_default_pointer_owner(seat);
|
||||||
|
seat.trigger_tree_changed();
|
||||||
|
seat.state.damage();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dnd_target_removed(&self, seat: &Rc<WlSeatGlobal>) {
|
fn dnd_target_removed(&self, seat: &Rc<WlSeatGlobal>) {
|
||||||
|
|
@ -310,7 +358,7 @@ impl PointerOwner for DefaultPointerOwner {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PointerOwner for GrabPointerOwner {
|
impl<T: SimplePointerOwnerUsecase> PointerOwner for SimpleGrabPointerOwner<T> {
|
||||||
fn button(&self, seat: &Rc<WlSeatGlobal>, time_usec: u64, button: u32, state: KeyState) {
|
fn button(&self, seat: &Rc<WlSeatGlobal>, time_usec: u64, button: u32, state: KeyState) {
|
||||||
match state {
|
match state {
|
||||||
KeyState::Released => {
|
KeyState::Released => {
|
||||||
|
|
@ -318,7 +366,7 @@ impl PointerOwner for GrabPointerOwner {
|
||||||
if self.buttons.is_empty() {
|
if self.buttons.is_empty() {
|
||||||
self.node.node_seat_state().remove_pointer_grab(seat);
|
self.node.node_seat_state().remove_pointer_grab(seat);
|
||||||
// log::info!("button");
|
// log::info!("button");
|
||||||
seat.pointer_owner.set_default_pointer_owner(seat);
|
self.usecase.release_grab(seat);
|
||||||
seat.tree_changed.trigger();
|
seat.tree_changed.trigger();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -354,57 +402,8 @@ impl PointerOwner for GrabPointerOwner {
|
||||||
icon: Option<Rc<WlSurface>>,
|
icon: Option<Rc<WlSurface>>,
|
||||||
serial: u32,
|
serial: u32,
|
||||||
) -> Result<(), WlSeatError> {
|
) -> Result<(), WlSeatError> {
|
||||||
let button = match self.buttons.iter().next() {
|
self.usecase
|
||||||
Some((b, _)) => b,
|
.start_drag(self, seat, origin, src, icon, serial)
|
||||||
None => return Ok(()),
|
|
||||||
};
|
|
||||||
if self.buttons.len() != 1 {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
if serial != self.serial {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
if self.node.node_id() != origin.node_id {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
if let Some(icon) = &icon {
|
|
||||||
icon.set_dnd_icon_seat(seat.id, Some(seat));
|
|
||||||
}
|
|
||||||
if let Some(new) = &src {
|
|
||||||
ipc::attach_seat(&**new, seat, ipc::Role::Dnd)?;
|
|
||||||
if let Some(drag) = new.toplevel_drag.get() {
|
|
||||||
drag.start_drag();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*seat.dropped_dnd.borrow_mut() = None;
|
|
||||||
let pointer_owner = Rc::new(DndPointerOwner {
|
|
||||||
button,
|
|
||||||
dnd: Dnd {
|
|
||||||
seat: seat.clone(),
|
|
||||||
client: origin.client.clone(),
|
|
||||||
src,
|
|
||||||
},
|
|
||||||
target: CloneCell::new(seat.state.root.clone()),
|
|
||||||
icon: CloneCell::new(icon),
|
|
||||||
pos_x: Cell::new(Fixed::from_int(0)),
|
|
||||||
pos_y: Cell::new(Fixed::from_int(0)),
|
|
||||||
});
|
|
||||||
{
|
|
||||||
let mut stack = seat.pointer_stack.borrow_mut();
|
|
||||||
for node in stack.drain(1..).rev() {
|
|
||||||
node.node_on_leave(seat);
|
|
||||||
node.node_seat_state().leave(seat);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.node.node_seat_state().remove_pointer_grab(seat);
|
|
||||||
// {
|
|
||||||
// let old = seat.keyboard_node.set(seat.state.root.clone());
|
|
||||||
// old.seat_state().unfocus(seat);
|
|
||||||
// old.unfocus(seat);
|
|
||||||
// }
|
|
||||||
seat.pointer_owner.owner.set(pointer_owner.clone());
|
|
||||||
pointer_owner.apply_changes(seat);
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cancel_dnd(&self, seat: &Rc<WlSeatGlobal>) {
|
fn cancel_dnd(&self, seat: &Rc<WlSeatGlobal>) {
|
||||||
|
|
@ -482,7 +481,7 @@ impl PointerOwner for DndPointerOwner {
|
||||||
});
|
});
|
||||||
seat.state
|
seat.state
|
||||||
.root
|
.root
|
||||||
.node_find_tree_at(x_int, y_int, &mut found_tree);
|
.node_find_tree_at(x_int, y_int, &mut found_tree, FindTreeUsecase::None);
|
||||||
let FoundNode { node, x, y } = found_tree.pop().unwrap();
|
let FoundNode { node, x, y } = found_tree.pop().unwrap();
|
||||||
found_tree.clear();
|
found_tree.clear();
|
||||||
(node, x, y)
|
(node, x, y)
|
||||||
|
|
@ -562,3 +561,192 @@ impl PointerOwner for DndPointerOwner {
|
||||||
self.icon.set(None);
|
self.icon.set(None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trait SimplePointerOwnerUsecase: Sized + Clone + 'static {
|
||||||
|
const FIND_TREE_USECASE: FindTreeUsecase;
|
||||||
|
const IS_DEFAULT: bool;
|
||||||
|
|
||||||
|
fn default_button(
|
||||||
|
&self,
|
||||||
|
spo: &SimplePointerOwner<Self>,
|
||||||
|
seat: &Rc<WlSeatGlobal>,
|
||||||
|
button: u32,
|
||||||
|
pn: &Rc<dyn Node>,
|
||||||
|
) -> bool;
|
||||||
|
|
||||||
|
fn start_drag(
|
||||||
|
&self,
|
||||||
|
grab: &SimpleGrabPointerOwner<Self>,
|
||||||
|
seat: &Rc<WlSeatGlobal>,
|
||||||
|
origin: &Rc<WlSurface>,
|
||||||
|
src: Option<Rc<WlDataSource>>,
|
||||||
|
icon: Option<Rc<WlSurface>>,
|
||||||
|
serial: u32,
|
||||||
|
) -> Result<(), WlSeatError>;
|
||||||
|
|
||||||
|
fn release_grab(&self, seat: &Rc<WlSeatGlobal>);
|
||||||
|
|
||||||
|
fn node_focus(&self, seat: &Rc<WlSeatGlobal>, node: &Rc<dyn Node>);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SimplePointerOwnerUsecase for DefaultPointerUsecase {
|
||||||
|
const FIND_TREE_USECASE: FindTreeUsecase = FindTreeUsecase::None;
|
||||||
|
const IS_DEFAULT: bool = true;
|
||||||
|
|
||||||
|
fn default_button(
|
||||||
|
&self,
|
||||||
|
_spo: &SimplePointerOwner<Self>,
|
||||||
|
_seat: &Rc<WlSeatGlobal>,
|
||||||
|
_button: u32,
|
||||||
|
_pn: &Rc<dyn Node>,
|
||||||
|
) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_drag(
|
||||||
|
&self,
|
||||||
|
grab: &SimpleGrabPointerOwner<Self>,
|
||||||
|
seat: &Rc<WlSeatGlobal>,
|
||||||
|
origin: &Rc<WlSurface>,
|
||||||
|
src: Option<Rc<WlDataSource>>,
|
||||||
|
icon: Option<Rc<WlSurface>>,
|
||||||
|
serial: u32,
|
||||||
|
) -> Result<(), WlSeatError> {
|
||||||
|
let button = match grab.buttons.iter().next() {
|
||||||
|
Some((b, _)) => b,
|
||||||
|
None => return Ok(()),
|
||||||
|
};
|
||||||
|
if grab.buttons.len() != 1 {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
if serial != grab.serial {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
if grab.node.node_id() != origin.node_id {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
if let Some(icon) = &icon {
|
||||||
|
icon.set_dnd_icon_seat(seat.id, Some(seat));
|
||||||
|
}
|
||||||
|
if let Some(new) = &src {
|
||||||
|
ipc::attach_seat(&**new, seat, ipc::Role::Dnd)?;
|
||||||
|
if let Some(drag) = new.toplevel_drag.get() {
|
||||||
|
drag.start_drag();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*seat.dropped_dnd.borrow_mut() = None;
|
||||||
|
let pointer_owner = Rc::new(DndPointerOwner {
|
||||||
|
button,
|
||||||
|
dnd: Dnd {
|
||||||
|
seat: seat.clone(),
|
||||||
|
client: origin.client.clone(),
|
||||||
|
src,
|
||||||
|
},
|
||||||
|
target: CloneCell::new(seat.state.root.clone()),
|
||||||
|
icon: CloneCell::new(icon),
|
||||||
|
pos_x: Cell::new(Fixed::from_int(0)),
|
||||||
|
pos_y: Cell::new(Fixed::from_int(0)),
|
||||||
|
});
|
||||||
|
{
|
||||||
|
let mut stack = seat.pointer_stack.borrow_mut();
|
||||||
|
for node in stack.drain(1..).rev() {
|
||||||
|
node.node_on_leave(seat);
|
||||||
|
node.node_seat_state().leave(seat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
grab.node.node_seat_state().remove_pointer_grab(seat);
|
||||||
|
// {
|
||||||
|
// let old = seat.keyboard_node.set(seat.state.root.clone());
|
||||||
|
// old.seat_state().unfocus(seat);
|
||||||
|
// old.unfocus(seat);
|
||||||
|
// }
|
||||||
|
seat.pointer_owner.owner.set(pointer_owner.clone());
|
||||||
|
pointer_owner.apply_changes(seat);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn release_grab(&self, seat: &Rc<WlSeatGlobal>) {
|
||||||
|
seat.pointer_owner.set_default_pointer_owner(seat);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn node_focus(&self, _seat: &Rc<WlSeatGlobal>, _node: &Rc<dyn Node>) {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: ToplevelSelector + ?Sized> SimplePointerOwnerUsecase for Rc<SelectToplevelUsecase<S>> {
|
||||||
|
const FIND_TREE_USECASE: FindTreeUsecase = FindTreeUsecase::SelectToplevel;
|
||||||
|
const IS_DEFAULT: bool = false;
|
||||||
|
|
||||||
|
fn default_button(
|
||||||
|
&self,
|
||||||
|
spo: &SimplePointerOwner<Self>,
|
||||||
|
seat: &Rc<WlSeatGlobal>,
|
||||||
|
button: u32,
|
||||||
|
pn: &Rc<dyn Node>,
|
||||||
|
) -> bool {
|
||||||
|
let Some(tl) = pn.clone().node_into_toplevel() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let selected_toplevel =
|
||||||
|
button == BTN_RIGHT || (button == BTN_LEFT && !tl.tl_admits_children());
|
||||||
|
if !selected_toplevel {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
self.selector.set(tl);
|
||||||
|
spo.revert_to_default(seat);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_drag(
|
||||||
|
&self,
|
||||||
|
_grab: &SimpleGrabPointerOwner<Self>,
|
||||||
|
seat: &Rc<WlSeatGlobal>,
|
||||||
|
_origin: &Rc<WlSurface>,
|
||||||
|
src: Option<Rc<WlDataSource>>,
|
||||||
|
_icon: Option<Rc<WlSurface>>,
|
||||||
|
_serial: u32,
|
||||||
|
) -> Result<(), WlSeatError> {
|
||||||
|
if let Some(src) = src {
|
||||||
|
src.send_cancelled(seat);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn release_grab(&self, seat: &Rc<WlSeatGlobal>) {
|
||||||
|
seat.pointer_owner.owner.set(Rc::new(SimplePointerOwner {
|
||||||
|
usecase: self.clone(),
|
||||||
|
}));
|
||||||
|
seat.changes.or_assign(CHANGE_CURSOR_MOVED);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn node_focus(&self, seat: &Rc<WlSeatGlobal>, node: &Rc<dyn Node>) {
|
||||||
|
let mut damage = false;
|
||||||
|
let tl = node.clone().node_into_toplevel();
|
||||||
|
if let Some(tl) = &tl {
|
||||||
|
tl.tl_data().render_highlight.fetch_add(1);
|
||||||
|
if !tl.tl_admits_children() {
|
||||||
|
seat.set_known_cursor(KnownCursor::Pointer);
|
||||||
|
}
|
||||||
|
damage = true;
|
||||||
|
}
|
||||||
|
if let Some(prev) = self.latest.set(tl) {
|
||||||
|
prev.tl_data().render_highlight.fetch_sub(1);
|
||||||
|
damage = true;
|
||||||
|
}
|
||||||
|
if damage {
|
||||||
|
seat.state.damage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: ?Sized> Drop for SelectToplevelUsecase<S> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Some(prev) = self.latest.take() {
|
||||||
|
prev.tl_data().render_highlight.fetch_sub(1);
|
||||||
|
if let Some(seat) = self.seat.upgrade() {
|
||||||
|
seat.state.damage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,8 +38,8 @@ use {
|
||||||
wp_linux_drm_syncobj_surface_v1::WpLinuxDrmSyncobjSurfaceV1,
|
wp_linux_drm_syncobj_surface_v1::WpLinuxDrmSyncobjSurfaceV1,
|
||||||
wp_tearing_control_v1::WpTearingControlV1,
|
wp_tearing_control_v1::WpTearingControlV1,
|
||||||
wp_viewport::WpViewport,
|
wp_viewport::WpViewport,
|
||||||
x_surface::XSurface,
|
x_surface::{xwindow::Xwindow, XSurface},
|
||||||
xdg_surface::{PendingXdgSurfaceData, XdgSurfaceError},
|
xdg_surface::{xdg_toplevel::XdgToplevel, PendingXdgSurfaceData, XdgSurfaceError},
|
||||||
zwlr_layer_surface_v1::{PendingLayerSurfaceData, ZwlrLayerSurfaceV1Error},
|
zwlr_layer_surface_v1::{PendingLayerSurfaceData, ZwlrLayerSurfaceV1Error},
|
||||||
},
|
},
|
||||||
wp_content_type_v1::ContentType,
|
wp_content_type_v1::ContentType,
|
||||||
|
|
@ -51,8 +51,8 @@ use {
|
||||||
rect::{Rect, Region},
|
rect::{Rect, Region},
|
||||||
renderer::Renderer,
|
renderer::Renderer,
|
||||||
tree::{
|
tree::{
|
||||||
FindTreeResult, FoundNode, Node, NodeId, NodeVisitor, NodeVisitorBase, OutputNode,
|
ContainerNode, FindTreeResult, FoundNode, Node, NodeId, NodeVisitor, NodeVisitorBase,
|
||||||
ToplevelNode,
|
OutputNode, PlaceholderNode, ToplevelNode,
|
||||||
},
|
},
|
||||||
utils::{
|
utils::{
|
||||||
cell_ext::CellExt, clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt,
|
cell_ext::CellExt, clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt,
|
||||||
|
|
@ -126,11 +126,40 @@ impl SurfaceRole {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SurfaceSendPreferredScaleVisitor;
|
pub struct SurfaceSendPreferredScaleVisitor;
|
||||||
|
|
||||||
|
impl SurfaceSendPreferredScaleVisitor {
|
||||||
|
fn schedule_realloc(&self, tl: &impl ToplevelNode) {
|
||||||
|
for sc in tl.tl_data().jay_screencasts.lock().values() {
|
||||||
|
sc.schedule_realloc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl NodeVisitorBase for SurfaceSendPreferredScaleVisitor {
|
impl NodeVisitorBase for SurfaceSendPreferredScaleVisitor {
|
||||||
fn visit_surface(&mut self, node: &Rc<WlSurface>) {
|
fn visit_surface(&mut self, node: &Rc<WlSurface>) {
|
||||||
node.on_scale_change();
|
node.on_scale_change();
|
||||||
node.node_visit_children(self);
|
node.node_visit_children(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn visit_toplevel(&mut self, node: &Rc<XdgToplevel>) {
|
||||||
|
self.schedule_realloc(&**node);
|
||||||
|
node.node_visit_children(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_xwindow(&mut self, node: &Rc<Xwindow>) {
|
||||||
|
self.schedule_realloc(&**node);
|
||||||
|
node.node_visit_children(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_container(&mut self, node: &Rc<ContainerNode>) {
|
||||||
|
self.schedule_realloc(&**node);
|
||||||
|
node.node_visit_children(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_placeholder(&mut self, node: &Rc<PlaceholderNode>) {
|
||||||
|
self.schedule_realloc(&**node);
|
||||||
|
node.node_visit_children(self);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SurfaceSendPreferredTransformVisitor;
|
pub struct SurfaceSendPreferredTransformVisitor;
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use {
|
||||||
leaks::Tracker,
|
leaks::Tracker,
|
||||||
object::{Object, Version},
|
object::{Object, Version},
|
||||||
rect::Rect,
|
rect::Rect,
|
||||||
tree::{FindTreeResult, FoundNode, Node, NodeId, NodeVisitor, OutputNode},
|
tree::{FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeVisitor, OutputNode},
|
||||||
utils::numcell::NumCell,
|
utils::numcell::NumCell,
|
||||||
wire::{ext_session_lock_surface_v1::*, ExtSessionLockSurfaceV1Id, WlSurfaceId},
|
wire::{ext_session_lock_surface_v1::*, ExtSessionLockSurfaceV1Id, WlSurfaceId},
|
||||||
},
|
},
|
||||||
|
|
@ -122,7 +122,13 @@ impl Node for ExtSessionLockSurfaceV1 {
|
||||||
self.surface.node_absolute_position()
|
self.surface.node_absolute_position()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node_find_tree_at(&self, x: i32, y: i32, tree: &mut Vec<FoundNode>) -> FindTreeResult {
|
fn node_find_tree_at(
|
||||||
|
&self,
|
||||||
|
x: i32,
|
||||||
|
y: i32,
|
||||||
|
tree: &mut Vec<FoundNode>,
|
||||||
|
_usecase: FindTreeUsecase,
|
||||||
|
) -> FindTreeResult {
|
||||||
self.surface.find_tree_at_(x, y, tree)
|
self.surface.find_tree_at_(x, y, tree)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,8 @@ use {
|
||||||
renderer::Renderer,
|
renderer::Renderer,
|
||||||
state::State,
|
state::State,
|
||||||
tree::{
|
tree::{
|
||||||
Direction, FindTreeResult, FoundNode, Node, NodeId, NodeVisitor, StackedNode,
|
Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeVisitor,
|
||||||
ToplevelData, ToplevelNode, ToplevelNodeBase, WorkspaceNode,
|
StackedNode, ToplevelData, ToplevelNode, ToplevelNodeBase, WorkspaceNode,
|
||||||
},
|
},
|
||||||
utils::{clonecell::CloneCell, copyhashmap::CopyHashMap, linkedlist::LinkedNode},
|
utils::{clonecell::CloneCell, copyhashmap::CopyHashMap, linkedlist::LinkedNode},
|
||||||
wire::WlSurfaceId,
|
wire::WlSurfaceId,
|
||||||
|
|
@ -326,7 +326,16 @@ impl Node for Xwindow {
|
||||||
self.toplevel_data.update_self_active(self, active);
|
self.toplevel_data.update_self_active(self, active);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node_find_tree_at(&self, x: i32, y: i32, tree: &mut Vec<FoundNode>) -> FindTreeResult {
|
fn node_find_tree_at(
|
||||||
|
&self,
|
||||||
|
x: i32,
|
||||||
|
y: i32,
|
||||||
|
tree: &mut Vec<FoundNode>,
|
||||||
|
usecase: FindTreeUsecase,
|
||||||
|
) -> FindTreeResult {
|
||||||
|
if usecase == FindTreeUsecase::SelectToplevel {
|
||||||
|
return FindTreeResult::AcceptsInput;
|
||||||
|
}
|
||||||
let rect = self.x.surface.buffer_abs_pos.get();
|
let rect = self.x.surface.buffer_abs_pos.get();
|
||||||
if x < rect.width() && y < rect.height() {
|
if x < rect.width() && y < rect.height() {
|
||||||
tree.push(FoundNode {
|
tree.push(FoundNode {
|
||||||
|
|
@ -340,7 +349,7 @@ impl Node for Xwindow {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node_render(&self, renderer: &mut Renderer, x: i32, y: i32, bounds: Option<&Rect>) {
|
fn node_render(&self, renderer: &mut Renderer, x: i32, y: i32, bounds: Option<&Rect>) {
|
||||||
renderer.render_surface(&self.x.surface, x, y, bounds)
|
renderer.render_xwindow(self, x, y, bounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node_client(&self) -> Option<Rc<Client>> {
|
fn node_client(&self) -> Option<Rc<Client>> {
|
||||||
|
|
@ -359,6 +368,10 @@ impl Node for Xwindow {
|
||||||
// log::info!("wl-surface focus");
|
// log::info!("wl-surface focus");
|
||||||
seat.set_known_cursor(KnownCursor::Default);
|
seat.set_known_cursor(KnownCursor::Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn node_into_toplevel(self: Rc<Self>) -> Option<Rc<dyn ToplevelNode>> {
|
||||||
|
Some(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToplevelNodeBase for Xwindow {
|
impl ToplevelNodeBase for Xwindow {
|
||||||
|
|
@ -428,6 +441,10 @@ impl ToplevelNodeBase for Xwindow {
|
||||||
fn tl_scanout_surface(&self) -> Option<Rc<WlSurface>> {
|
fn tl_scanout_surface(&self) -> Option<Rc<WlSurface>> {
|
||||||
Some(self.x.surface.clone())
|
Some(self.x.surface.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn tl_admits_children(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StackedNode for Xwindow {
|
impl StackedNode for Xwindow {
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,10 @@ use {
|
||||||
object::Object,
|
object::Object,
|
||||||
rect::Rect,
|
rect::Rect,
|
||||||
renderer::Renderer,
|
renderer::Renderer,
|
||||||
tree::{FindTreeResult, FoundNode, Node, NodeId, NodeVisitor, StackedNode, WorkspaceNode},
|
tree::{
|
||||||
|
FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeVisitor, StackedNode,
|
||||||
|
WorkspaceNode,
|
||||||
|
},
|
||||||
utils::{clonecell::CloneCell, linkedlist::LinkedNode},
|
utils::{clonecell::CloneCell, linkedlist::LinkedNode},
|
||||||
wire::{xdg_popup::*, XdgPopupId},
|
wire::{xdg_popup::*, XdgPopupId},
|
||||||
},
|
},
|
||||||
|
|
@ -314,7 +317,16 @@ impl Node for XdgPopup {
|
||||||
self.xdg.absolute_desired_extents.get()
|
self.xdg.absolute_desired_extents.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node_find_tree_at(&self, x: i32, y: i32, tree: &mut Vec<FoundNode>) -> FindTreeResult {
|
fn node_find_tree_at(
|
||||||
|
&self,
|
||||||
|
x: i32,
|
||||||
|
y: i32,
|
||||||
|
tree: &mut Vec<FoundNode>,
|
||||||
|
usecase: FindTreeUsecase,
|
||||||
|
) -> FindTreeResult {
|
||||||
|
if usecase == FindTreeUsecase::SelectToplevel {
|
||||||
|
return FindTreeResult::Other;
|
||||||
|
}
|
||||||
self.xdg.find_tree_at(x, y, tree)
|
self.xdg.find_tree_at(x, y, tree)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,9 @@ use {
|
||||||
renderer::Renderer,
|
renderer::Renderer,
|
||||||
state::State,
|
state::State,
|
||||||
tree::{
|
tree::{
|
||||||
Direction, FindTreeResult, FoundNode, Node, NodeId, NodeVisitor, OutputNode,
|
Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeVisitor,
|
||||||
ToplevelData, ToplevelNode, ToplevelNodeBase, ToplevelNodeId, WorkspaceNode,
|
OutputNode, ToplevelData, ToplevelNode, ToplevelNodeBase, ToplevelNodeId,
|
||||||
|
WorkspaceNode,
|
||||||
},
|
},
|
||||||
utils::clonecell::CloneCell,
|
utils::clonecell::CloneCell,
|
||||||
wire::{xdg_toplevel::*, XdgToplevelId},
|
wire::{xdg_toplevel::*, XdgToplevelId},
|
||||||
|
|
@ -492,12 +493,21 @@ impl Node for XdgToplevel {
|
||||||
self.toplevel_data.update_self_active(self, active);
|
self.toplevel_data.update_self_active(self, active);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node_find_tree_at(&self, x: i32, y: i32, tree: &mut Vec<FoundNode>) -> FindTreeResult {
|
fn node_find_tree_at(
|
||||||
|
&self,
|
||||||
|
x: i32,
|
||||||
|
y: i32,
|
||||||
|
tree: &mut Vec<FoundNode>,
|
||||||
|
usecase: FindTreeUsecase,
|
||||||
|
) -> FindTreeResult {
|
||||||
|
if usecase == FindTreeUsecase::SelectToplevel {
|
||||||
|
return FindTreeResult::AcceptsInput;
|
||||||
|
}
|
||||||
self.xdg.find_tree_at(x, y, tree)
|
self.xdg.find_tree_at(x, y, tree)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node_render(&self, renderer: &mut Renderer, x: i32, y: i32, bounds: Option<&Rect>) {
|
fn node_render(&self, renderer: &mut Renderer, x: i32, y: i32, bounds: Option<&Rect>) {
|
||||||
renderer.render_xdg_surface(&self.xdg, x, y, bounds)
|
renderer.render_xdg_toplevel(self, x, y, bounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node_client(&self) -> Option<Rc<Client>> {
|
fn node_client(&self) -> Option<Rc<Client>> {
|
||||||
|
|
@ -516,6 +526,10 @@ impl Node for XdgToplevel {
|
||||||
// log::info!("xdg-toplevel focus");
|
// log::info!("xdg-toplevel focus");
|
||||||
seat.set_known_cursor(KnownCursor::Default);
|
seat.set_known_cursor(KnownCursor::Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn node_into_toplevel(self: Rc<Self>) -> Option<Rc<dyn ToplevelNode>> {
|
||||||
|
Some(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToplevelNodeBase for XdgToplevel {
|
impl ToplevelNodeBase for XdgToplevel {
|
||||||
|
|
@ -619,6 +633,10 @@ impl ToplevelNodeBase for XdgToplevel {
|
||||||
fn tl_restack_popups(&self) {
|
fn tl_restack_popups(&self) {
|
||||||
self.xdg.restack_popups();
|
self.xdg.restack_popups();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn tl_admits_children(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl XdgSurfaceExt for XdgToplevel {
|
impl XdgSurfaceExt for XdgToplevel {
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ use {
|
||||||
object::Object,
|
object::Object,
|
||||||
rect::Rect,
|
rect::Rect,
|
||||||
renderer::Renderer,
|
renderer::Renderer,
|
||||||
tree::{FindTreeResult, FoundNode, Node, NodeId, NodeVisitor, OutputNode},
|
tree::{FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeVisitor, OutputNode},
|
||||||
utils::{
|
utils::{
|
||||||
bitflags::BitflagsExt, cell_ext::CellExt, linkedlist::LinkedNode, numcell::NumCell,
|
bitflags::BitflagsExt, cell_ext::CellExt, linkedlist::LinkedNode, numcell::NumCell,
|
||||||
option_ext::OptionExt,
|
option_ext::OptionExt,
|
||||||
|
|
@ -424,7 +424,13 @@ impl Node for ZwlrLayerSurfaceV1 {
|
||||||
self.pos.get()
|
self.pos.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node_find_tree_at(&self, x: i32, y: i32, tree: &mut Vec<FoundNode>) -> FindTreeResult {
|
fn node_find_tree_at(
|
||||||
|
&self,
|
||||||
|
x: i32,
|
||||||
|
y: i32,
|
||||||
|
tree: &mut Vec<FoundNode>,
|
||||||
|
_usecase: FindTreeUsecase,
|
||||||
|
) -> FindTreeResult {
|
||||||
self.surface.find_tree_at_(x, y, tree)
|
self.surface.find_tree_at_(x, y, tree)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -74,12 +74,24 @@ impl TestJayCompositor {
|
||||||
self.tran.client_id.set(ClientId::from_raw(ev.client_id));
|
self.tran.client_id.set(ClientId::from_raw(ev.client_id));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_seat(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> {
|
||||||
|
let _ev = Seat::parse_full(parser)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_capabilities(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> {
|
||||||
|
let _ev = Capabilities::parse_full(parser)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test_object! {
|
test_object! {
|
||||||
TestJayCompositor, JayCompositor;
|
TestJayCompositor, JayCompositor;
|
||||||
|
|
||||||
CLIENT_ID => handle_client_id,
|
CLIENT_ID => handle_client_id,
|
||||||
|
SEAT => handle_seat,
|
||||||
|
CAPABILITIES => handle_capabilities,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestObject for TestJayCompositor {}
|
impl TestObject for TestJayCompositor {}
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,7 @@ mod t0038_subsurface_parent_state;
|
||||||
mod t0039_alpha_modifier;
|
mod t0039_alpha_modifier;
|
||||||
mod t0040_virtual_keyboard;
|
mod t0040_virtual_keyboard;
|
||||||
mod t0041_input_method;
|
mod t0041_input_method;
|
||||||
|
mod t0042_toplevel_select;
|
||||||
|
|
||||||
pub trait TestCase: Sync {
|
pub trait TestCase: Sync {
|
||||||
fn name(&self) -> &'static str;
|
fn name(&self) -> &'static str;
|
||||||
|
|
@ -133,5 +134,6 @@ pub fn tests() -> Vec<&'static dyn TestCase> {
|
||||||
t0039_alpha_modifier,
|
t0039_alpha_modifier,
|
||||||
t0040_virtual_keyboard,
|
t0040_virtual_keyboard,
|
||||||
t0041_input_method,
|
t0041_input_method,
|
||||||
|
t0042_toplevel_select,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
68
src/it/tests/t0042_toplevel_select.rs
Normal file
68
src/it/tests/t0042_toplevel_select.rs
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
ifs::wl_seat::{ToplevelSelector, BTN_LEFT},
|
||||||
|
it::{test_error::TestResult, testrun::TestRun},
|
||||||
|
tree::{Node, ToplevelNode},
|
||||||
|
utils::clonecell::CloneCell,
|
||||||
|
},
|
||||||
|
std::rc::Rc,
|
||||||
|
};
|
||||||
|
|
||||||
|
testcase!();
|
||||||
|
|
||||||
|
async fn test(run: Rc<TestRun>) -> TestResult {
|
||||||
|
let ds = run.create_default_setup().await?;
|
||||||
|
|
||||||
|
let client = run.create_client().await?;
|
||||||
|
let win1 = client.create_window().await?;
|
||||||
|
win1.map2().await?;
|
||||||
|
let win2 = client.create_window().await?;
|
||||||
|
win2.map2().await?;
|
||||||
|
client.sync().await;
|
||||||
|
|
||||||
|
let win1pos = win1.tl.server.node_absolute_position().position();
|
||||||
|
let win2pos = win2.tl.server.node_absolute_position().position();
|
||||||
|
ds.mouse.abs(
|
||||||
|
&ds.connector,
|
||||||
|
win1pos.0 as f64 + 2.0,
|
||||||
|
win1pos.1 as f64 + 2.0,
|
||||||
|
);
|
||||||
|
run.sync().await;
|
||||||
|
|
||||||
|
struct Selector(CloneCell<Option<Rc<dyn ToplevelNode>>>);
|
||||||
|
impl ToplevelSelector for Rc<Selector> {
|
||||||
|
fn set(&self, toplevel: Rc<dyn ToplevelNode>) {
|
||||||
|
self.0.set(Some(toplevel));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let selector = Rc::new(Selector(Default::default()));
|
||||||
|
ds.seat.select_toplevel(selector.clone());
|
||||||
|
|
||||||
|
client.compare_screenshot("1", false).await?;
|
||||||
|
|
||||||
|
ds.mouse.abs(
|
||||||
|
&ds.connector,
|
||||||
|
win2pos.0 as f64 + 2.0,
|
||||||
|
win2pos.1 as f64 + 2.0,
|
||||||
|
);
|
||||||
|
run.sync().await;
|
||||||
|
|
||||||
|
client.compare_screenshot("2", false).await?;
|
||||||
|
|
||||||
|
ds.kb.press(1);
|
||||||
|
run.sync().await;
|
||||||
|
tassert!(selector.0.get().is_none());
|
||||||
|
|
||||||
|
ds.seat.select_toplevel(selector.clone());
|
||||||
|
|
||||||
|
client.compare_screenshot("3", false).await?;
|
||||||
|
|
||||||
|
ds.mouse.click(BTN_LEFT);
|
||||||
|
|
||||||
|
client.compare_screenshot("4", false).await?;
|
||||||
|
|
||||||
|
let tl = selector.0.get().expect("no toplevel selected");
|
||||||
|
tassert_eq!(tl.node_id(), win2.tl.server.node_id);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
BIN
src/it/tests/t0042_toplevel_select/screenshot_1.qoi
Normal file
BIN
src/it/tests/t0042_toplevel_select/screenshot_1.qoi
Normal file
Binary file not shown.
BIN
src/it/tests/t0042_toplevel_select/screenshot_2.qoi
Normal file
BIN
src/it/tests/t0042_toplevel_select/screenshot_2.qoi
Normal file
Binary file not shown.
BIN
src/it/tests/t0042_toplevel_select/screenshot_3.qoi
Normal file
BIN
src/it/tests/t0042_toplevel_select/screenshot_3.qoi
Normal file
Binary file not shown.
BIN
src/it/tests/t0042_toplevel_select/screenshot_4.qoi
Normal file
BIN
src/it/tests/t0042_toplevel_select/screenshot_4.qoi
Normal file
Binary file not shown.
|
|
@ -76,12 +76,13 @@ impl Logger {
|
||||||
self.path.lock().clone()
|
self.path.lock().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn redirect(&self, ty: &str) {
|
pub fn redirect(&self, ty: &str) -> Ustring {
|
||||||
let (file, fd) = open_log_file(ty);
|
let (file, fd) = open_log_file(ty);
|
||||||
log::info!("Redirecting logs to {}", file.display());
|
log::info!("Redirecting logs to {}", file.display());
|
||||||
*self.path.lock() = Arc::new(file.as_bytes().into());
|
*self.path.lock() = Arc::new(file.as_bytes().into());
|
||||||
self.file_fd.store(fd.raw(), Relaxed);
|
self.file_fd.store(fd.raw(), Relaxed);
|
||||||
*self._file.lock() = fd;
|
*self._file.lock() = fd;
|
||||||
|
file
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_raw(&self, buf: &[u8]) {
|
pub fn write_raw(&self, buf: &[u8]) {
|
||||||
|
|
|
||||||
|
|
@ -189,7 +189,7 @@ impl PwCon {
|
||||||
);
|
);
|
||||||
if log::log_enabled!(log::Level::Trace) {
|
if log::log_enabled!(log::Level::Trace) {
|
||||||
log::trace!("CALL {}@{}: `{:?}`:", interface, id, opcode);
|
log::trace!("CALL {}@{}: `{:?}`:", interface, id, opcode);
|
||||||
let mut parser = PwParser::new(&buf[16..], &fds);
|
let mut parser = PwParser::new(&buf[16..buf.len()], &fds);
|
||||||
while parser.len() > 0 {
|
while parser.len() > 0 {
|
||||||
log::trace!("{:#?}", parser.read_pod().unwrap());
|
log::trace!("{:#?}", parser.read_pod().unwrap());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,10 @@ mod pw_debug;
|
||||||
use {
|
use {
|
||||||
crate::pipewire::pw_parser::{PwParser, PwParserError},
|
crate::pipewire::pw_parser::{PwParser, PwParserError},
|
||||||
bstr::BStr,
|
bstr::BStr,
|
||||||
std::fmt::{Debug, Formatter},
|
std::{
|
||||||
|
fmt::{Debug, Formatter},
|
||||||
|
sync::atomic::AtomicU32,
|
||||||
|
},
|
||||||
uapi::{c, Pod},
|
uapi::{c, Pod},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1425,10 +1428,10 @@ bitflags! {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug)]
|
||||||
pub struct spa_io_buffers {
|
pub struct spa_io_buffers {
|
||||||
pub status: SpaStatus,
|
pub status: AtomicU32,
|
||||||
pub buffer_id: u32,
|
pub buffer_id: AtomicU32,
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl Pod for spa_io_buffers {}
|
unsafe impl Pod for spa_io_buffers {}
|
||||||
|
|
|
||||||
|
|
@ -38,11 +38,13 @@ use {
|
||||||
},
|
},
|
||||||
log::Level,
|
log::Level,
|
||||||
std::{
|
std::{
|
||||||
|
os::unix::process::CommandExt,
|
||||||
|
process::Command,
|
||||||
rc::{Rc, Weak},
|
rc::{Rc, Weak},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
},
|
},
|
||||||
thiserror::Error,
|
thiserror::Error,
|
||||||
uapi::{c, OwnedFd},
|
uapi::{c, getpid, OwnedFd},
|
||||||
};
|
};
|
||||||
|
|
||||||
const PORTAL_SUCCESS: u32 = 0;
|
const PORTAL_SUCCESS: u32 = 0;
|
||||||
|
|
@ -53,7 +55,7 @@ const PORTAL_ENDED: u32 = 2;
|
||||||
|
|
||||||
pub fn run_freestanding(global: GlobalArgs) {
|
pub fn run_freestanding(global: GlobalArgs) {
|
||||||
let logger = Logger::install_stderr(global.log_level.into());
|
let logger = Logger::install_stderr(global.log_level.into());
|
||||||
run(logger);
|
run(logger, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
|
|
@ -137,13 +139,13 @@ pub fn run_from_compositor(level: Level) -> Result<PortalStartup, PortalError> {
|
||||||
Forked::Child { .. } => {
|
Forked::Child { .. } => {
|
||||||
drop(read);
|
drop(read);
|
||||||
let logger = Logger::install_pipe(write, level);
|
let logger = Logger::install_pipe(write, level);
|
||||||
run(logger);
|
run(logger, false);
|
||||||
std::process::exit(0);
|
std::process::exit(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(logger: Arc<Logger>) {
|
fn run(logger: Arc<Logger>, freestanding: bool) {
|
||||||
let eng = AsyncEngine::new();
|
let eng = AsyncEngine::new();
|
||||||
let ring = match IoUring::new(&eng, 32) {
|
let ring = match IoUring::new(&eng, 32) {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
|
|
@ -151,16 +153,21 @@ fn run(logger: Arc<Logger>) {
|
||||||
fatal!("Could not create an IO-uring: {}", ErrorFmt(e));
|
fatal!("Could not create an IO-uring: {}", ErrorFmt(e));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let _f = eng.spawn(run_async(eng.clone(), ring.clone(), logger));
|
let _f = eng.spawn(run_async(eng.clone(), ring.clone(), logger, freestanding));
|
||||||
if let Err(e) = ring.run() {
|
if let Err(e) = ring.run() {
|
||||||
fatal!("The IO-uring returned an error: {}", ErrorFmt(e));
|
fatal!("The IO-uring returned an error: {}", ErrorFmt(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run_async(eng: Rc<AsyncEngine>, ring: Rc<IoUring>, logger: Arc<Logger>) {
|
async fn run_async(
|
||||||
|
eng: Rc<AsyncEngine>,
|
||||||
|
ring: Rc<IoUring>,
|
||||||
|
logger: Arc<Logger>,
|
||||||
|
freestanding: bool,
|
||||||
|
) {
|
||||||
let (_rtl_future, rtl) = RunToplevel::install(&eng);
|
let (_rtl_future, rtl) = RunToplevel::install(&eng);
|
||||||
let dbus = Dbus::new(&eng, &ring, &rtl);
|
let dbus = Dbus::new(&eng, &ring, &rtl);
|
||||||
let dbus = init_dbus_session(&dbus, logger).await;
|
let dbus = init_dbus_session(&dbus, logger, freestanding).await;
|
||||||
let xrd = match xrd() {
|
let xrd = match xrd() {
|
||||||
Some(xrd) => xrd,
|
Some(xrd) => xrd,
|
||||||
_ => {
|
_ => {
|
||||||
|
|
@ -206,7 +213,7 @@ async fn run_async(eng: Rc<AsyncEngine>, ring: Rc<IoUring>, logger: Arc<Logger>)
|
||||||
|
|
||||||
const UNIQUE_NAME: &str = "org.freedesktop.impl.portal.desktop.jay";
|
const UNIQUE_NAME: &str = "org.freedesktop.impl.portal.desktop.jay";
|
||||||
|
|
||||||
async fn init_dbus_session(dbus: &Dbus, logger: Arc<Logger>) -> Rc<DbusSocket> {
|
async fn init_dbus_session(dbus: &Dbus, logger: Arc<Logger>, freestanding: bool) -> Rc<DbusSocket> {
|
||||||
let session = match dbus.session().await {
|
let session = match dbus.session().await {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|
@ -226,18 +233,31 @@ async fn init_dbus_session(dbus: &Dbus, logger: Arc<Logger>) -> Rc<DbusSocket> {
|
||||||
match rv {
|
match rv {
|
||||||
Ok(r) if r.get().rv == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER => {
|
Ok(r) if r.get().rv == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER => {
|
||||||
log::info!("Acquired unique name {}", UNIQUE_NAME);
|
log::info!("Acquired unique name {}", UNIQUE_NAME);
|
||||||
logger.redirect("portal");
|
let log_file = logger.redirect("portal");
|
||||||
log::info!("version = {VERSION}");
|
log::info!("version = {VERSION}");
|
||||||
let fork = match fork_with_pidfd(false) {
|
let fork = match fork_with_pidfd(false) {
|
||||||
Ok(f) => f,
|
Ok(f) => f,
|
||||||
Err(e) => fatal!("Could not fork: {}", ErrorFmt(e)),
|
Err(e) => fatal!("Could not fork: {}", ErrorFmt(e)),
|
||||||
};
|
};
|
||||||
match fork {
|
match fork {
|
||||||
Forked::Parent { .. } => std::process::exit(0),
|
Forked::Parent { .. } => {
|
||||||
|
if freestanding {
|
||||||
|
let e = Command::new("tail")
|
||||||
|
.arg("-f")
|
||||||
|
.arg("-n")
|
||||||
|
.arg("+1")
|
||||||
|
.arg(&log_file)
|
||||||
|
.exec();
|
||||||
|
eprintln!("Could not exec `tail`: {}", ErrorFmt(e));
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
std::process::exit(0)
|
||||||
|
}
|
||||||
Forked::Child { .. } => {
|
Forked::Child { .. } => {
|
||||||
if let Err(e) = uapi::setsid() {
|
if let Err(e) = uapi::setsid() {
|
||||||
log::error!("setsid failed: {}", ErrorFmt(OsError::from(e)));
|
log::error!("setsid failed: {}", ErrorFmt(OsError::from(e)));
|
||||||
}
|
}
|
||||||
|
log::info!("pid = {}", getpid());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
set_process_name("jay portal");
|
set_process_name("jay portal");
|
||||||
|
|
|
||||||
|
|
@ -297,6 +297,7 @@ fn finish_display_connect(dpy: Rc<PortalDisplayPrelude>) {
|
||||||
id: dpy.con.id(),
|
id: dpy.con.id(),
|
||||||
con: dpy.con.clone(),
|
con: dpy.con.clone(),
|
||||||
owner: Default::default(),
|
owner: Default::default(),
|
||||||
|
window_capture: Cell::new(false),
|
||||||
});
|
});
|
||||||
dpy.con.add_object(jc.clone());
|
dpy.con.add_object(jc.clone());
|
||||||
dpy.registry.request_bind(name, version, jc.deref());
|
dpy.registry.request_bind(name, version, jc.deref());
|
||||||
|
|
|
||||||
|
|
@ -34,13 +34,18 @@ use {
|
||||||
session::{CloseReply as SessionCloseReply, Closed},
|
session::{CloseReply as SessionCloseReply, Closed},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
wl_usr::usr_ifs::usr_jay_screencast::{UsrJayScreencast, UsrJayScreencastOwner},
|
wl_usr::usr_ifs::{
|
||||||
|
usr_jay_screencast::{UsrJayScreencast, UsrJayScreencastOwner},
|
||||||
|
usr_jay_select_toplevel::UsrJaySelectToplevel,
|
||||||
|
usr_jay_toplevel::UsrJayToplevel,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
std::{
|
std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
cell::{Cell, RefCell},
|
cell::{Cell, RefCell},
|
||||||
ops::Deref,
|
ops::Deref,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
|
sync::atomic::Ordering::{Acquire, Relaxed, Release},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -58,6 +63,7 @@ pub enum ScreencastPhase {
|
||||||
Init,
|
Init,
|
||||||
SourcesSelected,
|
SourcesSelected,
|
||||||
Selecting(Rc<SelectingScreencast>),
|
Selecting(Rc<SelectingScreencast>),
|
||||||
|
SelectingWindow(Rc<SelectingWindowScreencast>),
|
||||||
Starting(Rc<StartingScreencast>),
|
Starting(Rc<StartingScreencast>),
|
||||||
Started(Rc<StartedScreencast>),
|
Started(Rc<StartedScreencast>),
|
||||||
Terminated,
|
Terminated,
|
||||||
|
|
@ -65,12 +71,22 @@ pub enum ScreencastPhase {
|
||||||
|
|
||||||
unsafe impl UnsafeCellCloneSafe for ScreencastPhase {}
|
unsafe impl UnsafeCellCloneSafe for ScreencastPhase {}
|
||||||
|
|
||||||
pub struct SelectingScreencast {
|
#[derive(Clone)]
|
||||||
|
pub struct SelectingScreencastCore {
|
||||||
pub session: Rc<ScreencastSession>,
|
pub session: Rc<ScreencastSession>,
|
||||||
pub request_obj: Rc<DbusObject>,
|
pub request_obj: Rc<DbusObject>,
|
||||||
pub reply: Rc<PendingReply<StartReply<'static>>>,
|
pub reply: Rc<PendingReply<StartReply<'static>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SelectingScreencast {
|
||||||
|
pub core: SelectingScreencastCore,
|
||||||
pub guis: CopyHashMap<PortalDisplayId, Rc<SelectionGui>>,
|
pub guis: CopyHashMap<PortalDisplayId, Rc<SelectionGui>>,
|
||||||
pub output_selected: Cell<bool>,
|
}
|
||||||
|
|
||||||
|
pub struct SelectingWindowScreencast {
|
||||||
|
pub core: SelectingScreencastCore,
|
||||||
|
pub dpy: Rc<PortalDisplay>,
|
||||||
|
pub selector: Rc<UsrJaySelectToplevel>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StartingScreencast {
|
pub struct StartingScreencast {
|
||||||
|
|
@ -79,7 +95,12 @@ pub struct StartingScreencast {
|
||||||
pub reply: Rc<PendingReply<StartReply<'static>>>,
|
pub reply: Rc<PendingReply<StartReply<'static>>>,
|
||||||
pub node: Rc<PwClientNode>,
|
pub node: Rc<PwClientNode>,
|
||||||
pub dpy: Rc<PortalDisplay>,
|
pub dpy: Rc<PortalDisplay>,
|
||||||
pub output: Rc<PortalOutput>,
|
pub target: ScreencastTarget,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum ScreencastTarget {
|
||||||
|
Output(Rc<PortalOutput>),
|
||||||
|
Toplevel(Rc<UsrJayToplevel>),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StartedScreencast {
|
pub struct StartedScreencast {
|
||||||
|
|
@ -87,6 +108,7 @@ pub struct StartedScreencast {
|
||||||
node: Rc<PwClientNode>,
|
node: Rc<PwClientNode>,
|
||||||
port: Rc<PwClientNodePort>,
|
port: Rc<PwClientNodePort>,
|
||||||
buffers: RefCell<PlaneVec<DmaBuf>>,
|
buffers: RefCell<PlaneVec<DmaBuf>>,
|
||||||
|
buffers_valid: Cell<bool>,
|
||||||
dpy: Rc<PortalDisplay>,
|
dpy: Rc<PortalDisplay>,
|
||||||
jay_screencast: Rc<UsrJayScreencast>,
|
jay_screencast: Rc<UsrJayScreencast>,
|
||||||
}
|
}
|
||||||
|
|
@ -133,15 +155,22 @@ impl PwClientNodeOwner for StartingScreencast {
|
||||||
port.can_alloc_buffers.set(true);
|
port.can_alloc_buffers.set(true);
|
||||||
port.supported_metas.set(SUPPORTED_META_VIDEO_CROP);
|
port.supported_metas.set(SUPPORTED_META_VIDEO_CROP);
|
||||||
let jsc = self.dpy.jc.create_screencast();
|
let jsc = self.dpy.jc.create_screencast();
|
||||||
jsc.set_output(&self.output.jay);
|
match &self.target {
|
||||||
|
ScreencastTarget::Output(o) => jsc.set_output(&o.jay),
|
||||||
|
ScreencastTarget::Toplevel(t) => jsc.set_toplevel(t),
|
||||||
|
}
|
||||||
jsc.set_use_linear_buffers(true);
|
jsc.set_use_linear_buffers(true);
|
||||||
jsc.set_allow_all_workspaces(true);
|
jsc.set_allow_all_workspaces(true);
|
||||||
jsc.configure();
|
jsc.configure();
|
||||||
|
if let ScreencastTarget::Toplevel(t) = &self.target {
|
||||||
|
self.dpy.con.remove_obj(&**t);
|
||||||
|
}
|
||||||
let started = Rc::new(StartedScreencast {
|
let started = Rc::new(StartedScreencast {
|
||||||
session: self.session.clone(),
|
session: self.session.clone(),
|
||||||
node: self.node.clone(),
|
node: self.node.clone(),
|
||||||
port,
|
port,
|
||||||
buffers: Default::default(),
|
buffers: Default::default(),
|
||||||
|
buffers_valid: Cell::new(false),
|
||||||
dpy: self.dpy.clone(),
|
dpy: self.dpy.clone(),
|
||||||
jay_screencast: jsc,
|
jay_screencast: jsc,
|
||||||
});
|
});
|
||||||
|
|
@ -161,6 +190,7 @@ impl PwClientNodeOwner for StartedScreencast {
|
||||||
fn use_buffers(&self, port: &Rc<PwClientNodePort>) {
|
fn use_buffers(&self, port: &Rc<PwClientNodePort>) {
|
||||||
self.node
|
self.node
|
||||||
.send_port_output_buffers(port, &self.buffers.borrow_mut());
|
.send_port_output_buffers(port, &self.buffers.borrow_mut());
|
||||||
|
self.buffers_valid.set(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start(self: Rc<Self>) {
|
fn start(self: Rc<Self>) {
|
||||||
|
|
@ -179,6 +209,32 @@ impl PwClientNodeOwner for StartedScreencast {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SelectingScreencastCore {
|
||||||
|
pub fn starting(&self, dpy: &Rc<PortalDisplay>, target: ScreencastTarget) {
|
||||||
|
let node = dpy.state.pw_con.create_client_node(&[
|
||||||
|
("media.class".to_string(), "Video/Source".to_string()),
|
||||||
|
("node.name".to_string(), "jay-desktop-portal".to_string()),
|
||||||
|
("node.driver".to_string(), "true".to_string()),
|
||||||
|
]);
|
||||||
|
let starting = Rc::new(StartingScreencast {
|
||||||
|
session: self.session.clone(),
|
||||||
|
request_obj: self.request_obj.clone(),
|
||||||
|
reply: self.reply.clone(),
|
||||||
|
node,
|
||||||
|
dpy: dpy.clone(),
|
||||||
|
target,
|
||||||
|
});
|
||||||
|
self.session
|
||||||
|
.phase
|
||||||
|
.set(ScreencastPhase::Starting(starting.clone()));
|
||||||
|
starting.node.owner.set(Some(starting.clone()));
|
||||||
|
dpy.screencasts.set(
|
||||||
|
self.session.session_obj.path().to_owned(),
|
||||||
|
self.session.clone(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ScreencastSession {
|
impl ScreencastSession {
|
||||||
pub(super) fn kill(&self) {
|
pub(super) fn kill(&self) {
|
||||||
self.session_obj.emit_signal(&Closed);
|
self.session_obj.emit_signal(&Closed);
|
||||||
|
|
@ -188,15 +244,22 @@ impl ScreencastSession {
|
||||||
ScreencastPhase::SourcesSelected => {}
|
ScreencastPhase::SourcesSelected => {}
|
||||||
ScreencastPhase::Terminated => {}
|
ScreencastPhase::Terminated => {}
|
||||||
ScreencastPhase::Selecting(s) => {
|
ScreencastPhase::Selecting(s) => {
|
||||||
s.reply.err("Session has been terminated");
|
s.core.reply.err("Session has been terminated");
|
||||||
for (_, gui) in s.guis.lock().drain() {
|
for (_, gui) in s.guis.lock().drain() {
|
||||||
gui.kill(false);
|
gui.kill(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ScreencastPhase::SelectingWindow(s) => {
|
||||||
|
s.dpy.con.remove_obj(&*s.selector);
|
||||||
|
s.core.reply.err("Session has been terminated");
|
||||||
|
}
|
||||||
ScreencastPhase::Starting(s) => {
|
ScreencastPhase::Starting(s) => {
|
||||||
s.reply.err("Session has been terminated");
|
s.reply.err("Session has been terminated");
|
||||||
s.node.con.destroy_obj(s.node.deref());
|
s.node.con.destroy_obj(s.node.deref());
|
||||||
s.dpy.screencasts.remove(self.session_obj.path());
|
s.dpy.screencasts.remove(self.session_obj.path());
|
||||||
|
if let ScreencastTarget::Toplevel(t) = &s.target {
|
||||||
|
s.dpy.con.remove_obj(&**t);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ScreencastPhase::Started(s) => {
|
ScreencastPhase::Started(s) => {
|
||||||
s.jay_screencast.con.remove_obj(s.jay_screencast.deref());
|
s.jay_screencast.con.remove_obj(s.jay_screencast.deref());
|
||||||
|
|
@ -266,11 +329,12 @@ impl ScreencastSession {
|
||||||
}
|
}
|
||||||
self.phase
|
self.phase
|
||||||
.set(ScreencastPhase::Selecting(Rc::new(SelectingScreencast {
|
.set(ScreencastPhase::Selecting(Rc::new(SelectingScreencast {
|
||||||
session: self.clone(),
|
core: SelectingScreencastCore {
|
||||||
request_obj: Rc::new(request_obj),
|
session: self.clone(),
|
||||||
reply: Rc::new(reply),
|
request_obj: Rc::new(request_obj),
|
||||||
|
reply: Rc::new(reply),
|
||||||
|
},
|
||||||
guis,
|
guis,
|
||||||
output_selected: Cell::new(false),
|
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -303,23 +367,30 @@ impl UsrJayScreencastOwner for StartedScreencast {
|
||||||
self.node.send_port_update(&self.port, true);
|
self.node.send_port_update(&self.port, true);
|
||||||
self.node.send_active(true);
|
self.node.send_active(true);
|
||||||
*self.buffers.borrow_mut() = buffers;
|
*self.buffers.borrow_mut() = buffers;
|
||||||
|
self.buffers_valid.set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ready(&self, ev: &Ready) {
|
fn ready(&self, ev: &Ready) {
|
||||||
let idx = ev.idx as usize;
|
let idx = ev.idx as usize;
|
||||||
|
if !self.buffers_valid.get() {
|
||||||
|
self.jay_screencast.release_buffer(idx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut used = false;
|
let mut used = false;
|
||||||
if let Some(io) = self.port.io_buffers.lock().values().next() {
|
if let Some(io) = self.port.io_buffers.lock().values().next() {
|
||||||
let io = io.write();
|
let io = io.write();
|
||||||
if io.status != SPA_STATUS_HAVE_DATA {
|
let status = io.status.load(Acquire);
|
||||||
|
if status != SPA_STATUS_HAVE_DATA.0 {
|
||||||
used = true;
|
used = true;
|
||||||
if io.buffer_id != ev.idx {
|
let buffer_id = io.buffer_id.load(Relaxed);
|
||||||
if (io.buffer_id as usize) < self.buffers.borrow_mut().len() {
|
if buffer_id != ev.idx {
|
||||||
self.jay_screencast.release_buffer(io.buffer_id as usize);
|
if (buffer_id as usize) < self.buffers.borrow_mut().len() {
|
||||||
|
self.jay_screencast.release_buffer(buffer_id as usize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
io.buffer_id = ev.idx;
|
io.buffer_id.store(ev.idx, Relaxed);
|
||||||
io.status = SPA_STATUS_HAVE_DATA;
|
io.status.store(SPA_STATUS_HAVE_DATA.0, Release);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !used {
|
if !used {
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,10 @@ use {
|
||||||
crate::{
|
crate::{
|
||||||
ifs::wl_seat::{wl_pointer::PRESSED, BTN_LEFT},
|
ifs::wl_seat::{wl_pointer::PRESSED, BTN_LEFT},
|
||||||
portal::{
|
portal::{
|
||||||
ptl_display::{PortalDisplay, PortalOutput},
|
ptl_display::{PortalDisplay, PortalOutput, PortalSeat},
|
||||||
ptl_screencast::{ScreencastPhase, ScreencastSession, StartingScreencast},
|
ptl_screencast::{
|
||||||
|
ScreencastPhase, ScreencastSession, ScreencastTarget, SelectingWindowScreencast,
|
||||||
|
},
|
||||||
ptr_gui::{
|
ptr_gui::{
|
||||||
Align, Button, ButtonOwner, Flow, GuiElement, Label, Orientation, OverlayWindow,
|
Align, Button, ButtonOwner, Flow, GuiElement, Label, Orientation, OverlayWindow,
|
||||||
OverlayWindowOwner,
|
OverlayWindowOwner,
|
||||||
|
|
@ -11,6 +13,9 @@ use {
|
||||||
},
|
},
|
||||||
theme::Color,
|
theme::Color,
|
||||||
utils::copyhashmap::CopyHashMap,
|
utils::copyhashmap::CopyHashMap,
|
||||||
|
wl_usr::usr_ifs::{
|
||||||
|
usr_jay_select_toplevel::UsrJaySelectToplevelOwner, usr_jay_toplevel::UsrJayToplevel,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
std::rc::Rc,
|
std::rc::Rc,
|
||||||
};
|
};
|
||||||
|
|
@ -38,6 +43,7 @@ struct StaticButton {
|
||||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||||
enum ButtonRole {
|
enum ButtonRole {
|
||||||
Accept,
|
Accept,
|
||||||
|
Window,
|
||||||
Reject,
|
Reject,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -65,17 +71,17 @@ fn create_accept_gui(surface: &Rc<SelectionGuiSurface>) -> Rc<dyn GuiElement> {
|
||||||
let label = Rc::new(Label::default());
|
let label = Rc::new(Label::default());
|
||||||
*label.text.borrow_mut() = text;
|
*label.text.borrow_mut() = text;
|
||||||
let accept_button = static_button(surface, ButtonRole::Accept, "Share This Output");
|
let accept_button = static_button(surface, ButtonRole::Accept, "Share This Output");
|
||||||
|
let window_button = static_button(surface, ButtonRole::Window, "Share A Window");
|
||||||
let reject_button = static_button(surface, ButtonRole::Reject, "Reject");
|
let reject_button = static_button(surface, ButtonRole::Reject, "Reject");
|
||||||
let buttons = [&accept_button, &reject_button];
|
for button in [&accept_button, &window_button, &reject_button] {
|
||||||
for button in buttons {
|
|
||||||
button.border_color.set(Color::from_gray(100));
|
button.border_color.set(Color::from_gray(100));
|
||||||
button.border.set(2.0);
|
button.border.set(2.0);
|
||||||
button.padding.set(5.0);
|
button.padding.set(5.0);
|
||||||
}
|
}
|
||||||
accept_button.bg_color.set(Color::from_rgb(170, 200, 170));
|
for button in [&accept_button, &window_button] {
|
||||||
accept_button
|
button.bg_color.set(Color::from_rgb(170, 200, 170));
|
||||||
.bg_hover_color
|
button.bg_hover_color.set(Color::from_rgb(170, 255, 170));
|
||||||
.set(Color::from_rgb(170, 255, 170));
|
}
|
||||||
reject_button.bg_color.set(Color::from_rgb(200, 170, 170));
|
reject_button.bg_color.set(Color::from_rgb(200, 170, 170));
|
||||||
reject_button
|
reject_button
|
||||||
.bg_hover_color
|
.bg_hover_color
|
||||||
|
|
@ -85,7 +91,12 @@ fn create_accept_gui(surface: &Rc<SelectionGuiSurface>) -> Rc<dyn GuiElement> {
|
||||||
flow.cross_align.set(Align::Center);
|
flow.cross_align.set(Align::Center);
|
||||||
flow.in_margin.set(V_MARGIN);
|
flow.in_margin.set(V_MARGIN);
|
||||||
flow.cross_margin.set(H_MARGIN);
|
flow.cross_margin.set(H_MARGIN);
|
||||||
*flow.elements.borrow_mut() = vec![label, accept_button, reject_button];
|
let mut elements: Vec<Rc<dyn GuiElement>> = vec![label, accept_button];
|
||||||
|
if surface.gui.dpy.jc.window_capture.get() {
|
||||||
|
elements.push(window_button);
|
||||||
|
}
|
||||||
|
elements.push(reject_button);
|
||||||
|
*flow.elements.borrow_mut() = elements;
|
||||||
flow
|
flow
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -124,12 +135,12 @@ impl SelectionGui {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ButtonOwner for StaticButton {
|
impl ButtonOwner for StaticButton {
|
||||||
fn button(&self, button: u32, state: u32) {
|
fn button(&self, seat: &PortalSeat, button: u32, state: u32) {
|
||||||
if button != BTN_LEFT || state != PRESSED {
|
if button != BTN_LEFT || state != PRESSED {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
match self.role {
|
match self.role {
|
||||||
ButtonRole::Accept => {
|
ButtonRole::Accept | ButtonRole::Window => {
|
||||||
log::info!("User has accepted the request");
|
log::info!("User has accepted the request");
|
||||||
let selecting = match self.surface.gui.screencast_session.phase.get() {
|
let selecting = match self.surface.gui.screencast_session.phase.get() {
|
||||||
ScreencastPhase::Selecting(selecting) => selecting,
|
ScreencastPhase::Selecting(selecting) => selecting,
|
||||||
|
|
@ -138,34 +149,25 @@ impl ButtonOwner for StaticButton {
|
||||||
for (_, gui) in selecting.guis.lock().drain() {
|
for (_, gui) in selecting.guis.lock().drain() {
|
||||||
gui.kill(false);
|
gui.kill(false);
|
||||||
}
|
}
|
||||||
let node = self.surface.gui.dpy.state.pw_con.create_client_node(&[
|
let dpy = &self.surface.output.dpy;
|
||||||
("media.class".to_string(), "Video/Source".to_string()),
|
if self.role == ButtonRole::Accept {
|
||||||
("node.name".to_string(), "jay-desktop-portal".to_string()),
|
selecting
|
||||||
("node.driver".to_string(), "true".to_string()),
|
.core
|
||||||
]);
|
.starting(dpy, ScreencastTarget::Output(self.surface.output.clone()));
|
||||||
let starting = Rc::new(StartingScreencast {
|
} else {
|
||||||
session: self.surface.gui.screencast_session.clone(),
|
let selector = dpy.jc.select_toplevel(&seat.wl);
|
||||||
request_obj: selecting.request_obj.clone(),
|
let selecting = Rc::new(SelectingWindowScreencast {
|
||||||
reply: selecting.reply.clone(),
|
core: selecting.core.clone(),
|
||||||
node,
|
dpy: dpy.clone(),
|
||||||
dpy: self.surface.gui.dpy.clone(),
|
selector: selector.clone(),
|
||||||
output: self.surface.output.clone(),
|
});
|
||||||
});
|
selector.owner.set(Some(selecting.clone()));
|
||||||
self.surface
|
|
||||||
.gui
|
|
||||||
.screencast_session
|
|
||||||
.phase
|
|
||||||
.set(ScreencastPhase::Starting(starting.clone()));
|
|
||||||
starting.node.owner.set(Some(starting.clone()));
|
|
||||||
self.surface.gui.dpy.screencasts.set(
|
|
||||||
self.surface
|
self.surface
|
||||||
.gui
|
.gui
|
||||||
.screencast_session
|
.screencast_session
|
||||||
.session_obj
|
.phase
|
||||||
.path()
|
.set(ScreencastPhase::SelectingWindow(selecting));
|
||||||
.to_owned(),
|
}
|
||||||
self.surface.gui.screencast_session.clone(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
ButtonRole::Reject => {
|
ButtonRole::Reject => {
|
||||||
log::info!("User has rejected the screencast request");
|
log::info!("User has rejected the screencast request");
|
||||||
|
|
@ -175,6 +177,28 @@ impl ButtonOwner for StaticButton {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl UsrJaySelectToplevelOwner for SelectingWindowScreencast {
|
||||||
|
fn done(&self, tl: Option<Rc<UsrJayToplevel>>) {
|
||||||
|
let Some(tl) = tl else {
|
||||||
|
log::info!("User has aborted the selection");
|
||||||
|
self.core.session.kill();
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
match self.core.session.phase.get() {
|
||||||
|
ScreencastPhase::SelectingWindow(s) => {
|
||||||
|
self.dpy.con.remove_obj(&*s.selector);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.dpy.con.remove_obj(&*tl);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log::info!("User has selected a window");
|
||||||
|
self.core
|
||||||
|
.starting(&self.dpy, ScreencastTarget::Toplevel(tl));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn static_button(surface: &Rc<SelectionGuiSurface>, role: ButtonRole, text: &str) -> Rc<Button> {
|
fn static_button(surface: &Rc<SelectionGuiSurface>, role: ButtonRole, text: &str) -> Rc<Button> {
|
||||||
let button = Rc::new(Button::default());
|
let button = Rc::new(Button::default());
|
||||||
let slf = Rc::new(StaticButton {
|
let slf = Rc::new(StaticButton {
|
||||||
|
|
|
||||||
|
|
@ -123,7 +123,7 @@ pub struct Button {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ButtonOwner {
|
pub trait ButtonOwner {
|
||||||
fn button(&self, button: u32, state: u32);
|
fn button(&self, seat: &PortalSeat, button: u32, state: u32);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Button {
|
impl Default for Button {
|
||||||
|
|
@ -251,9 +251,9 @@ impl GuiElement for Button {
|
||||||
self.owner.take();
|
self.owner.take();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn button(&self, _seat: &PortalSeat, button: u32, state: u32) {
|
fn button(&self, seat: &PortalSeat, button: u32, state: u32) {
|
||||||
if let Some(owner) = self.owner.get() {
|
if let Some(owner) = self.owner.get() {
|
||||||
owner.button(button, state);
|
owner.button(seat, button, state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,10 @@ use {
|
||||||
ifs::{
|
ifs::{
|
||||||
wl_callback::WlCallback,
|
wl_callback::WlCallback,
|
||||||
wl_surface::{
|
wl_surface::{
|
||||||
xdg_surface::XdgSurface, zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, SurfaceBuffer,
|
x_surface::xwindow::Xwindow,
|
||||||
WlSurface,
|
xdg_surface::{xdg_toplevel::XdgToplevel, XdgSurface},
|
||||||
|
zwlr_layer_surface_v1::ZwlrLayerSurfaceV1,
|
||||||
|
SurfaceBuffer, WlSurface,
|
||||||
},
|
},
|
||||||
wp_presentation_feedback::WpPresentationFeedback,
|
wp_presentation_feedback::WpPresentationFeedback,
|
||||||
},
|
},
|
||||||
|
|
@ -15,8 +17,8 @@ use {
|
||||||
state::State,
|
state::State,
|
||||||
theme::Color,
|
theme::Color,
|
||||||
tree::{
|
tree::{
|
||||||
ContainerNode, DisplayNode, FloatNode, OutputNode, PlaceholderNode, ToplevelNodeBase,
|
ContainerNode, DisplayNode, FloatNode, OutputNode, PlaceholderNode, ToplevelData,
|
||||||
WorkspaceNode,
|
ToplevelNodeBase, WorkspaceNode,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
std::{
|
std::{
|
||||||
|
|
@ -212,7 +214,13 @@ impl Renderer<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_placeholder(&mut self, placeholder: &PlaceholderNode, x: i32, y: i32) {
|
pub fn render_placeholder(
|
||||||
|
&mut self,
|
||||||
|
placeholder: &PlaceholderNode,
|
||||||
|
x: i32,
|
||||||
|
y: i32,
|
||||||
|
bounds: Option<&Rect>,
|
||||||
|
) {
|
||||||
let pos = placeholder.tl_data().pos.get();
|
let pos = placeholder.tl_data().pos.get();
|
||||||
self.base.fill_boxes(
|
self.base.fill_boxes(
|
||||||
std::slice::from_ref(&pos.at_point(x, y)),
|
std::slice::from_ref(&pos.at_point(x, y)),
|
||||||
|
|
@ -236,6 +244,7 @@ impl Renderer<'_> {
|
||||||
ReleaseSync::None,
|
ReleaseSync::None,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
self.render_tl_aux(placeholder.tl_data(), bounds, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_container(&mut self, container: &ContainerNode, x: i32, y: i32) {
|
pub fn render_container(&mut self, container: &ContainerNode, x: i32, y: i32) {
|
||||||
|
|
@ -300,6 +309,17 @@ impl Renderer<'_> {
|
||||||
.node_render(self, x + content.x1(), y + content.y1(), Some(&body));
|
.node_render(self, x + content.x1(), y + content.y1(), Some(&body));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.render_tl_aux(container.tl_data(), None, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_xwindow(&mut self, tl: &Xwindow, x: i32, y: i32, bounds: Option<&Rect>) {
|
||||||
|
self.render_surface(&tl.x.surface, x, y, bounds);
|
||||||
|
self.render_tl_aux(tl.tl_data(), bounds, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_xdg_toplevel(&mut self, tl: &XdgToplevel, x: i32, y: i32, bounds: Option<&Rect>) {
|
||||||
|
self.render_xdg_surface(&tl.xdg, x, y, bounds);
|
||||||
|
self.render_tl_aux(tl.tl_data(), bounds, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_xdg_surface(
|
pub fn render_xdg_surface(
|
||||||
|
|
@ -316,6 +336,33 @@ impl Renderer<'_> {
|
||||||
self.render_surface(surface, x, y, bounds);
|
self.render_surface(surface, x, y, bounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_tl_aux(
|
||||||
|
&mut self,
|
||||||
|
tl_data: &ToplevelData,
|
||||||
|
bounds: Option<&Rect>,
|
||||||
|
render_highlight: bool,
|
||||||
|
) {
|
||||||
|
if self.result.is_some() {
|
||||||
|
for screencast in tl_data.jay_screencasts.lock().values() {
|
||||||
|
screencast.schedule_toplevel_screencast();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if render_highlight {
|
||||||
|
self.render_highlight(tl_data, bounds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_highlight(&mut self, tl_data: &ToplevelData, bounds: Option<&Rect>) {
|
||||||
|
if tl_data.render_highlight.get() == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let Some(bounds) = bounds else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let color = self.state.theme.colors.highlight.get();
|
||||||
|
self.base.fill_boxes(slice::from_ref(bounds), &color);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn render_surface(&mut self, surface: &WlSurface, x: i32, y: i32, bounds: Option<&Rect>) {
|
pub fn render_surface(&mut self, surface: &WlSurface, x: i32, y: i32, bounds: Option<&Rect>) {
|
||||||
let (x, y) = self.base.scale_point(x, y);
|
let (x, y) = self.base.scale_point(x, y);
|
||||||
self.render_surface_scaled(surface, x, y, None, bounds, false);
|
self.render_surface_scaled(surface, x, y, None, bounds, false);
|
||||||
|
|
|
||||||
11
src/scale.rs
11
src/scale.rs
|
|
@ -37,6 +37,17 @@ impl Scale {
|
||||||
pub fn to_wl(self) -> u32 {
|
pub fn to_wl(self) -> u32 {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn pixel_size(self, width: i32, height: i32) -> (i32, i32) {
|
||||||
|
if self == Scale::default() {
|
||||||
|
return (width, height);
|
||||||
|
}
|
||||||
|
let scale = self.to_f64();
|
||||||
|
(
|
||||||
|
(width as f64 * scale).round() as i32,
|
||||||
|
(height as f64 * scale).round() as i32,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq<u32> for Scale {
|
impl PartialEq<u32> for Scale {
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ use {
|
||||||
ext_session_lock_v1::ExtSessionLockV1,
|
ext_session_lock_v1::ExtSessionLockV1,
|
||||||
ipc::{x_data_device::XIpcDeviceIds, DataOfferIds, DataSourceIds},
|
ipc::{x_data_device::XIpcDeviceIds, DataOfferIds, DataSourceIds},
|
||||||
jay_render_ctx::JayRenderCtx,
|
jay_render_ctx::JayRenderCtx,
|
||||||
|
jay_screencast::JayScreencast,
|
||||||
jay_seat_events::JaySeatEvents,
|
jay_seat_events::JaySeatEvents,
|
||||||
jay_workspace_watcher::JayWorkspaceWatcher,
|
jay_workspace_watcher::JayWorkspaceWatcher,
|
||||||
wl_drm::WlDrmGlobal,
|
wl_drm::WlDrmGlobal,
|
||||||
|
|
@ -136,6 +137,8 @@ pub struct State {
|
||||||
pub pending_float_layout: AsyncQueue<Rc<FloatNode>>,
|
pub pending_float_layout: AsyncQueue<Rc<FloatNode>>,
|
||||||
pub pending_float_titles: AsyncQueue<Rc<FloatNode>>,
|
pub pending_float_titles: AsyncQueue<Rc<FloatNode>>,
|
||||||
pub pending_input_popup_positioning: AsyncQueue<Rc<ZwpInputPopupSurfaceV2>>,
|
pub pending_input_popup_positioning: AsyncQueue<Rc<ZwpInputPopupSurfaceV2>>,
|
||||||
|
pub pending_toplevel_screencasts: AsyncQueue<Rc<JayScreencast>>,
|
||||||
|
pub pending_toplevel_screencast_reallocs: AsyncQueue<Rc<JayScreencast>>,
|
||||||
pub dbus: Dbus,
|
pub dbus: Dbus,
|
||||||
pub fdcloser: Arc<FdCloser>,
|
pub fdcloser: Arc<FdCloser>,
|
||||||
pub logger: Option<Arc<Logger>>,
|
pub logger: Option<Arc<Logger>>,
|
||||||
|
|
@ -735,6 +738,9 @@ impl State {
|
||||||
self.pending_output_render_data.clear();
|
self.pending_output_render_data.clear();
|
||||||
self.pending_float_layout.clear();
|
self.pending_float_layout.clear();
|
||||||
self.pending_float_titles.clear();
|
self.pending_float_titles.clear();
|
||||||
|
self.pending_input_popup_positioning.clear();
|
||||||
|
self.pending_toplevel_screencasts.clear();
|
||||||
|
self.pending_toplevel_screencast_reallocs.clear();
|
||||||
self.render_ctx_watchers.clear();
|
self.render_ctx_watchers.clear();
|
||||||
self.workspace_watchers.clear();
|
self.workspace_watchers.clear();
|
||||||
self.toplevel_lists.clear();
|
self.toplevel_lists.clear();
|
||||||
|
|
|
||||||
13
src/theme.rs
13
src/theme.rs
|
|
@ -137,7 +137,7 @@ impl From<jay_config::theme::Color> for Color {
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! colors {
|
macro_rules! colors {
|
||||||
($($name:ident = ($r:expr, $g:expr, $b:expr),)*) => {
|
($($name:ident = $colors:tt,)*) => {
|
||||||
pub struct ThemeColors {
|
pub struct ThemeColors {
|
||||||
$(
|
$(
|
||||||
pub $name: Cell<Color>,
|
pub $name: Cell<Color>,
|
||||||
|
|
@ -157,12 +157,18 @@ macro_rules! colors {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
$(
|
$(
|
||||||
$name: Cell::new(Color::from_rgb($r, $g, $b)),
|
$name: Cell::new(colors!(@colors $colors)),
|
||||||
)*
|
)*
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
(@colors ($r:expr, $g:expr, $b:expr)) => {
|
||||||
|
Color::from_rgb($r, $g, $b)
|
||||||
|
};
|
||||||
|
(@colors ($r:expr, $g:expr, $b:expr, $a:expr)) => {
|
||||||
|
Color::from_rgba_straight($r, $g, $b, $a)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
colors! {
|
colors! {
|
||||||
|
|
@ -180,6 +186,7 @@ colors! {
|
||||||
bar_background = (0x00, 0x00, 0x00),
|
bar_background = (0x00, 0x00, 0x00),
|
||||||
bar_text = (0xff, 0xff, 0xff),
|
bar_text = (0xff, 0xff, 0xff),
|
||||||
attention_requested_background = (0x23, 0x09, 0x2c),
|
attention_requested_background = (0x23, 0x09, 0x2c),
|
||||||
|
highlight = (0x9d, 0x28, 0xc6, 0x7f),
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! sizes {
|
macro_rules! sizes {
|
||||||
|
|
|
||||||
19
src/tree.rs
19
src/tree.rs
|
|
@ -100,6 +100,12 @@ impl FindTreeResult {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||||
|
pub enum FindTreeUsecase {
|
||||||
|
None,
|
||||||
|
SelectToplevel,
|
||||||
|
}
|
||||||
|
|
||||||
pub trait Node: 'static {
|
pub trait Node: 'static {
|
||||||
fn node_id(&self) -> NodeId;
|
fn node_id(&self) -> NodeId;
|
||||||
fn node_seat_state(&self) -> &NodeSeatState;
|
fn node_seat_state(&self) -> &NodeSeatState;
|
||||||
|
|
@ -122,10 +128,17 @@ pub trait Node: 'static {
|
||||||
let _ = active;
|
let _ = active;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node_find_tree_at(&self, x: i32, y: i32, tree: &mut Vec<FoundNode>) -> FindTreeResult {
|
fn node_find_tree_at(
|
||||||
|
&self,
|
||||||
|
x: i32,
|
||||||
|
y: i32,
|
||||||
|
tree: &mut Vec<FoundNode>,
|
||||||
|
usecase: FindTreeUsecase,
|
||||||
|
) -> FindTreeResult {
|
||||||
let _ = x;
|
let _ = x;
|
||||||
let _ = y;
|
let _ = y;
|
||||||
let _ = tree;
|
let _ = tree;
|
||||||
|
let _ = usecase;
|
||||||
FindTreeResult::Other
|
FindTreeResult::Other
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -296,6 +309,10 @@ pub trait Node: 'static {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn node_into_toplevel(self: Rc<Self>) -> Option<Rc<dyn ToplevelNode>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
// TYPE CHECKERS
|
// TYPE CHECKERS
|
||||||
|
|
||||||
fn node_is_container(&self) -> bool {
|
fn node_is_container(&self) -> bool {
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,8 @@ use {
|
||||||
state::State,
|
state::State,
|
||||||
text::{self, TextTexture},
|
text::{self, TextTexture},
|
||||||
tree::{
|
tree::{
|
||||||
walker::NodeVisitor, ContainingNode, Direction, FindTreeResult, FoundNode, Node,
|
walker::NodeVisitor, ContainingNode, Direction, FindTreeResult, FindTreeUsecase,
|
||||||
NodeId, ToplevelData, ToplevelNode, ToplevelNodeBase, WorkspaceNode,
|
FoundNode, Node, NodeId, ToplevelData, ToplevelNode, ToplevelNodeBase, WorkspaceNode,
|
||||||
},
|
},
|
||||||
utils::{
|
utils::{
|
||||||
clonecell::CloneCell,
|
clonecell::CloneCell,
|
||||||
|
|
@ -1131,7 +1131,13 @@ impl Node for ContainerNode {
|
||||||
self.toplevel_data.update_self_active(self, active);
|
self.toplevel_data.update_self_active(self, active);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node_find_tree_at(&self, x: i32, y: i32, tree: &mut Vec<FoundNode>) -> FindTreeResult {
|
fn node_find_tree_at(
|
||||||
|
&self,
|
||||||
|
x: i32,
|
||||||
|
y: i32,
|
||||||
|
tree: &mut Vec<FoundNode>,
|
||||||
|
usecase: FindTreeUsecase,
|
||||||
|
) -> FindTreeResult {
|
||||||
let mut recurse = |content: Rect, child: NodeRef<ContainerChild>| {
|
let mut recurse = |content: Rect, child: NodeRef<ContainerChild>| {
|
||||||
if content.contains(x, y) {
|
if content.contains(x, y) {
|
||||||
let (x, y) = content.translate(x, y);
|
let (x, y) = content.translate(x, y);
|
||||||
|
|
@ -1140,7 +1146,7 @@ impl Node for ContainerNode {
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
});
|
});
|
||||||
child.node.node_find_tree_at(x, y, tree);
|
child.node.node_find_tree_at(x, y, tree, usecase);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if let Some(child) = self.mono_child.get() {
|
if let Some(child) = self.mono_child.get() {
|
||||||
|
|
@ -1329,6 +1335,10 @@ impl Node for ContainerNode {
|
||||||
fn node_is_container(&self) -> bool {
|
fn node_is_container(&self) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn node_into_toplevel(self: Rc<Self>) -> Option<Rc<dyn ToplevelNode>> {
|
||||||
|
Some(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContainingNode for ContainerNode {
|
impl ContainingNode for ContainerNode {
|
||||||
|
|
@ -1549,6 +1559,10 @@ impl ToplevelNodeBase for ContainerNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn tl_admits_children(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn direction_to_split(dir: Direction) -> (ContainerSplit, bool) {
|
fn direction_to_split(dir: Direction) -> (ContainerSplit, bool) {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ use {
|
||||||
renderer::Renderer,
|
renderer::Renderer,
|
||||||
state::State,
|
state::State,
|
||||||
tree::{
|
tree::{
|
||||||
walker::NodeVisitor, FindTreeResult, FoundNode, Node, NodeId, OutputNode, StackedNode,
|
walker::NodeVisitor, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId,
|
||||||
|
OutputNode, StackedNode,
|
||||||
},
|
},
|
||||||
utils::{copyhashmap::CopyHashMap, linkedlist::LinkedList},
|
utils::{copyhashmap::CopyHashMap, linkedlist::LinkedList},
|
||||||
},
|
},
|
||||||
|
|
@ -109,7 +110,13 @@ impl Node for DisplayNode {
|
||||||
self.extents.get()
|
self.extents.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node_find_tree_at(&self, x: i32, y: i32, tree: &mut Vec<FoundNode>) -> FindTreeResult {
|
fn node_find_tree_at(
|
||||||
|
&self,
|
||||||
|
x: i32,
|
||||||
|
y: i32,
|
||||||
|
tree: &mut Vec<FoundNode>,
|
||||||
|
usecase: FindTreeUsecase,
|
||||||
|
) -> FindTreeResult {
|
||||||
let outputs = self.outputs.lock();
|
let outputs = self.outputs.lock();
|
||||||
for output in outputs.values() {
|
for output in outputs.values() {
|
||||||
let pos = output.global.pos.get();
|
let pos = output.global.pos.get();
|
||||||
|
|
@ -120,7 +127,7 @@ impl Node for DisplayNode {
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
});
|
});
|
||||||
output.node_find_tree_at(x, y, tree);
|
output.node_find_tree_at(x, y, tree, usecase);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,8 @@ use {
|
||||||
state::State,
|
state::State,
|
||||||
text::{self, TextTexture},
|
text::{self, TextTexture},
|
||||||
tree::{
|
tree::{
|
||||||
walker::NodeVisitor, ContainingNode, Direction, FindTreeResult, FoundNode, Node,
|
walker::NodeVisitor, ContainingNode, Direction, FindTreeResult, FindTreeUsecase,
|
||||||
NodeId, StackedNode, ToplevelNode, WorkspaceNode,
|
FoundNode, Node, NodeId, StackedNode, ToplevelNode, WorkspaceNode,
|
||||||
},
|
},
|
||||||
utils::{
|
utils::{
|
||||||
clonecell::CloneCell, copyhashmap::CopyHashMap, double_click_state::DoubleClickState,
|
clonecell::CloneCell, copyhashmap::CopyHashMap, double_click_state::DoubleClickState,
|
||||||
|
|
@ -437,7 +437,13 @@ impl Node for FloatNode {
|
||||||
self.update_child_title(title);
|
self.update_child_title(title);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node_find_tree_at(&self, x: i32, y: i32, tree: &mut Vec<FoundNode>) -> FindTreeResult {
|
fn node_find_tree_at(
|
||||||
|
&self,
|
||||||
|
x: i32,
|
||||||
|
y: i32,
|
||||||
|
tree: &mut Vec<FoundNode>,
|
||||||
|
usecase: FindTreeUsecase,
|
||||||
|
) -> FindTreeResult {
|
||||||
let theme = &self.state.theme;
|
let theme = &self.state.theme;
|
||||||
let th = theme.sizes.title_height.get();
|
let th = theme.sizes.title_height.get();
|
||||||
let bw = theme.sizes.border_width.get();
|
let bw = theme.sizes.border_width.get();
|
||||||
|
|
@ -459,7 +465,7 @@ impl Node for FloatNode {
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
});
|
});
|
||||||
child.node_find_tree_at(x, y, tree)
|
child.node_find_tree_at(x, y, tree, usecase)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node_child_active_changed(self: Rc<Self>, _child: &dyn Node, active: bool, _depth: u32) {
|
fn node_child_active_changed(self: Rc<Self>, _child: &dyn Node, active: bool, _depth: u32) {
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,8 @@ use {
|
||||||
state::State,
|
state::State,
|
||||||
text::{self, TextTexture},
|
text::{self, TextTexture},
|
||||||
tree::{
|
tree::{
|
||||||
walker::NodeVisitor, Direction, FindTreeResult, FoundNode, Node, NodeId, WorkspaceNode,
|
walker::NodeVisitor, Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node,
|
||||||
|
NodeId, WorkspaceNode,
|
||||||
},
|
},
|
||||||
utils::{
|
utils::{
|
||||||
clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt,
|
clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt,
|
||||||
|
|
@ -408,20 +409,8 @@ impl OutputNode {
|
||||||
self.change_extents_(&self.calculate_extents());
|
self.change_extents_(&self.calculate_extents());
|
||||||
|
|
||||||
if (old_width, old_height) != (new_width, new_height) {
|
if (old_width, old_height) != (new_width, new_height) {
|
||||||
let mut to_destroy = vec![];
|
for sc in self.screencasts.lock().values() {
|
||||||
if let Some(ctx) = self.state.render_ctx.get() {
|
sc.schedule_realloc();
|
||||||
for sc in self.screencasts.lock().values() {
|
|
||||||
if let Err(e) = sc.realloc(&ctx) {
|
|
||||||
log::error!(
|
|
||||||
"Could not re-allocate buffers for screencast after mode change: {}",
|
|
||||||
ErrorFmt(e)
|
|
||||||
);
|
|
||||||
to_destroy.push(sc.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for sc in to_destroy {
|
|
||||||
sc.do_destroy();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -470,14 +459,20 @@ impl OutputNode {
|
||||||
y: i32,
|
y: i32,
|
||||||
layers: &[u32],
|
layers: &[u32],
|
||||||
tree: &mut Vec<FoundNode>,
|
tree: &mut Vec<FoundNode>,
|
||||||
|
usecase: FindTreeUsecase,
|
||||||
) -> FindTreeResult {
|
) -> FindTreeResult {
|
||||||
|
if usecase == FindTreeUsecase::SelectToplevel {
|
||||||
|
return FindTreeResult::Other;
|
||||||
|
}
|
||||||
let len = tree.len();
|
let len = tree.len();
|
||||||
for layer in layers.iter().copied() {
|
for layer in layers.iter().copied() {
|
||||||
for surface in self.layers[layer as usize].rev_iter() {
|
for surface in self.layers[layer as usize].rev_iter() {
|
||||||
let pos = surface.output_position();
|
let pos = surface.output_position();
|
||||||
if pos.contains(x, y) {
|
if pos.contains(x, y) {
|
||||||
let (x, y) = pos.translate(x, y);
|
let (x, y) = pos.translate(x, y);
|
||||||
if surface.node_find_tree_at(x, y, tree) == FindTreeResult::AcceptsInput {
|
if surface.node_find_tree_at(x, y, tree, usecase)
|
||||||
|
== FindTreeResult::AcceptsInput
|
||||||
|
{
|
||||||
return FindTreeResult::AcceptsInput;
|
return FindTreeResult::AcceptsInput;
|
||||||
}
|
}
|
||||||
tree.truncate(len);
|
tree.truncate(len);
|
||||||
|
|
@ -627,20 +622,28 @@ impl Node for OutputNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node_find_tree_at(&self, x: i32, mut y: i32, tree: &mut Vec<FoundNode>) -> FindTreeResult {
|
fn node_find_tree_at(
|
||||||
|
&self,
|
||||||
|
x: i32,
|
||||||
|
mut y: i32,
|
||||||
|
tree: &mut Vec<FoundNode>,
|
||||||
|
usecase: FindTreeUsecase,
|
||||||
|
) -> FindTreeResult {
|
||||||
if self.state.lock.locked.get() {
|
if self.state.lock.locked.get() {
|
||||||
if let Some(ls) = self.lock_surface.get() {
|
if usecase != FindTreeUsecase::SelectToplevel {
|
||||||
tree.push(FoundNode {
|
if let Some(ls) = self.lock_surface.get() {
|
||||||
node: ls.clone(),
|
tree.push(FoundNode {
|
||||||
x,
|
node: ls.clone(),
|
||||||
y,
|
x,
|
||||||
});
|
y,
|
||||||
return ls.node_find_tree_at(x, y, tree);
|
});
|
||||||
|
return ls.node_find_tree_at(x, y, tree, usecase);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return FindTreeResult::AcceptsInput;
|
return FindTreeResult::AcceptsInput;
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let res = self.find_layer_surface_at(x, y, &[OVERLAY, TOP], tree);
|
let res = self.find_layer_surface_at(x, y, &[OVERLAY, TOP], tree, usecase);
|
||||||
if res.accepts_input() {
|
if res.accepts_input() {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
@ -665,7 +668,7 @@ impl Node for OutputNode {
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
});
|
});
|
||||||
match stacked.node_find_tree_at(x, y, tree) {
|
match stacked.node_find_tree_at(x, y, tree, usecase) {
|
||||||
FindTreeResult::AcceptsInput => {
|
FindTreeResult::AcceptsInput => {
|
||||||
return FindTreeResult::AcceptsInput;
|
return FindTreeResult::AcceptsInput;
|
||||||
}
|
}
|
||||||
|
|
@ -685,7 +688,7 @@ impl Node for OutputNode {
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
});
|
});
|
||||||
fs.tl_as_node().node_find_tree_at(x, y, tree)
|
fs.tl_as_node().node_find_tree_at(x, y, tree, usecase)
|
||||||
} else {
|
} else {
|
||||||
let bar_height = self.state.theme.sizes.title_height.get() + 1;
|
let bar_height = self.state.theme.sizes.title_height.get() + 1;
|
||||||
if y >= bar_height {
|
if y >= bar_height {
|
||||||
|
|
@ -697,10 +700,10 @@ impl Node for OutputNode {
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
});
|
});
|
||||||
ws.node_find_tree_at(x, y, tree);
|
ws.node_find_tree_at(x, y, tree, usecase);
|
||||||
}
|
}
|
||||||
if tree.len() == len {
|
if tree.len() == len {
|
||||||
self.find_layer_surface_at(x, y, &[BOTTOM, BACKGROUND], tree);
|
self.find_layer_surface_at(x, y, &[BOTTOM, BACKGROUND], tree, usecase);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FindTreeResult::AcceptsInput
|
FindTreeResult::AcceptsInput
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,8 @@ use {
|
||||||
state::State,
|
state::State,
|
||||||
text::{self, TextTexture},
|
text::{self, TextTexture},
|
||||||
tree::{
|
tree::{
|
||||||
Direction, FindTreeResult, FoundNode, Node, NodeId, NodeVisitor, ToplevelData,
|
Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeVisitor,
|
||||||
ToplevelNode, ToplevelNodeBase,
|
ToplevelData, ToplevelNode, ToplevelNodeBase,
|
||||||
},
|
},
|
||||||
utils::{errorfmt::ErrorFmt, smallmap::SmallMap},
|
utils::{errorfmt::ErrorFmt, smallmap::SmallMap},
|
||||||
},
|
},
|
||||||
|
|
@ -116,12 +116,18 @@ impl Node for PlaceholderNode {
|
||||||
self.toplevel.update_self_active(self, active);
|
self.toplevel.update_self_active(self, active);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node_find_tree_at(&self, _x: i32, _y: i32, _tree: &mut Vec<FoundNode>) -> FindTreeResult {
|
fn node_find_tree_at(
|
||||||
|
&self,
|
||||||
|
_x: i32,
|
||||||
|
_y: i32,
|
||||||
|
_tree: &mut Vec<FoundNode>,
|
||||||
|
_usecase: FindTreeUsecase,
|
||||||
|
) -> FindTreeResult {
|
||||||
FindTreeResult::AcceptsInput
|
FindTreeResult::AcceptsInput
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node_render(&self, renderer: &mut Renderer, x: i32, y: i32, _bounds: Option<&Rect>) {
|
fn node_render(&self, renderer: &mut Renderer, x: i32, y: i32, bounds: Option<&Rect>) {
|
||||||
renderer.render_placeholder(self, x, y);
|
renderer.render_placeholder(self, x, y, bounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node_client(&self) -> Option<Rc<Client>> {
|
fn node_client(&self) -> Option<Rc<Client>> {
|
||||||
|
|
@ -140,6 +146,10 @@ impl Node for PlaceholderNode {
|
||||||
fn node_is_placeholder(&self) -> bool {
|
fn node_is_placeholder(&self) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn node_into_toplevel(self: Rc<Self>) -> Option<Rc<dyn ToplevelNode>> {
|
||||||
|
Some(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToplevelNodeBase for PlaceholderNode {
|
impl ToplevelNodeBase for PlaceholderNode {
|
||||||
|
|
@ -173,4 +183,8 @@ impl ToplevelNodeBase for PlaceholderNode {
|
||||||
fn tl_last_active_child(self: Rc<Self>) -> Rc<dyn ToplevelNode> {
|
fn tl_last_active_child(self: Rc<Self>) -> Rc<dyn ToplevelNode> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn tl_admits_children(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ use {
|
||||||
ifs::{
|
ifs::{
|
||||||
ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1,
|
ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1,
|
||||||
ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1,
|
ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1,
|
||||||
|
jay_screencast::JayScreencast,
|
||||||
|
jay_toplevel::JayToplevel,
|
||||||
wl_seat::{collect_kb_foci, collect_kb_foci2, NodeSeatState, SeatId},
|
wl_seat::{collect_kb_foci, collect_kb_foci2, NodeSeatState, SeatId},
|
||||||
wl_surface::WlSurface,
|
wl_surface::WlSurface,
|
||||||
},
|
},
|
||||||
|
|
@ -13,11 +15,12 @@ use {
|
||||||
utils::{
|
utils::{
|
||||||
clonecell::CloneCell,
|
clonecell::CloneCell,
|
||||||
copyhashmap::CopyHashMap,
|
copyhashmap::CopyHashMap,
|
||||||
|
numcell::NumCell,
|
||||||
smallmap::SmallMap,
|
smallmap::SmallMap,
|
||||||
threshold_counter::ThresholdCounter,
|
threshold_counter::ThresholdCounter,
|
||||||
toplevel_identifier::{toplevel_identifier, ToplevelIdentifier},
|
toplevel_identifier::{toplevel_identifier, ToplevelIdentifier},
|
||||||
},
|
},
|
||||||
wire::ExtForeignToplevelHandleV1Id,
|
wire::{ExtForeignToplevelHandleV1Id, JayScreencastId, JayToplevelId},
|
||||||
},
|
},
|
||||||
std::{
|
std::{
|
||||||
cell::{Cell, RefCell},
|
cell::{Cell, RefCell},
|
||||||
|
|
@ -110,6 +113,12 @@ impl<T: ToplevelNodeBase> ToplevelNode for T {
|
||||||
|
|
||||||
fn tl_change_extents(self: Rc<Self>, rect: &Rect) {
|
fn tl_change_extents(self: Rc<Self>, rect: &Rect) {
|
||||||
let data = self.tl_data();
|
let data = self.tl_data();
|
||||||
|
let prev = data.desired_extents.replace(*rect);
|
||||||
|
if prev.size() != rect.size() {
|
||||||
|
for sc in data.jay_screencasts.lock().values() {
|
||||||
|
sc.schedule_realloc();
|
||||||
|
}
|
||||||
|
}
|
||||||
if data.is_floating.get() {
|
if data.is_floating.get() {
|
||||||
data.float_width.set(rect.width());
|
data.float_width.set(rect.width());
|
||||||
data.float_height.set(rect.height());
|
data.float_height.set(rect.height());
|
||||||
|
|
@ -173,6 +182,8 @@ pub trait ToplevelNodeBase: Node {
|
||||||
fn tl_restack_popups(&self) {
|
fn tl_restack_popups(&self) {
|
||||||
// nothing
|
// nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn tl_admits_children(&self) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct FullscreenedData {
|
pub struct FullscreenedData {
|
||||||
|
|
@ -196,6 +207,7 @@ pub struct ToplevelData {
|
||||||
pub title: RefCell<String>,
|
pub title: RefCell<String>,
|
||||||
pub parent: CloneCell<Option<Rc<dyn ContainingNode>>>,
|
pub parent: CloneCell<Option<Rc<dyn ContainingNode>>>,
|
||||||
pub pos: Cell<Rect>,
|
pub pos: Cell<Rect>,
|
||||||
|
pub desired_extents: Cell<Rect>,
|
||||||
pub seat_state: NodeSeatState,
|
pub seat_state: NodeSeatState,
|
||||||
pub wants_attention: Cell<bool>,
|
pub wants_attention: Cell<bool>,
|
||||||
pub requested_attention: Cell<bool>,
|
pub requested_attention: Cell<bool>,
|
||||||
|
|
@ -203,6 +215,9 @@ pub struct ToplevelData {
|
||||||
pub identifier: Cell<ToplevelIdentifier>,
|
pub identifier: Cell<ToplevelIdentifier>,
|
||||||
pub handles:
|
pub handles:
|
||||||
CopyHashMap<(ClientId, ExtForeignToplevelHandleV1Id), Rc<ExtForeignToplevelHandleV1>>,
|
CopyHashMap<(ClientId, ExtForeignToplevelHandleV1Id), Rc<ExtForeignToplevelHandleV1>>,
|
||||||
|
pub render_highlight: NumCell<u32>,
|
||||||
|
pub jay_toplevels: CopyHashMap<(ClientId, JayToplevelId), Rc<JayToplevel>>,
|
||||||
|
pub jay_screencasts: CopyHashMap<(ClientId, JayScreencastId), Rc<JayScreencast>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToplevelData {
|
impl ToplevelData {
|
||||||
|
|
@ -223,12 +238,16 @@ impl ToplevelData {
|
||||||
title: RefCell::new(title),
|
title: RefCell::new(title),
|
||||||
parent: Default::default(),
|
parent: Default::default(),
|
||||||
pos: Default::default(),
|
pos: Default::default(),
|
||||||
|
desired_extents: Default::default(),
|
||||||
seat_state: Default::default(),
|
seat_state: Default::default(),
|
||||||
wants_attention: Cell::new(false),
|
wants_attention: Cell::new(false),
|
||||||
requested_attention: Cell::new(false),
|
requested_attention: Cell::new(false),
|
||||||
app_id: Default::default(),
|
app_id: Default::default(),
|
||||||
identifier: Cell::new(toplevel_identifier()),
|
identifier: Cell::new(toplevel_identifier()),
|
||||||
handles: Default::default(),
|
handles: Default::default(),
|
||||||
|
render_highlight: Default::default(),
|
||||||
|
jay_toplevels: Default::default(),
|
||||||
|
jay_screencasts: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -266,6 +285,12 @@ impl ToplevelData {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn destroy_node(&self, node: &dyn Node) {
|
pub fn destroy_node(&self, node: &dyn Node) {
|
||||||
|
for (_, jay_tl) in self.jay_toplevels.lock().drain() {
|
||||||
|
jay_tl.destroy();
|
||||||
|
}
|
||||||
|
for (_, screencast) in self.jay_screencasts.lock().drain() {
|
||||||
|
screencast.do_destroy();
|
||||||
|
}
|
||||||
self.identifier.set(toplevel_identifier());
|
self.identifier.set(toplevel_identifier());
|
||||||
{
|
{
|
||||||
let mut handles = self.handles.lock();
|
let mut handles = self.handles.lock();
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,8 @@ use {
|
||||||
text::TextTexture,
|
text::TextTexture,
|
||||||
tree::{
|
tree::{
|
||||||
container::ContainerNode, walker::NodeVisitor, ContainingNode, Direction,
|
container::ContainerNode, walker::NodeVisitor, ContainingNode, Direction,
|
||||||
FindTreeResult, FoundNode, Node, NodeId, NodeVisitorBase, OutputNode, StackedNode,
|
FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeVisitorBase, OutputNode,
|
||||||
ToplevelNode,
|
StackedNode, ToplevelNode,
|
||||||
},
|
},
|
||||||
utils::{
|
utils::{
|
||||||
clonecell::CloneCell,
|
clonecell::CloneCell,
|
||||||
|
|
@ -224,14 +224,20 @@ impl Node for WorkspaceNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node_find_tree_at(&self, x: i32, y: i32, tree: &mut Vec<FoundNode>) -> FindTreeResult {
|
fn node_find_tree_at(
|
||||||
|
&self,
|
||||||
|
x: i32,
|
||||||
|
y: i32,
|
||||||
|
tree: &mut Vec<FoundNode>,
|
||||||
|
usecase: FindTreeUsecase,
|
||||||
|
) -> FindTreeResult {
|
||||||
if let Some(n) = self.container.get() {
|
if let Some(n) = self.container.get() {
|
||||||
tree.push(FoundNode {
|
tree.push(FoundNode {
|
||||||
node: n.clone(),
|
node: n.clone(),
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
});
|
});
|
||||||
n.node_find_tree_at(x, y, tree);
|
n.node_find_tree_at(x, y, tree, usecase);
|
||||||
}
|
}
|
||||||
FindTreeResult::AcceptsInput
|
FindTreeResult::AcceptsInput
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ pub mod usr_jay_output;
|
||||||
pub mod usr_jay_pointer;
|
pub mod usr_jay_pointer;
|
||||||
pub mod usr_jay_render_ctx;
|
pub mod usr_jay_render_ctx;
|
||||||
pub mod usr_jay_screencast;
|
pub mod usr_jay_screencast;
|
||||||
|
pub mod usr_jay_select_toplevel;
|
||||||
|
pub mod usr_jay_toplevel;
|
||||||
pub mod usr_jay_workspace;
|
pub mod usr_jay_workspace;
|
||||||
pub mod usr_jay_workspace_watcher;
|
pub mod usr_jay_workspace_watcher;
|
||||||
pub mod usr_linux_buffer_params;
|
pub mod usr_linux_buffer_params;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
|
ifs::jay_compositor::Cap,
|
||||||
utils::{
|
utils::{
|
||||||
buffd::{MsgParser, MsgParserError},
|
buffd::{MsgParser, MsgParserError},
|
||||||
clonecell::CloneCell,
|
clonecell::CloneCell,
|
||||||
|
|
@ -9,6 +10,7 @@ use {
|
||||||
usr_ifs::{
|
usr_ifs::{
|
||||||
usr_jay_output::UsrJayOutput, usr_jay_pointer::UsrJayPointer,
|
usr_jay_output::UsrJayOutput, usr_jay_pointer::UsrJayPointer,
|
||||||
usr_jay_render_ctx::UsrJayRenderCtx, usr_jay_screencast::UsrJayScreencast,
|
usr_jay_render_ctx::UsrJayRenderCtx, usr_jay_screencast::UsrJayScreencast,
|
||||||
|
usr_jay_select_toplevel::UsrJaySelectToplevel,
|
||||||
usr_jay_workspace_watcher::UsrJayWorkspaceWatcher, usr_wl_output::UsrWlOutput,
|
usr_jay_workspace_watcher::UsrJayWorkspaceWatcher, usr_wl_output::UsrWlOutput,
|
||||||
usr_wl_seat::UsrWlSeat,
|
usr_wl_seat::UsrWlSeat,
|
||||||
},
|
},
|
||||||
|
|
@ -16,13 +18,14 @@ use {
|
||||||
UsrCon,
|
UsrCon,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
std::rc::Rc,
|
std::{cell::Cell, rc::Rc},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct UsrJayCompositor {
|
pub struct UsrJayCompositor {
|
||||||
pub id: JayCompositorId,
|
pub id: JayCompositorId,
|
||||||
pub con: Rc<UsrCon>,
|
pub con: Rc<UsrCon>,
|
||||||
pub owner: CloneCell<Option<Rc<dyn UsrJayCompositorOwner>>>,
|
pub owner: CloneCell<Option<Rc<dyn UsrJayCompositorOwner>>>,
|
||||||
|
pub window_capture: Cell<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait UsrJayCompositorOwner {
|
pub trait UsrJayCompositorOwner {
|
||||||
|
|
@ -111,6 +114,21 @@ impl UsrJayCompositor {
|
||||||
jp
|
jp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn select_toplevel(&self, seat: &UsrWlSeat) -> Rc<UsrJaySelectToplevel> {
|
||||||
|
let sc = Rc::new(UsrJaySelectToplevel {
|
||||||
|
id: self.con.id(),
|
||||||
|
con: self.con.clone(),
|
||||||
|
owner: Default::default(),
|
||||||
|
});
|
||||||
|
self.con.request(SelectToplevel {
|
||||||
|
self_id: self.id,
|
||||||
|
id: sc.id,
|
||||||
|
seat: seat.id,
|
||||||
|
});
|
||||||
|
self.con.add_object(sc.clone());
|
||||||
|
sc
|
||||||
|
}
|
||||||
|
|
||||||
fn client_id(&self, parser: MsgParser<'_, '_>) -> Result<(), MsgParserError> {
|
fn client_id(&self, parser: MsgParser<'_, '_>) -> Result<(), MsgParserError> {
|
||||||
let ev: ClientId = self.con.parse(self, parser)?;
|
let ev: ClientId = self.con.parse(self, parser)?;
|
||||||
if let Some(owner) = self.owner.get() {
|
if let Some(owner) = self.owner.get() {
|
||||||
|
|
@ -126,6 +144,18 @@ impl UsrJayCompositor {
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn capabilities(&self, parser: MsgParser<'_, '_>) -> Result<(), MsgParserError> {
|
||||||
|
let ev: Capabilities = self.con.parse(self, parser)?;
|
||||||
|
for &cap in ev.cap {
|
||||||
|
match cap {
|
||||||
|
Cap::NONE => {}
|
||||||
|
Cap::WINDOW_CAPTURE => self.window_capture.set(true),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
usr_object_base! {
|
usr_object_base! {
|
||||||
|
|
@ -133,6 +163,7 @@ usr_object_base! {
|
||||||
|
|
||||||
CLIENT_ID => client_id,
|
CLIENT_ID => client_id,
|
||||||
SEAT => seat,
|
SEAT => seat,
|
||||||
|
CAPABILITIES => capabilities,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UsrObject for UsrJayCompositor {
|
impl UsrObject for UsrJayCompositor {
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,11 @@ use {
|
||||||
},
|
},
|
||||||
video::dmabuf::{DmaBuf, DmaBufPlane, PlaneVec},
|
video::dmabuf::{DmaBuf, DmaBufPlane, PlaneVec},
|
||||||
wire::{jay_screencast::*, JayScreencastId},
|
wire::{jay_screencast::*, JayScreencastId},
|
||||||
wl_usr::{usr_ifs::usr_jay_output::UsrJayOutput, usr_object::UsrObject, UsrCon},
|
wl_usr::{
|
||||||
|
usr_ifs::{usr_jay_output::UsrJayOutput, usr_jay_toplevel::UsrJayToplevel},
|
||||||
|
usr_object::UsrObject,
|
||||||
|
UsrCon,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
std::{cell::RefCell, mem, ops::DerefMut, rc::Rc},
|
std::{cell::RefCell, mem, ops::DerefMut, rc::Rc},
|
||||||
thiserror::Error,
|
thiserror::Error,
|
||||||
|
|
@ -60,6 +64,13 @@ impl UsrJayScreencast {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_toplevel(&self, tl: &UsrJayToplevel) {
|
||||||
|
self.con.request(SetToplevel {
|
||||||
|
self_id: self.id,
|
||||||
|
id: tl.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_allow_all_workspaces(&self, allow_all: bool) {
|
pub fn set_allow_all_workspaces(&self, allow_all: bool) {
|
||||||
self.con.request(SetAllowAllWorkspaces {
|
self.con.request(SetAllowAllWorkspaces {
|
||||||
self_id: self.id,
|
self_id: self.id,
|
||||||
|
|
|
||||||
71
src/wl_usr/usr_ifs/usr_jay_select_toplevel.rs
Normal file
71
src/wl_usr/usr_ifs/usr_jay_select_toplevel.rs
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
utils::{
|
||||||
|
buffd::{MsgParser, MsgParserError},
|
||||||
|
clonecell::CloneCell,
|
||||||
|
},
|
||||||
|
wire::{jay_select_toplevel::*, JaySelectToplevelId},
|
||||||
|
wl_usr::{usr_ifs::usr_jay_toplevel::UsrJayToplevel, usr_object::UsrObject, UsrCon},
|
||||||
|
},
|
||||||
|
std::rc::Rc,
|
||||||
|
thiserror::Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct UsrJaySelectToplevel {
|
||||||
|
pub id: JaySelectToplevelId,
|
||||||
|
pub con: Rc<UsrCon>,
|
||||||
|
pub owner: CloneCell<Option<Rc<dyn UsrJaySelectToplevelOwner>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait UsrJaySelectToplevelOwner {
|
||||||
|
fn done(&self, toplevel: Option<Rc<UsrJayToplevel>>);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UsrJaySelectToplevel {
|
||||||
|
fn done(&self, parser: MsgParser<'_, '_>) -> Result<(), UsrJaySelectToplevelError> {
|
||||||
|
let ev: Done = self.con.parse(self, parser)?;
|
||||||
|
let tl = if ev.id.is_none() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let tl = Rc::new(UsrJayToplevel {
|
||||||
|
id: ev.id,
|
||||||
|
con: self.con.clone(),
|
||||||
|
owner: Default::default(),
|
||||||
|
});
|
||||||
|
self.con.add_object(tl.clone());
|
||||||
|
Some(tl)
|
||||||
|
};
|
||||||
|
match self.owner.get() {
|
||||||
|
Some(owner) => owner.done(tl),
|
||||||
|
_ => {
|
||||||
|
if let Some(tl) = tl {
|
||||||
|
self.con.remove_obj(&*tl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.con.remove_obj(self);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
usr_object_base! {
|
||||||
|
UsrJaySelectToplevel, JaySelectToplevel;
|
||||||
|
|
||||||
|
DONE => done,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UsrObject for UsrJaySelectToplevel {
|
||||||
|
fn destroy(&self) {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
fn break_loops(&self) {
|
||||||
|
self.owner.take();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum UsrJaySelectToplevelError {
|
||||||
|
#[error("Parsing failed")]
|
||||||
|
MsgParserError(#[from] MsgParserError),
|
||||||
|
}
|
||||||
47
src/wl_usr/usr_ifs/usr_jay_toplevel.rs
Normal file
47
src/wl_usr/usr_ifs/usr_jay_toplevel.rs
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
utils::{
|
||||||
|
buffd::{MsgParser, MsgParserError},
|
||||||
|
clonecell::CloneCell,
|
||||||
|
},
|
||||||
|
wire::{jay_toplevel::*, JayToplevelId},
|
||||||
|
wl_usr::{usr_object::UsrObject, UsrCon},
|
||||||
|
},
|
||||||
|
std::rc::Rc,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct UsrJayToplevel {
|
||||||
|
pub id: JayToplevelId,
|
||||||
|
pub con: Rc<UsrCon>,
|
||||||
|
pub owner: CloneCell<Option<Rc<dyn UsrJayToplevelOwner>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait UsrJayToplevelOwner {
|
||||||
|
fn destroyed(&self) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UsrJayToplevel {
|
||||||
|
fn destroyed(&self, parser: MsgParser<'_, '_>) -> Result<(), MsgParserError> {
|
||||||
|
let _ev: Destroyed = self.con.parse(self, parser)?;
|
||||||
|
if let Some(owner) = self.owner.get() {
|
||||||
|
owner.destroyed();
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
usr_object_base! {
|
||||||
|
UsrJayToplevel, JayToplevel;
|
||||||
|
|
||||||
|
DESTROYED => destroyed,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UsrObject for UsrJayToplevel {
|
||||||
|
fn destroy(&self) {
|
||||||
|
self.con.request(Destroy { self_id: self.id });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn break_loops(&self) {
|
||||||
|
self.owner.set(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -139,6 +139,7 @@ pub struct Theme {
|
||||||
pub separator_color: Option<Color>,
|
pub separator_color: Option<Color>,
|
||||||
pub unfocused_title_bg_color: Option<Color>,
|
pub unfocused_title_bg_color: Option<Color>,
|
||||||
pub unfocused_title_text_color: Option<Color>,
|
pub unfocused_title_text_color: Option<Color>,
|
||||||
|
pub highlight_color: Option<Color>,
|
||||||
pub border_width: Option<i32>,
|
pub border_width: Option<i32>,
|
||||||
pub title_height: Option<i32>,
|
pub title_height: Option<i32>,
|
||||||
pub font: Option<String>,
|
pub font: Option<String>,
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ impl Parser for ThemeParser<'_> {
|
||||||
separator_color,
|
separator_color,
|
||||||
unfocused_title_bg_color,
|
unfocused_title_bg_color,
|
||||||
unfocused_title_text_color,
|
unfocused_title_text_color,
|
||||||
|
highlight_color,
|
||||||
border_width,
|
border_width,
|
||||||
title_height,
|
title_height,
|
||||||
font,
|
font,
|
||||||
|
|
@ -77,6 +78,7 @@ impl Parser for ThemeParser<'_> {
|
||||||
opt(val("separator-color")),
|
opt(val("separator-color")),
|
||||||
opt(val("unfocused-title-bg-color")),
|
opt(val("unfocused-title-bg-color")),
|
||||||
opt(val("unfocused-title-text-color")),
|
opt(val("unfocused-title-text-color")),
|
||||||
|
opt(val("highlight-color")),
|
||||||
recover(opt(s32("border-width"))),
|
recover(opt(s32("border-width"))),
|
||||||
recover(opt(s32("title-height"))),
|
recover(opt(s32("title-height"))),
|
||||||
recover(opt(str("font"))),
|
recover(opt(str("font"))),
|
||||||
|
|
@ -111,6 +113,7 @@ impl Parser for ThemeParser<'_> {
|
||||||
separator_color: color!(separator_color),
|
separator_color: color!(separator_color),
|
||||||
unfocused_title_bg_color: color!(unfocused_title_bg_color),
|
unfocused_title_bg_color: color!(unfocused_title_bg_color),
|
||||||
unfocused_title_text_color: color!(unfocused_title_text_color),
|
unfocused_title_text_color: color!(unfocused_title_text_color),
|
||||||
|
highlight_color: color!(highlight_color),
|
||||||
border_width: border_width.despan(),
|
border_width: border_width.despan(),
|
||||||
title_height: title_height.despan(),
|
title_height: title_height.despan(),
|
||||||
font: font.map(|f| f.value.to_string()),
|
font: font.map(|f| f.value.to_string()),
|
||||||
|
|
|
||||||
|
|
@ -665,6 +665,7 @@ impl State {
|
||||||
color!(SEPARATOR_COLOR, separator_color);
|
color!(SEPARATOR_COLOR, separator_color);
|
||||||
color!(UNFOCUSED_TITLE_BACKGROUND_COLOR, unfocused_title_bg_color);
|
color!(UNFOCUSED_TITLE_BACKGROUND_COLOR, unfocused_title_bg_color);
|
||||||
color!(UNFOCUSED_TITLE_TEXT_COLOR, unfocused_title_text_color);
|
color!(UNFOCUSED_TITLE_TEXT_COLOR, unfocused_title_text_color);
|
||||||
|
color!(HIGHLIGHT_COLOR, highlight_color);
|
||||||
macro_rules! size {
|
macro_rules! size {
|
||||||
($sized:ident, $field:ident) => {
|
($sized:ident, $field:ident) => {
|
||||||
if let Some(size) = theme.$field {
|
if let Some(size) = theme.$field {
|
||||||
|
|
|
||||||
|
|
@ -1166,6 +1166,10 @@
|
||||||
"description": "The text color of unfocused titles.",
|
"description": "The text color of unfocused titles.",
|
||||||
"$ref": "#/$defs/Color"
|
"$ref": "#/$defs/Color"
|
||||||
},
|
},
|
||||||
|
"highlight-color": {
|
||||||
|
"description": "Color used to highlight parts of the UI.",
|
||||||
|
"$ref": "#/$defs/Color"
|
||||||
|
},
|
||||||
"border-width": {
|
"border-width": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "The width of borders between windows.",
|
"description": "The width of borders between windows.",
|
||||||
|
|
|
||||||
|
|
@ -2472,6 +2472,12 @@ The table has the following fields:
|
||||||
|
|
||||||
The value of this field should be a [Color](#types-Color).
|
The value of this field should be a [Color](#types-Color).
|
||||||
|
|
||||||
|
- `highlight-color` (optional):
|
||||||
|
|
||||||
|
Color used to highlight parts of the UI.
|
||||||
|
|
||||||
|
The value of this field should be a [Color](#types-Color).
|
||||||
|
|
||||||
- `border-width` (optional):
|
- `border-width` (optional):
|
||||||
|
|
||||||
The width of borders between windows.
|
The width of borders between windows.
|
||||||
|
|
|
||||||
|
|
@ -1602,6 +1602,10 @@ Theme:
|
||||||
ref: Color
|
ref: Color
|
||||||
required: false
|
required: false
|
||||||
description: The text color of unfocused titles.
|
description: The text color of unfocused titles.
|
||||||
|
highlight-color:
|
||||||
|
ref: Color
|
||||||
|
required: false
|
||||||
|
description: Color used to highlight parts of the UI.
|
||||||
border-width:
|
border-width:
|
||||||
kind: number
|
kind: number
|
||||||
integer_only: true
|
integer_only: true
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,11 @@ request take_screenshot2 {
|
||||||
include_cursor: u32,
|
include_cursor: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
request select_toplevel {
|
||||||
|
id: id(jay_select_toplevel),
|
||||||
|
seat: id(wl_seat),
|
||||||
|
}
|
||||||
|
|
||||||
# events
|
# events
|
||||||
|
|
||||||
event client_id {
|
event client_id {
|
||||||
|
|
@ -88,3 +93,7 @@ event seat {
|
||||||
id: u32,
|
id: u32,
|
||||||
name: str,
|
name: str,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
event capabilities {
|
||||||
|
cap: array(pod(u16)),
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,10 @@ request release_buffer {
|
||||||
idx: u32,
|
idx: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
request set_toplevel {
|
||||||
|
id: id(jay_toplevel),
|
||||||
|
}
|
||||||
|
|
||||||
# events
|
# events
|
||||||
|
|
||||||
event plane {
|
event plane {
|
||||||
|
|
|
||||||
3
wire/jay_select_toplevel.txt
Normal file
3
wire/jay_select_toplevel.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
event done {
|
||||||
|
id: id(jay_toplevel),
|
||||||
|
}
|
||||||
5
wire/jay_toplevel.txt
Normal file
5
wire/jay_toplevel.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
request destroy {
|
||||||
|
}
|
||||||
|
|
||||||
|
event destroyed {
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue