Merge pull request #307 from mahkoh/jorth/jay-tray
wayland: implement jay-tray-v1
This commit is contained in:
commit
d2fa4bedc0
33 changed files with 1025 additions and 56 deletions
|
|
@ -32,7 +32,10 @@ keymap = """
|
|||
"""
|
||||
|
||||
# An action that will be executed when the GPU has been initialized.
|
||||
on-graphics-initialized = { type = "exec", exec = "mako" }
|
||||
on-graphics-initialized = [
|
||||
{ type = "exec", exec = "mako" },
|
||||
{ type = "exec", exec = "wl-tray-bridge" },
|
||||
]
|
||||
|
||||
# Shortcuts that are processed by the compositor.
|
||||
# The left hand side should be a key, possibly prefixed with modifiers.
|
||||
|
|
@ -266,7 +269,10 @@ If you want to run an action at startup, you can use the top-level `on-graphics-
|
|||
field:
|
||||
|
||||
```toml
|
||||
on-graphics-initialized = { type = "exec", exec = "mako" }
|
||||
on-graphics-initialized = [
|
||||
{ type = "exec", exec = "mako" },
|
||||
{ type = "exec", exec = "wl-tray-bridge" },
|
||||
]
|
||||
```
|
||||
|
||||
### Setting Environment Variables
|
||||
|
|
@ -490,7 +496,7 @@ output.name = "left"
|
|||
|
||||
See the specification for more details.
|
||||
|
||||
# Theming
|
||||
### Theming
|
||||
|
||||
You can configure the colors, sizes, and fonts used by the compositor with the top-level `theme` table.
|
||||
|
||||
|
|
@ -500,3 +506,10 @@ bg-color = "#ff000"
|
|||
```
|
||||
|
||||
See the specification for more details.
|
||||
|
||||
### Tray Icons and Menus
|
||||
|
||||
The default configuration will try to start [wl-tray-bridge] to give you access to tray
|
||||
icons and menus.
|
||||
|
||||
[wl-tray-bridge]: https://github.com/mahkoh/wl-tray-bridge
|
||||
|
|
|
|||
|
|
@ -147,6 +147,7 @@ Jay supports the following wayland protocols:
|
|||
| ext_output_image_capture_source_manager_v1 | 1 | |
|
||||
| ext_session_lock_manager_v1 | 1 | Yes |
|
||||
| ext_transient_seat_manager_v1 | 1[^ts_rejected] | Yes |
|
||||
| jay_tray_v1 | 1 | |
|
||||
| org_kde_kwin_server_decoration_manager | 1 | |
|
||||
| wl_compositor | 6 | |
|
||||
| wl_data_device_manager | 3 | |
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@
|
|||
- Fix screen sharing in zoom.
|
||||
- Implement wp-fifo-v1.
|
||||
- Implement wp-commit-timing-v1.
|
||||
- Implement jay-tray-v1. You can get tray icons and menus by using
|
||||
https://github.com/mahkoh/wl-tray-bridge.
|
||||
|
||||
# 1.6.0 (2024-09-25)
|
||||
|
||||
|
|
|
|||
|
|
@ -175,6 +175,7 @@ impl Clients {
|
|||
slf,
|
||||
)),
|
||||
wire_scale: Default::default(),
|
||||
focus_stealing_serial: Default::default(),
|
||||
});
|
||||
track!(data, data);
|
||||
let display = Rc::new(WlDisplay::new(&data));
|
||||
|
|
@ -286,6 +287,7 @@ pub struct Client {
|
|||
pub activation_tokens: RefCell<VecDeque<ActivationToken>>,
|
||||
pub commit_timelines: Rc<CommitTimelines>,
|
||||
pub wire_scale: Cell<Option<i32>>,
|
||||
pub focus_stealing_serial: Cell<Option<u64>>,
|
||||
}
|
||||
|
||||
pub const NUM_CACHED_SERIAL_RANGES: usize = 64;
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
109
src/ifs/jay_tray_v1.rs
Normal 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);
|
||||
|
|
@ -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 })
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
|
|
@ -200,6 +206,8 @@ pub struct WlSeatGlobal {
|
|||
tablet: TabletSeatData,
|
||||
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;
|
||||
|
|
@ -229,6 +237,7 @@ impl WlSeatGlobal {
|
|||
pointer_stack_modified: Cell::new(false),
|
||||
found_tree: RefCell::new(vec![]),
|
||||
keyboard_node: CloneCell::new(state.root.clone()),
|
||||
keyboard_node_serial: Default::default(),
|
||||
bindings: Default::default(),
|
||||
x_data_devices: Default::default(),
|
||||
data_devices: RefCell::new(Default::default()),
|
||||
|
|
@ -271,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();
|
||||
|
|
@ -1039,6 +1049,48 @@ impl WlSeatGlobal {
|
|||
ei_seat.regions_changed();
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
let serial = serial.min(max_serial);
|
||||
if serial <= self.keyboard_node_serial.get() {
|
||||
return;
|
||||
}
|
||||
self.focus_node_with_serial(node, serial);
|
||||
}
|
||||
}
|
||||
|
||||
impl CursorUserOwner for WlSeatGlobal {
|
||||
|
|
|
|||
|
|
@ -159,7 +159,8 @@ impl NodeSeatState {
|
|||
fn release_kb_focus2(&self, focus_last: bool) {
|
||||
self.release_kb_grab();
|
||||
while let Some((_, seat)) = self.kb_foci.pop() {
|
||||
seat.kb_owner.set_kb_node(&seat, seat.state.root.clone());
|
||||
seat.kb_owner
|
||||
.set_kb_node(&seat, seat.state.root.clone(), seat.state.next_serial(None));
|
||||
// log::info!("keyboard_node = root");
|
||||
if focus_last {
|
||||
seat.get_output()
|
||||
|
|
@ -935,7 +936,15 @@ impl WlSeatGlobal {
|
|||
}
|
||||
|
||||
pub fn focus_node(self: &Rc<Self>, node: Rc<dyn Node>) {
|
||||
self.kb_owner.set_kb_node(self, node);
|
||||
if self.keyboard_node.get().node_id() == node.node_id() {
|
||||
return;
|
||||
}
|
||||
let serial = self.state.next_serial(node.node_client().as_deref());
|
||||
self.focus_node_with_serial(node, serial);
|
||||
}
|
||||
|
||||
pub fn focus_node_with_serial(self: &Rc<Self>, node: Rc<dyn Node>, serial: u64) {
|
||||
self.kb_owner.set_kb_node(self, node, serial);
|
||||
}
|
||||
|
||||
pub(super) fn for_each_seat<C>(&self, ver: Version, client: ClientId, mut f: C)
|
||||
|
|
@ -1141,7 +1150,10 @@ impl WlSeatGlobal {
|
|||
) {
|
||||
let (state, pressed) = match state {
|
||||
KeyState::Released => (wl_pointer::RELEASED, false),
|
||||
KeyState::Pressed => (wl_pointer::PRESSED, true),
|
||||
KeyState::Pressed => {
|
||||
surface.client.focus_stealing_serial.set(Some(serial));
|
||||
(wl_pointer::PRESSED, true)
|
||||
}
|
||||
};
|
||||
let time = (time_usec / 1000) as u32;
|
||||
self.surface_pointer_event(Version::ALL, surface, |p| {
|
||||
|
|
@ -1150,7 +1162,7 @@ impl WlSeatGlobal {
|
|||
self.surface_pointer_frame(surface);
|
||||
if pressed {
|
||||
if let Some(node) = surface.get_focus_node(self.id) {
|
||||
self.focus_node(node);
|
||||
self.focus_node_with_serial(node, serial);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1363,12 +1375,13 @@ impl WlSeatGlobal {
|
|||
y: Fixed,
|
||||
) {
|
||||
let serial = surface.client.next_serial();
|
||||
surface.client.focus_stealing_serial.set(Some(serial));
|
||||
let time = (time_usec / 1000) as _;
|
||||
self.surface_touch_event(Version::ALL, surface, |t| {
|
||||
t.send_down(serial, time, surface.id, id, x, y)
|
||||
});
|
||||
if let Some(node) = surface.get_focus_node(self.id) {
|
||||
self.focus_node(node);
|
||||
self.focus_node_with_serial(node, serial);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,8 +29,8 @@ impl KbOwnerHolder {
|
|||
self.owner.get().ungrab(seat)
|
||||
}
|
||||
|
||||
pub fn set_kb_node(&self, seat: &Rc<WlSeatGlobal>, node: Rc<dyn Node>) {
|
||||
self.owner.get().set_kb_node(seat, node);
|
||||
pub fn set_kb_node(&self, seat: &Rc<WlSeatGlobal>, node: Rc<dyn Node>, serial: u64) {
|
||||
self.owner.get().set_kb_node(seat, node, serial);
|
||||
}
|
||||
|
||||
pub fn clear(&self) {
|
||||
|
|
@ -45,12 +45,13 @@ struct GrabKbOwner;
|
|||
trait KbOwner {
|
||||
fn grab(&self, seat: &Rc<WlSeatGlobal>, node: Rc<dyn Node>) -> bool;
|
||||
fn ungrab(&self, seat: &Rc<WlSeatGlobal>);
|
||||
fn set_kb_node(&self, seat: &Rc<WlSeatGlobal>, node: Rc<dyn Node>);
|
||||
fn set_kb_node(&self, seat: &Rc<WlSeatGlobal>, node: Rc<dyn Node>, serial: u64);
|
||||
}
|
||||
|
||||
impl KbOwner for DefaultKbOwner {
|
||||
fn grab(&self, seat: &Rc<WlSeatGlobal>, node: Rc<dyn Node>) -> bool {
|
||||
self.set_kb_node(seat, node);
|
||||
let serial = seat.state.next_serial(node.node_client().as_deref());
|
||||
self.set_kb_node(seat, node, serial);
|
||||
seat.kb_owner.owner.set(Rc::new(GrabKbOwner));
|
||||
true
|
||||
}
|
||||
|
|
@ -59,7 +60,7 @@ impl KbOwner for DefaultKbOwner {
|
|||
// nothing
|
||||
}
|
||||
|
||||
fn set_kb_node(&self, seat: &Rc<WlSeatGlobal>, node: Rc<dyn Node>) {
|
||||
fn set_kb_node(&self, seat: &Rc<WlSeatGlobal>, node: Rc<dyn Node>, serial: u64) {
|
||||
let old = seat.keyboard_node.get();
|
||||
if old.node_id() == node.node_id() {
|
||||
return;
|
||||
|
|
@ -78,6 +79,7 @@ impl KbOwner for DefaultKbOwner {
|
|||
}
|
||||
// log::info!("focus {}", node.node_id());
|
||||
node.clone().node_on_focus(seat);
|
||||
seat.keyboard_node_serial.set(serial);
|
||||
seat.keyboard_node.set(node.clone());
|
||||
seat.tablet_on_keyboard_node_change();
|
||||
}
|
||||
|
|
@ -92,7 +94,7 @@ impl KbOwner for GrabKbOwner {
|
|||
seat.kb_owner.owner.set(seat.kb_owner.default.clone());
|
||||
}
|
||||
|
||||
fn set_kb_node(&self, _seat: &Rc<WlSeatGlobal>, _node: Rc<dyn Node>) {
|
||||
fn set_kb_node(&self, _seat: &Rc<WlSeatGlobal>, _node: Rc<dyn Node>, _serial: u64) {
|
||||
// nothing
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>> {
|
||||
|
|
|
|||
|
|
@ -211,8 +211,9 @@ impl TabletTool {
|
|||
tool.send_frame(time);
|
||||
});
|
||||
if state == ToolButtonState::Pressed {
|
||||
n.client.focus_stealing_serial.set(Some(serial.get()));
|
||||
if let Some(node) = n.get_focus_node(self.tablet.seat.id) {
|
||||
self.tablet.seat.focus_node(node);
|
||||
self.tablet.seat.focus_node_with_serial(node, serial.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -259,8 +260,9 @@ impl TabletTool {
|
|||
});
|
||||
if let Some(changes) = changes {
|
||||
if changes.down == Some(true) {
|
||||
n.client.focus_stealing_serial.set(Some(serial.get()));
|
||||
if let Some(node) = n.get_focus_node(self.tablet.seat.id) {
|
||||
self.tablet.seat.focus_node(node);
|
||||
self.tablet.seat.focus_node_with_serial(node, serial.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ impl Node for ExtSessionLockSurfaceV1 {
|
|||
}
|
||||
|
||||
fn node_on_pointer_enter(self: Rc<Self>, seat: &Rc<WlSeatGlobal>, _x: Fixed, _y: Fixed) {
|
||||
seat.focus_node(self.surface.clone());
|
||||
seat.focus_node_with_serial(self.surface.clone(), self.client.next_serial());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
406
src/ifs/wl_surface/tray.rs
Normal file
406
src/ifs/wl_surface/tray.rs
Normal 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);
|
||||
153
src/ifs/wl_surface/tray/jay_tray_item_v1.rs
Normal file
153
src/ifs/wl_surface/tray/jay_tray_item_v1.rs
Normal 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),
|
||||
}
|
||||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
11
src/state.rs
11
src/state.rs
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -7,7 +7,10 @@ keymap = """
|
|||
};
|
||||
"""
|
||||
|
||||
on-graphics-initialized = { type = "exec", exec = "mako" }
|
||||
on-graphics-initialized = [
|
||||
{ type = "exec", exec = "mako" },
|
||||
{ type = "exec", exec = "wl-tray-bridge" },
|
||||
]
|
||||
|
||||
[shortcuts]
|
||||
alt-h = "focus-left"
|
||||
|
|
|
|||
30
wire/jay_tray_item_v1.txt
Normal file
30
wire/jay_tray_item_v1.txt
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
request destroy {
|
||||
}
|
||||
|
||||
request ack_configure {
|
||||
serial: u32,
|
||||
}
|
||||
|
||||
request get_popup {
|
||||
popup: id(xdg_popup),
|
||||
seat: id(wl_seat),
|
||||
serial: u32,
|
||||
keyboard_focus: u32,
|
||||
}
|
||||
|
||||
event configure_size {
|
||||
width: i32,
|
||||
height: i32,
|
||||
}
|
||||
|
||||
event preferred_anchor {
|
||||
anchor: u32,
|
||||
}
|
||||
|
||||
event preferred_gravity {
|
||||
gravity: u32,
|
||||
}
|
||||
|
||||
event configure {
|
||||
serial: u32,
|
||||
}
|
||||
7
wire/jay_tray_v1.txt
Normal file
7
wire/jay_tray_v1.txt
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
request destroy {
|
||||
}
|
||||
|
||||
request get_tray_item {
|
||||
id: id(jay_tray_item_v1),
|
||||
surface: id(wl_surface),
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue