Skip to content

Commit

Permalink
Merge pull request #85 from mahkoh/jorth/activation
Browse files Browse the repository at this point in the history
wayland: implement xdg-activation
  • Loading branch information
mahkoh authored Feb 14, 2024
2 parents d725a1e + 41d7531 commit ccacdda
Show file tree
Hide file tree
Showing 28 changed files with 676 additions and 58 deletions.
4 changes: 4 additions & 0 deletions jay-config/src/theme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}

Expand Down
10 changes: 10 additions & 0 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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();
}
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down
2 changes: 2 additions & 0 deletions src/compositor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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()),
Expand Down
1 change: 1 addition & 0 deletions src/config/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions src/globals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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>) {
Expand Down
2 changes: 2 additions & 0 deletions src/ifs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 6 additions & 0 deletions src/ifs/wl_surface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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! {
Expand Down
6 changes: 2 additions & 4 deletions src/ifs/wl_surface/x_surface/xwindow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>>>>,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down
108 changes: 108 additions & 0 deletions src/ifs/xdg_activation_token_v1.rs
Original file line number Diff line number Diff line change
@@ -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);
120 changes: 120 additions & 0 deletions src/ifs/xdg_activation_v1.rs
Original file line number Diff line number Diff line change
@@ -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);
Loading

0 comments on commit ccacdda

Please sign in to comment.