1
0
Fork 0
forked from wry/wry

wayland: implement jay-tray-v1

This commit is contained in:
Julian Orth 2024-10-17 16:05:19 +02:00
parent 18bddbc987
commit 8c3cd97ae3
28 changed files with 979 additions and 43 deletions

View file

@ -273,6 +273,7 @@ fn start_compositor2(
ui_drag_threshold_squared: Cell::new(10),
toplevels: Default::default(),
const_40hz_latch: Default::default(),
tray_item_ids: Default::default(),
});
state.tracker.register(ClientId::from_raw(0));
create_dummy_output(&state);
@ -586,6 +587,8 @@ fn create_dummy_output(state: &Rc<State>) {
flip_margin_ns: Default::default(),
ext_copy_sessions: Default::default(),
before_latch_event: Default::default(),
tray_start_rel: Default::default(),
tray_items: Default::default(),
});
let dummy_workspace = Rc::new(WorkspaceNode {
id: state.node_ids.next(),

View file

@ -261,11 +261,11 @@ impl Globals {
pub fn remove<T: RemovableWaylandGlobal>(
&self,
state: &State,
global: &T,
global: &Rc<T>,
) -> Result<(), GlobalsError> {
let _global = self.take(global.name(), true)?;
global.remove(self);
let replacement = global.create_replacement();
let replacement = global.clone().create_replacement();
assert_eq!(global.name(), replacement.name());
assert_eq!(global.interface().0, replacement.interface().0);
self.removed.set(global.name(), replacement);
@ -360,5 +360,5 @@ pub trait WaylandGlobal: Global + 'static {
}
pub trait RemovableWaylandGlobal: WaylandGlobal {
fn create_replacement(&self) -> Rc<dyn Global>;
fn create_replacement(self: Rc<Self>) -> Rc<dyn Global>;
}

View file

@ -26,6 +26,7 @@ pub mod jay_seat_events;
pub mod jay_select_toplevel;
pub mod jay_select_workspace;
pub mod jay_toplevel;
pub mod jay_tray_v1;
pub mod jay_workspace;
pub mod jay_workspace_watcher;
pub mod jay_xwayland;

109
src/ifs/jay_tray_v1.rs Normal file
View file

@ -0,0 +1,109 @@
use {
crate::{
client::{Client, ClientError},
globals::{Global, GlobalName, RemovableWaylandGlobal},
ifs::{
wl_output::OutputGlobalOpt,
wl_surface::tray::jay_tray_item_v1::{JayTrayItemV1, JayTrayItemV1Error},
},
leaks::Tracker,
object::{Object, Version},
wire::{jay_tray_v1::*, JayTrayV1Id},
},
std::rc::Rc,
thiserror::Error,
};
pub struct JayTrayV1Global {
pub name: GlobalName,
pub output: Rc<OutputGlobalOpt>,
}
pub struct JayTrayV1 {
pub id: JayTrayV1Id,
pub client: Rc<Client>,
pub tracker: Tracker<Self>,
pub version: Version,
pub output: Rc<OutputGlobalOpt>,
}
impl JayTrayV1Global {
fn bind_(
self: Rc<Self>,
id: JayTrayV1Id,
client: &Rc<Client>,
version: Version,
) -> Result<(), JayTrayManagerV1Error> {
let obj = Rc::new(JayTrayV1 {
id,
client: client.clone(),
tracker: Default::default(),
version,
output: self.output.clone(),
});
track!(client, obj);
client.add_client_obj(&obj)?;
Ok(())
}
}
global_base!(JayTrayV1Global, JayTrayV1, JayTrayManagerV1Error);
impl Global for JayTrayV1Global {
fn singleton(&self) -> bool {
false
}
fn version(&self) -> u32 {
1
}
}
simple_add_global!(JayTrayV1Global);
impl RemovableWaylandGlobal for JayTrayV1Global {
fn create_replacement(self: Rc<Self>) -> Rc<dyn Global> {
self
}
}
impl JayTrayV1RequestHandler for JayTrayV1 {
type Error = JayTrayManagerV1Error;
fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.client.remove_obj(self)?;
Ok(())
}
fn get_tray_item(&self, req: GetTrayItem, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let surface = self.client.lookup(req.surface)?;
let fs = Rc::new(JayTrayItemV1::new(
req.id,
self.version,
&surface,
&self.output,
));
track!(self.client, fs);
fs.install()?;
self.client.add_client_obj(&fs)?;
Ok(())
}
}
object_base! {
self = JayTrayV1;
version = self.version;
}
impl Object for JayTrayV1 {}
simple_add_obj!(JayTrayV1);
#[derive(Debug, Error)]
pub enum JayTrayManagerV1Error {
#[error(transparent)]
ClientError(Box<ClientError>),
#[error(transparent)]
ExtTrayItemV1Error(#[from] JayTrayItemV1Error),
}
efrom!(JayTrayManagerV1Error, ClientError);

View file

@ -50,7 +50,7 @@ impl Global for RemovedOutputGlobal {
simple_add_global!(RemovedOutputGlobal);
impl RemovableWaylandGlobal for WlOutputGlobal {
fn create_replacement(&self) -> Rc<dyn Global> {
fn create_replacement(self: Rc<Self>) -> Rc<dyn Global> {
Rc::new(RemovedOutputGlobal { name: self.name })
}
}

View file

@ -23,6 +23,7 @@ pub mod zwp_virtual_keyboard_v1;
use {
crate::{
async_engine::SpawnedFuture,
backend::KeyState,
client::{Client, ClientError, ClientId},
cursor_user::{CursorUser, CursorUserGroup, CursorUserOwner},
ei::ei_ifs::ei_seat::EiSeat,
@ -64,7 +65,12 @@ use {
zwp_pointer_gesture_swipe_v1::ZwpPointerGestureSwipeV1,
zwp_relative_pointer_v1::ZwpRelativePointerV1,
},
wl_surface::{dnd_icon::DndIcon, WlSurface},
wl_surface::{
dnd_icon::DndIcon,
tray::{DynTrayItem, TrayItemId},
xdg_surface::xdg_popup::XdgPopup,
WlSurface,
},
xdg_toplevel_drag_v1::XdgToplevelDragV1,
},
leaks::Tracker,
@ -82,8 +88,8 @@ use {
},
wire::{
wl_seat::*, ExtIdleNotificationV1Id, WlDataDeviceId, WlKeyboardId, WlPointerId,
WlSeatId, WlTouchId, ZwlrDataControlDeviceV1Id, ZwpPrimarySelectionDeviceV1Id,
ZwpRelativePointerV1Id, ZwpTextInputV3Id,
WlSeatId, WlTouchId, XdgPopupId, ZwlrDataControlDeviceV1Id,
ZwpPrimarySelectionDeviceV1Id, ZwpRelativePointerV1Id, ZwpTextInputV3Id,
},
wire_ei::EiSeatId,
xkbcommon::{DynKeyboardState, KeyboardState, KeymapId, XkbKeymap, XkbState},
@ -201,6 +207,7 @@ pub struct WlSeatGlobal {
ei_seats: CopyHashMap<(ClientId, EiSeatId), Rc<EiSeat>>,
ui_drag_highlight: Cell<Option<Rect>>,
keyboard_node_serial: Cell<u64>,
tray_popups: CopyHashMap<(TrayItemId, XdgPopupId), Rc<dyn DynTrayItem>>,
}
const CHANGE_CURSOR_MOVED: u32 = 1 << 0;
@ -273,6 +280,7 @@ impl WlSeatGlobal {
tablet: Default::default(),
ei_seats: Default::default(),
ui_drag_highlight: Default::default(),
tray_popups: Default::default(),
});
slf.pointer_cursor.set_owner(slf.clone());
let seat = slf.clone();
@ -1042,7 +1050,37 @@ impl WlSeatGlobal {
});
}
#[expect(dead_code)]
pub fn add_tray_item_popup<T: DynTrayItem>(&self, item: &Rc<T>, popup: &Rc<XdgPopup>) {
self.tray_popups
.set((item.data().tray_item_id, popup.id), item.clone());
}
pub fn remove_tray_item_popup<T: DynTrayItem>(&self, item: &T, popup: &Rc<XdgPopup>) {
self.tray_popups
.remove(&(item.data().tray_item_id, popup.id));
}
fn handle_node_button(
self: &Rc<Self>,
node: Rc<dyn Node>,
time_usec: u64,
button: u32,
state: KeyState,
serial: u64,
) {
if self.tray_popups.is_not_empty() && state == KeyState::Pressed {
let id = node.node_tray_item();
self.tray_popups.lock().retain(|&(tray_item_id, _), item| {
let retain = Some(tray_item_id) == id;
if !retain {
item.destroy_popups();
}
retain
})
}
node.node_on_button(self, time_usec, button, state, serial);
}
pub fn handle_focus_request(self: &Rc<Self>, client: &Client, node: Rc<dyn Node>, serial: u64) {
let Some(max_serial) = client.focus_stealing_serial.get() else {
return;

View file

@ -336,7 +336,7 @@ impl<T: SimplePointerOwnerUsecase> PointerOwner for SimplePointerOwner<T> {
serial,
}));
pn.node_seat_state().add_pointer_grab(seat);
pn.node_on_button(seat, time_usec, button, state, serial);
seat.handle_node_button(pn, time_usec, button, state, serial);
}
fn axis_node(&self, seat: &Rc<WlSeatGlobal>) -> Option<Rc<dyn Node>> {
@ -448,9 +448,7 @@ impl<T: SimplePointerOwnerUsecase> PointerOwner for SimpleGrabPointerOwner<T> {
}
}
let serial = seat.state.next_serial(self.node.node_client().as_deref());
self.node
.clone()
.node_on_button(seat, time_usec, button, state, serial);
seat.handle_node_button(self.node.clone(), time_usec, button, state, serial);
}
fn axis_node(&self, _seat: &Rc<WlSeatGlobal>) -> Option<Rc<dyn Node>> {

View file

@ -2,6 +2,7 @@ pub mod commit_timeline;
pub mod cursor;
pub mod dnd_icon;
pub mod ext_session_lock_surface_v1;
pub mod tray;
pub mod wl_subsurface;
pub mod wp_alpha_modifier_surface_v1;
pub mod wp_commit_timer_v1;
@ -46,6 +47,7 @@ use {
commit_timeline::{ClearReason, CommitTimeline, CommitTimelineError},
cursor::CursorSurface,
dnd_icon::DndIcon,
tray::TrayItemId,
wl_subsurface::{PendingSubsurfaceData, SubsurfaceId, WlSubsurface},
wp_alpha_modifier_surface_v1::WpAlphaModifierSurfaceV1,
wp_commit_timer_v1::WpCommitTimerV1,
@ -126,6 +128,7 @@ pub enum SurfaceRole {
XSurface,
ExtSessionLockSurface,
InputPopup,
TrayItem,
}
impl SurfaceRole {
@ -140,6 +143,7 @@ impl SurfaceRole {
SurfaceRole::XSurface => "xwayland surface",
SurfaceRole::ExtSessionLockSurface => "ext_session_lock_surface",
SurfaceRole::InputPopup => "input_popup_surface",
SurfaceRole::TrayItem => "tray_item",
}
}
}
@ -412,6 +416,10 @@ trait SurfaceExt {
) -> Result<(), WlSurfaceError> {
surface.pending.borrow_mut().consume_child(child, consume)
}
fn tray_item(self: Rc<Self>) -> Option<TrayItemId> {
None
}
}
pub struct NoneSurfaceExt;
@ -450,6 +458,7 @@ struct PendingState {
fifo_barrier_set: bool,
fifo_barrier_wait: bool,
commit_time: Option<u64>,
tray_item_ack_serial: Option<u32>,
}
struct AttachedSubsurfaceState {
@ -501,6 +510,7 @@ impl PendingState {
opt!(content_type);
opt!(alpha_multiplier);
opt!(commit_time);
opt!(tray_item_ack_serial);
{
let (dx1, dy1) = self.offset;
let (dx2, dy2) = mem::take(&mut next.offset);
@ -1721,6 +1731,10 @@ impl Node for WlSurface {
self.toplevel.get()
}
fn node_tray_item(&self) -> Option<TrayItemId> {
self.ext.get().tray_item()
}
fn node_on_key(
&self,
seat: &WlSeatGlobal,

406
src/ifs/wl_surface/tray.rs Normal file
View file

@ -0,0 +1,406 @@
use {
crate::{
client::{Client, ClientError, ClientId},
ifs::{
wl_output::OutputGlobalOpt,
wl_seat::{NodeSeatState, WlSeatGlobal},
wl_surface::{
xdg_surface::xdg_popup::{XdgPopup, XdgPopupParent},
PendingState, SurfaceExt, SurfaceRole, WlSurface, WlSurfaceError,
},
},
rect::Rect,
tree::{
FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeVisitor, OutputNode,
StackedNode,
},
utils::{
copyhashmap::CopyHashMap,
hash_map_ext::HashMapExt,
linkedlist::{LinkedList, LinkedNode},
numcell::NumCell,
},
wire::{WlSeatId, XdgPopupId},
},
std::{
cell::{Cell, RefCell},
rc::Rc,
},
thiserror::Error,
};
pub mod jay_tray_item_v1;
tree_id!(TrayItemNodeId);
linear_ids!(TrayItemIds, TrayItemId, u64);
pub struct TrayItemData {
node_id: TrayItemNodeId,
pub tray_item_id: TrayItemId,
seat_state: NodeSeatState,
client: Rc<Client>,
visible: Cell<bool>,
pub surface: Rc<WlSurface>,
output: Rc<OutputGlobalOpt>,
attached: Cell<bool>,
sent_serial: NumCell<u32>,
ack_serial: NumCell<u32>,
linked_node: Cell<Option<LinkedNode<Rc<dyn DynTrayItem>>>>,
abs_pos: Cell<Rect>,
pub rel_pos: Cell<Rect>,
}
impl TrayItemData {
fn new(surface: &Rc<WlSurface>, output: &Rc<OutputGlobalOpt>) -> Self {
TrayItemData {
node_id: surface.client.state.node_ids.next(),
tray_item_id: surface.client.state.tray_item_ids.next(),
seat_state: Default::default(),
client: surface.client.clone(),
visible: Cell::new(surface.client.state.root_visible()),
surface: surface.clone(),
output: output.clone(),
attached: Default::default(),
sent_serial: Default::default(),
ack_serial: Default::default(),
linked_node: Default::default(),
abs_pos: Default::default(),
rel_pos: Default::default(),
}
}
pub fn find_tree_at(&self, x: i32, y: i32, tree: &mut Vec<FoundNode>) -> FindTreeResult {
self.surface.find_tree_at_(x, y, tree)
}
}
pub trait DynTrayItem: Node {
fn send_current_configure(&self);
fn data(&self) -> &TrayItemData;
fn into_node(self: Rc<Self>) -> Rc<dyn Node>;
fn set_position(&self, abs_pos: Rect, rel_pos: Rect);
fn destroy_popups(&self);
fn destroy_node(&self);
fn set_visible(&self, visible: bool);
}
impl<T: TrayItem> DynTrayItem for T {
fn send_current_configure(&self) {
<Self as TrayItem>::send_current_configure(self)
}
fn data(&self) -> &TrayItemData {
<Self as TrayItem>::data(self)
}
fn into_node(self: Rc<Self>) -> Rc<dyn Node> {
self
}
fn set_position(&self, abs_pos: Rect, rel_pos: Rect) {
let data = self.data();
data.surface
.set_absolute_position(abs_pos.x1(), abs_pos.y1());
data.rel_pos.set(rel_pos);
if data.abs_pos.replace(abs_pos) != abs_pos {
for popup in self.popups().lock().values() {
popup.popup.update_absolute_position();
}
}
}
fn destroy_popups(&self) {
for popup in self.popups().lock().drain_values() {
popup.popup.destroy_node();
}
}
fn destroy_node(&self) {
let data = self.data();
data.linked_node.take();
data.attached.set(false);
self.destroy_popups();
data.surface.destroy_node();
data.seat_state.destroy_node(self);
data.client.state.tree_changed();
if let Some(node) = data.output.node() {
node.update_tray_positions();
}
}
fn set_visible(&self, visible: bool) {
let data = self.data();
data.visible.set(visible);
let visible = visible && data.surface.buffer.is_some();
data.surface.set_visible(visible);
if !visible {
self.destroy_popups();
}
}
}
trait TrayItem: Sized + 'static {
fn send_initial_configure(&self);
fn send_current_configure(&self);
fn data(&self) -> &TrayItemData;
fn popups(&self) -> &CopyHashMap<XdgPopupId, Rc<Popup<Self>>>;
fn visit(self: Rc<Self>, visitor: &mut dyn NodeVisitor);
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum FocusHint {
None,
OnDemand,
Immediate,
}
struct Popup<T> {
parent: Rc<T>,
popup: Rc<XdgPopup>,
seat: Rc<WlSeatGlobal>,
serial: u64,
focus: FocusHint,
stack: Rc<LinkedList<Rc<dyn StackedNode>>>,
stack_link: RefCell<Option<LinkedNode<Rc<dyn StackedNode>>>>,
}
impl<T: TrayItem> XdgPopupParent for Popup<T> {
fn position(&self) -> Rect {
self.parent.data().abs_pos.get()
}
fn remove_popup(&self) {
self.seat.remove_tray_item_popup(&*self.parent, &self.popup);
self.parent.popups().remove(&self.popup.id);
}
fn output(&self) -> Rc<OutputNode> {
self.parent.data().surface.output.get()
}
fn has_workspace_link(&self) -> bool {
false
}
fn post_commit(&self) {
let mut dl = self.stack_link.borrow_mut();
let surface = &self.popup.xdg.surface;
let state = &surface.client.state;
if surface.buffer.is_some() {
if dl.is_none() {
let data = self.parent.data();
if data.surface.visible.get() {
self.popup.set_visible(true);
*dl = Some(self.stack.add_last(self.popup.clone()));
state.tree_changed();
if self.focus == FocusHint::Immediate {
self.seat.handle_focus_request(
&data.client,
self.popup.xdg.surface.clone(),
self.serial,
);
}
} else {
self.popup.destroy_node();
}
}
} else {
if dl.take().is_some() {
drop(dl);
self.popup.set_visible(false);
self.popup.destroy_node();
}
}
}
fn tray_item(&self) -> Option<TrayItemId> {
Some(self.parent.data().tray_item_id)
}
fn allow_popup_focus(&self) -> bool {
match self.focus {
FocusHint::None => false,
FocusHint::OnDemand => true,
FocusHint::Immediate => true,
}
}
}
impl<T: TrayItem> SurfaceExt for T {
fn before_apply_commit(
self: Rc<Self>,
pending: &mut PendingState,
) -> Result<(), WlSurfaceError> {
if let Some(serial) = pending.tray_item_ack_serial.take() {
self.data().ack_serial.set(serial);
}
Ok(())
}
fn after_apply_commit(self: Rc<Self>) {
let data = self.data();
if data.surface.visible.get() {
if data.surface.buffer.is_none() {
self.destroy_node();
}
} else {
if data.ack_serial.get() != data.sent_serial.get() {
return;
}
if data.surface.buffer.is_some() {
data.surface.set_visible(data.visible.get());
if let Some(node) = data.output.node() {
if !data.attached.replace(true) {
let link = node.tray_items.add_last(self.clone());
data.linked_node.set(Some(link));
node.update_tray_positions();
}
}
}
}
}
fn extents_changed(&self) {
let data = self.data();
if data.surface.visible.get() {
data.client.state.tree_changed();
}
}
fn tray_item(self: Rc<Self>) -> Option<TrayItemId> {
Some(self.data().tray_item_id)
}
}
impl<T: TrayItem> Node for T {
fn node_id(&self) -> NodeId {
self.data().node_id.into()
}
fn node_seat_state(&self) -> &NodeSeatState {
&self.data().seat_state
}
fn node_visit(self: Rc<Self>, visitor: &mut dyn NodeVisitor) {
self.visit(visitor);
}
fn node_visit_children(&self, visitor: &mut dyn NodeVisitor) {
self.data().surface.clone().node_visit(visitor);
}
fn node_visible(&self) -> bool {
self.data().surface.visible.get()
}
fn node_absolute_position(&self) -> Rect {
self.data().surface.node_absolute_position()
}
fn node_find_tree_at(
&self,
x: i32,
y: i32,
tree: &mut Vec<FoundNode>,
_usecase: FindTreeUsecase,
) -> FindTreeResult {
self.data().find_tree_at(x, y, tree)
}
fn node_client(&self) -> Option<Rc<Client>> {
Some(self.data().client.clone())
}
fn node_client_id(&self) -> Option<ClientId> {
Some(self.data().client.id)
}
}
fn install<T: TrayItem>(item: &Rc<T>) -> Result<(), TrayItemError> {
let data = item.data();
data.surface.set_role(SurfaceRole::TrayItem)?;
if data.surface.ext.get().is_some() {
return Err(TrayItemError::Exists);
}
data.surface.ext.set(item.clone());
data.surface.set_visible(false);
if let Some(node) = data.output.node() {
data.surface.set_output(&node);
item.send_initial_configure();
}
Ok(())
}
fn destroy<T: TrayItem>(item: &T) -> Result<(), TrayItemError> {
if item.popups().is_not_empty() {
return Err(TrayItemError::HasPopups);
}
item.destroy_node();
item.data().surface.unset_ext();
item.data().surface.set_visible(false);
Ok(())
}
fn ack_configure<T: TrayItem>(item: &T, serial: u32) {
item.data()
.surface
.pending
.borrow_mut()
.tray_item_ack_serial = Some(serial);
}
fn get_popup<T: TrayItem>(
item: &Rc<T>,
popup: XdgPopupId,
seat: WlSeatId,
serial: u32,
focus: FocusHint,
) -> Result<(), TrayItemError> {
let data = item.data();
let popup = data.client.lookup(popup)?;
let seat = data.client.lookup(seat)?;
let seat = &seat.global;
let Some(serial) = data.client.map_serial(serial) else {
return Err(TrayItemError::InvalidSerial);
};
if popup.parent.is_some() {
return Err(TrayItemError::PopupHasParent);
}
let Some(node) = data.output.node() else {
popup.destroy_node();
return Ok(());
};
seat.add_tray_item_popup(item, &popup);
let stack = data.client.state.root.stacked.clone();
popup.xdg.set_popup_stack(&stack);
popup.xdg.set_output(&node);
let user = Rc::new(Popup {
parent: item.clone(),
popup: popup.clone(),
seat: seat.clone(),
serial,
focus,
stack,
stack_link: Default::default(),
});
popup.parent.set(Some(user.clone()));
item.popups().set(popup.id, user);
Ok(())
}
#[derive(Debug, Error)]
pub enum TrayItemError {
#[error(transparent)]
ClientError(Box<ClientError>),
#[error("The surface already has a tray item role object")]
Exists,
#[error(transparent)]
WlSurfaceError(#[from] WlSurfaceError),
#[error("Popup already has a parent")]
PopupHasParent,
#[error("Surface still has popups")]
HasPopups,
#[error("The serial is not valid")]
InvalidSerial,
}
efrom!(TrayItemError, ClientError);

View file

@ -0,0 +1,153 @@
use {
crate::{
ifs::{
wl_output::OutputGlobalOpt,
wl_surface::{
tray::{
ack_configure, destroy, get_popup, install, DynTrayItem, FocusHint, Popup,
TrayItem, TrayItemData, TrayItemError,
},
WlSurface,
},
xdg_positioner::{ANCHOR_BOTTOM_LEFT, ANCHOR_BOTTOM_RIGHT},
},
leaks::Tracker,
object::{Object, Version},
tree::NodeVisitor,
utils::copyhashmap::CopyHashMap,
wire::{jay_tray_item_v1::*, JayTrayItemV1Id, XdgPopupId},
},
std::rc::Rc,
thiserror::Error,
};
pub struct JayTrayItemV1 {
id: JayTrayItemV1Id,
pub tracker: Tracker<Self>,
version: Version,
data: TrayItemData,
popups: CopyHashMap<XdgPopupId, Rc<Popup<Self>>>,
}
impl JayTrayItemV1 {
pub fn new(
id: JayTrayItemV1Id,
version: Version,
surface: &Rc<WlSurface>,
output: &Rc<OutputGlobalOpt>,
) -> Self {
Self {
id,
tracker: Default::default(),
version,
popups: Default::default(),
data: TrayItemData::new(surface, output),
}
}
pub fn install(self: &Rc<Self>) -> Result<(), JayTrayItemV1Error> {
install(self)?;
Ok(())
}
fn send_configure_size(&self, width: i32, height: i32) {
self.data.client.event(ConfigureSize {
self_id: self.id,
width,
height,
});
}
fn send_preferred_anchor(&self) {
self.data.client.event(PreferredAnchor {
self_id: self.id,
anchor: ANCHOR_BOTTOM_LEFT,
});
}
fn send_preferred_gravity(&self) {
self.data.client.event(PreferredGravity {
self_id: self.id,
gravity: ANCHOR_BOTTOM_RIGHT,
});
}
fn send_configure(&self) {
self.data.client.event(Configure {
self_id: self.id,
serial: self.data.sent_serial.add_fetch(1),
});
}
}
impl JayTrayItemV1RequestHandler for JayTrayItemV1 {
type Error = JayTrayItemV1Error;
fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
destroy(self)?;
Ok(())
}
fn ack_configure(&self, req: AckConfigure, _slf: &Rc<Self>) -> Result<(), Self::Error> {
ack_configure(self, req.serial);
Ok(())
}
fn get_popup(&self, req: GetPopup, slf: &Rc<Self>) -> Result<(), Self::Error> {
let focus = match req.keyboard_focus {
0 => FocusHint::None,
1 => FocusHint::OnDemand,
2 => FocusHint::Immediate,
n => return Err(JayTrayItemV1Error::InvalidFocusHint(n)),
};
get_popup(slf, req.popup, req.seat, req.serial, focus)?;
Ok(())
}
}
impl TrayItem for JayTrayItemV1 {
fn send_initial_configure(&self) {
self.send_preferred_anchor();
self.send_preferred_gravity();
<Self as TrayItem>::send_current_configure(self);
}
fn send_current_configure(&self) {
let size = self.data.client.state.tray_icon_size().max(1);
self.send_configure_size(size, size);
self.send_configure();
}
fn data(&self) -> &TrayItemData {
&self.data
}
fn popups(&self) -> &CopyHashMap<XdgPopupId, Rc<Popup<Self>>> {
&self.popups
}
fn visit(self: Rc<Self>, visitor: &mut dyn NodeVisitor) {
visitor.visit_tray_item(&self);
}
}
object_base! {
self = JayTrayItemV1;
version = self.version;
}
impl Object for JayTrayItemV1 {
fn break_loops(&self) {
self.destroy_node();
}
}
simple_add_obj!(JayTrayItemV1);
#[derive(Debug, Error)]
pub enum JayTrayItemV1Error {
#[error(transparent)]
TrayItemError(#[from] TrayItemError),
#[error("The focus hint {} is invalid", .0)]
InvalidFocusHint(u32),
}

View file

@ -6,6 +6,7 @@ use {
client::ClientError,
ifs::{
wl_surface::{
tray::TrayItemId,
xdg_surface::{
xdg_popup::{XdgPopup, XdgPopupError, XdgPopupParent},
xdg_toplevel::{XdgToplevel, WM_CAPABILITIES_SINCE},
@ -17,7 +18,7 @@ use {
leaks::Tracker,
object::Object,
rect::Rect,
tree::{FindTreeResult, FoundNode, OutputNode, StackedNode, WorkspaceNode},
tree::{FindTreeResult, FoundNode, Node, OutputNode, StackedNode, WorkspaceNode},
utils::{
clonecell::CloneCell,
copyhashmap::CopyHashMap,
@ -138,6 +139,10 @@ impl XdgPopupParent for Popup {
}
}
}
fn tray_item(&self) -> Option<TrayItemId> {
self.parent.clone().tray_item()
}
}
#[derive(Default, Debug)]
@ -174,6 +179,14 @@ pub trait XdgSurfaceExt: Debug {
fn geometry_changed(&self) {
// nothing
}
fn focus_node(&self) -> Option<Rc<dyn Node>> {
None
}
fn tray_item(&self) -> Option<TrayItemId> {
None
}
}
impl XdgSurface {
@ -526,6 +539,14 @@ impl SurfaceExt for XdgSurface {
fn extents_changed(&self) {
self.update_extents();
}
fn focus_node(&self) -> Option<Rc<dyn Node>> {
self.ext.get()?.focus_node()
}
fn tray_item(self: Rc<Self>) -> Option<TrayItemId> {
self.ext.get()?.tray_item()
}
}
#[derive(Debug, Error)]

View file

@ -5,7 +5,10 @@ use {
fixed::Fixed,
ifs::{
wl_seat::{tablet::TabletTool, NodeSeatState, WlSeatGlobal},
wl_surface::xdg_surface::{XdgSurface, XdgSurfaceError, XdgSurfaceExt},
wl_surface::{
tray::TrayItemId,
xdg_surface::{XdgSurface, XdgSurfaceError, XdgSurfaceExt},
},
xdg_positioner::{
XdgPositioned, XdgPositioner, CA_FLIP_X, CA_FLIP_Y, CA_RESIZE_X, CA_RESIZE_Y,
CA_SLIDE_X, CA_SLIDE_Y,
@ -41,6 +44,12 @@ pub trait XdgPopupParent {
fn output(&self) -> Rc<OutputNode>;
fn has_workspace_link(&self) -> bool;
fn post_commit(&self);
fn tray_item(&self) -> Option<TrayItemId> {
None
}
fn allow_popup_focus(&self) -> bool {
false
}
}
pub struct XdgPopup {
@ -392,6 +401,17 @@ impl XdgSurfaceExt for XdgPopup {
fn extents_changed(&self) {
self.xdg.surface.client.state.tree_changed();
}
fn focus_node(&self) -> Option<Rc<dyn Node>> {
if self.parent.get()?.allow_popup_focus() {
return Some(self.xdg.surface.clone());
}
None
}
fn tray_item(&self) -> Option<TrayItemId> {
self.parent.get()?.tray_item()
}
}
#[derive(Debug, Error)]

View file

@ -64,7 +64,7 @@ impl Global for RemovedWpDrmLeaseDeviceV1Global {
}
impl RemovableWaylandGlobal for WpDrmLeaseDeviceV1Global {
fn create_replacement(&self) -> Rc<dyn Global> {
fn create_replacement(self: Rc<Self>) -> Rc<dyn Global> {
Rc::new(RemovedWpDrmLeaseDeviceV1Global {
name: self.name,
bindings: Default::default(),

View file

@ -13,15 +13,15 @@ use {
const INVALID_INPUT: u32 = 0;
const NONE: u32 = 0;
const TOP: u32 = 1;
const BOTTOM: u32 = 2;
const LEFT: u32 = 3;
const RIGHT: u32 = 4;
const TOP_LEFT: u32 = 5;
const BOTTOM_LEFT: u32 = 6;
const TOP_RIGHT: u32 = 7;
const BOTTOM_RIGHT: u32 = 8;
pub const ANCHOR_NONE: u32 = 0;
pub const ANCHOR_TOP: u32 = 1;
pub const ANCHOR_BOTTOM: u32 = 2;
pub const ANCHOR_LEFT: u32 = 3;
pub const ANCHOR_RIGHT: u32 = 4;
pub const ANCHOR_TOP_LEFT: u32 = 5;
pub const ANCHOR_BOTTOM_LEFT: u32 = 6;
pub const ANCHOR_TOP_RIGHT: u32 = 7;
pub const ANCHOR_BOTTOM_RIGHT: u32 = 8;
bitflags! {
Edge: u32;
@ -34,15 +34,15 @@ bitflags! {
impl Edge {
fn from_enum(e: u32) -> Option<Self> {
let s = match e {
NONE => Self::none(),
TOP => E_TOP,
BOTTOM => E_BOTTOM,
LEFT => E_LEFT,
RIGHT => E_RIGHT,
TOP_LEFT => E_TOP | E_LEFT,
BOTTOM_LEFT => E_BOTTOM | E_LEFT,
TOP_RIGHT => E_TOP | E_RIGHT,
BOTTOM_RIGHT => E_BOTTOM | E_RIGHT,
ANCHOR_NONE => Self::none(),
ANCHOR_TOP => E_TOP,
ANCHOR_BOTTOM => E_BOTTOM,
ANCHOR_LEFT => E_LEFT,
ANCHOR_RIGHT => E_RIGHT,
ANCHOR_TOP_LEFT => E_TOP | E_LEFT,
ANCHOR_BOTTOM_LEFT => E_BOTTOM | E_LEFT,
ANCHOR_TOP_RIGHT => E_TOP | E_RIGHT,
ANCHOR_BOTTOM_RIGHT => E_BOTTOM | E_RIGHT,
_ => return None,
};
Some(s)

View file

@ -148,6 +148,14 @@ impl Renderer<'_> {
);
}
}
for item in output.tray_items.iter() {
let data = item.data();
if data.surface.buffer.is_some() {
let rect = data.rel_pos.get().move_(x, y);
let bounds = self.base.scale_rect(rect);
self.render_surface(&data.surface, rect.x1(), rect.y1(), Some(&bounds));
}
}
}
if let Some(ws) = output.workspace.get() {
self.render_workspace(&ws, x, y + th + 1);

View file

@ -47,6 +47,7 @@ use {
SeatIds, WlSeatGlobal,
},
wl_surface::{
tray::TrayItemIds,
wl_subsurface::SubsurfaceIds,
zwp_idle_inhibitor_v1::{IdleInhibitorId, IdleInhibitorIds, ZwpIdleInhibitorV1},
zwp_input_popup_surface_v2::ZwpInputPopupSurfaceV2,
@ -222,6 +223,7 @@ pub struct State {
pub ui_drag_threshold_squared: Cell<i32>,
pub toplevels: CopyHashMap<ToplevelIdentifier, Weak<dyn ToplevelNode>>,
pub const_40hz_latch: EventSource<dyn LatchListener>,
pub tray_item_ids: TrayItemIds,
}
// impl Drop for State {
@ -586,7 +588,10 @@ impl State {
self.globals.add_global(self, global)
}
pub fn remove_global<T: RemovableWaylandGlobal>(&self, global: &T) -> Result<(), GlobalsError> {
pub fn remove_global<T: RemovableWaylandGlobal>(
&self,
global: &Rc<T>,
) -> Result<(), GlobalsError> {
self.globals.remove(self, global)
}
@ -1254,6 +1259,10 @@ impl State {
}
}
}
pub fn tray_icon_size(&self) -> i32 {
(self.theme.sizes.title_height.get() - 2).max(0)
}
}
#[derive(Debug, Error)]

View file

@ -2,7 +2,10 @@ use {
crate::{
backend::{Connector, ConnectorEvent, ConnectorId, MonitorInfo},
globals::GlobalName,
ifs::wl_output::{PersistentOutputState, WlOutputGlobal},
ifs::{
jay_tray_v1::JayTrayV1Global,
wl_output::{PersistentOutputState, WlOutputGlobal},
},
output_schedule::OutputSchedule,
state::{ConnectorData, OutputData, State},
tree::{move_ws_to_output, OutputNode, OutputRenderData, WsMoveConfig},
@ -146,6 +149,10 @@ impl ConnectorHandler {
.state
.eng
.spawn("output schedule", schedule.clone().drive());
let tray = Rc::new(JayTrayV1Global {
name: self.state.globals.name(),
output: global.opt.clone(),
});
let on = Rc::new(OutputNode {
id: self.state.node_ids.next(),
workspaces: Default::default(),
@ -188,6 +195,8 @@ impl ConnectorHandler {
flip_margin_ns: Default::default(),
ext_copy_sessions: Default::default(),
before_latch_event: Default::default(),
tray_start_rel: Default::default(),
tray_items: Default::default(),
});
on.update_visible();
on.update_rects();
@ -247,6 +256,7 @@ impl ConnectorHandler {
config.connector_connected(self.id);
}
self.state.add_global(&global);
self.state.add_global(&tray);
self.state.tree_changed();
on.update_presentation_type();
'outer: loop {
@ -324,9 +334,13 @@ impl ConnectorHandler {
for seat in self.state.globals.seats.lock().values() {
seat.cursor_group().output_disconnected(&on, &target);
}
for item in on.tray_items.iter() {
item.destroy_node();
}
self.state
.remove_output_scale(on.global.persistent.scale.get());
let _ = self.state.remove_global(&*global);
let _ = self.state.remove_global(&global);
let _ = self.state.remove_global(&tray);
self.state.tree_changed();
self.state.damage(self.state.root.extents.get());
}

View file

@ -74,7 +74,7 @@ impl DrvDevHandler {
config.del_drm_dev(self.id);
}
self.data.lease_global.bindings.clear();
let _ = self.state.remove_global(&*self.data.lease_global);
let _ = self.state.remove_global(&self.data.lease_global);
self.data.handler.set(None);
self.state.drm_devs.remove(&self.id);
}

View file

@ -13,7 +13,7 @@ use {
wl_pointer::PendingScroll,
Dnd, NodeSeatState, WlSeatGlobal,
},
wl_surface::WlSurface,
wl_surface::{tray::TrayItemId, WlSurface},
},
rect::Rect,
renderer::Renderer,
@ -178,6 +178,10 @@ pub trait Node: 'static {
None
}
fn node_tray_item(&self) -> Option<TrayItemId> {
None
}
// EVENT HANDLERS
fn node_on_key(

View file

@ -19,6 +19,7 @@ use {
},
wl_surface::{
ext_session_lock_surface_v1::ExtSessionLockSurfaceV1,
tray::DynTrayItem,
zwlr_layer_surface_v1::{ExclusiveSize, ZwlrLayerSurfaceV1},
SurfaceSendPreferredScaleVisitor, SurfaceSendPreferredTransformVisitor,
},
@ -94,6 +95,8 @@ pub struct OutputNode {
pub ext_copy_sessions:
CopyHashMap<(ClientId, ExtImageCopyCaptureSessionV1Id), Rc<ExtImageCopyCaptureSessionV1>>,
pub before_latch_event: EventSource<dyn BeforeLatchListener>,
pub tray_start_rel: Cell<i32>,
pub tray_items: LinkedList<Rc<dyn DynTrayItem>>,
}
#[derive(Copy, Clone, Debug, PartialEq)]
@ -418,6 +421,9 @@ impl OutputNode {
if let Some(c) = self.workspace.get() {
c.change_extents(&self.workspace_rect.get());
}
for item in self.tray_items.iter() {
item.send_current_configure();
}
}
pub fn set_preferred_scale(self: &Rc<Self>, scale: Scale) {
@ -579,7 +585,7 @@ impl OutputNode {
if let Some(scale) = scale {
width = (width as f64 / scale).round() as _;
}
let pos = output_width - width - 1;
let pos = self.tray_start_rel.get() - width - 1;
status.tex_x = pos;
}
}
@ -714,6 +720,7 @@ impl OutputNode {
let height = (y2 - y1).max(0);
self.workspace_rect
.set(Rect::new_sized_unchecked(x1, y1, width, height));
self.update_tray_positions();
self.schedule_update_render_data();
}
@ -929,6 +936,9 @@ impl OutputNode {
self.title_visible.set(lower_visible);
set_layer_visible!(self.layers[0], lower_visible);
set_layer_visible!(self.layers[1], lower_visible);
for item in self.tray_items.iter() {
item.set_visible(lower_visible);
}
if let Some(ws) = self.workspace.get() {
ws.set_visible(visible);
}
@ -1164,6 +1174,37 @@ impl OutputNode {
before: None,
});
}
pub fn update_tray_positions(self: &Rc<Self>) {
let th = self.state.theme.sizes.title_height.get();
let rect = self.non_exclusive_rect.get();
let output_width = rect.width();
let mut right = output_width;
let mut have_any = false;
let icon_size = self.state.tray_icon_size();
for item in self.tray_items.rev_iter() {
if item.data().surface.buffer.is_none() {
continue;
}
have_any = true;
right -= th;
let rel_pos = Rect::new_sized(right, 1, icon_size, icon_size).unwrap();
let abs_pos = rel_pos.move_(rect.x1(), rect.y1());
item.set_position(abs_pos, rel_pos);
}
if have_any {
right -= 2;
}
let prev_right = self.tray_start_rel.replace(right);
if prev_right != right {
{
let min = prev_right.min(right);
let rect = Rect::new_sized(rect.x1() + min, 0, output_width, th).unwrap();
self.state.damage(rect);
}
self.schedule_update_render_data();
}
}
}
pub struct OutputTitle {
@ -1228,6 +1269,9 @@ impl Node for OutputNode {
visitor.visit_layer_surface(surface.deref());
}
}
for item in self.tray_items.iter() {
item.deref().clone().node_visit(visitor);
}
}
fn node_visible(&self) -> bool {
@ -1321,6 +1365,19 @@ impl Node for OutputNode {
let (x, y) = non_exclusive_rect.translate(x, y);
if y < bar_height {
search_layers = false;
for item in self.tray_items.iter() {
let data = item.data();
let pos = data.rel_pos.get();
if pos.contains(x, y) {
let (x, y) = pos.translate(x, y);
tree.push(FoundNode {
node: item.deref().clone().into_node(),
x,
y,
});
return data.find_tree_at(x, y, tree);
}
}
} else {
if let Some(ws) = self.workspace.get() {
let y = y - bar_height;

View file

@ -2,6 +2,7 @@ use {
crate::{
ifs::wl_surface::{
ext_session_lock_surface_v1::ExtSessionLockSurfaceV1,
tray::jay_tray_item_v1::JayTrayItemV1,
x_surface::xwindow::Xwindow,
xdg_surface::{xdg_popup::XdgPopup, xdg_toplevel::XdgToplevel},
zwlr_layer_surface_v1::ZwlrLayerSurfaceV1,
@ -62,6 +63,10 @@ pub trait NodeVisitorBase: Sized {
fn visit_lock_surface(&mut self, node: &Rc<ExtSessionLockSurfaceV1>) {
node.node_visit_children(self);
}
fn visit_tray_item(&mut self, node: &Rc<JayTrayItemV1>) {
node.node_visit_children(self);
}
}
pub trait NodeVisitor {
@ -77,6 +82,7 @@ pub trait NodeVisitor {
fn visit_xwindow(&mut self, node: &Rc<Xwindow>);
fn visit_placeholder(&mut self, node: &Rc<PlaceholderNode>);
fn visit_lock_surface(&mut self, node: &Rc<ExtSessionLockSurfaceV1>);
fn visit_tray_item(&mut self, node: &Rc<JayTrayItemV1>);
}
impl<T: NodeVisitorBase> NodeVisitor for T {
@ -127,6 +133,10 @@ impl<T: NodeVisitorBase> NodeVisitor for T {
fn visit_lock_surface(&mut self, node: &Rc<ExtSessionLockSurfaceV1>) {
<T as NodeVisitorBase>::visit_lock_surface(self, node)
}
fn visit_tray_item(&mut self, node: &Rc<JayTrayItemV1>) {
<T as NodeVisitorBase>::visit_tray_item(self, node)
}
}
pub struct GenericNodeVisitor<F> {
@ -197,6 +207,11 @@ impl<F: FnMut(Rc<dyn Node>)> NodeVisitor for GenericNodeVisitor<F> {
(self.f)(node.clone());
node.node_visit_children(self);
}
fn visit_tray_item(&mut self, node: &Rc<JayTrayItemV1>) {
(self.f)(node.clone());
node.node_visit_children(self);
}
}
// pub fn visit_containers<F: FnMut(&Rc<ContainerNode>)>(f: F) -> impl NodeVisitor {

View file

@ -49,6 +49,16 @@ impl<T> NumCell<T> {
res
}
#[inline(always)]
pub fn add_fetch(&self, n: T) -> T
where
T: Copy + Add<T, Output = T>,
{
let res = self.t.get() + n;
self.t.set(res);
res
}
#[inline(always)]
pub fn fetch_sub(&self, n: T) -> T
where