config: allow configuring client capabilities
This commit is contained in:
parent
76a1a86091
commit
e680a3dc09
21 changed files with 624 additions and 39 deletions
|
|
@ -12,7 +12,7 @@ use {
|
|||
logging,
|
||||
},
|
||||
Axis, Direction, ModifiedKeySym, PciId, Workspace,
|
||||
client::{Client, ClientCriterion, ClientMatcher, MatchedClient},
|
||||
client::{Client, ClientCapabilities, ClientCriterion, ClientMatcher, MatchedClient},
|
||||
exec::Command,
|
||||
input::{
|
||||
FocusFollowsMouseMode, InputDevice, LayerDirection, Seat, SwitchEvent, Timeline,
|
||||
|
|
@ -1343,6 +1343,22 @@ impl ConfigClient {
|
|||
workspaces
|
||||
}
|
||||
|
||||
pub fn set_client_matcher_capabilities(
|
||||
&self,
|
||||
matcher: ClientMatcher,
|
||||
caps: ClientCapabilities,
|
||||
) {
|
||||
self.send(&ClientMessage::SetClientMatcherCapabilities { matcher, caps });
|
||||
}
|
||||
|
||||
pub fn set_client_matcher_bounding_capabilities(
|
||||
&self,
|
||||
matcher: ClientMatcher,
|
||||
caps: ClientCapabilities,
|
||||
) {
|
||||
self.send(&ClientMessage::SetClientMatcherBoundingCapabilities { matcher, caps });
|
||||
}
|
||||
|
||||
pub fn latch<F: FnOnce() + 'static>(&self, seat: Seat, f: F) {
|
||||
if !self.feat_mod_mask.get() {
|
||||
log::error!("compositor does not support latching");
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use {
|
|||
crate::{
|
||||
_private::{ClientCriterionIpc, PollableId, WindowCriterionIpc, WireMode},
|
||||
Axis, Direction, PciId, Workspace,
|
||||
client::{Client, ClientMatcher},
|
||||
client::{Client, ClientCapabilities, ClientMatcher},
|
||||
input::{
|
||||
FocusFollowsMouseMode, InputDevice, LayerDirection, Seat, SwitchEvent, Timeline,
|
||||
acceleration::AccelProfile, capability::Capability, clickmethod::ClickMethod,
|
||||
|
|
@ -774,6 +774,14 @@ pub enum ClientMessage<'a> {
|
|||
SetTitleFont {
|
||||
font: &'a str,
|
||||
},
|
||||
SetClientMatcherCapabilities {
|
||||
matcher: ClientMatcher,
|
||||
caps: ClientCapabilities,
|
||||
},
|
||||
SetClientMatcherBoundingCapabilities {
|
||||
matcher: ClientMatcher,
|
||||
caps: ClientCapabilities,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
|
|
|
|||
|
|
@ -105,6 +105,20 @@ impl ClientCriterion<'_> {
|
|||
pub fn bind<F: FnMut(MatchedClient) + 'static>(self, cb: F) {
|
||||
self.to_matcher().bind(cb);
|
||||
}
|
||||
|
||||
/// Sets the capabilities granted to clients matching this matcher.
|
||||
///
|
||||
/// This leaks the matcher.
|
||||
pub fn set_capabilities(self, caps: ClientCapabilities) {
|
||||
self.to_matcher().set_capabilities(caps);
|
||||
}
|
||||
|
||||
/// Sets the upper capability bounds for clients in sandboxes created by this client.
|
||||
///
|
||||
/// This leaks the matcher.
|
||||
pub fn set_sandbox_bounding_capabilities(self, caps: ClientCapabilities) {
|
||||
self.to_matcher().set_sandbox_bounding_capabilities(caps);
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientMatcher {
|
||||
|
|
@ -121,6 +135,36 @@ impl ClientMatcher {
|
|||
pub fn bind<F: FnMut(MatchedClient) + 'static>(self, cb: F) {
|
||||
get!().set_client_matcher_handler(self, cb);
|
||||
}
|
||||
|
||||
/// Sets the capabilities granted to clients matching this matcher.
|
||||
///
|
||||
/// If multiple matchers match a client, the capabilities are added.
|
||||
///
|
||||
/// If no matcher matches a client, it is granted the default capabilities depending
|
||||
/// on whether it's sandboxed or not. If it is not sandboxed, it is granted the
|
||||
/// capabilities [`CC_LAYER_SHELL`] and [`CC_DRM_LEASE`]. Otherwise it is granted the
|
||||
/// capability [`CC_DRM_LEASE`].
|
||||
///
|
||||
/// Regardless of any capabilities set through this function, the capabilities of the
|
||||
/// client can never exceed its bounding capabilities.
|
||||
pub fn set_capabilities(self, caps: ClientCapabilities) {
|
||||
get!().set_client_matcher_capabilities(self, caps);
|
||||
}
|
||||
|
||||
/// Sets the upper capability bounds for clients in sandboxes created by this client.
|
||||
///
|
||||
/// If multiple matchers match a client, the capabilities are added.
|
||||
///
|
||||
/// If no matcher matches a client, the bounding capabilities for sandboxes depend on
|
||||
/// whether the client is itself sandboxed. If it is sandboxed, the bounding
|
||||
/// capabilities are the effective capabilities of the client. Otherwise the bounding
|
||||
/// capabilities are all capabilities.
|
||||
///
|
||||
/// Regardless of any capabilities set through this function, the capabilities set
|
||||
/// through this function can never exceed the client's bounding capabilities.
|
||||
pub fn set_sandbox_bounding_capabilities(self, caps: ClientCapabilities) {
|
||||
get!().set_client_matcher_bounding_capabilities(self, caps);
|
||||
}
|
||||
}
|
||||
|
||||
impl MatchedClient {
|
||||
|
|
@ -147,3 +191,39 @@ impl Deref for MatchedClient {
|
|||
&self.client
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// Capabilities granted to a client.
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Hash, Eq, PartialEq)]
|
||||
pub struct ClientCapabilities(pub u64) {
|
||||
/// Grants access to the `ext_data_control_manager_v1` and
|
||||
/// `zwlr_data_control_manager_v1` globals.
|
||||
pub const CC_DATA_CONTROL = 1 << 0,
|
||||
/// Grants access to the `zwp_virtual_keyboard_manager_v1` global.
|
||||
pub const CC_VIRTUAL_KEYBOARD = 1 << 1,
|
||||
/// Grants access to the `ext_foreign_toplevel_list_v1` global.
|
||||
pub const CC_FOREIGN_TOPLEVEL_LIST = 1 << 2,
|
||||
/// Grants access to the `ext_idle_notifier_v1` global.
|
||||
pub const CC_IDLE_NOTIFIER = 1 << 3,
|
||||
/// Grants access to the `ext_session_lock_manager_v1` global.
|
||||
pub const CC_SESSION_LOCK = 1 << 4,
|
||||
/// Grants access to the `zwlr_layer_shell_v1` global.
|
||||
pub const CC_LAYER_SHELL = 1 << 6,
|
||||
/// Grants access to the `ext_image_copy_capture_manager_v1` and
|
||||
/// `zwlr_screencopy_manager_v1` globals.
|
||||
pub const CC_SCREENCOPY = 1 << 7,
|
||||
/// Grants access to the `ext_transient_seat_manager_v1` global.
|
||||
pub const CC_SEAT_MANAGER = 1 << 8,
|
||||
/// Grants access to the `wp_drm_lease_device_v1` global.
|
||||
pub const CC_DRM_LEASE = 1 << 9,
|
||||
/// Grants access to the `zwp_input_method_manager_v2` global.
|
||||
pub const CC_INPUT_METHOD = 1 << 10,
|
||||
/// Grants access to the `ext_workspace_manager_v1` global.
|
||||
pub const CC_WORKSPACE_MANAGER = 1 << 11,
|
||||
/// Grants access to the `zwlr_foreign_toplevel_manager_v1` global.
|
||||
pub const CC_FOREIGN_TOPLEVEL_MANAGER = 1 << 12,
|
||||
/// Grants access to the `jay_head_manager_v1` and `zwlr_output_manager_v1`
|
||||
/// globals.
|
||||
pub const CC_HEAD_MANAGER = 1 << 13,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use {
|
||||
crate::{
|
||||
async_engine::SpawnedFuture,
|
||||
client::{CAPS_DEFAULT, ClientCaps},
|
||||
client::ClientCaps,
|
||||
security_context_acceptor::AcceptorMetadata,
|
||||
state::State,
|
||||
utils::{errorfmt::ErrorFmt, oserror::OsError, xrd::xrd},
|
||||
|
|
@ -149,11 +149,11 @@ impl Acceptor {
|
|||
let futures = vec![
|
||||
state.eng.spawn(
|
||||
"secure acceptor",
|
||||
accept(acc.socket.secure.clone(), state.clone(), ClientCaps::all()),
|
||||
accept(acc.socket.secure.clone(), state.clone(), true),
|
||||
),
|
||||
state.eng.spawn(
|
||||
"insecure acceptor",
|
||||
accept(acc.socket.insecure.clone(), state.clone(), CAPS_DEFAULT),
|
||||
accept(acc.socket.insecure.clone(), state.clone(), false),
|
||||
),
|
||||
];
|
||||
state.acceptor.set(Some(acc.clone()));
|
||||
|
|
@ -170,8 +170,11 @@ impl Acceptor {
|
|||
}
|
||||
}
|
||||
|
||||
async fn accept(fd: Rc<OwnedFd>, state: Rc<State>, effective_caps: ClientCaps) {
|
||||
let metadata = Rc::new(AcceptorMetadata::default());
|
||||
async fn accept(fd: Rc<OwnedFd>, state: Rc<State>, secure: bool) {
|
||||
let metadata = Rc::new(AcceptorMetadata {
|
||||
secure,
|
||||
..Default::default()
|
||||
});
|
||||
loop {
|
||||
let fd = match state.ring.accept(&fd, c::SOCK_CLOEXEC).await {
|
||||
Ok(fd) => fd,
|
||||
|
|
@ -181,10 +184,9 @@ async fn accept(fd: Rc<OwnedFd>, state: Rc<State>, effective_caps: ClientCaps) {
|
|||
}
|
||||
};
|
||||
let id = state.clients.id();
|
||||
if let Err(e) =
|
||||
state
|
||||
.clients
|
||||
.spawn(id, &state, fd, effective_caps, ClientCaps::all(), &metadata)
|
||||
if let Err(e) = state
|
||||
.clients
|
||||
.spawn(id, &state, fd, ClientCaps::all(), false, &metadata)
|
||||
{
|
||||
log::error!("Could not spawn a client: {}", ErrorFmt(e));
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -126,8 +126,8 @@ impl Clients {
|
|||
id: ClientId,
|
||||
global: &Rc<State>,
|
||||
socket: Rc<OwnedFd>,
|
||||
effective_caps: ClientCaps,
|
||||
bounding_caps: ClientCaps,
|
||||
set_bounding_caps_for_children: bool,
|
||||
acceptor: &Rc<AcceptorMetadata>,
|
||||
) -> Result<(), ClientError> {
|
||||
let Some((uid, pid)) = get_socket_creds(&socket) else {
|
||||
|
|
@ -139,8 +139,8 @@ impl Clients {
|
|||
socket,
|
||||
uid,
|
||||
pid,
|
||||
effective_caps,
|
||||
bounding_caps,
|
||||
set_bounding_caps_for_children,
|
||||
false,
|
||||
acceptor,
|
||||
)?;
|
||||
|
|
@ -154,11 +154,15 @@ impl Clients {
|
|||
socket: Rc<OwnedFd>,
|
||||
uid: c::uid_t,
|
||||
pid: c::pid_t,
|
||||
effective_caps: ClientCaps,
|
||||
bounding_caps: ClientCaps,
|
||||
set_bounding_caps_for_children: bool,
|
||||
is_xwayland: bool,
|
||||
acceptor: &Rc<AcceptorMetadata>,
|
||||
) -> Result<Rc<Client>, ClientError> {
|
||||
let effective_caps = match acceptor.sandboxed {
|
||||
true => CAPS_DEFAULT_SANDBOXED,
|
||||
false => CAPS_DEFAULT,
|
||||
};
|
||||
let data = Rc::new_cyclic(|slf| Client {
|
||||
id,
|
||||
state: global.clone(),
|
||||
|
|
@ -170,8 +174,8 @@ impl Clients {
|
|||
shutdown: Default::default(),
|
||||
tracker: Default::default(),
|
||||
is_xwayland,
|
||||
effective_caps,
|
||||
bounding_caps,
|
||||
effective_caps: Cell::new(effective_caps & bounding_caps),
|
||||
bounding_caps_for_children: Cell::new(bounding_caps),
|
||||
last_enter_serial: Default::default(),
|
||||
pid_info: get_pid_info(uid, pid),
|
||||
serials: Default::default(),
|
||||
|
|
@ -192,6 +196,10 @@ impl Clients {
|
|||
acceptor: acceptor.clone(),
|
||||
});
|
||||
track!(data, data);
|
||||
global.update_capabilities(&data, bounding_caps, set_bounding_caps_for_children);
|
||||
if acceptor.secure || is_xwayland {
|
||||
data.effective_caps.set(ClientCaps::all());
|
||||
}
|
||||
let display = Rc::new(WlDisplay::new(&data));
|
||||
track!(data, display);
|
||||
data.objects.display.set(Some(display.clone()));
|
||||
|
|
@ -207,7 +215,7 @@ impl Clients {
|
|||
uid,
|
||||
client.data.socket.raw(),
|
||||
data.pid_info.comm,
|
||||
effective_caps,
|
||||
data.effective_caps.get(),
|
||||
);
|
||||
client.data.property_changed(CL_CHANGED_NEW);
|
||||
self.clients.borrow_mut().insert(client.data.id, client);
|
||||
|
|
@ -236,7 +244,7 @@ impl Clients {
|
|||
{
|
||||
let clients = self.clients.borrow();
|
||||
for client in clients.values() {
|
||||
if client.data.effective_caps.contains(required_caps)
|
||||
if client.data.effective_caps.get().contains(required_caps)
|
||||
&& (!xwayland_only || client.data.is_xwayland)
|
||||
{
|
||||
f(&client.data);
|
||||
|
|
@ -298,8 +306,8 @@ pub struct Client {
|
|||
shutdown: AsyncEvent,
|
||||
pub tracker: Tracker<Client>,
|
||||
pub is_xwayland: bool,
|
||||
pub effective_caps: ClientCaps,
|
||||
pub bounding_caps: ClientCaps,
|
||||
pub effective_caps: Cell<ClientCaps>,
|
||||
pub bounding_caps_for_children: Cell<ClientCaps>,
|
||||
pub last_enter_serial: Cell<Option<u64>>,
|
||||
pub pid_info: PidInfo,
|
||||
pub serials: RefCell<VecDeque<SerialRange>>,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ use crate::it::test_config::TEST_CONFIG_ENTRY;
|
|||
use {
|
||||
crate::{
|
||||
backend::{ConnectorId, DrmDeviceId, InputDeviceId},
|
||||
client::{Client, ClientCaps},
|
||||
config::handler::ConfigProxyHandler,
|
||||
ifs::wl_seat::SeatId,
|
||||
state::State,
|
||||
|
|
@ -180,6 +181,17 @@ impl ConfigProxy {
|
|||
pub fn initial_tile_state(&self, data: &ToplevelData) -> Option<TileState> {
|
||||
self.handler.get()?.initial_tile_state(data)
|
||||
}
|
||||
|
||||
pub fn update_capabilities(
|
||||
&self,
|
||||
data: &Rc<Client>,
|
||||
bounding_caps: ClientCaps,
|
||||
set_bounding_caps: bool,
|
||||
) {
|
||||
if let Some(handler) = self.handler.get() {
|
||||
handler.update_capabilities(data, bounding_caps, set_bounding_caps);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ConfigProxy {
|
||||
|
|
@ -238,6 +250,8 @@ impl ConfigProxy {
|
|||
client_matchers: Default::default(),
|
||||
client_matcher_cache: Default::default(),
|
||||
client_matcher_leafs: Default::default(),
|
||||
client_matcher_capabilities: Default::default(),
|
||||
client_matcher_bounding_capabilities: Default::default(),
|
||||
window_matcher_ids: NumCell::new(1),
|
||||
window_matchers: Default::default(),
|
||||
window_matcher_cache: Default::default(),
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use {
|
|||
InputDeviceAccelProfile, InputDeviceCapability, InputDeviceClickMethod, InputDeviceId,
|
||||
transaction::BackendConnectorTransactionError,
|
||||
},
|
||||
client::{Client, ClientId},
|
||||
client::{CAP_JAY_COMPOSITOR, Client, ClientCaps, ClientId},
|
||||
cmm::cmm_eotf::Eotf,
|
||||
compositor::MAX_EXTENTS,
|
||||
config::ConfigProxy,
|
||||
|
|
@ -53,7 +53,7 @@ use {
|
|||
ipc::{ClientMessage, Response, ServerMessage, WorkspaceSource},
|
||||
},
|
||||
Axis, Direction, Workspace,
|
||||
client::{Client as ConfigClient, ClientMatcher},
|
||||
client::{Client as ConfigClient, ClientCapabilities, ClientMatcher},
|
||||
input::{
|
||||
FocusFollowsMouseMode, InputDevice, LayerDirection, Seat, Timeline,
|
||||
acceleration::{ACCEL_PROFILE_ADAPTIVE, ACCEL_PROFILE_FLAT, AccelProfile},
|
||||
|
|
@ -127,6 +127,20 @@ pub(super) struct ConfigProxyHandler {
|
|||
CopyHashMap<ClientMatcher, Rc<CachedCriterion<ClientCriterionIpc, Rc<Client>>>>,
|
||||
pub client_matcher_cache: CriterionCache<ClientCriterionIpc, Rc<Client>>,
|
||||
pub client_matcher_leafs: CopyHashMap<ClientMatcher, Rc<ClmLeafMatcher>>,
|
||||
pub client_matcher_capabilities: CopyHashMap<
|
||||
ClientMatcher,
|
||||
(
|
||||
Rc<CachedCriterion<ClientCriterionIpc, Rc<Client>>>,
|
||||
ClientCaps,
|
||||
),
|
||||
>,
|
||||
pub client_matcher_bounding_capabilities: CopyHashMap<
|
||||
ClientMatcher,
|
||||
(
|
||||
Rc<CachedCriterion<ClientCriterionIpc, Rc<Client>>>,
|
||||
ClientCaps,
|
||||
),
|
||||
>,
|
||||
|
||||
pub window_matcher_ids: NumCell<u64>,
|
||||
pub window_matchers:
|
||||
|
|
@ -2009,6 +2023,8 @@ impl ConfigProxyHandler {
|
|||
fn handle_destroy_client_matcher(&self, matcher: ClientMatcher) {
|
||||
self.client_matchers.remove(&matcher);
|
||||
self.client_matcher_leafs.remove(&matcher);
|
||||
self.client_matcher_capabilities.remove(&matcher);
|
||||
self.client_matcher_bounding_capabilities.remove(&matcher);
|
||||
}
|
||||
|
||||
fn handle_enable_client_matcher_events(
|
||||
|
|
@ -2579,6 +2595,28 @@ impl ConfigProxyHandler {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_set_client_matcher_capabilities(
|
||||
&self,
|
||||
matcher: ClientMatcher,
|
||||
caps: ClientCapabilities,
|
||||
) -> Result<(), CphError> {
|
||||
let m = self.get_client_matcher(matcher)?;
|
||||
self.client_matcher_capabilities
|
||||
.set(matcher, (m, caps.to_client_caps()));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_set_client_matcher_bounding_capabilities(
|
||||
&self,
|
||||
matcher: ClientMatcher,
|
||||
caps: ClientCapabilities,
|
||||
) -> Result<(), CphError> {
|
||||
let m = self.get_client_matcher(matcher)?;
|
||||
self.client_matcher_bounding_capabilities
|
||||
.set(matcher, (m, caps.to_client_caps()));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn handle_request(self: &Rc<Self>, msg: &[u8]) {
|
||||
if let Err(e) = self.handle_request_(msg) {
|
||||
log::error!("Could not handle client request: {}", ErrorFmt(e));
|
||||
|
|
@ -3159,6 +3197,12 @@ impl ConfigProxyHandler {
|
|||
.wrn("connector_set_blend_space")?,
|
||||
ClientMessage::SetBarFont { font } => self.handle_set_bar_font(font),
|
||||
ClientMessage::SetTitleFont { font } => self.handle_set_title_font(font),
|
||||
ClientMessage::SetClientMatcherCapabilities { matcher, caps } => self
|
||||
.handle_set_client_matcher_capabilities(matcher, caps)
|
||||
.wrn("set_client_matcher_capabilities")?,
|
||||
ClientMessage::SetClientMatcherBoundingCapabilities { matcher, caps } => self
|
||||
.handle_set_client_matcher_bounding_capabilities(matcher, caps)
|
||||
.wrn("set_client_matcher_bounding_capabilities")?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -3180,6 +3224,42 @@ impl ConfigProxyHandler {
|
|||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn update_capabilities(
|
||||
&self,
|
||||
data: &Rc<Client>,
|
||||
bounding_caps: ClientCaps,
|
||||
set_bounding_caps: bool,
|
||||
) {
|
||||
let mut have_caps = false;
|
||||
let mut have_bounding_caps = false;
|
||||
let mut caps = ClientCaps::none();
|
||||
let mut new_bounding_caps = ClientCaps::none();
|
||||
for (matcher, state) in self.client_matcher_capabilities.lock().values() {
|
||||
if matcher.node.pull(data) {
|
||||
have_caps = true;
|
||||
caps |= *state;
|
||||
}
|
||||
}
|
||||
for (matcher, state) in self.client_matcher_bounding_capabilities.lock().values() {
|
||||
if matcher.node.pull(data) {
|
||||
have_bounding_caps = true;
|
||||
new_bounding_caps |= *state;
|
||||
}
|
||||
}
|
||||
if have_caps {
|
||||
caps &= bounding_caps;
|
||||
data.effective_caps.set(caps);
|
||||
}
|
||||
if !have_bounding_caps && set_bounding_caps {
|
||||
have_bounding_caps = true;
|
||||
new_bounding_caps = data.effective_caps.get();
|
||||
}
|
||||
if have_bounding_caps {
|
||||
new_bounding_caps &= bounding_caps;
|
||||
data.bounding_caps_for_children.set(new_bounding_caps);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
|
|
@ -3281,3 +3361,13 @@ impl WithRequestName for Result<(), CphError> {
|
|||
self.map_err(move |e| CphError::FailedRequest(request, Box::new(e)))
|
||||
}
|
||||
}
|
||||
|
||||
trait ClientCapabilitiesExt {
|
||||
fn to_client_caps(self) -> ClientCaps;
|
||||
}
|
||||
|
||||
impl ClientCapabilitiesExt for ClientCapabilities {
|
||||
fn to_client_caps(self) -> ClientCaps {
|
||||
ClientCaps(self.0 as u32) & !CAP_JAY_COMPOSITOR & ClientCaps::all()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -304,7 +304,7 @@ impl Globals {
|
|||
}
|
||||
|
||||
pub fn notify_all(&self, registry: &Rc<WlRegistry>) {
|
||||
let caps = registry.client.effective_caps;
|
||||
let caps = registry.client.effective_caps.get();
|
||||
let xwayland = registry.client.is_xwayland;
|
||||
let globals = self.registry.lock();
|
||||
macro_rules! emit {
|
||||
|
|
|
|||
|
|
@ -48,7 +48,11 @@ impl WlRegistryRequestHandler for WlRegistry {
|
|||
fn bind(&self, bind: Bind, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
let name = GlobalName::from_raw(bind.name);
|
||||
let globals = &self.client.state.globals;
|
||||
let global = globals.get(name, self.client.effective_caps, self.client.is_xwayland)?;
|
||||
let global = globals.get(
|
||||
name,
|
||||
self.client.effective_caps.get(),
|
||||
self.client.is_xwayland,
|
||||
)?;
|
||||
if global.interface().name() != bind.interface {
|
||||
return Err(WlRegistryError::InvalidInterface(InterfaceError {
|
||||
name: global.name(),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use {
|
||||
crate::{
|
||||
client::{CAPS_DEFAULT_SANDBOXED, Client, ClientError},
|
||||
client::{Client, ClientError},
|
||||
leaks::Tracker,
|
||||
object::{Object, Version},
|
||||
wire::{WpSecurityContextV1Id, wp_security_context_v1::*},
|
||||
|
|
@ -80,7 +80,6 @@ impl WpSecurityContextV1RequestHandler for WpSecurityContextV1 {
|
|||
fn commit(&self, _req: Commit, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
self.check_committed()?;
|
||||
self.committed.set(true);
|
||||
let caps = CAPS_DEFAULT_SANDBOXED & self.client.bounding_caps;
|
||||
self.client.state.security_context_acceptors.spawn(
|
||||
&self.client.state,
|
||||
self.sandbox_engine.take(),
|
||||
|
|
@ -88,7 +87,7 @@ impl WpSecurityContextV1RequestHandler for WpSecurityContextV1 {
|
|||
self.instance_id.take(),
|
||||
&self.listen_fd,
|
||||
&self.close_fd,
|
||||
caps,
|
||||
self.client.bounding_caps_for_children.get(),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,13 +27,14 @@ struct Acceptor {
|
|||
metadata: Rc<AcceptorMetadata>,
|
||||
listen_fd: Rc<OwnedFd>,
|
||||
close_fd: Rc<OwnedFd>,
|
||||
caps: ClientCaps,
|
||||
bounding_caps: ClientCaps,
|
||||
listen_future: Cell<Option<SpawnedFuture<()>>>,
|
||||
close_future: Cell<Option<SpawnedFuture<()>>>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct AcceptorMetadata {
|
||||
pub secure: bool,
|
||||
pub sandboxed: bool,
|
||||
pub sandbox_engine: Option<String>,
|
||||
pub app_id: Option<String>,
|
||||
|
|
@ -55,12 +56,13 @@ impl SecurityContextAcceptors {
|
|||
instance_id: Option<String>,
|
||||
listen_fd: &Rc<OwnedFd>,
|
||||
close_fd: &Rc<OwnedFd>,
|
||||
caps: ClientCaps,
|
||||
bounding_caps: ClientCaps,
|
||||
) {
|
||||
let acceptor = Rc::new(Acceptor {
|
||||
id: self.ids.next(),
|
||||
state: state.clone(),
|
||||
metadata: Rc::new(AcceptorMetadata {
|
||||
secure: false,
|
||||
sandboxed: true,
|
||||
sandbox_engine,
|
||||
app_id,
|
||||
|
|
@ -68,7 +70,7 @@ impl SecurityContextAcceptors {
|
|||
}),
|
||||
listen_fd: listen_fd.clone(),
|
||||
close_fd: close_fd.clone(),
|
||||
caps,
|
||||
bounding_caps,
|
||||
listen_future: Cell::new(None),
|
||||
close_future: Cell::new(None),
|
||||
});
|
||||
|
|
@ -111,7 +113,7 @@ impl Acceptor {
|
|||
let id = s.clients.id();
|
||||
if let Err(e) = s
|
||||
.clients
|
||||
.spawn(id, s, fd, self.caps, self.caps, &self.metadata)
|
||||
.spawn(id, s, fd, self.bounding_caps, true, &self.metadata)
|
||||
{
|
||||
log::error!("Could not spawn a client: {}", ErrorFmt(e));
|
||||
break;
|
||||
|
|
|
|||
13
src/state.rs
13
src/state.rs
|
|
@ -10,7 +10,7 @@ use {
|
|||
},
|
||||
backends::dummy::DummyBackend,
|
||||
cli::RunArgs,
|
||||
client::{Client, ClientId, Clients, NUM_CACHED_SERIAL_RANGES, SerialRange},
|
||||
client::{Client, ClientCaps, ClientId, Clients, NUM_CACHED_SERIAL_RANGES, SerialRange},
|
||||
clientmem::ClientMemOffset,
|
||||
cmm::{cmm_description::ColorDescription, cmm_manager::ColorManager},
|
||||
compositor::LIBEI_SOCKET,
|
||||
|
|
@ -1541,6 +1541,17 @@ impl State {
|
|||
self.config.get()?.initial_tile_state(data)
|
||||
}
|
||||
|
||||
pub fn update_capabilities(
|
||||
&self,
|
||||
data: &Rc<Client>,
|
||||
bounding_caps: ClientCaps,
|
||||
set_bounding_caps: bool,
|
||||
) {
|
||||
if let Some(config) = self.config.get() {
|
||||
config.update_capabilities(data, bounding_caps, set_bounding_caps);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn node_at(&self, x: i32, y: i32) -> FoundNode {
|
||||
let mut found_tree = self.node_at_tree.borrow_mut();
|
||||
found_tree.push(FoundNode {
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@ async fn run(
|
|||
uapi::getuid(),
|
||||
pid,
|
||||
ClientCaps::all(),
|
||||
ClientCaps::all(),
|
||||
false,
|
||||
true,
|
||||
&Rc::new(AcceptorMetadata::default()),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ use {
|
|||
ahash::AHashMap,
|
||||
jay_config::{
|
||||
Axis, Direction, Workspace,
|
||||
client::ClientCapabilities,
|
||||
input::{
|
||||
LayerDirection, SwitchEvent, Timeline, acceleration::AccelProfile,
|
||||
clickmethod::ClickMethod,
|
||||
|
|
@ -247,6 +248,8 @@ pub struct ClientRule {
|
|||
pub match_: ClientMatch,
|
||||
pub action: Option<Action>,
|
||||
pub latch: Option<Action>,
|
||||
pub capabilities: Option<ClientCapabilities>,
|
||||
pub bounding_capabilities: Option<ClientCapabilities>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ use {
|
|||
|
||||
pub mod action;
|
||||
mod actions;
|
||||
mod capabilities;
|
||||
mod client_match;
|
||||
mod client_rule;
|
||||
mod color;
|
||||
|
|
|
|||
66
toml-config/src/config/parsers/capabilities.rs
Normal file
66
toml-config/src/config/parsers/capabilities.rs
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
use {
|
||||
crate::{
|
||||
config::parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||
toml::{
|
||||
toml_span::{Span, Spanned, SpannedExt},
|
||||
toml_value::Value,
|
||||
},
|
||||
},
|
||||
jay_config::client::{
|
||||
CC_DATA_CONTROL, CC_DRM_LEASE, CC_FOREIGN_TOPLEVEL_LIST, CC_FOREIGN_TOPLEVEL_MANAGER,
|
||||
CC_HEAD_MANAGER, CC_IDLE_NOTIFIER, CC_INPUT_METHOD, CC_LAYER_SHELL, CC_SCREENCOPY,
|
||||
CC_SEAT_MANAGER, CC_SESSION_LOCK, CC_VIRTUAL_KEYBOARD, CC_WORKSPACE_MANAGER,
|
||||
ClientCapabilities,
|
||||
},
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum CapabilitiesParserError {
|
||||
#[error(transparent)]
|
||||
Expected(#[from] UnexpectedDataType),
|
||||
#[error("Unknown capability `{}`", .0)]
|
||||
UnknownCapability(String),
|
||||
}
|
||||
|
||||
pub struct CapabilitiesParser;
|
||||
|
||||
impl Parser for CapabilitiesParser {
|
||||
type Value = ClientCapabilities;
|
||||
type Error = CapabilitiesParserError;
|
||||
const EXPECTED: &'static [DataType] = &[DataType::Array, DataType::String];
|
||||
|
||||
fn parse_string(&mut self, span: Span, string: &str) -> ParseResult<Self> {
|
||||
let ty = match string {
|
||||
"none" => ClientCapabilities(0),
|
||||
"all" => ClientCapabilities(!0),
|
||||
"data-control" => CC_DATA_CONTROL,
|
||||
"virtual-keyboard" => CC_VIRTUAL_KEYBOARD,
|
||||
"foreign-toplevel-list" => CC_FOREIGN_TOPLEVEL_LIST,
|
||||
"idle-notifier" => CC_IDLE_NOTIFIER,
|
||||
"session-lock" => CC_SESSION_LOCK,
|
||||
"layer-shell" => CC_LAYER_SHELL,
|
||||
"screencopy" => CC_SCREENCOPY,
|
||||
"seat-manager" => CC_SEAT_MANAGER,
|
||||
"drm-lease" => CC_DRM_LEASE,
|
||||
"input-method" => CC_INPUT_METHOD,
|
||||
"workspace-manager" => CC_WORKSPACE_MANAGER,
|
||||
"foreign-toplevel-manager" => CC_FOREIGN_TOPLEVEL_MANAGER,
|
||||
"head-manager" => CC_HEAD_MANAGER,
|
||||
_ => {
|
||||
return Err(
|
||||
CapabilitiesParserError::UnknownCapability(string.to_owned()).spanned(span),
|
||||
);
|
||||
}
|
||||
};
|
||||
Ok(ty)
|
||||
}
|
||||
|
||||
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
|
||||
let mut ty = ClientCapabilities(0);
|
||||
for el in array {
|
||||
ty |= el.parse(&mut CapabilitiesParser)?;
|
||||
}
|
||||
Ok(ty)
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ use {
|
|||
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||
parsers::{
|
||||
action::{ActionParser, ActionParserError},
|
||||
capabilities::CapabilitiesParser,
|
||||
client_match::{ClientMatchParser, ClientMatchParserError},
|
||||
},
|
||||
spanned::SpannedErrorExt,
|
||||
|
|
@ -47,12 +48,15 @@ impl Parser for ClientRuleParser<'_> {
|
|||
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||
) -> ParseResult<Self> {
|
||||
let mut ext = Extractor::new(self.0, span, table);
|
||||
let (name, match_val, action_val, latch_val) = ext.extract((
|
||||
opt(str("name")),
|
||||
opt(val("match")),
|
||||
opt(val("action")),
|
||||
opt(val("latch")),
|
||||
))?;
|
||||
let (name, match_val, action_val, latch_val, capabilities_val, bounding_capabilities_val) =
|
||||
ext.extract((
|
||||
opt(str("name")),
|
||||
opt(val("match")),
|
||||
opt(val("action")),
|
||||
opt(val("latch")),
|
||||
opt(val("capabilities")),
|
||||
opt(val("sandbox-bounding-capabilities")),
|
||||
))?;
|
||||
let mut action = None;
|
||||
if let Some(value) = action_val {
|
||||
action = Some(
|
||||
|
|
@ -73,11 +77,34 @@ impl Parser for ClientRuleParser<'_> {
|
|||
None => ClientMatch::default(),
|
||||
Some(m) => m.parse_map(&mut ClientMatchParser(self.0))?,
|
||||
};
|
||||
let mut capabilities = None;
|
||||
if let Some(value) = capabilities_val {
|
||||
match value.parse(&mut CapabilitiesParser) {
|
||||
Ok(v) => capabilities = Some(v),
|
||||
Err(e) => {
|
||||
log::warn!("Could not parse the capabilities: {}", self.0.error(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut bounding_capabilities = None;
|
||||
if let Some(value) = bounding_capabilities_val {
|
||||
match value.parse(&mut CapabilitiesParser) {
|
||||
Ok(v) => bounding_capabilities = Some(v),
|
||||
Err(e) => {
|
||||
log::warn!(
|
||||
"Could not parse the bounding capabilities: {}",
|
||||
self.0.error(e)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(ClientRule {
|
||||
name: name.despan_into(),
|
||||
match_,
|
||||
action,
|
||||
latch,
|
||||
capabilities,
|
||||
bounding_capabilities,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -175,6 +175,12 @@ impl Rule for ClientRule {
|
|||
});
|
||||
}
|
||||
}
|
||||
if let Some(caps) = self.capabilities {
|
||||
matcher.set_capabilities(caps);
|
||||
}
|
||||
if let Some(caps) = self.bounding_capabilities {
|
||||
matcher.set_sandbox_bounding_capabilities(caps);
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_matcher(m: Self::Matcher) -> Self::Criterion<'static> {
|
||||
|
|
|
|||
|
|
@ -605,6 +605,40 @@
|
|||
"clickfinger"
|
||||
]
|
||||
},
|
||||
"ClientCapabilities": {
|
||||
"description": "A mask of client capabilities.\n",
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "A named mask.",
|
||||
"enum": [
|
||||
"none",
|
||||
"all",
|
||||
"data-control",
|
||||
"virtual-keyboard",
|
||||
"foreign-toplevel-list",
|
||||
"idle-notifier",
|
||||
"session-lock",
|
||||
"layer-shell",
|
||||
"screencopy",
|
||||
"seat-manager",
|
||||
"drm-lease",
|
||||
"input-method",
|
||||
"workspace-manager",
|
||||
"foreign-toplevel-manager",
|
||||
"head-manager"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"description": "An array of masks that are OR'd.",
|
||||
"items": {
|
||||
"description": "",
|
||||
"$ref": "#/$defs/ClientCapabilities"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"ClientMatch": {
|
||||
"description": "Criteria for matching clients.\n\nIf no fields are set, all clients are matched. If multiple fields are set, all fields\nmust match the client.\n",
|
||||
"type": "object",
|
||||
|
|
@ -737,6 +771,14 @@
|
|||
"latch": {
|
||||
"description": "An action to execute when a client no longer matches the criteria.",
|
||||
"$ref": "#/$defs/Action"
|
||||
},
|
||||
"capabilities": {
|
||||
"description": "Sets the capabilities granted to clients matching this matcher.\n\nIf multiple matchers match a client, the capabilities are added.\n\nIf no matcher matches a client, it is granted the default capabilities depending\non whether it's sandboxed or not. If it is not sandboxed, it is granted the\ncapabilities `layer-shell` and `drm-lease`. Otherwise it is granted the\ncapability `drm-lease`.\n\nRegardless of any capabilities set through this function, the capabilities of the\nclient can never exceed its bounding capabilities.\n",
|
||||
"$ref": "#/$defs/ClientCapabilities"
|
||||
},
|
||||
"sandbox-bounding-capabilities": {
|
||||
"description": "Sets the upper capability bounds for clients in sandboxes created by this client.\n\nIf multiple matchers match a client, the capabilities are added.\n\nIf no matcher matches a client, the bounding capabilities for sandboxes depend on\nwhether the client is itself sandboxed. If it is sandboxed, the bounding\ncapabilities are the effective capabilities of the client. Otherwise the bounding\ncapabilities are all capabilities.\n\nRegardless of any capabilities set through this function, the capabilities set\nthrough this function can never exceed the client's bounding capabilities.\n",
|
||||
"$ref": "#/$defs/ClientCapabilities"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
|
|
|
|||
|
|
@ -870,6 +870,90 @@ The string should have one of the following values:
|
|||
|
||||
|
||||
|
||||
<a name="types-ClientCapabilities"></a>
|
||||
### `ClientCapabilities`
|
||||
|
||||
A mask of client capabilities.
|
||||
|
||||
Values of this type should have one of the following forms:
|
||||
|
||||
#### A string
|
||||
|
||||
A named mask.
|
||||
|
||||
The string should have one of the following values:
|
||||
|
||||
- `none`:
|
||||
|
||||
No capabilities.
|
||||
|
||||
- `all`:
|
||||
|
||||
The mask containing all capabilities.
|
||||
|
||||
- `data-control`:
|
||||
|
||||
Grants access to the `ext_data_control_manager_v1` and
|
||||
`zwlr_data_control_manager_v1` globals.
|
||||
|
||||
- `virtual-keyboard`:
|
||||
|
||||
Grants access to the `zwp_virtual_keyboard_manager_v1` global.
|
||||
|
||||
- `foreign-toplevel-list`:
|
||||
|
||||
Grants access to the `ext_foreign_toplevel_list_v1` global.
|
||||
|
||||
- `idle-notifier`:
|
||||
|
||||
Grants access to the `ext_idle_notifier_v1` global.
|
||||
|
||||
- `session-lock`:
|
||||
|
||||
Grants access to the `ext_session_lock_manager_v1` global.
|
||||
|
||||
- `layer-shell`:
|
||||
|
||||
Grants access to the `zwlr_layer_shell_v1` global.
|
||||
|
||||
- `screencopy`:
|
||||
|
||||
Grants access to the `ext_image_copy_capture_manager_v1` and
|
||||
`zwlr_screencopy_manager_v1` globals.
|
||||
|
||||
- `seat-manager`:
|
||||
|
||||
Grants access to the `ext_transient_seat_manager_v1` global.
|
||||
|
||||
- `drm-lease`:
|
||||
|
||||
Grants access to the `wp_drm_lease_device_v1` global.
|
||||
|
||||
- `input-method`:
|
||||
|
||||
Grants access to the `zwp_input_method_manager_v2` global.
|
||||
|
||||
- `workspace-manager`:
|
||||
|
||||
Grants access to the `ext_workspace_manager_v1` global.
|
||||
|
||||
- `foreign-toplevel-manager`:
|
||||
|
||||
Grants access to the `zwlr_foreign_toplevel_manager_v1` global.
|
||||
|
||||
- `head-manager`:
|
||||
|
||||
Grants access to the `jay_head_manager_v1` and `zwlr_output_manager_v1`
|
||||
globals.
|
||||
|
||||
|
||||
#### An array
|
||||
|
||||
An array of masks that are OR'd.
|
||||
|
||||
Each element of this array should be a [ClientCapabilities](#types-ClientCapabilities).
|
||||
|
||||
|
||||
<a name="types-ClientMatch"></a>
|
||||
### `ClientMatch`
|
||||
|
||||
|
|
@ -1157,6 +1241,38 @@ The table has the following fields:
|
|||
|
||||
The value of this field should be a [Action](#types-Action).
|
||||
|
||||
- `capabilities` (optional):
|
||||
|
||||
Sets the capabilities granted to clients matching this matcher.
|
||||
|
||||
If multiple matchers match a client, the capabilities are added.
|
||||
|
||||
If no matcher matches a client, it is granted the default capabilities depending
|
||||
on whether it's sandboxed or not. If it is not sandboxed, it is granted the
|
||||
capabilities `layer-shell` and `drm-lease`. Otherwise it is granted the
|
||||
capability `drm-lease`.
|
||||
|
||||
Regardless of any capabilities set through this function, the capabilities of the
|
||||
client can never exceed its bounding capabilities.
|
||||
|
||||
The value of this field should be a [ClientCapabilities](#types-ClientCapabilities).
|
||||
|
||||
- `sandbox-bounding-capabilities` (optional):
|
||||
|
||||
Sets the upper capability bounds for clients in sandboxes created by this client.
|
||||
|
||||
If multiple matchers match a client, the capabilities are added.
|
||||
|
||||
If no matcher matches a client, the bounding capabilities for sandboxes depend on
|
||||
whether the client is itself sandboxed. If it is sandboxed, the bounding
|
||||
capabilities are the effective capabilities of the client. Otherwise the bounding
|
||||
capabilities are all capabilities.
|
||||
|
||||
Regardless of any capabilities set through this function, the capabilities set
|
||||
through this function can never exceed the client's bounding capabilities.
|
||||
|
||||
The value of this field should be a [ClientCapabilities](#types-ClientCapabilities).
|
||||
|
||||
|
||||
<a name="types-Color"></a>
|
||||
### `Color`
|
||||
|
|
|
|||
|
|
@ -3404,6 +3404,36 @@ ClientRule:
|
|||
ref: Action
|
||||
required: false
|
||||
description: An action to execute when a client no longer matches the criteria.
|
||||
capabilities:
|
||||
ref: ClientCapabilities
|
||||
required: false
|
||||
description: |
|
||||
Sets the capabilities granted to clients matching this matcher.
|
||||
|
||||
If multiple matchers match a client, the capabilities are added.
|
||||
|
||||
If no matcher matches a client, it is granted the default capabilities depending
|
||||
on whether it's sandboxed or not. If it is not sandboxed, it is granted the
|
||||
capabilities `layer-shell` and `drm-lease`. Otherwise it is granted the
|
||||
capability `drm-lease`.
|
||||
|
||||
Regardless of any capabilities set through this function, the capabilities of the
|
||||
client can never exceed its bounding capabilities.
|
||||
sandbox-bounding-capabilities:
|
||||
ref: ClientCapabilities
|
||||
required: false
|
||||
description: |
|
||||
Sets the upper capability bounds for clients in sandboxes created by this client.
|
||||
|
||||
If multiple matchers match a client, the capabilities are added.
|
||||
|
||||
If no matcher matches a client, the bounding capabilities for sandboxes depend on
|
||||
whether the client is itself sandboxed. If it is sandboxed, the bounding
|
||||
capabilities are the effective capabilities of the client. Otherwise the bounding
|
||||
capabilities are all capabilities.
|
||||
|
||||
Regardless of any capabilities set through this function, the capabilities set
|
||||
through this function can never exceed the client's bounding capabilities.
|
||||
|
||||
|
||||
ClientMatch:
|
||||
|
|
@ -4061,3 +4091,63 @@ BlendSpace:
|
|||
description: The sRGB blend space. This is the classic desktop blend space.
|
||||
- value: linear
|
||||
description: Linear color space. This is the physically correct blend space.
|
||||
|
||||
|
||||
ClientCapabilities:
|
||||
description: |
|
||||
A mask of client capabilities.
|
||||
kind: variable
|
||||
variants:
|
||||
- kind: string
|
||||
description: A named mask.
|
||||
values:
|
||||
- value: none
|
||||
description: No capabilities.
|
||||
- value: all
|
||||
description: The mask containing all capabilities.
|
||||
- value: data-control
|
||||
description: |
|
||||
Grants access to the `ext_data_control_manager_v1` and
|
||||
`zwlr_data_control_manager_v1` globals.
|
||||
- value: virtual-keyboard
|
||||
description: |
|
||||
Grants access to the `zwp_virtual_keyboard_manager_v1` global.
|
||||
- value: foreign-toplevel-list
|
||||
description: |
|
||||
Grants access to the `ext_foreign_toplevel_list_v1` global.
|
||||
- value: idle-notifier
|
||||
description: |
|
||||
Grants access to the `ext_idle_notifier_v1` global.
|
||||
- value: session-lock
|
||||
description: |
|
||||
Grants access to the `ext_session_lock_manager_v1` global.
|
||||
- value: layer-shell
|
||||
description: |
|
||||
Grants access to the `zwlr_layer_shell_v1` global.
|
||||
- value: screencopy
|
||||
description: |
|
||||
Grants access to the `ext_image_copy_capture_manager_v1` and
|
||||
`zwlr_screencopy_manager_v1` globals.
|
||||
- value: seat-manager
|
||||
description: |
|
||||
Grants access to the `ext_transient_seat_manager_v1` global.
|
||||
- value: drm-lease
|
||||
description: |
|
||||
Grants access to the `wp_drm_lease_device_v1` global.
|
||||
- value: input-method
|
||||
description: |
|
||||
Grants access to the `zwp_input_method_manager_v2` global.
|
||||
- value: workspace-manager
|
||||
description: |
|
||||
Grants access to the `ext_workspace_manager_v1` global.
|
||||
- value: foreign-toplevel-manager
|
||||
description: |
|
||||
Grants access to the `zwlr_foreign_toplevel_manager_v1` global.
|
||||
- value: head-manager
|
||||
description: |
|
||||
Grants access to the `jay_head_manager_v1` and `zwlr_output_manager_v1`
|
||||
globals.
|
||||
- kind: array
|
||||
description: An array of masks that are OR'd.
|
||||
items:
|
||||
ref: ClientCapabilities
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue