1
0
Fork 0
forked from wry/wry

config: allow configuring client capabilities

This commit is contained in:
Julian Orth 2025-09-18 21:06:28 +02:00
parent 76a1a86091
commit e680a3dc09
21 changed files with 624 additions and 39 deletions

View file

@ -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;

View file

@ -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>>,

View file

@ -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(),

View file

@ -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()
}
}

View file

@ -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 {

View file

@ -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(),

View file

@ -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(())
}

View file

@ -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;

View file

@ -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 {

View file

@ -178,7 +178,7 @@ async fn run(
uapi::getuid(),
pid,
ClientCaps::all(),
ClientCaps::all(),
false,
true,
&Rc::new(AcceptorMetadata::default()),
);