feat: gate semantic effects by client capability
This commit is contained in:
1 parent
6d19932a64
commit
a6ec721437
9 files changed
+387
-5
No files matched your search
Generated
+1
@@ -422,6 +422,7 @@ dependencies = [
|
|||||||
"ferese-core",
|
"ferese-core",
|
||||||
"ferese-ipc",
|
"ferese-ipc",
|
||||||
"ferese-layout",
|
"ferese-layout",
|
||||||
|
"ferese-protocols",
|
||||||
"libc",
|
"libc",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|||||||
@@ -31,6 +31,20 @@ pixels, average and longest render time, and DRM missed-deadline totals.
|
|||||||
The native-client soak runner and its nested/DRM procedure are documented in
|
The native-client soak runner and its nested/DRM procedure are documented in
|
||||||
[docs/native-soak-testing.md](docs/native-soak-testing.md).
|
[docs/native-soak-testing.md](docs/native-soak-testing.md).
|
||||||
|
|
||||||
|
The M7 semantic-effects probe must use a compositor-created private connection.
|
||||||
|
Build it, then grant only the `effects` capability to that launched process:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo build -p ferese-effects-probe
|
||||||
|
cargo run -p ferese -- --grant-effects -- target/debug/ferese-effects-probe
|
||||||
|
```
|
||||||
|
|
||||||
|
To verify that the private global is absent from the public Wayland socket:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo run -p ferese -- target/debug/ferese-effects-probe --expect-hidden
|
||||||
|
```
|
||||||
|
|
||||||
Ferese reads `$XDG_CONFIG_HOME/ferese/config.toml`, or
|
Ferese reads `$XDG_CONFIG_HOME/ferese/config.toml`, or
|
||||||
`~/.config/ferese/config.toml` when `XDG_CONFIG_HOME` is unset. New scrolling
|
`~/.config/ferese/config.toml` when `XDG_CONFIG_HOME` is unset. New scrolling
|
||||||
columns use half the viewport by default. To make every newly opened column use
|
columns use half the viewport by default. To make every newly opened column use
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ ferese-animation = { path = "../crates/ferese-animation" }
|
|||||||
ferese-core = { path = "../crates/ferese-core" }
|
ferese-core = { path = "../crates/ferese-core" }
|
||||||
ferese-ipc = { path = "../crates/ferese-ipc" }
|
ferese-ipc = { path = "../crates/ferese-ipc" }
|
||||||
ferese-layout = { path = "../crates/ferese-layout" }
|
ferese-layout = { path = "../crates/ferese-layout" }
|
||||||
|
ferese-protocols = { path = "../crates/ferese-protocols", features = ["server"] }
|
||||||
libc.workspace = true
|
libc.workspace = true
|
||||||
smithay.workspace = true
|
smithay.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use std::ffi::OsString;
|
|||||||
|
|
||||||
use smithay::reexports::calloop::EventLoop;
|
use smithay::reexports::calloop::EventLoop;
|
||||||
|
|
||||||
use crate::Ferese;
|
use crate::{Ferese, private_client::ClientCapabilities};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
pub enum BackendKind {
|
pub enum BackendKind {
|
||||||
@@ -16,6 +16,7 @@ pub enum BackendKind {
|
|||||||
pub struct LaunchConfig {
|
pub struct LaunchConfig {
|
||||||
pub backend: BackendKind,
|
pub backend: BackendKind,
|
||||||
pub client: Vec<OsString>,
|
pub client: Vec<OsString>,
|
||||||
|
pub client_capabilities: ClientCapabilities,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LaunchConfig {
|
impl LaunchConfig {
|
||||||
@@ -33,6 +34,7 @@ impl LaunchConfig {
|
|||||||
let mut args = args.into_iter().peekable();
|
let mut args = args.into_iter().peekable();
|
||||||
let mut requested_backend = None;
|
let mut requested_backend = None;
|
||||||
let mut client = Vec::new();
|
let mut client = Vec::new();
|
||||||
|
let mut client_capabilities = ClientCapabilities::default();
|
||||||
|
|
||||||
while let Some(argument) = args.next() {
|
while let Some(argument) = args.next() {
|
||||||
if argument == "--" {
|
if argument == "--" {
|
||||||
@@ -46,6 +48,10 @@ impl LaunchConfig {
|
|||||||
requested_backend = Some(parse_backend(&value)?);
|
requested_backend = Some(parse_backend(&value)?);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if argument == "--grant-effects" {
|
||||||
|
client_capabilities.insert(ClientCapabilities::EFFECTS);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if let Some(value) = argument
|
if let Some(value) = argument
|
||||||
.to_str()
|
.to_str()
|
||||||
.and_then(|argument| argument.strip_prefix("--backend="))
|
.and_then(|argument| argument.strip_prefix("--backend="))
|
||||||
@@ -65,7 +71,11 @@ impl LaunchConfig {
|
|||||||
BackendKind::Drm
|
BackendKind::Drm
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(Self { backend, client })
|
Ok(Self {
|
||||||
|
backend,
|
||||||
|
client,
|
||||||
|
client_capabilities,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,6 +112,7 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(config.backend, BackendKind::Nested);
|
assert_eq!(config.backend, BackendKind::Nested);
|
||||||
assert_eq!(config.client, [OsString::from("foot")]);
|
assert_eq!(config.client, [OsString::from("foot")]);
|
||||||
|
assert!(config.client_capabilities.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -110,6 +121,7 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(config.backend, BackendKind::Drm);
|
assert_eq!(config.backend, BackendKind::Drm);
|
||||||
assert!(config.client.is_empty());
|
assert!(config.client.is_empty());
|
||||||
|
assert!(config.client_capabilities.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -130,6 +142,26 @@ mod tests {
|
|||||||
config.client,
|
config.client,
|
||||||
[OsString::from("foot"), OsString::from("--title=Ferese")]
|
[OsString::from("foot"), OsString::from("--title=Ferese")]
|
||||||
);
|
);
|
||||||
|
assert!(config.client_capabilities.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn explicitly_grants_effects_to_the_launched_private_client() {
|
||||||
|
let config = LaunchConfig::parse(
|
||||||
|
[
|
||||||
|
OsString::from("--grant-effects"),
|
||||||
|
OsString::from("ferese-effects-probe"),
|
||||||
|
],
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(config.client, [OsString::from("ferese-effects-probe")]);
|
||||||
|
assert!(
|
||||||
|
config
|
||||||
|
.client_capabilities
|
||||||
|
.contains(ClientCapabilities::EFFECTS)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -0,0 +1,224 @@
|
|||||||
|
use std::sync::{
|
||||||
|
Mutex,
|
||||||
|
atomic::{AtomicBool, AtomicU64, Ordering},
|
||||||
|
};
|
||||||
|
|
||||||
|
use ferese_protocols::effects::v1::server::{
|
||||||
|
ferese_effects_manager_v1::{self, FereseEffectsManagerV1},
|
||||||
|
ferese_surface_effects_v1::{self, FereseSurfaceEffectsV1},
|
||||||
|
};
|
||||||
|
use smithay::{
|
||||||
|
reexports::wayland_server::{
|
||||||
|
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, WEnum, Weak,
|
||||||
|
backend::ClientId, protocol::wl_surface::WlSurface,
|
||||||
|
},
|
||||||
|
wayland::compositor::with_states,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{Ferese, private_client::ClientCapabilities, state::ClientState};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub(crate) enum SemanticRole {
|
||||||
|
Panel,
|
||||||
|
PanelElevated,
|
||||||
|
Popover,
|
||||||
|
Menu,
|
||||||
|
Notification,
|
||||||
|
Hud,
|
||||||
|
Modal,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct SurfaceEffectsState {
|
||||||
|
attached: AtomicBool,
|
||||||
|
generation: AtomicU64,
|
||||||
|
role: Mutex<Option<SemanticRole>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SurfaceEffectsState {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
attached: AtomicBool::new(false),
|
||||||
|
generation: AtomicU64::new(0),
|
||||||
|
role: Mutex::new(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_role(&self, role: Option<SemanticRole>) {
|
||||||
|
let mut current = self.role.lock().unwrap();
|
||||||
|
if *current != role {
|
||||||
|
*current = role;
|
||||||
|
self.generation.fetch_add(1, Ordering::Release);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct SurfaceEffectsUserData(Mutex<Weak<WlSurface>>);
|
||||||
|
|
||||||
|
impl SurfaceEffectsUserData {
|
||||||
|
fn new(surface: WlSurface) -> Self {
|
||||||
|
Self(Mutex::new(surface.downgrade()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn surface(&self) -> Option<WlSurface> {
|
||||||
|
self.0.lock().unwrap().upgrade().ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn init_global(display: &DisplayHandle) {
|
||||||
|
display.create_global::<Ferese, FereseEffectsManagerV1, _>(1, ());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consumed by the material renderer in the next M7 slice.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn surface_role(surface: &WlSurface) -> Option<(SemanticRole, u64)> {
|
||||||
|
with_states(surface, |states| {
|
||||||
|
let effects = states.data_map.get::<SurfaceEffectsState>()?;
|
||||||
|
let role = *effects.role.lock().unwrap();
|
||||||
|
role.map(|role| (role, effects.generation.load(Ordering::Acquire)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GlobalDispatch<FereseEffectsManagerV1, ()> for Ferese {
|
||||||
|
fn bind(
|
||||||
|
_state: &mut Self,
|
||||||
|
_display: &DisplayHandle,
|
||||||
|
_client: &Client,
|
||||||
|
resource: New<FereseEffectsManagerV1>,
|
||||||
|
_global_data: &(),
|
||||||
|
data_init: &mut DataInit<'_, Self>,
|
||||||
|
) {
|
||||||
|
data_init.init(resource, ());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_view(client: Client, _global_data: &()) -> bool {
|
||||||
|
client
|
||||||
|
.get_data::<ClientState>()
|
||||||
|
.is_some_and(|state| state.capabilities.contains(ClientCapabilities::EFFECTS))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dispatch<FereseEffectsManagerV1, ()> for Ferese {
|
||||||
|
fn request(
|
||||||
|
_state: &mut Self,
|
||||||
|
_client: &Client,
|
||||||
|
manager: &FereseEffectsManagerV1,
|
||||||
|
request: ferese_effects_manager_v1::Request,
|
||||||
|
_data: &(),
|
||||||
|
_display: &DisplayHandle,
|
||||||
|
data_init: &mut DataInit<'_, Self>,
|
||||||
|
) {
|
||||||
|
match request {
|
||||||
|
ferese_effects_manager_v1::Request::GetSurfaceEffects { id, surface } => {
|
||||||
|
let already_attached = with_states(&surface, |states| {
|
||||||
|
states
|
||||||
|
.data_map
|
||||||
|
.insert_if_missing_threadsafe(SurfaceEffectsState::new);
|
||||||
|
states
|
||||||
|
.data_map
|
||||||
|
.get::<SurfaceEffectsState>()
|
||||||
|
.unwrap()
|
||||||
|
.attached
|
||||||
|
.swap(true, Ordering::AcqRel)
|
||||||
|
});
|
||||||
|
|
||||||
|
if already_attached {
|
||||||
|
manager.post_error(
|
||||||
|
ferese_effects_manager_v1::Error::AlreadyConstructed,
|
||||||
|
"wl_surface already has a Ferese effects object",
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
data_init.init(id, SurfaceEffectsUserData::new(surface));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ferese_effects_manager_v1::Request::Destroy => {}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dispatch<FereseSurfaceEffectsV1, SurfaceEffectsUserData> for Ferese {
|
||||||
|
fn request(
|
||||||
|
_state: &mut Self,
|
||||||
|
_client: &Client,
|
||||||
|
_resource: &FereseSurfaceEffectsV1,
|
||||||
|
request: ferese_surface_effects_v1::Request,
|
||||||
|
data: &SurfaceEffectsUserData,
|
||||||
|
_display: &DisplayHandle,
|
||||||
|
_data_init: &mut DataInit<'_, Self>,
|
||||||
|
) {
|
||||||
|
let Some(surface) = data.surface() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
match request {
|
||||||
|
ferese_surface_effects_v1::Request::SetRole { role } => {
|
||||||
|
let role = match role {
|
||||||
|
WEnum::Value(ferese_surface_effects_v1::Role::Panel) => SemanticRole::Panel,
|
||||||
|
WEnum::Value(ferese_surface_effects_v1::Role::PanelElevated) => {
|
||||||
|
SemanticRole::PanelElevated
|
||||||
|
}
|
||||||
|
WEnum::Value(ferese_surface_effects_v1::Role::Popover) => SemanticRole::Popover,
|
||||||
|
WEnum::Value(ferese_surface_effects_v1::Role::Menu) => SemanticRole::Menu,
|
||||||
|
WEnum::Value(ferese_surface_effects_v1::Role::Notification) => {
|
||||||
|
SemanticRole::Notification
|
||||||
|
}
|
||||||
|
WEnum::Value(ferese_surface_effects_v1::Role::Hud) => SemanticRole::Hud,
|
||||||
|
WEnum::Value(ferese_surface_effects_v1::Role::Modal) => SemanticRole::Modal,
|
||||||
|
WEnum::Unknown(_) | WEnum::Value(_) => return,
|
||||||
|
};
|
||||||
|
set_surface_role(&surface, Some(role));
|
||||||
|
}
|
||||||
|
ferese_surface_effects_v1::Request::ClearRole => set_surface_role(&surface, None),
|
||||||
|
ferese_surface_effects_v1::Request::Destroy => detach(&surface),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn destroyed(
|
||||||
|
_state: &mut Self,
|
||||||
|
_client: ClientId,
|
||||||
|
_resource: &FereseSurfaceEffectsV1,
|
||||||
|
data: &SurfaceEffectsUserData,
|
||||||
|
) {
|
||||||
|
if let Some(surface) = data.surface() {
|
||||||
|
detach(&surface);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_surface_role(surface: &WlSurface, role: Option<SemanticRole>) {
|
||||||
|
with_states(surface, |states| {
|
||||||
|
if let Some(effects) = states.data_map.get::<SurfaceEffectsState>() {
|
||||||
|
effects.set_role(role);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn detach(surface: &WlSurface) {
|
||||||
|
with_states(surface, |states| {
|
||||||
|
if let Some(effects) = states.data_map.get::<SurfaceEffectsState>() {
|
||||||
|
effects.set_role(None);
|
||||||
|
effects.attached.store(false, Ordering::Release);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn role_changes_advance_generation_only_when_material_changes() {
|
||||||
|
let effects = SurfaceEffectsState::new();
|
||||||
|
effects.set_role(Some(SemanticRole::Panel));
|
||||||
|
assert_eq!(effects.generation.load(Ordering::Acquire), 1);
|
||||||
|
|
||||||
|
effects.set_role(Some(SemanticRole::Panel));
|
||||||
|
assert_eq!(effects.generation.load(Ordering::Acquire), 1);
|
||||||
|
|
||||||
|
effects.set_role(Some(SemanticRole::Popover));
|
||||||
|
assert_eq!(effects.generation.load(Ordering::Acquire), 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
+24
-3
@@ -3,11 +3,13 @@
|
|||||||
mod backends;
|
mod backends;
|
||||||
mod config;
|
mod config;
|
||||||
mod cursor;
|
mod cursor;
|
||||||
|
mod effects;
|
||||||
mod grabs;
|
mod grabs;
|
||||||
mod handlers;
|
mod handlers;
|
||||||
mod input;
|
mod input;
|
||||||
mod ipc;
|
mod ipc;
|
||||||
mod metrics;
|
mod metrics;
|
||||||
|
mod private_client;
|
||||||
mod stacking;
|
mod stacking;
|
||||||
mod state;
|
mod state;
|
||||||
mod window_rules;
|
mod window_rules;
|
||||||
@@ -66,7 +68,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
unsafe { std::env::set_var("WAYLAND_DISPLAY", &state.socket_name) };
|
unsafe { std::env::set_var("WAYLAND_DISPLAY", &state.socket_name) };
|
||||||
|
|
||||||
info!(socket = ?state.socket_name, backend = ?launch.backend, "Ferese is accepting Wayland clients");
|
info!(socket = ?state.socket_name, backend = ?launch.backend, "Ferese is accepting Wayland clients");
|
||||||
spawn_client(launch.client);
|
spawn_client(&mut state, launch.client, launch.client_capabilities);
|
||||||
event_loop.run(None, &mut state, |_| {})?;
|
event_loop.run(None, &mut state, |_| {})?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -77,15 +79,34 @@ fn init_logging() {
|
|||||||
tracing_subscriber::fmt().with_env_filter(filter).init();
|
tracing_subscriber::fmt().with_env_filter(filter).init();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn_client(mut args: Vec<std::ffi::OsString>) {
|
fn spawn_client(
|
||||||
|
state: &mut Ferese,
|
||||||
|
mut args: Vec<std::ffi::OsString>,
|
||||||
|
capabilities: private_client::ClientCapabilities,
|
||||||
|
) {
|
||||||
if args.is_empty() {
|
if args.is_empty() {
|
||||||
info!("no client requested; pass one after `--`, for example `-- foot`");
|
info!("no client requested; pass one after `--`, for example `-- foot`");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let program = args.remove(0);
|
let program = args.remove(0);
|
||||||
|
|
||||||
match Command::new(&program).args(args).spawn() {
|
let mut command = Command::new(&program);
|
||||||
|
command.args(args);
|
||||||
|
let private_connection = if capabilities.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
match private_client::prepare_command(state, &mut command, capabilities) {
|
||||||
|
Ok(connection) => Some(connection),
|
||||||
|
Err(error) => {
|
||||||
|
warn!(program = ?program, %error, "failed to create private Wayland connection");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match command.spawn() {
|
||||||
Ok(child) => info!(program = ?program, pid = child.id(), "spawned Wayland client"),
|
Ok(child) => info!(program = ?program, pid = child.id(), "spawned Wayland client"),
|
||||||
Err(error) => warn!(program = ?program, %error, "failed to spawn Wayland client"),
|
Err(error) => warn!(program = ?program, %error, "failed to spawn Wayland client"),
|
||||||
}
|
}
|
||||||
|
drop(private_connection);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
use std::{
|
||||||
|
io,
|
||||||
|
os::{
|
||||||
|
fd::AsRawFd,
|
||||||
|
unix::{net::UnixStream, process::CommandExt},
|
||||||
|
},
|
||||||
|
process::Command,
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{Ferese, state::ClientState};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||||
|
pub(crate) struct ClientCapabilities(u8);
|
||||||
|
|
||||||
|
impl ClientCapabilities {
|
||||||
|
pub(crate) const EFFECTS: Self = Self(1 << 0);
|
||||||
|
|
||||||
|
pub(crate) fn contains(self, capability: Self) -> bool {
|
||||||
|
self.0 & capability.0 == capability.0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn insert(&mut self, capability: Self) {
|
||||||
|
self.0 |= capability.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_empty(self) -> bool {
|
||||||
|
self.0 == 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn prepare_command(
|
||||||
|
state: &mut Ferese,
|
||||||
|
command: &mut Command,
|
||||||
|
capabilities: ClientCapabilities,
|
||||||
|
) -> io::Result<UnixStream> {
|
||||||
|
let (server, client) = UnixStream::pair()?;
|
||||||
|
state.display_handle.insert_client(
|
||||||
|
server,
|
||||||
|
Arc::new(ClientState {
|
||||||
|
capabilities,
|
||||||
|
..ClientState::default()
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let client_fd = client.as_raw_fd();
|
||||||
|
command.env_remove("WAYLAND_DISPLAY");
|
||||||
|
command.env("WAYLAND_SOCKET", client_fd.to_string());
|
||||||
|
// SAFETY: fcntl(F_SETFD) is async-signal-safe. The closure only clears
|
||||||
|
// CLOEXEC on the socket already owned by this Command's parent process.
|
||||||
|
unsafe {
|
||||||
|
command.pre_exec(move || {
|
||||||
|
if libc::fcntl(client_fd, libc::F_SETFD, 0) == -1 {
|
||||||
|
return Err(io::Error::last_os_error());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(client)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn capabilities_are_explicit_and_independent() {
|
||||||
|
let mut capabilities = ClientCapabilities::default();
|
||||||
|
assert!(capabilities.is_empty());
|
||||||
|
assert!(!capabilities.contains(ClientCapabilities::EFFECTS));
|
||||||
|
|
||||||
|
capabilities.insert(ClientCapabilities::EFFECTS);
|
||||||
|
assert!(!capabilities.is_empty());
|
||||||
|
assert!(capabilities.contains(ClientCapabilities::EFFECTS));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -355,6 +355,7 @@ impl Ferese {
|
|||||||
) -> Result<Self, Box<dyn Error>> {
|
) -> Result<Self, Box<dyn Error>> {
|
||||||
let display_handle = display.handle();
|
let display_handle = display.handle();
|
||||||
crate::handlers::screencopy::init_global(&display_handle);
|
crate::handlers::screencopy::init_global(&display_handle);
|
||||||
|
crate::effects::init_global(&display_handle);
|
||||||
let alpha_modifier_state = AlphaModifierState::new::<Self>(&display_handle);
|
let alpha_modifier_state = AlphaModifierState::new::<Self>(&display_handle);
|
||||||
let compositor_state = CompositorState::new::<Self>(&display_handle);
|
let compositor_state = CompositorState::new::<Self>(&display_handle);
|
||||||
let cursor_shape_state = CursorShapeManagerState::new::<Self>(&display_handle);
|
let cursor_shape_state = CursorShapeManagerState::new::<Self>(&display_handle);
|
||||||
@@ -1856,6 +1857,7 @@ fn client_size(window: &Window) -> Option<ClientSize> {
|
|||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct ClientState {
|
pub struct ClientState {
|
||||||
pub compositor_state: CompositorClientState,
|
pub compositor_state: CompositorClientState,
|
||||||
|
pub(crate) capabilities: crate::private_client::ClientCapabilities,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientData for ClientState {
|
impl ClientData for ClientState {
|
||||||
|
|||||||
@@ -19,12 +19,22 @@ const WIDTH: u32 = 640;
|
|||||||
const HEIGHT: u32 = 64;
|
const HEIGHT: u32 = 64;
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn Error>> {
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
let expect_hidden = std::env::args_os().any(|argument| argument == "--expect-hidden");
|
||||||
let connection = Connection::connect_to_env()?;
|
let connection = Connection::connect_to_env()?;
|
||||||
let mut queue = connection.new_event_queue();
|
let mut queue = connection.new_event_queue();
|
||||||
let qh = queue.handle();
|
let qh = queue.handle();
|
||||||
connection.display().get_registry(&qh, ());
|
connection.display().get_registry(&qh, ());
|
||||||
let mut state = ProbeState::default();
|
let mut state = ProbeState::default();
|
||||||
|
|
||||||
|
if expect_hidden {
|
||||||
|
queue.roundtrip(&mut state)?;
|
||||||
|
if state.effects_manager.is_some() {
|
||||||
|
return Err("public client could see the private effects global".into());
|
||||||
|
}
|
||||||
|
println!("PASS public client cannot see ferese_effects_manager_v1");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
while state.effects.is_none() || !state.configured {
|
while state.effects.is_none() || !state.configured {
|
||||||
queue.blocking_dispatch(&mut state)?;
|
queue.blocking_dispatch(&mut state)?;
|
||||||
state.try_initialize(&qh)?;
|
state.try_initialize(&qh)?;
|
||||||
|
|||||||
Reference in new issue
Block a user