1
0
Fork 0
forked from wry/wry

Merge pull request #85 from mahkoh/jorth/activation

wayland: implement xdg-activation
This commit is contained in:
mahkoh 2024-02-14 21:13:03 +01:00 committed by GitHub
commit ccacdda03e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 676 additions and 58 deletions

View file

@ -253,6 +253,10 @@ pub mod colors {
///
/// Default: `#772831`.
const 13 => CAPTURED_FOCUSED_TITLE_BACKGROUND_COLOR,
/// The title background color of a window that has requested attention.
///
/// Default: `#23092c`.
const 14 => ATTENTION_REQUESTED_BACKGROUND_COLOR,
}
}

View file

@ -7,6 +7,7 @@ use {
object::{Interface, Object, ObjectId, WL_DISPLAY_ID},
state::State,
utils::{
activation_token::ActivationToken,
asyncevent::AsyncEvent,
buffd::{MsgFormatter, MsgParser, MsgParserError, OutBufferSwapchain},
copyhashmap::{CopyHashMap, Locked},
@ -147,6 +148,7 @@ impl Clients {
symmetric_delete: Cell::new(false),
last_xwayland_serial: Cell::new(0),
surfaces_by_xwayland_serial: Default::default(),
activation_tokens: Default::default(),
});
track!(data, data);
let display = Rc::new(WlDisplay::new(&data));
@ -217,6 +219,7 @@ impl Drop for ClientHolder {
self.data.flush_request.clear();
self.data.shutdown.clear();
self.data.surfaces_by_xwayland_serial.clear();
self.data.remove_activation_tokens();
}
}
@ -256,6 +259,7 @@ pub struct Client {
pub symmetric_delete: Cell<bool>,
pub last_xwayland_serial: Cell<u64>,
pub surfaces_by_xwayland_serial: CopyHashMap<u64, Rc<WlSurface>>,
pub activation_tokens: RefCell<VecDeque<ActivationToken>>,
}
pub const NUM_CACHED_SERIAL_RANGES: usize = 64;
@ -444,6 +448,12 @@ impl Client {
})),
}
}
fn remove_activation_tokens(&self) {
for token in &*self.activation_tokens.borrow() {
self.state.activation_tokens.remove(token);
}
}
}
pub trait WaylandObject: Object {

View file

@ -199,6 +199,7 @@ fn start_compositor2(
workspace_watchers: Default::default(),
default_workspace_capture: Cell::new(true),
default_gfx_api: Cell::new(GfxApi::OpenGl),
activation_tokens: Default::default(),
});
state.tracker.register(ClientId::from_raw(0));
create_dummy_output(&state);
@ -413,6 +414,7 @@ fn create_dummy_output(state: &Rc<State>) {
jay_workspaces: Default::default(),
capture: Cell::new(false),
title_texture: Cell::new(None),
attention_requests: Default::default(),
});
dummy_workspace.output_link.set(Some(
dummy_output.workspaces.add_last(dummy_workspace.clone()),

View file

@ -1066,6 +1066,7 @@ impl ConfigProxyHandler {
FOCUSED_TITLE_TEXT_COLOR => &colors.focused_title_text,
FOCUSED_INACTIVE_TITLE_TEXT_COLOR => &colors.focused_inactive_title_text,
BAR_STATUS_TEXT_COLOR => &colors.bar_text,
ATTENTION_REQUESTED_BACKGROUND_COLOR => &colors.attention_requested_background,
_ => return Err(CphError::UnknownColor(colorable.0)),
};
Ok(colorable)

View file

@ -27,6 +27,7 @@ use {
wp_single_pixel_buffer_manager_v1::WpSinglePixelBufferManagerV1Global,
wp_tearing_control_manager_v1::WpTearingControlManagerV1Global,
wp_viewporter::WpViewporterGlobal,
xdg_activation_v1::XdgActivationV1Global,
xdg_wm_base::XdgWmBaseGlobal,
zwlr_layer_shell_v1::ZwlrLayerShellV1Global,
zwlr_screencopy_manager_v1::ZwlrScreencopyManagerV1Global,
@ -162,6 +163,7 @@ impl Globals {
add_singleton!(WpSinglePixelBufferManagerV1Global);
add_singleton!(WpCursorShapeManagerV1Global);
add_singleton!(WpContentTypeManagerV1Global);
add_singleton!(XdgActivationV1Global);
}
pub fn add_backend_singletons(&self, backend: &Rc<dyn Backend>) {

View file

@ -37,6 +37,8 @@ pub mod wp_presentation_feedback;
pub mod wp_single_pixel_buffer_manager_v1;
pub mod wp_tearing_control_manager_v1;
pub mod wp_viewporter;
pub mod xdg_activation_token_v1;
pub mod xdg_activation_v1;
pub mod xdg_positioner;
pub mod xdg_wm_base;
pub mod zwlr_layer_shell_v1;

View file

@ -1059,6 +1059,12 @@ impl WlSurface {
pub fn set_content_type(&self, content_type: Option<ContentType>) {
self.pending.content_type.set(Some(content_type));
}
pub fn request_activation(&self) {
if let Some(tl) = self.toplevel.get() {
tl.tl_data().request_attention(tl.tl_as_node());
}
}
}
object_base! {

View file

@ -131,7 +131,6 @@ pub struct XwindowData {
tree_id!(XwindowId);
pub struct Xwindow {
pub id: XwindowId,
pub seat_state: NodeSeatState,
pub data: Rc<XwindowData>,
pub x: Rc<XSurface>,
pub display_link: RefCell<Option<LinkedNode<Rc<dyn StackedNode>>>>,
@ -214,7 +213,6 @@ impl Xwindow {
tld.pos.set(surface.extents.get());
let slf = Rc::new(Self {
id: data.state.node_ids.next(),
seat_state: Default::default(),
data: data.clone(),
display_link: Default::default(),
toplevel_data: tld,
@ -298,7 +296,7 @@ impl Node for Xwindow {
}
fn node_seat_state(&self) -> &NodeSeatState {
&self.seat_state
&self.toplevel_data.seat_state
}
fn node_visit(self: Rc<Self>, visitor: &mut dyn NodeVisitor) {
@ -422,7 +420,7 @@ impl ToplevelNode for Xwindow {
fn tl_set_visible(&self, visible: bool) {
self.x.surface.set_visible(visible);
self.seat_state.set_visible(self, visible);
self.toplevel_data.set_visible(self, visible);
}
fn tl_destroy(&self) {

View file

@ -0,0 +1,108 @@
use {
crate::{
client::{Client, ClientError},
leaks::Tracker,
object::Object,
utils::{
activation_token::{activation_token, ActivationToken},
buffd::{MsgParser, MsgParserError},
},
wire::{xdg_activation_token_v1::*, XdgActivationTokenV1Id},
},
std::{cell::Cell, rc::Rc},
thiserror::Error,
};
const MAX_TOKENS_PER_CLIENT: usize = 8;
pub struct XdgActivationTokenV1 {
pub id: XdgActivationTokenV1Id,
pub client: Rc<Client>,
pub tracker: Tracker<Self>,
already_used: Cell<bool>,
}
impl XdgActivationTokenV1 {
pub fn new(id: XdgActivationTokenV1Id, client: &Rc<Client>) -> Self {
Self {
id,
client: client.clone(),
tracker: Default::default(),
already_used: Cell::new(false),
}
}
fn set_serial(&self, parser: MsgParser<'_, '_>) -> Result<(), XdgActivationTokenV1Error> {
let _req: SetSerial = self.client.parse(self, parser)?;
Ok(())
}
fn set_app_id(&self, parser: MsgParser<'_, '_>) -> Result<(), XdgActivationTokenV1Error> {
let _req: SetAppId = self.client.parse(self, parser)?;
Ok(())
}
fn set_surface(&self, parser: MsgParser<'_, '_>) -> Result<(), XdgActivationTokenV1Error> {
let req: SetSurface = self.client.parse(self, parser)?;
self.client.lookup(req.surface)?;
Ok(())
}
fn commit(&self, parser: MsgParser<'_, '_>) -> Result<(), XdgActivationTokenV1Error> {
let _req: Commit = self.client.parse(self, parser)?;
if self.already_used.replace(true) {
return Err(XdgActivationTokenV1Error::AlreadyUsed);
}
let token = activation_token();
self.client.state.activation_tokens.set(token, ());
let mut tokens = self.client.activation_tokens.borrow_mut();
if tokens.len() >= MAX_TOKENS_PER_CLIENT {
if let Some(oldest) = tokens.pop_front() {
self.client.state.activation_tokens.remove(&oldest);
}
}
tokens.push_back(token);
self.send_done(token);
Ok(())
}
fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), XdgActivationTokenV1Error> {
let _req: Destroy = self.client.parse(self, parser)?;
self.client.remove_obj(self)?;
Ok(())
}
fn send_done(&self, token: ActivationToken) {
let token = token.to_string();
self.client.event(Done {
self_id: self.id,
token: &token,
});
}
}
object_base! {
self = XdgActivationTokenV1;
SET_SERIAL => set_serial,
SET_APP_ID => set_app_id,
SET_SURFACE => set_surface,
COMMIT => commit,
DESTROY => destroy,
}
impl Object for XdgActivationTokenV1 {}
simple_add_obj!(XdgActivationTokenV1);
#[derive(Debug, Error)]
pub enum XdgActivationTokenV1Error {
#[error(transparent)]
ClientError(Box<ClientError>),
#[error("Parsing failed")]
MsgParserError(#[source] Box<MsgParserError>),
#[error("The activation token has already been used")]
AlreadyUsed,
}
efrom!(XdgActivationTokenV1Error, ClientError);
efrom!(XdgActivationTokenV1Error, MsgParserError);

View file

@ -0,0 +1,120 @@
use {
crate::{
client::{Client, ClientError},
globals::{Global, GlobalName},
ifs::xdg_activation_token_v1::XdgActivationTokenV1,
leaks::Tracker,
object::Object,
utils::{
activation_token::ActivationToken,
buffd::{MsgParser, MsgParserError},
opaque::OpaqueError,
},
wire::{xdg_activation_v1::*, XdgActivationV1Id},
},
std::rc::Rc,
thiserror::Error,
};
pub struct XdgActivationV1Global {
pub name: GlobalName,
}
impl XdgActivationV1Global {
pub fn new(name: GlobalName) -> Self {
Self { name }
}
fn bind_(
self: Rc<Self>,
id: XdgActivationV1Id,
client: &Rc<Client>,
version: u32,
) -> Result<(), XdgActivationV1Error> {
let mgr = Rc::new(XdgActivationV1 {
id,
client: client.clone(),
tracker: Default::default(),
version,
});
track!(client, mgr);
client.add_client_obj(&mgr)?;
Ok(())
}
}
global_base!(XdgActivationV1Global, XdgActivationV1, XdgActivationV1Error);
simple_add_global!(XdgActivationV1Global);
impl Global for XdgActivationV1Global {
fn singleton(&self) -> bool {
true
}
fn version(&self) -> u32 {
1
}
}
pub struct XdgActivationV1 {
pub id: XdgActivationV1Id,
pub client: Rc<Client>,
pub tracker: Tracker<Self>,
pub version: u32,
}
impl XdgActivationV1 {
fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), XdgActivationV1Error> {
let _req: Destroy = self.client.parse(self, parser)?;
self.client.remove_obj(self)?;
Ok(())
}
fn get_activation_token(&self, parser: MsgParser<'_, '_>) -> Result<(), XdgActivationV1Error> {
let req: GetActivationToken = self.client.parse(self, parser)?;
let token = Rc::new(XdgActivationTokenV1::new(req.id, &self.client));
track!(self.client, token);
self.client.add_client_obj(&token)?;
Ok(())
}
fn activate(&self, parser: MsgParser<'_, '_>) -> Result<(), XdgActivationV1Error> {
let req: Activate = self.client.parse(self, parser)?;
let token: ActivationToken = req.token.parse()?;
let surface = self.client.lookup(req.surface)?;
if self.client.state.activation_tokens.remove(&token).is_none() {
log::warn!(
"Client requested activation with unknown token {}",
req.token
);
return Ok(());
}
surface.request_activation();
Ok(())
}
}
object_base! {
self = XdgActivationV1;
DESTROY => destroy,
GET_ACTIVATION_TOKEN => get_activation_token,
ACTIVATE => activate,
}
impl Object for XdgActivationV1 {}
simple_add_obj!(XdgActivationV1);
#[derive(Debug, Error)]
pub enum XdgActivationV1Error {
#[error(transparent)]
ClientError(Box<ClientError>),
#[error("Parsing failed")]
MsgParserError(#[source] Box<MsgParserError>),
#[error("Could not parse the activation token")]
ParseActivationToken(#[from] OpaqueError),
}
efrom!(XdgActivationV1Error, ClientError);
efrom!(XdgActivationV1Error, MsgParserError);

View file

@ -136,6 +136,9 @@ impl Renderer<'_> {
};
self.base
.fill_boxes2(&rd.captured_inactive_workspaces, &c, x, y);
let c = theme.colors.attention_requested_background.get();
self.base
.fill_boxes2(&rd.attention_requested_workspaces, &c, x, y);
let scale = output.preferred_scale.get();
for title in &rd.titles {
let (x, y) = self.base.scale_point(x + title.tex_x, y + title.tex_y);
@ -209,6 +212,8 @@ impl Renderer<'_> {
self.base.fill_boxes2(&rd.title_rects, &c, x, y);
let c = self.state.theme.colors.focused_title_background.get();
self.base.fill_boxes2(&rd.active_title_rects, &c, x, y);
let c = self.state.theme.colors.attention_requested_background.get();
self.base.fill_boxes2(&rd.attention_title_rects, &c, x, y);
let c = self.state.theme.colors.separator.get();
self.base.fill_boxes2(&rd.underline_rects, &c, x, y);
let c = self.state.theme.colors.border.get();
@ -408,9 +413,12 @@ impl Renderer<'_> {
let th = theme.sizes.title_height.get();
let bw = theme.sizes.border_width.get();
let bc = theme.colors.border.get();
let tc = match floating.active.get() {
true => theme.colors.focused_title_background.get(),
false => theme.colors.unfocused_title_background.get(),
let tc = if floating.active.get() {
theme.colors.focused_title_background.get()
} else if floating.attention_requested.get() {
theme.colors.attention_requested_background.get()
} else {
theme.colors.unfocused_title_background.get()
};
let uc = theme.colors.separator.get();
let borders = [

View file

@ -42,9 +42,10 @@ use {
NodeVisitorBase, OutputNode, PlaceholderNode, ToplevelNode, WorkspaceNode,
},
utils::{
asyncevent::AsyncEvent, clonecell::CloneCell, copyhashmap::CopyHashMap,
errorfmt::ErrorFmt, fdcloser::FdCloser, linkedlist::LinkedList, numcell::NumCell,
queue::AsyncQueue, refcounted::RefCounted, run_toplevel::RunToplevel,
activation_token::ActivationToken, asyncevent::AsyncEvent, clonecell::CloneCell,
copyhashmap::CopyHashMap, errorfmt::ErrorFmt, fdcloser::FdCloser,
linkedlist::LinkedList, numcell::NumCell, queue::AsyncQueue, refcounted::RefCounted,
run_toplevel::RunToplevel,
},
video::drm::Drm,
wheel::Wheel,
@ -134,6 +135,7 @@ pub struct State {
pub workspace_watchers: CopyHashMap<(ClientId, JayWorkspaceWatcherId), Rc<JayWorkspaceWatcher>>,
pub default_workspace_capture: Cell<bool>,
pub default_gfx_api: Cell<GfxApi>,
pub activation_tokens: CopyHashMap<ActivationToken, ()>,
}
// impl Drop for State {

View file

@ -113,6 +113,7 @@ impl ConnectorHandler {
active_workspace: None,
underline: Default::default(),
inactive_workspaces: Default::default(),
attention_requested_workspaces: Default::default(),
captured_inactive_workspaces: Default::default(),
titles: Default::default(),
status: None,

View file

@ -157,6 +157,7 @@ colors! {
border = (0x3f, 0x47, 0x4a),
bar_background = (0x00, 0x00, 0x00),
bar_text = (0xff, 0xff, 0xff),
attention_requested_background = (0x23, 0x09, 0x2c),
}
macro_rules! sizes {

View file

@ -24,6 +24,7 @@ use {
rc_eq::rc_eq,
scroller::Scroller,
smallmap::{SmallMap, SmallMapMut},
threshold_counter::ThresholdCounter,
},
},
ahash::AHashMap,
@ -83,6 +84,7 @@ pub struct ContainerTitle {
pub struct ContainerRenderData {
pub title_rects: Vec<Rect>,
pub active_title_rects: Vec<Rect>,
pub attention_title_rects: Vec<Rect>,
pub last_active_rect: Option<Rect>,
pub border_rects: Vec<Rect>,
pub underline_rects: Vec<Rect>,
@ -115,6 +117,7 @@ pub struct ContainerNode {
pub render_data: RefCell<ContainerRenderData>,
scroller: Scroller,
toplevel_data: ToplevelData,
attention_requests: ThresholdCounter,
}
impl Debug for ContainerNode {
@ -126,6 +129,7 @@ impl Debug for ContainerNode {
pub struct ContainerChild {
pub node: Rc<dyn ToplevelNode>,
pub active: Cell<bool>,
pub attention_requested: Cell<bool>,
title: RefCell<String>,
pub title_tex: SmallMap<Scale, TextTexture, 2>,
pub title_rect: Cell<Rect>,
@ -172,21 +176,21 @@ impl ContainerNode {
) -> Rc<Self> {
child.clone().tl_set_workspace(workspace);
let children = LinkedList::new();
let child_node = children.add_last(ContainerChild {
node: child.clone(),
active: Default::default(),
body: Default::default(),
content: Default::default(),
factor: Cell::new(1.0),
title: Default::default(),
title_tex: Default::default(),
title_rect: Default::default(),
focus_history: Default::default(),
attention_requested: Cell::new(false),
});
let child_node_ref = child_node.clone();
let mut child_nodes = AHashMap::new();
child_nodes.insert(
child.node_id(),
children.add_last(ContainerChild {
node: child.clone(),
active: Default::default(),
body: Default::default(),
content: Default::default(),
factor: Cell::new(1.0),
title: Default::default(),
title_tex: Default::default(),
title_rect: Default::default(),
focus_history: Default::default(),
}),
);
child_nodes.insert(child.node_id(), child_node);
let slf = Rc::new(Self {
id: state.node_ids.next(),
parent: CloneCell::new(parent.clone()),
@ -213,9 +217,11 @@ impl ContainerNode {
render_data: Default::default(),
scroller: Default::default(),
toplevel_data: ToplevelData::new(state, Default::default(), None),
attention_requests: Default::default(),
});
slf.tl_set_parent(parent);
child.tl_set_parent(slf.clone());
slf.apply_child_flags(&child_node_ref);
slf
}
@ -293,11 +299,13 @@ impl ContainerNode {
title_tex: Default::default(),
title_rect: Default::default(),
focus_history: Default::default(),
attention_requested: Default::default(),
});
let r = link.to_ref();
links.insert(new.node_id(), link);
r
};
self.apply_child_flags(&new_ref);
new.clone().tl_set_workspace(&self.workspace.get());
new.tl_set_parent(self.clone());
new.tl_set_visible(self.toplevel_data.visible.get());
@ -644,6 +652,7 @@ impl ContainerNode {
}
rd.title_rects.clear();
rd.active_title_rects.clear();
rd.attention_title_rects.clear();
rd.border_rects.clear();
rd.underline_rects.clear();
rd.last_active_rect.take();
@ -667,6 +676,9 @@ impl ContainerNode {
let color = if child.active.get() {
rd.active_title_rects.push(rect);
theme.colors.focused_title_text.get()
} else if child.attention_requested.get() {
rd.attention_title_rects.push(rect);
theme.colors.unfocused_title_text.get()
} else if !have_active && last_active == Some(child.node.node_id()) {
rd.last_active_rect = Some(rect);
theme.colors.focused_inactive_title_text.get()
@ -760,7 +772,7 @@ impl ContainerNode {
return;
}
let child = {
let children = self.child_nodes.borrow_mut();
let children = self.child_nodes.borrow();
match child {
Some(c) => match children.get(&c.node_id()) {
Some(c) => Some(c.to_ref()),
@ -815,7 +827,7 @@ impl ContainerNode {
child: &dyn ToplevelNode,
direction: Direction,
) {
let child = match self.child_nodes.borrow_mut().get(&child.node_id()) {
let child = match self.child_nodes.borrow().get(&child.node_id()) {
Some(c) => c.to_ref(),
_ => return,
};
@ -880,7 +892,7 @@ impl ContainerNode {
if split == self.split.get()
|| (split == ContainerSplit::Horizontal && self.mono_child.get().is_some())
{
let cc = match self.child_nodes.borrow_mut().get(&child.node_id()) {
let cc = match self.child_nodes.borrow().get(&child.node_id()) {
Some(l) => l.to_ref(),
None => return,
};
@ -934,6 +946,33 @@ impl ContainerNode {
self.prepend_child(node);
}
}
fn apply_child_flags(&self, child: &ContainerChild) {
let data = child.node.tl_data();
let attention_requested = data.wants_attention.get();
child.attention_requested.set(attention_requested);
if attention_requested {
self.mod_attention_requests(true);
}
}
fn discard_child_flags(&self, child: &ContainerChild) {
if child.attention_requested.get() {
self.mod_attention_requests(false);
}
}
fn mod_attention_requests(&self, set: bool) {
let propagate = self.attention_requests.adj(set);
if set || propagate {
self.toplevel_data.wants_attention.set(set);
}
if propagate {
self.parent
.get()
.cnode_child_attention_request_changed(self, set);
}
}
}
struct SeatOp {
@ -999,7 +1038,7 @@ impl Node for ContainerNode {
}
fn node_child_title_changed(self: Rc<Self>, child: &dyn Node, title: &str) {
let child = match self.child_nodes.borrow_mut().get(&child.node_id()) {
let child = match self.child_nodes.borrow().get(&child.node_id()) {
Some(cn) => cn.to_ref(),
_ => return,
};
@ -1083,7 +1122,7 @@ impl Node for ContainerNode {
}
fn node_child_active_changed(self: Rc<Self>, child: &dyn Node, active: bool, depth: u32) {
let node = match self.child_nodes.borrow_mut().get(&child.node_id()) {
let node = match self.child_nodes.borrow().get(&child.node_id()) {
Some(l) => l.to_ref(),
None => return,
};
@ -1276,6 +1315,7 @@ impl ContainingNode for ContainerNode {
None => (false, false),
Some(mc) => (true, mc.node.node_id() == old.node_id()),
};
self.discard_child_flags(&node);
let link = node.append(ContainerChild {
node: new.clone(),
active: Cell::new(false),
@ -1286,7 +1326,9 @@ impl ContainingNode for ContainerNode {
title_tex: Default::default(),
title_rect: Cell::new(node.title_rect.get()),
focus_history: Cell::new(None),
attention_requested: Cell::new(false),
});
self.apply_child_flags(&link);
if let Some(fh) = node.focus_history.take() {
link.focus_history.set(Some(fh.append(link.to_ref())));
}
@ -1314,6 +1356,7 @@ impl ContainingNode for ContainerNode {
None => return,
};
node.focus_history.set(None);
self.discard_child_flags(&node);
if let Some(mono) = self.mono_child.get() {
if mono.node.node_id() == child.node_id() {
let mut new = self.focus_history.last().map(|n| n.deref().clone());
@ -1364,6 +1407,19 @@ impl ContainingNode for ContainerNode {
fn cnode_accepts_child(&self, _node: &dyn Node) -> bool {
true
}
fn cnode_child_attention_request_changed(self: Rc<Self>, child: &dyn Node, set: bool) {
let children = self.child_nodes.borrow();
let child = match children.get(&child.node_id()) {
Some(c) => c,
_ => return,
};
if child.attention_requested.replace(set) == set {
return;
}
self.mod_attention_requests(set);
self.schedule_compute_render_data();
}
}
impl ToplevelNode for ContainerNode {
@ -1429,9 +1485,12 @@ impl ToplevelNode for ContainerNode {
}
fn tl_set_visible(&self, visible: bool) {
self.toplevel_data.visible.set(visible);
for child in self.children.iter() {
child.node.tl_set_visible(visible);
if let Some(mc) = self.mono_child.get() {
mc.node.tl_set_visible(visible);
} else {
for child in self.children.iter() {
child.node.tl_set_visible(visible);
}
}
self.toplevel_data.set_visible(self, visible);
}

View file

@ -14,4 +14,5 @@ pub trait ContainingNode: Node {
}
fn cnode_remove_child2(self: Rc<Self>, child: &dyn Node, preserve_focus: bool);
fn cnode_accepts_child(&self, node: &dyn Node) -> bool;
fn cnode_child_attention_request_changed(self: Rc<Self>, child: &dyn Node, set: bool);
}

View file

@ -45,6 +45,7 @@ pub struct FloatNode {
pub title: RefCell<String>,
pub title_textures: CopyHashMap<Scale, TextTexture>,
seats: RefCell<AHashMap<SeatId, SeatState>>,
pub attention_requested: Cell<bool>,
}
struct SeatState {
@ -112,7 +113,9 @@ impl FloatNode {
title: Default::default(),
title_textures: Default::default(),
seats: Default::default(),
attention_requested: Cell::new(false),
});
floater.apply_child_flags();
floater
.display_link
.set(Some(state.root.stacked.add_last(floater.clone())));
@ -345,6 +348,29 @@ impl FloatNode {
self.workspace.set(ws.clone());
self.stacked_set_visible(ws.stacked_visible());
}
fn apply_child_flags(&self) {
let child = match self.child.get() {
None => return,
Some(c) => c,
};
let data = child.tl_data();
let activation_requested = data.wants_attention.get();
self.attention_requested.set(activation_requested);
if activation_requested {
self.workspace
.get()
.cnode_child_attention_request_changed(self, true);
}
}
fn discard_child_flags(&self) {
if self.attention_requested.get() {
self.workspace
.get()
.cnode_child_attention_request_changed(self, false);
}
}
}
impl Debug for FloatNode {
@ -527,13 +553,16 @@ impl ContainingNode for FloatNode {
containing_node_impl!();
fn cnode_replace_child(self: Rc<Self>, _old: &dyn Node, new: Rc<dyn ToplevelNode>) {
self.discard_child_flags();
self.child.set(Some(new.clone()));
self.apply_child_flags();
new.tl_set_parent(self.clone());
new.clone().tl_set_workspace(&self.workspace.get());
self.schedule_layout();
}
fn cnode_remove_child2(self: Rc<Self>, _child: &dyn Node, _preserve_focus: bool) {
self.discard_child_flags();
self.child.set(None);
self.display_link.set(None);
self.workspace_link.set(None);
@ -542,6 +571,14 @@ impl ContainingNode for FloatNode {
fn cnode_accepts_child(&self, _node: &dyn Node) -> bool {
true
}
fn cnode_child_attention_request_changed(self: Rc<Self>, _node: &dyn Node, set: bool) {
if self.attention_requested.replace(set) != set {
self.workspace
.get()
.cnode_child_attention_request_changed(&*self, set);
}
}
}
impl StackedNode for FloatNode {

View file

@ -144,6 +144,7 @@ impl OutputNode {
let mut rd = self.render_data.borrow_mut();
rd.titles.clear();
rd.inactive_workspaces.clear();
rd.attention_requested_workspaces.clear();
rd.captured_inactive_workspaces.clear();
rd.active_workspace = None;
rd.status = None;
@ -219,10 +220,15 @@ impl OutputNode {
rect,
captured: ws.capture.get(),
});
} else if ws.capture.get() {
rd.captured_inactive_workspaces.push(rect);
} else {
rd.inactive_workspaces.push(rect);
if ws.attention_requests.active() {
rd.attention_requested_workspaces.push(rect);
}
if ws.capture.get() {
rd.captured_inactive_workspaces.push(rect);
} else {
rd.inactive_workspaces.push(rect);
}
}
pos += title_width;
}
@ -330,6 +336,7 @@ impl OutputNode {
jay_workspaces: Default::default(),
capture: self.state.default_workspace_capture.clone(),
title_texture: Default::default(),
attention_requests: Default::default(),
});
ws.output_link
.set(Some(self.workspaces.add_last(ws.clone())));
@ -493,6 +500,7 @@ pub struct OutputRenderData {
pub active_workspace: Option<OutputWorkspaceRenderData>,
pub underline: Rect,
pub inactive_workspaces: Vec<Rect>,
pub attention_requested_workspaces: Vec<Rect>,
pub captured_inactive_workspaces: Vec<Rect>,
pub titles: Vec<OutputTitle>,
pub status: Option<OutputStatus>,

View file

@ -179,7 +179,7 @@ impl ToplevelNode for PlaceholderNode {
}
fn tl_set_visible(&self, visible: bool) {
self.toplevel.visible.set(visible);
self.toplevel.set_visible(self, visible);
}
fn tl_destroy(&self) {

View file

@ -5,7 +5,7 @@ use {
rect::Rect,
state::State,
tree::{ContainingNode, Direction, Node, OutputNode, PlaceholderNode, WorkspaceNode},
utils::{clonecell::CloneCell, numcell::NumCell, smallmap::SmallMap},
utils::{clonecell::CloneCell, smallmap::SmallMap, threshold_counter::ThresholdCounter},
},
std::{
cell::{Cell, RefCell},
@ -42,14 +42,14 @@ pub trait ToplevelNode: Node {
fn tl_surface_active_changed(&self, active: bool) {
let data = self.tl_data();
if active {
if data.active_surfaces.fetch_add(1) == 0 {
if data.active_surfaces.inc() {
self.tl_set_active(true);
if let Some(parent) = data.parent.get() {
parent.node_child_active_changed(self.tl_as_node(), true, 1);
}
}
} else {
if data.active_surfaces.fetch_sub(1) == 1 {
if data.active_surfaces.dec() {
self.tl_set_active(false);
if let Some(parent) = data.parent.get() {
parent.node_child_active_changed(self.tl_as_node(), false, 1);
@ -109,7 +109,7 @@ pub trait ToplevelNode: Node {
_ => return,
};
let node = self.tl_as_node();
if data.active.get() || data.active_surfaces.get() > 0 {
if data.active.get() || data.active_surfaces.active() {
parent.clone().node_child_active_changed(node, true, 1);
}
}
@ -161,7 +161,7 @@ pub struct ToplevelData {
pub active: Cell<bool>,
pub client: Option<Rc<Client>>,
pub state: Rc<State>,
pub active_surfaces: NumCell<u32>,
pub active_surfaces: ThresholdCounter,
pub focus_node: SmallMap<SeatId, Rc<dyn Node>, 1>,
pub visible: Cell<bool>,
pub is_floating: Cell<bool>,
@ -174,6 +174,8 @@ pub struct ToplevelData {
pub parent: CloneCell<Option<Rc<dyn ContainingNode>>>,
pub pos: Cell<Rect>,
pub seat_state: NodeSeatState,
pub wants_attention: Cell<bool>,
pub requested_attention: Cell<bool>,
}
impl ToplevelData {
@ -195,6 +197,8 @@ impl ToplevelData {
parent: Default::default(),
pos: Default::default(),
seat_state: Default::default(),
wants_attention: Cell::new(false),
requested_attention: Cell::new(false),
}
}
@ -263,12 +267,10 @@ impl ToplevelData {
let mut kb_foci = Default::default();
if ws.visible.get() {
if let Some(container) = ws.container.get() {
kb_foci = collect_kb_foci(container.clone());
container.tl_set_visible(false);
kb_foci = collect_kb_foci(container);
}
for stacked in ws.stacked.iter() {
collect_kb_foci2(stacked.deref().clone().stacked_into_node(), &mut kb_foci);
stacked.stacked_set_visible(false);
}
}
*data = Some(FullscreenedData {
@ -277,7 +279,7 @@ impl ToplevelData {
});
drop(data);
self.is_fullscreen.set(true);
ws.fullscreen.set(Some(node.clone()));
ws.set_fullscreen_node(&node);
node.tl_set_parent(ws.clone());
node.clone().tl_set_workspace(ws);
node.clone()
@ -313,11 +315,7 @@ impl ToplevelData {
}
_ => {}
}
fd.workspace.fullscreen.take();
if node.node_visible() {
fd.workspace.set_visible(true);
fd.workspace.flush_jay_workspaces();
}
fd.workspace.remove_fullscreen_node();
if fd.placeholder.is_destroyed() {
state.map_tiled(node);
return;
@ -339,6 +337,31 @@ impl ToplevelData {
pub fn set_visible(&self, node: &dyn Node, visible: bool) {
self.visible.set(visible);
self.seat_state.set_visible(node, visible)
self.seat_state.set_visible(node, visible);
if !visible {
return;
}
if !self.requested_attention.replace(false) {
return;
}
self.wants_attention.set(false);
if let Some(parent) = self.parent.get() {
parent.cnode_child_attention_request_changed(node, false);
}
self.state.damage();
}
pub fn request_attention(&self, node: &dyn Node) {
if self.visible.get() {
return;
}
if self.requested_attention.replace(true) {
return;
}
self.wants_attention.set(true);
if let Some(parent) = self.parent.get() {
parent.cnode_child_attention_request_changed(node, true);
}
self.state.damage();
}
}

View file

@ -20,6 +20,7 @@ use {
clonecell::CloneCell,
copyhashmap::CopyHashMap,
linkedlist::{LinkedList, LinkedNode},
threshold_counter::ThresholdCounter,
},
wire::JayWorkspaceId,
},
@ -45,6 +46,7 @@ pub struct WorkspaceNode {
pub jay_workspaces: CopyHashMap<(ClientId, JayWorkspaceId), Rc<JayWorkspace>>,
pub capture: Cell<bool>,
pub title_texture: Cell<Option<TextTexture>>,
pub attention_requests: ThresholdCounter,
}
impl WorkspaceNode {
@ -74,6 +76,10 @@ impl WorkspaceNode {
}
pub fn set_container(self: &Rc<Self>, container: &Rc<ContainerNode>) {
if let Some(prev) = self.container.get() {
self.discard_child_flags(&*prev);
}
self.apply_child_flags(&**container);
let pos = self.position.get();
container.clone().tl_change_extents(&pos);
container.clone().tl_set_workspace(self);
@ -103,6 +109,15 @@ impl WorkspaceNode {
}
}
fn plane_set_visible(&self, visible: bool) {
if let Some(container) = self.container.get() {
container.tl_set_visible(visible);
}
for stacked in self.stacked.iter() {
stacked.stacked_set_visible(visible);
}
}
pub fn set_visible(&self, visible: bool) {
for jw in self.jay_workspaces.lock().values() {
jw.send_visible(visible);
@ -111,15 +126,52 @@ impl WorkspaceNode {
if let Some(fs) = self.fullscreen.get() {
fs.tl_set_visible(visible);
} else {
if let Some(container) = self.container.get() {
container.tl_set_visible(visible);
}
for stacked in self.stacked.iter() {
stacked.stacked_set_visible(visible);
}
self.plane_set_visible(visible);
}
self.seat_state.set_visible(self, visible);
}
pub fn set_fullscreen_node(&self, node: &Rc<dyn ToplevelNode>) {
let visible = self.visible.get();
let mut plane_was_visible = visible;
if let Some(prev) = self.fullscreen.set(Some(node.clone())) {
plane_was_visible = false;
self.discard_child_flags(&*prev);
}
self.apply_child_flags(&**node);
node.tl_set_visible(visible);
if plane_was_visible {
self.plane_set_visible(false);
}
}
pub fn remove_fullscreen_node(&self) {
if let Some(node) = self.fullscreen.take() {
self.discard_child_flags(&*node);
if self.visible.get() {
self.plane_set_visible(true);
}
}
}
fn apply_child_flags(&self, child: &dyn ToplevelNode) {
if child.tl_data().wants_attention.get() {
self.mod_attention_requested(true);
}
}
fn discard_child_flags(&self, child: &dyn ToplevelNode) {
if child.tl_data().wants_attention.get() {
self.mod_attention_requested(false);
}
}
fn mod_attention_requested(&self, set: bool) {
let crossed_threshold = self.attention_requests.adj(set);
if crossed_threshold {
self.output.get().schedule_update_render_data();
}
}
}
impl Node for WorkspaceNode {
@ -224,12 +276,14 @@ impl ContainingNode for WorkspaceNode {
fn cnode_remove_child2(self: Rc<Self>, child: &dyn Node, _preserve_focus: bool) {
if let Some(container) = self.container.get() {
if container.node_id() == child.node_id() {
self.discard_child_flags(&*container);
self.container.set(None);
return;
}
}
if let Some(fs) = self.fullscreen.get() {
if fs.tl_as_node().node_id() == child.node_id() {
self.discard_child_flags(&*fs);
self.fullscreen.set(None);
return;
}
@ -240,4 +294,8 @@ impl ContainingNode for WorkspaceNode {
fn cnode_accepts_child(&self, node: &dyn Node) -> bool {
node.node_is_container()
}
fn cnode_child_attention_request_changed(self: Rc<Self>, _node: &dyn Node, set: bool) {
self.mod_attention_requested(set);
}
}

View file

@ -1,3 +1,4 @@
pub mod activation_token;
pub mod array;
pub mod asyncevent;
pub mod bitfield;
@ -18,6 +19,7 @@ pub mod nonblock;
pub mod num_cpus;
pub mod numcell;
pub mod once;
pub mod opaque;
pub mod option_ext;
pub mod oserror;
pub mod page_size;
@ -30,6 +32,7 @@ pub mod scroller;
pub mod smallmap;
pub mod stack;
pub mod syncqueue;
pub mod threshold_counter;
pub mod timer;
pub mod tri;
pub mod trim;

View file

@ -0,0 +1,28 @@
use {
crate::utils::opaque::{opaque, Opaque, OpaqueError},
std::{
fmt::{Display, Formatter},
str::FromStr,
},
};
#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)]
pub struct ActivationToken(Opaque);
pub fn activation_token() -> ActivationToken {
ActivationToken(opaque())
}
impl Display for ActivationToken {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl FromStr for ActivationToken {
type Err = OpaqueError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(s.parse()?))
}
}

66
src/utils/opaque.rs Normal file
View file

@ -0,0 +1,66 @@
use {
rand::{thread_rng, Rng},
std::{
fmt::{Debug, Display, Formatter},
num::ParseIntError,
str::FromStr,
},
thiserror::Error,
};
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct Opaque {
lo: u64,
hi: u64,
}
pub fn opaque() -> Opaque {
let mut rng = thread_rng();
Opaque {
lo: rng.gen(),
hi: rng.gen(),
}
}
impl Display for Opaque {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:016x}", self.hi)?;
write!(f, "{:016x}", self.lo)?;
Ok(())
}
}
impl Debug for Opaque {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Display::fmt(self, f)
}
}
impl FromStr for Opaque {
type Err = OpaqueError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() != LEN {
return Err(OpaqueError::InvalidLength);
}
if !s.is_char_boundary(LEN / 2) {
return Err(OpaqueError::NotAscii);
}
let (hi, lo) = s.split_at(LEN / 2);
let hi = u64::from_str_radix(hi, 16).map_err(OpaqueError::Parse)?;
let lo = u64::from_str_radix(lo, 16).map_err(OpaqueError::Parse)?;
Ok(Self { lo, hi })
}
}
const LEN: usize = 32;
#[derive(Debug, Error)]
pub enum OpaqueError {
#[error("The string is not exactly 32 bytes long")]
InvalidLength,
#[error("The string is not ascii")]
NotAscii,
#[error("Could not parse the string as a hex number")]
Parse(ParseIntError),
}

View file

@ -0,0 +1,28 @@
use crate::utils::numcell::NumCell;
#[derive(Default)]
pub struct ThresholdCounter {
counter: NumCell<usize>,
}
impl ThresholdCounter {
pub fn inc(&self) -> bool {
self.counter.fetch_add(1) == 0
}
pub fn dec(&self) -> bool {
self.counter.fetch_sub(1) == 1
}
pub fn adj(&self, inc: bool) -> bool {
if inc {
self.inc()
} else {
self.dec()
}
}
pub fn active(&self) -> bool {
self.counter.get() > 0
}
}

View file

@ -2234,7 +2234,7 @@ impl Wm {
async fn handle_minimize_requested(&self, data: &Rc<XwindowData>) -> bool {
if let Some(w) = data.window.get() {
if w.toplevel_data.active_surfaces.get() > 0 {
if w.toplevel_data.active_surfaces.active() {
self.set_wm_state(data, ICCCM_WM_STATE_NORMAL).await;
return false;
}

View file

@ -0,0 +1,28 @@
# requests
msg set_serial = 0 {
serial: u32,
seat: id(wl_seat),
}
msg set_app_id = 1 {
app_id: str,
}
msg set_surface = 2 {
surface: id(wl_surface),
}
msg commit = 3 {
}
msg destroy = 4 {
}
# events
msg done = 0 {
token: str,
}

View file

@ -0,0 +1,14 @@
# requests
msg destroy = 0 {
}
msg get_activation_token = 1 {
id: id(xdg_activation_token_v1),
}
msg activate = 2 {
token: str,
surface: id(wl_surface),
}