1
0
Fork 0
forked from wry/wry

Merge pull request #778 from mahkoh/jorth/refactoring

Various refactorings
This commit is contained in:
mahkoh 2026-03-07 18:55:17 +01:00 committed by GitHub
commit 94816aec78
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
94 changed files with 2428 additions and 527 deletions

View file

@ -1,17 +1,24 @@
mod hash;
use {
crate::vulkan::hash::{ROOT, unchanged},
crate::vulkan::hash::{TREES, Tree, unchanged},
anyhow::bail,
std::process::Command,
};
pub fn main() -> anyhow::Result<()> {
println!("cargo:rerun-if-changed={}", ROOT);
for tree in TREES {
main_(tree)?;
}
Ok(())
}
fn main_(tree: &Tree) -> anyhow::Result<()> {
println!("cargo:rerun-if-changed={}", tree.root);
if !std::fs::exists("compile-shaders")? {
return Ok(());
}
if unchanged() {
if unchanged(tree) {
return Ok(());
}
let code = Command::new("cargo")

View file

@ -1,10 +1,33 @@
use {std::fmt::Write, walkdir::WalkDir};
pub const ROOT: &str = "src/gfx_apis/vulkan/shaders";
pub const HASH: &str = "src/gfx_apis/vulkan/shaders_hash.txt";
#[allow(dead_code)]
pub struct Tree {
pub root: &'static str,
pub hash: &'static str,
pub bin: &'static str,
pub shaders: &'static [&'static str],
}
fn calculate_hash() -> anyhow::Result<String> {
let dir = WalkDir::new(ROOT);
pub const TREES: &[Tree] = &[Tree {
root: "src/gfx_apis/vulkan/shaders",
hash: "src/gfx_apis/vulkan/shaders_hash.txt",
bin: "src/gfx_apis/vulkan/shaders_bin",
shaders: &[
"fill.frag",
"fill.vert",
"tex.vert",
"tex.frag",
"out.vert",
"out.frag",
"legacy/fill.frag",
"legacy/fill.vert",
"legacy/tex.vert",
"legacy/tex.frag",
],
}];
fn calculate_hash(tree: &Tree) -> anyhow::Result<String> {
let dir = WalkDir::new(tree.root);
let mut files = vec![];
for file in dir {
let file = file?;
@ -21,11 +44,11 @@ fn calculate_hash() -> anyhow::Result<String> {
Ok(out)
}
pub fn unchanged() -> bool {
let Ok(actual) = std::fs::read_to_string(HASH) else {
pub fn unchanged(tree: &Tree) -> bool {
let Ok(actual) = std::fs::read_to_string(tree.hash) else {
return false;
};
let Ok(expected) = calculate_hash() else {
let Ok(expected) = calculate_hash(tree) else {
return false;
};
actual == expected

View file

@ -1,32 +1,27 @@
use {
anyhow::{Context, anyhow, bail},
compile_shaders_core::{BIN, ROOT, update_hash},
compile_shaders_core::{TREES, Tree, update_hash},
shaderc::{CompileOptions, ResolvedInclude},
std::{fs::File, io::Write, path::Path},
};
fn main() -> anyhow::Result<()> {
compile("fill.frag")?;
compile("fill.vert")?;
compile("tex.vert")?;
compile("tex.frag")?;
compile("out.vert")?;
compile("out.frag")?;
compile("legacy/fill.frag")?;
compile("legacy/fill.vert")?;
compile("legacy/tex.vert")?;
compile("legacy/tex.frag")?;
update_hash()?;
for tree in TREES {
for shader in tree.shaders {
compile(tree, shader)?;
}
update_hash(tree)?;
}
Ok(())
}
fn compile(name: &str) -> anyhow::Result<()> {
fn compile(tree: &Tree, name: &str) -> anyhow::Result<()> {
let out = format!("{name}.spv").replace("/", "_");
compile_shader(name, &out).with_context(|| name.to_string())
compile_shader(tree, name, &out).with_context(|| name.to_string())
}
fn compile_shader(name: &str, out: &str) -> anyhow::Result<()> {
let root = Path::new(ROOT).join(Path::new(name).parent().unwrap());
fn compile_shader(tree: &Tree, name: &str, out: &str) -> anyhow::Result<()> {
let root = Path::new(tree.root).join(Path::new(name).parent().unwrap());
let read = |path: &str| std::fs::read_to_string(root.join(path));
let mut options = CompileOptions::new()?;
options.set_include_callback(|name, _, _, _| {
@ -44,10 +39,10 @@ fn compile_shader(name: &str, out: &str) -> anyhow::Result<()> {
"vert" => shaderc::ShaderKind::Vertex,
n => bail!("Unknown shader stage {}", n),
};
let src = std::fs::read_to_string(format!("{}/{}", ROOT, name))?;
let src = std::fs::read_to_string(format!("{}/{}", tree.root, name))?;
let compiler = shaderc::Compiler::new()?;
let binary = compiler.compile_into_spirv(&src, stage, name, "main", Some(&options))?;
let mut file = File::create(Path::new(BIN).join(out))?;
let mut file = File::create(Path::new(tree.bin).join(out))?;
file.write_all(binary.as_binary_u8())?;
file.flush()?;
Ok(())

View file

@ -1,8 +1,6 @@
include!("../../../build/vulkan/hash.rs");
pub const BIN: &str = "src/gfx_apis/vulkan/shaders_bin";
pub fn update_hash() -> anyhow::Result<()> {
std::fs::write(HASH, calculate_hash()?)?;
pub fn update_hash(tree: &Tree) -> anyhow::Result<()> {
std::fs::write(tree.hash, calculate_hash(tree)?)?;
Ok(())
}

View file

@ -1,5 +1,8 @@
use compile_shaders_core::update_hash;
use compile_shaders_core::{update_hash, TREES};
fn main() -> anyhow::Result<()> {
update_hash()
for tree in TREES {
update_hash(tree)?;
}
Ok(())
}

View file

@ -22,6 +22,7 @@ use {
},
},
libinput::consts::DeviceCapability,
utils::static_text::StaticText,
video::drm::{
ConnectorType, DRM_MODE_COLORIMETRY_BT2020_RGB, DRM_MODE_COLORIMETRY_DEFAULT,
DrmConnector, DrmError, DrmVersion, HDMI_EOTF_SMPTE_ST2084,
@ -267,7 +268,7 @@ pub trait InputDevice {
}
}
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Linearize)]
pub enum InputDeviceCapability {
Keyboard,
Pointer,
@ -278,6 +279,20 @@ pub enum InputDeviceCapability {
Switch,
}
impl StaticText for InputDeviceCapability {
fn text(&self) -> &'static str {
match self {
InputDeviceCapability::Keyboard => "keyboard",
InputDeviceCapability::Pointer => "pointer",
InputDeviceCapability::Touch => "touch",
InputDeviceCapability::TabletTool => "tablet tool",
InputDeviceCapability::TabletPad => "tablet pad",
InputDeviceCapability::Gesture => "gesture",
InputDeviceCapability::Switch => "switch",
}
}
}
impl InputDeviceCapability {
pub fn to_libinput(self) -> DeviceCapability {
use crate::libinput::consts::*;
@ -293,19 +308,38 @@ impl InputDeviceCapability {
}
}
#[derive(Debug, Copy, Clone)]
#[derive(Debug, Copy, Clone, PartialEq, Linearize)]
pub enum InputDeviceAccelProfile {
Flat,
Adaptive,
}
#[derive(Debug, Copy, Clone)]
impl StaticText for InputDeviceAccelProfile {
fn text(&self) -> &'static str {
match self {
InputDeviceAccelProfile::Flat => "Flat",
InputDeviceAccelProfile::Adaptive => "Adaptive",
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Linearize)]
pub enum InputDeviceClickMethod {
None,
ButtonAreas,
Clickfinger,
}
impl StaticText for InputDeviceClickMethod {
fn text(&self) -> &'static str {
match self {
InputDeviceClickMethod::None => "none",
InputDeviceClickMethod::ButtonAreas => "button-areas",
InputDeviceClickMethod::Clickfinger => "clickfinger",
}
}
}
pub enum BackendEvent {
NewDrmDevice(Rc<dyn BackendDrmDevice>),
NewConnector(Rc<dyn Connector>),
@ -540,6 +574,9 @@ pub trait BackendDrmDevice {
fn version(&self) -> Result<DrmVersion, DrmError>;
fn set_direct_scanout_enabled(&self, enabled: bool);
fn is_render_device(&self) -> bool;
fn direct_scanout_enabled(&self) -> bool {
false
}
fn create_lease(
self: Rc<Self>,
lessee: Rc<dyn BackendDrmLessee>,
@ -551,6 +588,10 @@ pub trait BackendDrmDevice {
fn set_flip_margin(&self, margin: u64) {
let _ = margin;
}
#[expect(dead_code)]
fn flip_margin(&self) -> Option<u64> {
None
}
}
pub trait BackendDrmLease {

View file

@ -1,6 +1,6 @@
use {
crate::{
backend::Connector,
backend::{BackendDrmDevice, Connector},
backends::metal::{
MetalError,
allocator::{RenderBuffer, RenderBufferCopy},
@ -814,13 +814,6 @@ impl MetalConnector {
data
}
fn direct_scanout_enabled(&self) -> bool {
self.dev
.direct_scanout_enabled
.get()
.unwrap_or(self.state.direct_scanout_enabled.get())
}
fn prepare_present_fb(
&self,
cd: &Rc<ColorDescription>,
@ -832,7 +825,7 @@ impl MetalConnector {
) -> Result<PresentFb, MetalError> {
self.trim_scanout_cache();
let try_direct_scanout = try_direct_scanout
&& self.direct_scanout_enabled()
&& self.dev.direct_scanout_enabled()
// at least on AMD, using a FB on a different device for rendering will fail
// and destroy the render context. it's possible to work around this by waiting
// until the FB is no longer being scanned out, but if a notification pops up

View file

@ -186,6 +186,12 @@ impl BackendDrmDevice for MetalDrmDevice {
Some(self.id) == self.backend.ctx.get().map(|c| c.dev_id)
}
fn direct_scanout_enabled(&self) -> bool {
self.direct_scanout_enabled
.get()
.unwrap_or(self.backend.state.direct_scanout_enabled.get())
}
fn create_lease(
self: Rc<Self>,
lessee: Rc<dyn BackendDrmLessee>,
@ -309,11 +315,15 @@ impl BackendDrmDevice for MetalDrmDevice {
c.post_commit_margin.set(margin);
c.post_commit_margin_decay.reset(margin);
if let Some(output) = self.backend.state.root.outputs.get(&c.connector_id) {
output.flip_margin_ns.set(Some(margin));
output.set_flip_margin(margin);
}
}
}
}
fn flip_margin(&self) -> Option<u64> {
Some(self.min_post_commit_margin.get())
}
}
pub struct HandleEvents {
@ -2457,7 +2467,7 @@ impl MetalBackend {
};
connector.post_commit_margin.set(new_margin);
if let Some(global) = &global {
global.flip_margin_ns.set(Some(new_margin));
global.set_flip_margin(new_margin);
}
}

View file

@ -27,12 +27,11 @@ use {
damage_tracking::DamageTrackingArgs, idle::IdleCmd, input::InputArgs, randr::RandrArgs,
reexec::ReexecArgs, run_tagged::RunTaggedArgs, tree::TreeArgs, xwayland::XwaylandArgs,
},
compositor::start_compositor,
compositor::{LogLevel, start_compositor},
format::{Format, ref_formats},
portal,
pr_caps::drop_all_pr_caps,
},
::log::Level,
clap::{Args, Parser, Subcommand, ValueEnum, ValueHint, builder::PossibleValue},
clap_complete::Shell,
};
@ -50,7 +49,7 @@ struct Jay {
pub struct GlobalArgs {
/// The log level.
#[clap(value_enum, long, default_value_t)]
pub log_level: CliLogLevel,
pub log_level: LogLevel,
}
#[derive(Subcommand, Debug)]
@ -176,7 +175,7 @@ pub struct LogArgs {
pub struct SetLogArgs {
/// The new log level.
#[clap(value_enum)]
level: CliLogLevel,
level: LogLevel,
}
#[derive(Args, Debug)]
@ -194,28 +193,6 @@ pub enum CliBackend {
Metal,
}
#[derive(ValueEnum, Debug, Copy, Clone, Hash, Default)]
pub enum CliLogLevel {
Trace,
Debug,
#[default]
Info,
Warn,
Error,
}
impl Into<Level> for CliLogLevel {
fn into(self) -> Level {
match self {
CliLogLevel::Trace => Level::Trace,
CliLogLevel::Debug => Level::Debug,
CliLogLevel::Info => Level::Info,
CliLogLevel::Warn => Level::Warn,
CliLogLevel::Error => Level::Error,
}
}
}
#[derive(Args, Debug)]
pub struct GenerateArgs {
/// The shell to generate completions for

View file

@ -67,7 +67,7 @@ struct KillIdArgs {
}
pub fn main(global: GlobalArgs, clients_args: ClientsArgs) {
with_tool_client(global.log_level.into(), |tc| async move {
with_tool_client(global.log_level, |tc| async move {
let clients = Rc::new(Clients { tc: tc.clone() });
clients.run(clients_args).await;
});

View file

@ -26,7 +26,7 @@ pub enum ColorManagementCmd {
}
pub fn main(global: GlobalArgs, args: ColorManagementArgs) {
with_tool_client(global.log_level.into(), |tc| async move {
with_tool_client(global.log_level, |tc| async move {
let cm = ColorManagement { tc: tc.clone() };
cm.run(args).await;
});

View file

@ -55,7 +55,7 @@ pub struct DecayArgs {
}
pub fn main(global: GlobalArgs, damage_tracking_args: DamageTrackingArgs) {
with_tool_client(global.log_level.into(), |tc| async move {
with_tool_client(global.log_level, |tc| async move {
let damage_tracking = Rc::new(DamageTracking { tc: tc.clone() });
damage_tracking.run(damage_tracking_args).await;
});

View file

@ -51,7 +51,7 @@ pub struct IdleSetGracePeriodArgs {
}
pub fn main(global: GlobalArgs, args: IdleArgs) {
with_tool_client(global.log_level.into(), |tc| async move {
with_tool_client(global.log_level, |tc| async move {
let idle = Idle { tc: tc.clone() };
idle.run(args).await;
});

View file

@ -9,7 +9,7 @@ use {
LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER, LIBINPUT_CONFIG_CLICK_METHOD_NONE,
},
tools::tool_client::{Handle, ToolClient, with_tool_client},
utils::{errorfmt::ErrorFmt, string_ext::StringExt},
utils::{errorfmt::ErrorFmt, static_text::StaticText, string_ext::StringExt},
wire::{JayInputId, jay_compositor, jay_input},
},
clap::{Args, Subcommand, ValueEnum, ValueHint},
@ -324,7 +324,7 @@ pub struct UseHardwareCursorArgs {
}
pub fn main(global: GlobalArgs, args: InputArgs) {
with_tool_client(global.log_level.into(), |tc| async move {
with_tool_client(global.log_level, |tc| async move {
let idle = Rc::new(Input { tc: tc.clone() });
idle.run(args).await;
});
@ -854,21 +854,11 @@ impl Input {
print!("{prefix} capabilities:");
let mut first = true;
for cap in &device.capabilities {
use InputDeviceCapability::*;
print!(" ");
if !mem::take(&mut first) {
print!("| ");
}
let name = match cap {
Keyboard => "keyboard",
Pointer => "pointer",
Touch => "touch",
TabletTool => "tablet tool",
TabletPad => "tablet pad",
Gesture => "gesture",
Switch => "switch",
};
print!("{}", name);
print!("{}", cap.text());
}
println!();
if let Some(v) = &device.accel_profile {

View file

@ -18,7 +18,7 @@ use {
};
pub fn main(global: GlobalArgs, args: LogArgs) {
with_tool_client(global.log_level.into(), |tc| async move {
with_tool_client(global.log_level, |tc| async move {
let logger = Rc::new(Log {
tc: tc.clone(),
path: RefCell::new(None),

View file

@ -8,7 +8,7 @@ use {
};
pub fn main(global: GlobalArgs) {
with_tool_client(global.log_level.into(), |tc| async move {
with_tool_client(global.log_level, |tc| async move {
run(tc).await;
});
}

View file

@ -466,7 +466,7 @@ fn blend_space_possible_values() -> Vec<PossibleValue> {
}
pub fn main(global: GlobalArgs, args: RandrArgs) {
with_tool_client(global.log_level.into(), |tc| async move {
with_tool_client(global.log_level, |tc| async move {
let idle = Rc::new(Randr { tc: tc.clone() });
idle.run(args).await;
});

View file

@ -20,7 +20,7 @@ pub struct ReexecArgs {
}
pub fn main(global: GlobalArgs, reexec_args: ReexecArgs) {
with_tool_client(global.log_level.into(), |tc| async move {
with_tool_client(global.log_level, |tc| async move {
let rexec = Rc::new(Reexec { tc: tc.clone() });
rexec.run(reexec_args).await;
});

View file

@ -10,7 +10,7 @@ use {
};
pub fn main(global: GlobalArgs, args: RunPrivilegedArgs) {
Logger::install_stderr(global.log_level.into());
Logger::install_stderr(global.log_level);
if let Some(xrd) = xrd() {
let mut wd = match std::env::var(WAYLAND_DISPLAY) {
Ok(v) => v,

View file

@ -21,7 +21,7 @@ pub struct RunTaggedArgs {
}
pub fn main(global: GlobalArgs, run_tagged_args: RunTaggedArgs) {
with_tool_client(global.log_level.into(), |tc| async move {
with_tool_client(global.log_level, |tc| async move {
let run_tagged = Rc::new(RunTagged { tc: tc.clone() });
run_tagged.run(run_tagged_args).await;
});

View file

@ -30,7 +30,7 @@ use {
};
pub fn main(global: GlobalArgs, args: ScreenshotArgs) {
with_tool_client(global.log_level.into(), |tc| async move {
with_tool_client(global.log_level, |tc| async move {
let screenshot = Rc::new(Screenshot {
tc: tc.clone(),
args,

View file

@ -27,7 +27,7 @@ use {
};
pub fn main(global: GlobalArgs, args: SeatTestArgs) {
with_tool_client(global.log_level.into(), |tc| async move {
with_tool_client(global.log_level, |tc| async move {
let screenshot = Rc::new(SeatTest {
tc: tc.clone(),
args,

View file

@ -4,11 +4,12 @@ use {
tools::tool_client::{ToolClient, with_tool_client},
wire::jay_compositor::SetLogLevel,
},
linearize::Linearize,
std::rc::Rc,
};
pub fn main(global: GlobalArgs, args: SetLogArgs) {
with_tool_client(global.log_level.into(), |tc| async move {
with_tool_client(global.log_level, |tc| async move {
let logger = Rc::new(Log {
tc: tc.clone(),
args,
@ -27,7 +28,7 @@ async fn run(log: Rc<Log>) {
let comp = tc.jay_compositor().await;
tc.send(SetLogLevel {
self_id: comp,
level: log.args.level as u32,
level: log.args.level.linearize() as u32,
});
tc.round_trip().await;
}

View file

@ -62,7 +62,7 @@ struct QueryWorkspaceNameArgs {
}
pub fn main(global: GlobalArgs, tree_args: TreeArgs) {
with_tool_client(global.log_level.into(), |tc| async move {
with_tool_client(global.log_level, |tc| async move {
let comp = tc.jay_compositor().await;
let tree = Rc::new(Tree {
tc: tc.clone(),

View file

@ -8,7 +8,7 @@ use {
};
pub fn main(global: GlobalArgs) {
with_tool_client(global.log_level.into(), |tc| async move {
with_tool_client(global.log_level, |tc| async move {
let logger = Rc::new(Unlocker { tc: tc.clone() });
run(logger).await;
});

View file

@ -39,7 +39,7 @@ pub enum CliScalingMode {
}
pub fn main(global: GlobalArgs, args: XwaylandArgs) {
with_tool_client(global.log_level.into(), |tc| async move {
with_tool_client(global.log_level, |tc| async move {
let xwayland = Xwayland { tc: tc.clone() };
xwayland.run(args).await;
});

View file

@ -25,6 +25,7 @@ use {
pending_serial::PendingSerial,
pid_info::{PidInfo, get_pid_info, get_socket_creds},
pidfd_send_signal::pidfd_send_signal,
static_text::StaticText,
},
wire::WlRegistryId,
},
@ -68,6 +69,28 @@ bitflags! {
CAP_GAMMA_CONTROL_MANAGER = 1 << 14,
}
impl StaticText for ClientCapsEnum {
fn text(&self) -> &'static str {
match self {
ClientCapsEnum::CAP_DATA_CONTROL_MANAGER => "data-control",
ClientCapsEnum::CAP_VIRTUAL_KEYBOARD_MANAGER => "virtual-keyboard",
ClientCapsEnum::CAP_FOREIGN_TOPLEVEL_LIST => "foreign-toplevel-list",
ClientCapsEnum::CAP_IDLE_NOTIFIER => "idle-notifier",
ClientCapsEnum::CAP_SESSION_LOCK_MANAGER => "session-lock",
ClientCapsEnum::CAP_JAY_COMPOSITOR => "jay-compositor",
ClientCapsEnum::CAP_LAYER_SHELL => "layer-shell",
ClientCapsEnum::CAP_SCREENCOPY_MANAGER => "screencopy",
ClientCapsEnum::CAP_SEAT_MANAGER => "seat-manager",
ClientCapsEnum::CAP_DRM_LEASE => "drm-lease",
ClientCapsEnum::CAP_INPUT_METHOD => "input-method",
ClientCapsEnum::CAP_WORKSPACE => "workspace-manager",
ClientCapsEnum::CAP_FOREIGN_TOPLEVEL_MANAGER => "foreign-toplevel-manager",
ClientCapsEnum::CAP_HEAD_MANAGER => "head-manager",
ClientCapsEnum::CAP_GAMMA_CONTROL_MANAGER => "gamma-control-manager",
}
}
}
pub const CAPS_DEFAULT: ClientCaps = ClientCaps(CAP_LAYER_SHELL.0 | CAP_DRM_LEASE.0);
pub const CAPS_DEFAULT_SANDBOXED: ClientCaps = ClientCaps(CAP_DRM_LEASE.0);

View file

@ -36,7 +36,7 @@ use {
HeadManagers, HeadState, jay_head_manager_session_v1::handle_jay_head_manager_done,
},
jay_screencast::{perform_screencast_realloc, perform_toplevel_screencasts},
wl_output::{OutputId, PersistentOutputState, WlOutputGlobal},
wl_output::{BlendSpace, OutputId, PersistentOutputState, WlOutputGlobal},
wl_seat::handle_position_hint_requests,
wl_surface::{
NoneSurfaceExt, xdg_surface::handle_xdg_surface_configure_events,
@ -68,6 +68,7 @@ use {
clone3::ensure_reaper,
clonecell::CloneCell,
errorfmt::ErrorFmt,
event_listener::handle_lazy_event_sources,
fdcloser::FdCloser,
nice::{did_elevate_scheduler, elevate_scheduler},
numcell::NumCell,
@ -76,6 +77,7 @@ use {
rc_eq::RcEq,
refcounted::RefCounted,
run_toplevel::RunToplevel,
static_text::StaticText,
tri::Try,
},
version::VERSION,
@ -83,8 +85,11 @@ use {
wheel::{Wheel, WheelError},
},
ahash::AHashSet,
clap::ValueEnum,
forker::ForkerProxy,
jay_config::_private::DEFAULT_SEAT_NAME,
jay_config::{_private::DEFAULT_SEAT_NAME, logging::LogLevel as ConfigLogLevel},
linearize::Linearize,
log::LevelFilter,
std::{
cell::{Cell, RefCell},
env,
@ -100,6 +105,9 @@ use {
pub const MAX_EXTENTS: i32 = (1 << 22) - 1;
pub const MIN_SCALE: Scale = Scale::from_wl(60);
pub const MAX_SCALE: Scale = Scale::from_int(16);
pub fn start_compositor(global: GlobalArgs, args: RunArgs) {
sighand::reset_all();
let reaper_pid = ensure_reaper();
@ -112,9 +120,9 @@ pub fn start_compositor(global: GlobalArgs, args: RunArgs) {
None
};
let forker = create_forker(reaper_pid);
let portal = portal::run_from_compositor(global.log_level.into());
let portal = portal::run_from_compositor(global.log_level);
enable_profiler();
let logger = Logger::install_compositor(global.log_level.into());
let logger = Logger::install_compositor(global.log_level);
let portal = match portal {
Ok(p) => Some(p),
Err(e) => {
@ -191,7 +199,8 @@ fn start_compositor2(
test_future: Option<TestFuture>,
caps_thread: Option<PrCapsThread>,
) -> Result<(), CompositorError> {
log::info!("pid = {}", uapi::getpid());
let pid = uapi::getpid();
log::info!("pid = {pid}");
log::info!("version = {VERSION}");
if did_elevate_scheduler() {
log::info!("Running with elevated scheduler: SCHED_RR");
@ -216,6 +225,7 @@ fn start_compositor2(
let crit_ids = Rc::new(CritMatcherIds::default());
let eventfd_cache = EventfdCache::new(&ring, &engine);
let state = Rc::new(State {
pid,
kb_ctx,
backend: CloneCell::new(Rc::new(DummyBackend)),
forker: Default::default(),
@ -286,6 +296,8 @@ fn start_compositor2(
use_wire_scale: Default::default(),
wire_scale: Default::default(),
windows: Default::default(),
client: Default::default(),
display: Default::default(),
},
acceptor: Default::default(),
serial: Default::default(),
@ -377,6 +389,7 @@ fn start_compositor2(
copy_device_registry: Rc::new(CopyDeviceRegistry::new(&ring, &engine, &eventfd_cache)),
supports_presentation_feedback: Default::default(),
eventfd_cache,
lazy_event_sources: Default::default(),
});
state.tracker.register(ClientId::from_raw(0));
create_dummy_output(&state);
@ -577,6 +590,10 @@ fn start_global_event_handlers(state: &Rc<State>) -> Vec<SpawnedFuture<()>> {
Phase::PostLayout,
handle_xdg_surface_configure_events(state.clone()),
),
eng.spawn(
"lazy event sources",
handle_lazy_event_sources(state.clone()),
),
]
}
@ -695,6 +712,9 @@ fn create_dummy_output(state: &Rc<State>) {
eotf: backend_state.eotf,
supported_formats: Default::default(),
brightness: None,
blend_space: BlendSpace::Srgb,
use_native_gamut: false,
vrr_cursor_hz: None,
};
let connector_data = Rc::new(ConnectorData {
id,
@ -798,3 +818,65 @@ pub fn config_dir() -> Option<String> {
None
}
}
#[derive(ValueEnum, Debug, Copy, Clone, Hash, Default, Eq, PartialEq, Linearize)]
pub enum LogLevel {
Trace,
Debug,
#[default]
Info,
Warn,
Error,
Off,
}
impl Into<LevelFilter> for LogLevel {
fn into(self) -> LevelFilter {
match self {
LogLevel::Trace => LevelFilter::Trace,
LogLevel::Debug => LevelFilter::Debug,
LogLevel::Info => LevelFilter::Info,
LogLevel::Warn => LevelFilter::Warn,
LogLevel::Error => LevelFilter::Error,
LogLevel::Off => LevelFilter::Off,
}
}
}
impl From<LevelFilter> for LogLevel {
fn from(value: LevelFilter) -> Self {
match value {
LevelFilter::Trace => LogLevel::Trace,
LevelFilter::Debug => LogLevel::Debug,
LevelFilter::Info => LogLevel::Info,
LevelFilter::Warn => LogLevel::Warn,
LevelFilter::Error => LogLevel::Error,
LevelFilter::Off => LogLevel::Off,
}
}
}
impl StaticText for LogLevel {
fn text(&self) -> &'static str {
match self {
LogLevel::Off => "Off",
LogLevel::Error => "Error",
LogLevel::Warn => "Warn",
LogLevel::Info => "Info",
LogLevel::Debug => "Debug",
LogLevel::Trace => "Trace",
}
}
}
impl From<ConfigLogLevel> for LogLevel {
fn from(value: ConfigLogLevel) -> Self {
match value {
ConfigLogLevel::Trace => LogLevel::Trace,
ConfigLogLevel::Debug => LogLevel::Debug,
ConfigLogLevel::Info => LogLevel::Info,
ConfigLogLevel::Warn => LogLevel::Warn,
ConfigLogLevel::Error => LogLevel::Error,
}
}
}

View file

@ -9,7 +9,6 @@ use {
client::{CAP_JAY_COMPOSITOR, Client, ClientCaps, ClientId},
cmm::cmm_eotf::Eotf,
compositor::{MAX_EXTENTS, WAYLAND_DISPLAY},
config::ConfigProxy,
criteria::{
CritLiteralOrRegex, CritMgrExt, CritTarget, CritUpstreamNode,
clm::ClmLeafMatcher,
@ -27,12 +26,11 @@ use {
scale::Scale,
state::{ConnectorData, DeviceHandlerData, DrmDevData, OutputData, State},
tagged_acceptor::TaggedAcceptorError,
theme::{Color, ThemeSized},
theme::{ThemeColor, ThemeSized},
tree::{
ContainerNode, ContainerSplit, FloatNode, Node, NodeVisitorBase, OutputNode,
TearingMode, TileState, ToplevelData, ToplevelNode, VrrMode, WorkspaceNode,
toplevel_create_split, toplevel_parent_container, toplevel_set_floating,
toplevel_set_workspace,
ContainerSplit, OutputNode, TearingMode, TileState, ToplevelData, ToplevelNode,
VrrMode, WorkspaceNode, toplevel_create_split, toplevel_parent_container,
toplevel_set_floating, toplevel_set_workspace,
},
utils::{
asyncevent::AsyncEvent,
@ -66,7 +64,7 @@ use {
},
},
keyboard::{Group, Keymap, mods::Modifiers, syms::KeySym},
logging::LogLevel,
logging::LogLevel as ConfigLogLevel,
theme::{BarPosition, colors::Colorable, sized::Resizable},
timer::Timer as JayTimer,
video::{
@ -88,7 +86,6 @@ use {
hash::Hash,
ops::Deref,
rc::{Rc, Weak},
sync::Arc,
time::Duration,
},
thiserror::Error,
@ -263,17 +260,17 @@ impl ConfigProxyHandler {
fn handle_log_request(
&self,
level: LogLevel,
level: ConfigLogLevel,
msg: &str,
file: Option<&str>,
line: Option<u32>,
) {
let level = match level {
LogLevel::Error => Level::Error,
LogLevel::Warn => Level::Warn,
LogLevel::Info => Level::Info,
LogLevel::Debug => Level::Debug,
LogLevel::Trace => Level::Trace,
ConfigLogLevel::Error => Level::Error,
ConfigLogLevel::Warn => Level::Warn,
ConfigLogLevel::Info => Level::Info,
ConfigLogLevel::Debug => Level::Debug,
ConfigLogLevel::Trace => Level::Trace,
};
let debug = fmt::from_fn(|fmt| {
if let Some(file) = file {
@ -424,22 +421,7 @@ impl ConfigProxyHandler {
}
fn handle_reload(&self) {
log::info!("Reloading config");
let config = match ConfigProxy::from_config_dir(&self.state) {
Ok(c) => c,
Err(e) => {
log::error!("Cannot reload config: {}", ErrorFmt(e));
return;
}
};
if let Some(config) = self.state.config.take() {
config.destroy();
for seat in self.state.globals.seats.lock().values() {
seat.clear_shortcuts();
}
}
config.configure(true);
self.state.config.set(Some(Rc::new(config)));
self.state.reload_config();
}
fn handle_get_seat_fullscreen(&self, seat: Seat) -> Result<(), CphError> {
@ -816,7 +798,7 @@ impl ConfigProxyHandler {
left_handed: bool,
) -> Result<(), CphError> {
let dev = self.get_device_handler_data(device)?;
dev.device.set_left_handed(left_handed);
dev.set_left_handed(left_handed);
Ok(())
}
@ -831,31 +813,31 @@ impl ConfigProxyHandler {
ACCEL_PROFILE_ADAPTIVE => InputDeviceAccelProfile::Adaptive,
_ => return Err(CphError::UnknownAccelProfile(accel_profile)),
};
dev.device.set_accel_profile(profile);
dev.set_accel_profile(profile);
Ok(())
}
fn handle_set_accel_speed(&self, device: InputDevice, speed: f64) -> Result<(), CphError> {
let dev = self.get_device_handler_data(device)?;
dev.device.set_accel_speed(speed);
dev.set_accel_speed(speed);
Ok(())
}
fn handle_set_px_per_wheel_scroll(&self, device: InputDevice, px: f64) -> Result<(), CphError> {
let dev = self.get_device_handler_data(device)?;
dev.px_per_scroll_wheel.set(px);
dev.set_px_per_scroll_wheel(px);
Ok(())
}
fn handle_set_tap_enabled(&self, device: InputDevice, enabled: bool) -> Result<(), CphError> {
let dev = self.get_device_handler_data(device)?;
dev.device.set_tap_enabled(enabled);
dev.set_tap_enabled(enabled);
Ok(())
}
fn handle_set_drag_enabled(&self, device: InputDevice, enabled: bool) -> Result<(), CphError> {
let dev = self.get_device_handler_data(device)?;
dev.device.set_drag_enabled(enabled);
dev.set_drag_enabled(enabled);
Ok(())
}
@ -865,7 +847,7 @@ impl ConfigProxyHandler {
enabled: bool,
) -> Result<(), CphError> {
let dev = self.get_device_handler_data(device)?;
dev.device.set_natural_scrolling_enabled(enabled);
dev.set_natural_scrolling_enabled(enabled);
Ok(())
}
@ -875,7 +857,7 @@ impl ConfigProxyHandler {
enabled: bool,
) -> Result<(), CphError> {
let dev = self.get_device_handler_data(device)?;
dev.device.set_drag_lock_enabled(enabled);
dev.set_drag_lock_enabled(enabled);
Ok(())
}
@ -885,7 +867,7 @@ impl ConfigProxyHandler {
matrix: [[f64; 2]; 2],
) -> Result<(), CphError> {
let dev = self.get_device_handler_data(device)?;
dev.device.set_transform_matrix(matrix);
dev.set_transform_matrix(matrix);
Ok(())
}
@ -895,7 +877,7 @@ impl ConfigProxyHandler {
matrix: [[f32; 3]; 2],
) -> Result<(), CphError> {
let dev = self.get_device_handler_data(device)?;
dev.device.set_calibration_matrix(matrix);
dev.set_calibration_matrix(matrix);
Ok(())
}
@ -911,7 +893,7 @@ impl ConfigProxyHandler {
CLICK_METHOD_CLICKFINGER => InputDeviceClickMethod::Clickfinger,
_ => return Err(CphError::UnknownClickMethod(click_method)),
};
dev.device.set_click_method(method);
dev.set_click_method(method);
Ok(())
}
@ -921,13 +903,12 @@ impl ConfigProxyHandler {
enabled: bool,
) -> Result<(), CphError> {
let dev = self.get_device_handler_data(device)?;
dev.device.set_middle_button_emulation_enabled(enabled);
dev.set_middle_button_emulation_enabled(enabled);
Ok(())
}
fn handle_set_ei_socket_enabled(&self, enabled: bool) {
self.state.enable_ei_acceptor.set(enabled);
self.state.update_ei_acceptor();
self.state.set_ei_socket_enabled(enabled);
}
fn handle_get_workspace(&self, name: &str) {
@ -971,7 +952,6 @@ impl ConfigProxyHandler {
fn handle_set_flip_margin(&self, device: DrmDevice, margin: Duration) -> Result<(), CphError> {
self.get_drm_device(device)?
.dev
.set_flip_margin(margin.as_nanos().try_into().unwrap_or(u64::MAX));
Ok(())
}
@ -982,8 +962,7 @@ impl ConfigProxyHandler {
XScalingMode::DOWNSCALED => true,
_ => return Err(CphError::UnknownXScalingMode(mode)),
};
self.state.xwayland.use_wire_scale.set(use_wire_scale);
self.state.update_xwayland_wire_scale();
self.state.set_xwayland_use_wire_scale(use_wire_scale);
Ok(())
}
@ -993,13 +972,11 @@ impl ConfigProxyHandler {
}
fn handle_set_ui_drag_enabled(&self, enabled: bool) {
self.state.ui_drag_enabled.set(enabled);
self.state.set_ui_drag_enabled(enabled);
}
fn handle_set_ui_drag_threshold(&self, threshold: i32) {
let threshold = threshold.max(1);
let squared = threshold.saturating_mul(threshold);
self.state.ui_drag_threshold_squared.set(squared);
self.state.set_ui_drag_threshold(threshold.max(1));
}
fn handle_set_direct_scanout_enabled(
@ -1010,7 +987,6 @@ impl ConfigProxyHandler {
match device {
Some(dev) => self
.get_drm_device(dev)?
.dev
.set_direct_scanout_enabled(enabled),
_ => self.state.direct_scanout_enabled.set(enabled),
}
@ -1403,12 +1379,7 @@ impl ConfigProxyHandler {
}
fn handle_set_float_above_fullscreen(&self, above: bool) {
self.state.float_above_fullscreen.set(above);
for seat in self.state.globals.seats.lock().values() {
seat.emulate_cursor_moved();
seat.trigger_tree_changed(false);
}
self.state.root.update_visible(&self.state);
self.state.set_float_above_fullscreen(above);
}
fn handle_get_float_above_fullscreen(&self) {
@ -1418,10 +1389,7 @@ impl ConfigProxyHandler {
}
fn handle_set_show_bar(&self, show: bool) {
self.state.show_bar.set(show);
for output in self.state.root.outputs.lock().values() {
output.on_spaces_changed();
}
self.state.set_show_bar(show);
}
fn handle_get_show_bar(&self) {
@ -1431,8 +1399,7 @@ impl ConfigProxyHandler {
}
fn handle_set_show_titles(&self, show: bool) {
self.state.theme.show_titles.set(show);
self.spaces_change();
self.state.set_show_titles(show);
}
fn handle_get_show_titles(&self) {
@ -1445,8 +1412,7 @@ impl ConfigProxyHandler {
let Ok(position) = position.try_into() else {
return Err(CphError::UnknownBarPosition(position));
};
self.state.theme.bar_position.set(position);
self.spaces_change();
self.state.set_bar_position(position);
Ok(())
}
@ -1457,19 +1423,11 @@ impl ConfigProxyHandler {
}
fn handle_set_show_float_pin_icon(&self, show: bool) {
self.state.show_pin_icon.set(show);
for stacked in self.state.root.stacked.iter() {
if let Some(float) = stacked.deref().clone().node_into_float() {
float.schedule_render_titles();
}
}
self.state.set_show_pin_icon(show);
}
fn handle_set_workspace_display_order(&self, order: WorkspaceDisplayOrder) {
self.state.workspace_display_order.set(order.into());
for output in self.state.root.outputs.lock().values() {
output.handle_workspace_display_order_update();
}
self.state.set_workspace_display_order(order.into());
}
fn handle_get_seat_float_pinned(&self, seat: Seat) -> Result<(), CphError> {
@ -1849,17 +1807,8 @@ impl ConfigProxyHandler {
Ok(())
}
fn handle_set_log_level(&self, level: LogLevel) {
let level = match level {
LogLevel::Error => Level::Error,
LogLevel::Warn => Level::Warn,
LogLevel::Info => Level::Info,
LogLevel::Debug => Level::Debug,
LogLevel::Trace => Level::Trace,
};
if let Some(logger) = &self.state.logger {
logger.set_level(level);
}
fn handle_set_log_level(&self, level: ConfigLogLevel) {
self.state.set_log_level(level.into());
}
fn handle_grab(&self, kb: InputDevice, grab: bool) -> Result<(), CphError> {
@ -2421,44 +2370,6 @@ impl ConfigProxyHandler {
Ok(())
}
fn spaces_change(&self) {
struct V;
impl NodeVisitorBase for V {
fn visit_output(&mut self, node: &Rc<OutputNode>) {
node.on_spaces_changed();
node.node_visit_children(self);
}
fn visit_container(&mut self, node: &Rc<ContainerNode>) {
node.on_spaces_changed();
node.node_visit_children(self);
}
fn visit_float(&mut self, node: &Rc<FloatNode>) {
node.on_spaces_changed();
node.node_visit_children(self);
}
}
self.state.root.clone().node_visit(&mut V);
self.state.damage(self.state.root.extents.get());
self.state.icons.update_sizes(&self.state);
}
fn colors_changed(&self) {
struct V;
impl NodeVisitorBase for V {
fn visit_container(&mut self, node: &Rc<ContainerNode>) {
node.on_colors_changed();
node.node_visit_children(self);
}
fn visit_float(&mut self, node: &Rc<FloatNode>) {
node.on_colors_changed();
node.node_visit_children(self);
}
}
self.state.root.clone().node_visit(&mut V);
self.state.damage(self.state.root.extents.get());
self.state.icons.clear();
}
fn get_sized(&self, sized: Resizable) -> Result<ThemeSized, CphError> {
use jay_config::theme::sized::*;
let sized = match sized {
@ -2486,46 +2397,32 @@ impl ConfigProxyHandler {
if size > sized.max() {
return Err(CphError::InvalidSize(size, sized));
}
let field = sized.field(&self.state.theme);
field.val.set(size);
field.set.set(true);
self.spaces_change();
self.state.set_size(sized, size);
Ok(())
}
fn handle_reset_colors(&self) {
self.state.theme.colors.reset();
self.colors_changed();
self.state.reset_colors();
}
fn handle_reset_sizes(&self) {
self.state.theme.sizes.reset();
self.spaces_change();
self.state.reset_sizes();
}
fn handle_reset_font(&self) {
let theme = &self.state.theme;
theme.font.set(self.state.theme.default_font.clone());
theme.bar_font.set(None);
theme.title_font.set(None);
self.state.reset_fonts();
}
fn handle_set_font(&self, font: &str) {
self.state.theme.font.set(Arc::new(font.to_string()));
self.state.set_font(font);
}
fn handle_set_bar_font(&self, font: &str) {
self.state
.theme
.bar_font
.set(Some(Arc::new(font.to_string())));
self.state.set_bar_font(Some(font));
}
fn handle_set_title_font(&self, font: &str) {
self.state
.theme
.title_font
.set(Some(Arc::new(font.to_string())));
self.state.set_title_font(Some(font));
}
fn handle_get_font(&self) {
@ -2533,34 +2430,37 @@ impl ConfigProxyHandler {
self.respond(Response::GetFont { font });
}
fn get_color(&self, colorable: Colorable) -> Result<&Cell<Color>, CphError> {
let colors = &self.state.theme.colors;
fn get_color(&self, colorable: Colorable) -> Result<ThemeColor, CphError> {
use jay_config::theme::colors::*;
let colorable = match colorable {
UNFOCUSED_TITLE_BACKGROUND_COLOR => &colors.unfocused_title_background,
FOCUSED_TITLE_BACKGROUND_COLOR => &colors.focused_title_background,
UNFOCUSED_TITLE_BACKGROUND_COLOR => ThemeColor::unfocused_title_background,
FOCUSED_TITLE_BACKGROUND_COLOR => ThemeColor::focused_title_background,
CAPTURED_UNFOCUSED_TITLE_BACKGROUND_COLOR => {
&colors.captured_unfocused_title_background
ThemeColor::captured_unfocused_title_background
}
CAPTURED_FOCUSED_TITLE_BACKGROUND_COLOR => &colors.captured_focused_title_background,
FOCUSED_INACTIVE_TITLE_BACKGROUND_COLOR => &colors.focused_inactive_title_background,
BACKGROUND_COLOR => &colors.background,
BAR_BACKGROUND_COLOR => &colors.bar_background,
SEPARATOR_COLOR => &colors.separator,
BORDER_COLOR => &colors.border,
UNFOCUSED_TITLE_TEXT_COLOR => &colors.unfocused_title_text,
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,
HIGHLIGHT_COLOR => &colors.highlight,
CAPTURED_FOCUSED_TITLE_BACKGROUND_COLOR => {
ThemeColor::captured_focused_title_background
}
FOCUSED_INACTIVE_TITLE_BACKGROUND_COLOR => {
ThemeColor::focused_inactive_title_background
}
BACKGROUND_COLOR => ThemeColor::background,
BAR_BACKGROUND_COLOR => ThemeColor::bar_background,
SEPARATOR_COLOR => ThemeColor::separator,
BORDER_COLOR => ThemeColor::border,
UNFOCUSED_TITLE_TEXT_COLOR => ThemeColor::unfocused_title_text,
FOCUSED_TITLE_TEXT_COLOR => ThemeColor::focused_title_text,
FOCUSED_INACTIVE_TITLE_TEXT_COLOR => ThemeColor::focused_inactive_title_text,
BAR_STATUS_TEXT_COLOR => ThemeColor::bar_text,
ATTENTION_REQUESTED_BACKGROUND_COLOR => ThemeColor::attention_requested_background,
HIGHLIGHT_COLOR => ThemeColor::highlight,
_ => return Err(CphError::UnknownColor(colorable.0)),
};
Ok(colorable)
}
fn handle_get_color(&self, colorable: Colorable) -> Result<(), CphError> {
let color = self.get_color(colorable)?.get();
let color = self.get_color(colorable)?.field(&self.state.theme).get();
let [r, g, b, a] = color.to_array(Eotf::Gamma22);
let color = jay_config::theme::Color::new_f32_premultiplied(r, g, b, a);
self.respond(Response::GetColor { color });
@ -2572,8 +2472,8 @@ impl ConfigProxyHandler {
colorable: Colorable,
color: jay_config::theme::Color,
) -> Result<(), CphError> {
self.get_color(colorable)?.set(color.into());
self.colors_changed();
self.state
.set_color(self.get_color(colorable)?, color.into());
Ok(())
}

View file

@ -7,6 +7,7 @@ use {
CritDestroyListener, CritLiteralOrRegex, CritMatcherId, CritMatcherIds, CritMgrExt,
CritUpstreamNode, FixedRootMatcher, RootMatcherMap,
clm::clm_matchers::{
clmm_id::ClmMatchId,
clmm_is_xwayland::ClmMatchIsXwayland,
clmm_pid::ClmMatchPid,
clmm_sandboxed::ClmMatchSandboxed,
@ -62,6 +63,7 @@ pub struct RootMatchers {
comm: ClmRootMatcherMap<ClmMatchComm>,
exe: ClmRootMatcherMap<ClmMatchExe>,
tag: ClmRootMatcherMap<ClmMatchTag>,
id: ClmRootMatcherMap<ClmMatchId>,
}
impl RootMatchers {
@ -74,6 +76,7 @@ impl RootMatchers {
self.comm.clear();
self.exe.clear();
self.tag.clear();
self.id.clear();
}
}
@ -184,6 +187,7 @@ impl ClMatcherManager {
unconditional!(comm);
unconditional!(exe);
unconditional!(tag);
unconditional!(id);
fixed!(sandboxed);
fixed!(is_xwayland);
self.constant[true].handle(data);
@ -229,6 +233,11 @@ impl ClMatcherManager {
pub fn tag(&self, string: CritLiteralOrRegex) -> Rc<ClmUpstreamNode> {
self.root(ClmMatchTag::new(string))
}
#[expect(dead_code)]
pub fn id(&self, id: ClientId) -> Rc<ClmUpstreamNode> {
self.root(ClmMatchId(id))
}
}
impl CritTarget for Rc<Client> {

View file

@ -17,6 +17,7 @@ macro_rules! fixed_root_criterion {
};
}
pub mod clmm_id;
pub mod clmm_is_xwayland;
pub mod clmm_pid;
pub mod clmm_sandboxed;

View file

@ -0,0 +1,19 @@
use {
crate::{
client::{Client, ClientId},
criteria::{RootMatcherMap, clm::RootMatchers, crit_graph::CritRootCriterion},
},
std::rc::Rc,
};
pub struct ClmMatchId(pub ClientId);
impl CritRootCriterion<Rc<Client>> for ClmMatchId {
fn matches(&self, data: &Rc<Client>) -> bool {
data.id == self.0
}
fn nodes(roots: &RootMatchers) -> Option<&RootMatcherMap<Rc<Client>, Self>> {
Some(&roots.id)
}
}

View file

@ -198,6 +198,11 @@ impl CursorUserGroup {
}
}
#[expect(dead_code)]
pub fn cursor_size(&self) -> u32 {
self.size.get()
}
fn output_center(&self, output: &Rc<OutputNode>) -> (Fixed, Fixed) {
let pos = output.global.pos.get();
let x = Fixed::from_int((pos.x1() + pos.x2()) / 2);

View file

@ -15,7 +15,7 @@ use {
state::State,
theme::Color,
tree::{Node, OutputNode, Transform},
utils::{clonecell::UnsafeCellCloneSafe, errorfmt::ErrorFmt},
utils::{clonecell::UnsafeCellCloneSafe, errorfmt::ErrorFmt, static_text::StaticText},
video::{
Modifier,
dmabuf::DmaBuf,
@ -47,6 +47,12 @@ pub enum GfxApi {
Vulkan,
}
impl StaticText for GfxApi {
fn text(&self) -> &'static str {
self.to_str()
}
}
impl TryFrom<ConfigGfxApi> for GfxApi {
type Error = ();

View file

@ -10,7 +10,6 @@ mod device;
mod dmabuf_buffer;
mod eotfs;
mod format;
mod gpu_alloc_ash;
mod image;
mod instance;
mod pipeline;

View file

@ -1,13 +1,9 @@
use {
crate::{
cpu_worker::{AsyncCpuWork, CpuJob, CpuWork, CpuWorker},
gfx_apis::vulkan::{
VulkanError,
device::VulkanDevice,
gpu_alloc_ash::{self, AshMemoryDevice},
renderer::VulkanRenderer,
},
gfx_apis::vulkan::{VulkanError, device::VulkanDevice, renderer::VulkanRenderer},
utils::{numcell::NumCell, page_size::page_size, ptr_ext::MutPtrExt},
vulkan_core::gpu_alloc_ash::{self, AshMemoryDevice},
},
ash::{
Device,

View file

@ -7,9 +7,12 @@ use {
client::ClientId,
format::Format,
globals::GlobalName,
ifs::head_management::{
head_management_macros::HeadExts, jay_head_manager_session_v1::JayHeadManagerSessionV1,
jay_head_v1::JayHeadV1,
ifs::{
head_management::{
head_management_macros::HeadExts,
jay_head_manager_session_v1::JayHeadManagerSessionV1, jay_head_v1::JayHeadV1,
},
wl_output::BlendSpace,
},
scale::Scale,
state::OutputData,
@ -18,7 +21,7 @@ use {
wire::JayHeadManagerSessionV1Id,
},
std::{
cell::{Cell, RefCell},
cell::{Cell, Ref, RefCell},
rc::Rc,
},
thiserror::Error,
@ -92,6 +95,20 @@ pub struct HeadState {
pub eotf: BackendEotfs,
pub supported_formats: RcEq<Vec<&'static Format>>,
pub brightness: Option<f64>,
pub blend_space: BlendSpace,
pub use_native_gamut: bool,
pub vrr_cursor_hz: Option<f64>,
}
pub struct ReadOnlyHeadState {
state: Rc<RefCell<HeadState>>,
}
impl ReadOnlyHeadState {
#[expect(dead_code)]
pub fn borrow(&self) -> Ref<'_, HeadState> {
self.state.borrow()
}
}
impl HeadState {
@ -218,6 +235,13 @@ impl HeadManagers {
}
}
#[expect(dead_code)]
pub fn state(&self) -> ReadOnlyHeadState {
ReadOnlyHeadState {
state: self.state.clone(),
}
}
pub fn handle_removed(&self) {
for head in self.managers.lock().drain_values() {
skip_in_transaction!(head);
@ -240,6 +264,10 @@ impl HeadManagers {
state.transform = n.global.persistent.transform.get();
state.vrr_mode = n.global.persistent.vrr_mode.get();
state.tearing_mode = n.global.persistent.tearing_mode.get();
state.brightness = n.global.persistent.brightness.get();
state.blend_space = n.global.persistent.blend_space.get();
state.use_native_gamut = n.global.persistent.use_native_gamut.get();
state.vrr_cursor_hz = n.global.persistent.vrr_cursor_hz.get();
}
for head in self.managers.lock().values() {
skip_in_transaction!(head);
@ -530,4 +558,19 @@ impl HeadManagers {
}
}
}
pub fn handle_blend_space_change(&self, blend_space: BlendSpace) {
let state = &mut *self.state.borrow_mut();
state.blend_space = blend_space;
}
pub fn handle_use_native_gamut_change(&self, use_native_gamut: bool) {
let state = &mut *self.state.borrow_mut();
state.use_native_gamut = use_native_gamut;
}
pub fn handle_cursor_hz_change(&self, cursor_hz: Option<f64>) {
let state = &mut *self.state.borrow_mut();
state.vrr_cursor_hz = cursor_hz;
}
}

View file

@ -1,5 +1,6 @@
use {
crate::{
compositor::{MAX_SCALE, MIN_SCALE},
ifs::head_management::{HeadOp, HeadState},
scale::Scale,
wire::{
@ -17,9 +18,6 @@ impl_compositor_space_scaler_v1! {
after_announce = after_announce,
}
const MIN_SCALE: Scale = Scale::from_wl(60);
const MAX_SCALE: Scale = Scale::from_int(16);
impl HeadName {
fn after_announce(&self, _shared: &HeadState) {
self.send_range(MIN_SCALE, MAX_SCALE);

View file

@ -1,7 +1,7 @@
use {
crate::{
cli::CliLogLevel,
client::{CAP_JAY_COMPOSITOR, Client, ClientCaps, ClientError, ClientId},
compositor::LogLevel,
globals::{Global, GlobalName},
ifs::{
jay_acceptor_request::JayAcceptorRequest,
@ -35,7 +35,7 @@ use {
},
},
bstr::ByteSlice,
log::Level,
linearize::LinearizeExt,
std::{cell::Cell, ops::Deref, rc::Rc, str::FromStr},
thiserror::Error,
};
@ -179,28 +179,15 @@ impl JayCompositorRequestHandler for JayCompositor {
}
fn quit(&self, _req: Quit, _slf: &Rc<Self>) -> Result<(), Self::Error> {
log::info!("Quitting");
self.client.state.ring.stop();
self.client.state.quit();
Ok(())
}
fn set_log_level(&self, req: SetLogLevel, _slf: &Rc<Self>) -> Result<(), Self::Error> {
const ERROR: u32 = CliLogLevel::Error as u32;
const WARN: u32 = CliLogLevel::Warn as u32;
const INFO: u32 = CliLogLevel::Info as u32;
const DEBUG: u32 = CliLogLevel::Debug as u32;
const TRACE: u32 = CliLogLevel::Trace as u32;
let level = match req.level {
ERROR => Level::Error,
WARN => Level::Warn,
INFO => Level::Info,
DEBUG => Level::Debug,
TRACE => Level::Trace,
_ => return Err(JayCompositorError::UnknownLogLevel(req.level)),
let Some(level) = LogLevel::from_linear(req.level as usize) else {
return Err(JayCompositorError::UnknownLogLevel(req.level));
};
if let Some(logger) = &self.client.state.logger {
logger.set_level(level);
}
self.client.state.set_log_level(level);
Ok(())
}

View file

@ -67,13 +67,15 @@ impl JayIdleRequestHandler for JayIdle {
fn set_interval(&self, req: SetInterval, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let interval = Duration::from_secs(req.interval);
self.client.state.idle.set_timeout(interval);
let state = &self.client.state;
state.idle.set_timeout(interval);
Ok(())
}
fn set_grace_period(&self, req: SetGracePeriod, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let period = Duration::from_secs(req.period);
self.client.state.idle.set_grace_period(period);
let state = &self.client.state;
state.idle.set_grace_period(period);
Ok(())
}
}

View file

@ -1,6 +1,8 @@
use {
crate::{
backend::{self, InputDeviceAccelProfile, InputDeviceClickMethod, InputDeviceId},
backend::{
InputDeviceAccelProfile, InputDeviceCapability, InputDeviceClickMethod, InputDeviceId,
},
client::{Client, ClientError},
clientmem::{ClientMem, ClientMemError},
ifs::wl_seat::WlSeatGlobal,
@ -16,7 +18,8 @@ use {
utils::errorfmt::ErrorFmt,
wire::{JayInputId, jay_input::*},
},
kbvm::xkb::rmlvo::Group,
arrayvec::ArrayVec,
linearize::{Linearize, LinearizeExt},
std::rc::Rc,
thiserror::Error,
uapi::OwnedFd,
@ -85,11 +88,8 @@ impl JayInput {
}
fn send_input_device(&self, data: &InputDeviceData) {
use backend::InputDeviceCapability::*;
let mut caps = vec![];
for cap in [
Keyboard, Pointer, Touch, TabletTool, TabletPad, Gesture, Switch,
] {
let mut caps = ArrayVec::<_, { InputDeviceCapability::LENGTH }>::new();
for cap in InputDeviceCapability::variants() {
if data.data.device.has_capability(cap) {
caps.push(cap.to_libinput().raw());
}
@ -228,26 +228,11 @@ impl JayInput {
F: FnOnce(&Rc<KbvmMap>) -> Result<(), JayInputError>,
{
self.or_error(|| {
let mut groups = None::<Vec<_>>;
if layout.is_some() || variant.is_some() {
groups = Some(
Group::from_layouts_and_variants(
layout.unwrap_or_default(),
variant.unwrap_or_default(),
)
.collect(),
);
}
let mut options_vec = None::<Vec<_>>;
if let Some(options) = options {
options_vec = Some(options.split(",").collect());
}
let keymap = self.client.state.kb_ctx.keymap_from_names(
rules,
model,
groups.as_deref(),
options_vec.as_deref(),
)?;
let keymap = self
.client
.state
.kb_ctx
.keymap_from_rmlvo(rules, model, layout, variant, options)?;
f(&keymap)?;
Ok(())
})
@ -324,7 +309,7 @@ impl JayInputRequestHandler for JayInput {
LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE => InputDeviceAccelProfile::Adaptive,
_ => return Err(JayInputError::UnknownAccelerationProfile(req.profile)),
};
dev.device.set_accel_profile(profile);
dev.set_accel_profile(profile);
Ok(())
})
}
@ -332,7 +317,7 @@ impl JayInputRequestHandler for JayInput {
fn set_accel_speed(&self, req: SetAccelSpeed, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.or_error(|| {
let dev = self.device(req.id)?;
dev.device.set_accel_speed(req.speed);
dev.set_accel_speed(req.speed);
Ok(())
})
}
@ -340,7 +325,7 @@ impl JayInputRequestHandler for JayInput {
fn set_tap_enabled(&self, req: SetTapEnabled, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.or_error(|| {
let dev = self.device(req.id)?;
dev.device.set_tap_enabled(req.enabled != 0);
dev.set_tap_enabled(req.enabled != 0);
Ok(())
})
}
@ -352,7 +337,7 @@ impl JayInputRequestHandler for JayInput {
) -> Result<(), Self::Error> {
self.or_error(|| {
let dev = self.device(req.id)?;
dev.device.set_drag_enabled(req.enabled != 0);
dev.set_drag_enabled(req.enabled != 0);
Ok(())
})
}
@ -364,7 +349,7 @@ impl JayInputRequestHandler for JayInput {
) -> Result<(), Self::Error> {
self.or_error(|| {
let dev = self.device(req.id)?;
dev.device.set_drag_lock_enabled(req.enabled != 0);
dev.set_drag_lock_enabled(req.enabled != 0);
Ok(())
})
}
@ -372,7 +357,7 @@ impl JayInputRequestHandler for JayInput {
fn set_left_handed(&self, req: SetLeftHanded, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.or_error(|| {
let dev = self.device(req.id)?;
dev.device.set_left_handed(req.enabled != 0);
dev.set_left_handed(req.enabled != 0);
Ok(())
})
}
@ -384,7 +369,7 @@ impl JayInputRequestHandler for JayInput {
) -> Result<(), Self::Error> {
self.or_error(|| {
let dev = self.device(req.id)?;
dev.device.set_natural_scrolling_enabled(req.enabled != 0);
dev.set_natural_scrolling_enabled(req.enabled != 0);
Ok(())
})
}
@ -396,7 +381,7 @@ impl JayInputRequestHandler for JayInput {
) -> Result<(), Self::Error> {
self.or_error(|| {
let dev = self.device(req.id)?;
dev.px_per_scroll_wheel.set(req.px);
dev.set_px_per_scroll_wheel(req.px);
Ok(())
})
}
@ -408,8 +393,7 @@ impl JayInputRequestHandler for JayInput {
) -> Result<(), Self::Error> {
self.or_error(|| {
let dev = self.device(req.id)?;
dev.device
.set_transform_matrix([[req.m11, req.m12], [req.m21, req.m22]]);
dev.set_transform_matrix([[req.m11, req.m12], [req.m21, req.m22]]);
Ok(())
})
}
@ -523,8 +507,7 @@ impl JayInputRequestHandler for JayInput {
) -> Result<(), Self::Error> {
self.or_error(|| {
let dev = self.device(req.id)?;
dev.device
.set_calibration_matrix([[req.m00, req.m01, req.m02], [req.m10, req.m11, req.m12]]);
dev.set_calibration_matrix([[req.m00, req.m01, req.m02], [req.m10, req.m11, req.m12]]);
Ok(())
})
}
@ -538,7 +521,7 @@ impl JayInputRequestHandler for JayInput {
LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER => InputDeviceClickMethod::Clickfinger,
_ => return Err(JayInputError::UnknownClickMethod(req.method)),
};
dev.device.set_click_method(method);
dev.set_click_method(method);
Ok(())
})
}
@ -550,8 +533,7 @@ impl JayInputRequestHandler for JayInput {
) -> Result<(), Self::Error> {
self.or_error(|| {
let dev = self.device(req.id)?;
dev.device
.set_middle_button_emulation_enabled(req.enabled != 0);
dev.set_middle_button_emulation_enabled(req.enabled != 0);
Ok(())
})
}

View file

@ -350,7 +350,7 @@ impl JayRandrRequestHandler for JayRandr {
let Some(dev) = self.get_device(req.dev) else {
return Ok(());
};
dev.dev.set_direct_scanout_enabled(req.enabled != 0);
dev.set_direct_scanout_enabled(req.enabled != 0);
Ok(())
}
@ -493,7 +493,7 @@ impl JayRandrRequestHandler for JayRandr {
let Some(dev) = self.get_device(req.dev) else {
return Ok(());
};
dev.dev.set_flip_margin(req.margin_ns);
dev.set_flip_margin(req.margin_ns);
Ok(())
}

View file

@ -57,10 +57,7 @@ impl JayXwaylandRequestHandler for JayXwayland {
};
self.client
.state
.xwayland
.use_wire_scale
.set(use_wire_scale);
self.client.state.update_xwayland_wire_scale();
.set_xwayland_use_wire_scale(use_wire_scale);
Ok(())
}
}

View file

@ -94,6 +94,12 @@ pub struct WlOutputGlobal {
CopyHashMap<(ClientId, WpColorManagementOutputV1Id), Rc<WpColorManagementOutputV1>>,
}
impl PartialEq for WlOutputGlobal {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
}
}
#[derive(Default)]
pub struct OutputGlobalOpt {
pub global: CloneCell<Option<Rc<WlOutputGlobal>>>,

View file

@ -24,7 +24,9 @@ pub mod zwp_virtual_keyboard_v1;
use {
crate::{
async_engine::SpawnedFuture,
backend::{ButtonState, Leds},
backend::{
ButtonState, InputDeviceAccelProfile, InputDeviceClickMethod, Leds, TransformMatrix,
},
client::{Client, ClientError, ClientId},
cursor_user::{CursorUser, CursorUserGroup, CursorUserOwner},
ei::ei_ifs::ei_seat::EiSeat,
@ -253,6 +255,12 @@ pub struct WlSeatGlobal {
simple_im_enabled: Cell<bool>,
}
impl PartialEq for WlSeatGlobal {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
#[derive(Copy, Clone)]
enum MarkMode {
Mark,
@ -954,10 +962,20 @@ impl WlSeatGlobal {
self.focus_history_visible_only.set(visible);
}
#[expect(dead_code)]
pub fn focus_history_visible(&self) -> bool {
self.focus_history_visible_only.get()
}
pub fn focus_history_set_same_workspace(&self, same_workspace: bool) {
self.focus_history_same_workspace.set(same_workspace);
}
#[expect(dead_code)]
pub fn focus_history_same_workspace(&self) -> bool {
self.focus_history_same_workspace.get()
}
fn focus_layer_rel<LI, SI>(
self: &Rc<Self>,
next_layer: impl Fn(NodeLayer) -> NodeLayer,
@ -1460,10 +1478,20 @@ impl WlSeatGlobal {
self.focus_follows_mouse.set(focus_follows_mouse);
}
#[expect(dead_code)]
pub fn focus_follows_mouse(&self) -> bool {
self.focus_follows_mouse.get()
}
pub fn set_fallback_output_mode(&self, fallback_output_mode: FallbackOutputMode) {
self.fallback_output_mode.set(fallback_output_mode);
}
#[expect(dead_code)]
pub fn fallback_output_mode(&self) -> FallbackOutputMode {
self.fallback_output_mode.get()
}
pub fn set_window_management_enabled(self: &Rc<Self>, enabled: bool) {
self.pointer_owner
.set_window_management_enabled(self, enabled);
@ -1580,6 +1608,11 @@ impl WlSeatGlobal {
pub fn set_pointer_revert_key(&self, key: KeySym) {
self.revert_key.set(key);
}
#[expect(dead_code)]
pub fn pointer_revert_key(&self) -> KeySym {
self.revert_key.get()
}
}
impl CursorUserOwner for WlSeatGlobal {
@ -1860,6 +1893,54 @@ impl DeviceHandlerData {
}
state.root.extents.get()
}
pub fn set_accel_profile(&self, v: InputDeviceAccelProfile) {
self.device.set_accel_profile(v);
}
pub fn set_accel_speed(&self, v: f64) {
self.device.set_accel_speed(v);
}
pub fn set_tap_enabled(&self, v: bool) {
self.device.set_tap_enabled(v);
}
pub fn set_drag_enabled(&self, v: bool) {
self.device.set_drag_enabled(v);
}
pub fn set_drag_lock_enabled(&self, v: bool) {
self.device.set_drag_lock_enabled(v);
}
pub fn set_left_handed(&self, v: bool) {
self.device.set_left_handed(v);
}
pub fn set_natural_scrolling_enabled(&self, v: bool) {
self.device.set_natural_scrolling_enabled(v);
}
pub fn set_px_per_scroll_wheel(&self, v: f64) {
self.px_per_scroll_wheel.set(v);
}
pub fn set_transform_matrix(&self, v: TransformMatrix) {
self.device.set_transform_matrix(v);
}
pub fn set_calibration_matrix(&self, v: [[f32; 3]; 2]) {
self.device.set_calibration_matrix(v);
}
pub fn set_click_method(&self, v: InputDeviceClickMethod) {
self.device.set_click_method(v);
}
pub fn set_middle_button_emulation_enabled(&self, v: bool) {
self.device.set_middle_button_emulation_enabled(v);
}
}
impl LedsListener for DeviceHandlerData {

View file

@ -43,11 +43,13 @@ impl ZwpIdleInhibitorV1 {
}
pub fn activate(self: &Rc<Self>) {
self.client.state.idle.add_inhibitor(self);
let state = &self.client.state;
state.idle.add_inhibitor(self);
}
pub fn deactivate(&self) {
self.client.state.idle.remove_inhibitor(self);
let state = &self.client.state;
state.idle.remove_inhibitor(self);
}
}

View file

@ -4,6 +4,7 @@ use {
ifs::wl_surface::WlSurface,
leaks::Tracker,
object::{Object, Version},
utils::static_text::StaticText,
wire::{WpContentTypeV1Id, wp_content_type_v1::*},
},
jay_config::window::{
@ -26,6 +27,16 @@ pub enum ContentType {
Game,
}
impl StaticText for ContentType {
fn text(&self) -> &'static str {
match self {
Self::Photo => "Photo",
Self::Video => "Video",
Self::Game => "Game",
}
}
}
pub trait ContentTypeExt {
fn to_config(&self) -> ConfigContentType;
}

View file

@ -72,7 +72,33 @@ impl WpCursorShapeDeviceV1RequestHandler for WpCursorShapeDeviceV1 {
}
fn set_shape(&self, req: SetShape, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let cursor = match req.shape {
let cursor = KnownCursor::from_shape(req.shape, self.version)
.ok_or(WpCursorShapeDeviceV1Error::UnknownShape(req.shape))?;
let tablet_tool;
let (node_client_id, user) = match &self.cursor_user {
CursorShapeCursorUser::Seat(s) => match s.pointer_node() {
Some(n) => (n.node_client_id(), s.pointer_cursor()),
_ => return Ok(()),
},
CursorShapeCursorUser::TabletTool(t) => match t.get() {
Some(t) => {
tablet_tool = t;
(tablet_tool.node().node_client_id(), tablet_tool.cursor())
}
_ => return Ok(()),
},
};
if node_client_id != Some(self.client.id) {
return Ok(());
}
user.set_known(cursor);
Ok(())
}
}
impl KnownCursor {
pub fn from_shape(shape: u32, version: Version) -> Option<Self> {
let cursor = match shape {
DEFAULT => KnownCursor::Default,
CONTEXT_MENU => KnownCursor::ContextMenu,
HELP => KnownCursor::Help,
@ -107,29 +133,52 @@ impl WpCursorShapeDeviceV1RequestHandler for WpCursorShapeDeviceV1 {
ALL_SCROLL => KnownCursor::AllScroll,
ZOOM_IN => KnownCursor::ZoomIn,
ZOOM_OUT => KnownCursor::ZoomOut,
DND_ASK if self.version >= V2 => KnownCursor::DndAsk,
ALL_RESIZE if self.version >= V2 => KnownCursor::AllResize,
_ => return Err(WpCursorShapeDeviceV1Error::UnknownShape(req.shape)),
DND_ASK if version >= V2 => KnownCursor::DndAsk,
ALL_RESIZE if version >= V2 => KnownCursor::AllResize,
_ => return None,
};
let tablet_tool;
let (node_client_id, user) = match &self.cursor_user {
CursorShapeCursorUser::Seat(s) => match s.pointer_node() {
Some(n) => (n.node_client_id(), s.pointer_cursor()),
_ => return Ok(()),
},
CursorShapeCursorUser::TabletTool(t) => match t.get() {
Some(t) => {
tablet_tool = t;
(tablet_tool.node().node_client_id(), tablet_tool.cursor())
}
_ => return Ok(()),
},
};
if node_client_id != Some(self.client.id) {
return Ok(());
Some(cursor)
}
pub fn to_shape(self) -> u32 {
match self {
KnownCursor::Default => DEFAULT,
KnownCursor::ContextMenu => CONTEXT_MENU,
KnownCursor::Help => HELP,
KnownCursor::Pointer => POINTER,
KnownCursor::Progress => PROGRESS,
KnownCursor::Wait => WAIT,
KnownCursor::Cell => CELL,
KnownCursor::Crosshair => CROSSHAIR,
KnownCursor::Text => TEXT,
KnownCursor::VerticalText => VERTICAL_TEXT,
KnownCursor::Alias => ALIAS,
KnownCursor::Copy => COPY,
KnownCursor::Move => MOVE,
KnownCursor::NoDrop => NO_DROP,
KnownCursor::NotAllowed => NOT_ALLOWED,
KnownCursor::Grab => GRAB,
KnownCursor::Grabbing => GRABBING,
KnownCursor::EResize => E_RESIZE,
KnownCursor::NResize => N_RESIZE,
KnownCursor::NeResize => NE_RESIZE,
KnownCursor::NwResize => NW_RESIZE,
KnownCursor::SResize => S_RESIZE,
KnownCursor::SeResize => SE_RESIZE,
KnownCursor::SwResize => SW_RESIZE,
KnownCursor::WResize => W_RESIZE,
KnownCursor::EwResize => EW_RESIZE,
KnownCursor::NsResize => NS_RESIZE,
KnownCursor::NeswResize => NESW_RESIZE,
KnownCursor::NwseResize => NWSE_RESIZE,
KnownCursor::ColResize => COL_RESIZE,
KnownCursor::RowResize => ROW_RESIZE,
KnownCursor::AllScroll => ALL_SCROLL,
KnownCursor::ZoomIn => ZOOM_IN,
KnownCursor::ZoomOut => ZOOM_OUT,
KnownCursor::DndAsk => DND_ASK,
KnownCursor::AllResize => ALL_RESIZE,
}
user.set_known(cursor);
Ok(())
}
}

View file

@ -13,6 +13,7 @@ use {
self, Keymap,
diagnostic::{Diagnostic, WriteToLog},
keymap::{Indicator, IndicatorMatcher},
rmlvo::Group,
},
},
std::{
@ -51,6 +52,8 @@ pub struct KbvmMap {
pub id: KbvmMapId,
pub state_machine: StateMachine,
pub lookup_table: LookupTable,
#[expect(dead_code)]
pub map_text: String,
pub map: KeymapFd,
pub xwayland_map: KeymapFd,
pub has_indicators: bool,
@ -96,11 +99,36 @@ impl KbvmContext {
self.create_keymap(id, map)
}
pub fn keymap_from_rmlvo(
&self,
rules: Option<&str>,
model: Option<&str>,
layout: Option<&str>,
variant: Option<&str>,
options: Option<&str>,
) -> Result<Rc<KbvmMap>, KbvmError> {
let mut groups = None::<Vec<_>>;
if layout.is_some() || variant.is_some() {
groups = Some(
Group::from_layouts_and_variants(
layout.unwrap_or_default(),
variant.unwrap_or_default(),
)
.collect(),
);
}
let mut options_vec = None::<Vec<_>>;
if let Some(options) = options {
options_vec = Some(options.split(",").collect());
}
self.keymap_from_names(rules, model, groups.as_deref(), options_vec.as_deref())
}
pub fn keymap_from_names(
&self,
rules: Option<&str>,
model: Option<&str>,
groups: Option<&[xkb::rmlvo::Group<'_>]>,
groups: Option<&[Group<'_>]>,
options: Option<&[&str]>,
) -> Result<Rc<KbvmMap>, KbvmError> {
let map = self
@ -129,11 +157,14 @@ impl KbvmContext {
has_indicators = true;
}
let builder = map.to_builder();
let (_, xwayland_map) = create_keymap_memfd(&map, true).map_err(KbvmError::KeymapMemfd)?;
let (map_text, map) = create_keymap_memfd(&map, false).map_err(KbvmError::KeymapMemfd)?;
Ok(Rc::new(KbvmMap {
id,
state_machine: builder.build_state_machine(),
map: create_keymap_memfd(&map, false).map_err(KbvmError::KeymapMemfd)?,
xwayland_map: create_keymap_memfd(&map, true).map_err(KbvmError::KeymapMemfd)?,
map,
map_text,
xwayland_map,
lookup_table: builder.build_lookup_table(),
has_indicators,
num_lock,
@ -145,7 +176,7 @@ impl KbvmContext {
}
}
fn create_keymap_memfd(map: &Keymap, xwayland: bool) -> Result<KeymapFd, OsError> {
fn create_keymap_memfd(map: &Keymap, xwayland: bool) -> Result<(String, KeymapFd), OsError> {
let mut format = map.format();
if xwayland {
format = format.lookup_only(true).rename_long_keys(true);
@ -159,10 +190,11 @@ fn create_keymap_memfd(map: &Keymap, xwayland: bool) -> Result<KeymapFd, OsError
memfd.raw(),
c::F_SEAL_SEAL | c::F_SEAL_GROW | c::F_SEAL_SHRINK | c::F_SEAL_WRITE,
)?;
Ok(KeymapFd {
let fd = KeymapFd {
map: Rc::new(memfd),
len: str.len() + 1,
})
};
Ok((str, fd))
}
impl KbvmMap {

View file

@ -1,8 +1,11 @@
use {
crate::utils::{errorfmt::ErrorFmt, oserror::OsError},
crate::{
compositor::LogLevel,
utils::{atomic_enum::AtomicEnum, errorfmt::ErrorFmt, oserror::OsError},
},
backtrace::Backtrace,
bstr::BString,
log::{Level, Log, Metadata, Record},
log::{LevelFilter, Log, Metadata, Record},
parking_lot::Mutex,
std::{
cell::Cell,
@ -24,14 +27,15 @@ thread_local! {
}
pub struct Logger {
level: AtomicU32,
level: AtomicEnum<LogLevel>,
filter: AtomicU32,
path: Mutex<Arc<BString>>,
_file: Mutex<OwnedFd>,
file_fd: AtomicI32,
}
impl Logger {
pub fn install_stderr(level: Level) -> Arc<Self> {
pub fn install_stderr(level: LogLevel) -> Arc<Self> {
let file = match uapi::fcntl_dupfd_cloexec(2, 0) {
Ok(fd) => fd,
Err(e) => {
@ -42,18 +46,20 @@ impl Logger {
Self::install(level, b"STDERR", file)
}
pub fn install_compositor(level: Level) -> Arc<Self> {
pub fn install_compositor(level: LogLevel) -> Arc<Self> {
let (path, file) = open_log_file("jay");
Self::install(level, path.as_bytes(), file)
}
pub fn install_pipe(file: OwnedFd, level: Level) -> Arc<Self> {
pub fn install_pipe(file: OwnedFd, level: LogLevel) -> Arc<Self> {
Self::install(level, b"PIPE", file)
}
fn install(level: Level, path: &[u8], file: OwnedFd) -> Arc<Self> {
fn install(level: LogLevel, path: &[u8], file: OwnedFd) -> Arc<Self> {
let filter: LevelFilter = level.into();
let slf = Arc::new(Self {
level: AtomicU32::new(level as _),
level: AtomicEnum::new(level),
filter: AtomicU32::new(filter as _),
path: Mutex::new(Arc::new(path.to_vec().into())),
file_fd: AtomicI32::new(file.raw()),
_file: Mutex::new(file),
@ -62,14 +68,21 @@ impl Logger {
logger: slf.clone(),
}))
.unwrap();
log::set_max_level(level.to_level_filter());
log::set_max_level(filter);
set_panic_hook();
slf
}
pub fn set_level(&self, level: Level) {
self.level.store(level as _, Relaxed);
log::set_max_level(level.to_level_filter());
pub fn set_level(&self, level: LogLevel) {
let filter: LevelFilter = level.into();
self.level.store(level, Relaxed);
self.filter.store(filter as _, Relaxed);
log::set_max_level(filter);
}
#[expect(dead_code)]
pub fn level(&self) -> LogLevel {
self.level.load(Relaxed)
}
pub fn path(&self) -> Arc<BString> {
@ -166,11 +179,11 @@ struct LogWrapper {
impl Log for LogWrapper {
fn enabled(&self, metadata: &Metadata) -> bool {
metadata.level() as u32 <= self.logger.level.load(Relaxed)
metadata.level() as u32 <= self.logger.filter.load(Relaxed)
}
fn log(&self, record: &Record) {
if record.level() as u32 > self.logger.level.load(Relaxed) {
if record.level() as u32 > self.logger.filter.load(Relaxed) {
return;
}
let mut buffer = BUFFER.get();

View file

@ -127,6 +127,7 @@ impl OutputSchedule {
Some(v) => v,
};
self.persistent.vrr_cursor_hz.set(hz);
self.connector.head_managers.handle_cursor_hz_change(hz);
self.cursor_delta_nsec.set(delta);
self.trigger();
}

View file

@ -11,6 +11,7 @@ use {
async_engine::AsyncEngine,
cli::GlobalArgs,
cmm::cmm_manager::ColorManager,
compositor::LogLevel,
dbus::{
BUS_DEST, BUS_PATH, DBUS_NAME_FLAG_DO_NOT_QUEUE, DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER,
Dbus, DbusSocket,
@ -44,7 +45,6 @@ use {
wheel::Wheel,
wire_dbus::org,
},
log::Level,
std::{
ffi::OsStr,
io::{BufReader, BufWriter},
@ -64,7 +64,7 @@ const PORTAL_CANCELLED: u32 = 1;
const PORTAL_ENDED: u32 = 2;
pub fn run_freestanding(global: GlobalArgs) {
let logger = Logger::install_stderr(global.log_level.into());
let logger = Logger::install_stderr(global.log_level);
run(logger, true);
}
@ -131,7 +131,7 @@ impl PortalStartup {
}
}
pub fn run_from_compositor(level: Level) -> Result<PortalStartup, PortalError> {
pub fn run_from_compositor(level: LogLevel) -> Result<PortalStartup, PortalError> {
let Pipe { read, write } = match pipe() {
Ok(p) => p,
Err(e) => return Err(PortalError::CreatePipe(e)),

View file

@ -2,6 +2,7 @@ use {
crate::{
gfx_api::{GfxApi, GfxFormat, cross_intersect_formats},
gfx_apis::create_gfx_context,
globals::GlobalName,
ifs::wl_seat::POINTER,
object::Version,
portal::{
@ -60,7 +61,7 @@ struct PortalDisplayPrelude {
con: Rc<UsrCon>,
state: Rc<PortalState>,
registry: Rc<UsrWlRegistry>,
globals: RefCell<AHashMap<String, Vec<(u32, u32)>>>,
globals: RefCell<AHashMap<String, Vec<(GlobalName, u32)>>>,
}
shared_ids!(PortalDisplayId);
@ -80,8 +81,8 @@ pub struct PortalDisplay {
pub vp: Rc<UsrWpViewporter>,
pub render_ctx: CloneCell<Option<Rc<PortalServerRenderCtx>>>,
pub outputs: CopyHashMap<u32, Rc<PortalOutput>>,
pub seats: CopyHashMap<u32, Rc<PortalSeat>>,
pub outputs: CopyHashMap<GlobalName, Rc<PortalOutput>>,
pub seats: CopyHashMap<GlobalName, Rc<PortalSeat>>,
pub workspaces: CopyHashMap<u32, Rc<UsrJayWorkspace>>,
pub windows: CopyHashMap<WlSurfaceId, Rc<WindowData>>,
@ -89,14 +90,14 @@ pub struct PortalDisplay {
}
pub struct PortalOutput {
pub global_id: u32,
pub global_id: GlobalName,
pub dpy: Rc<PortalDisplay>,
pub wl: Rc<UsrWlOutput>,
pub jay: Rc<UsrJayOutput>,
}
pub struct PortalSeat {
pub global_id: u32,
pub global_id: GlobalName,
pub dpy: Rc<PortalDisplay>,
pub wl: Rc<UsrWlSeat>,
pub jay_pointer: Rc<UsrJayPointer>,
@ -128,32 +129,32 @@ impl UsrWlSeatOwner for PortalSeat {
}
impl UsrWlPointerOwner for PortalSeat {
fn enter(&self, ev: &wl_pointer::Enter) {
fn enter(self: Rc<Self>, ev: &wl_pointer::Enter) {
if let Some(window) = self.dpy.windows.get(&ev.surface) {
self.pointer_focus.set(Some(window.clone()));
window.motion(self, ev.surface_x, ev.surface_y, true);
window.motion(&self, ev.surface_x, ev.surface_y, true);
}
}
fn leave(&self, _ev: &wl_pointer::Leave) {
fn leave(self: Rc<Self>, _ev: &wl_pointer::Leave) {
self.pointer_focus.take();
}
fn motion(&self, ev: &wl_pointer::Motion) {
fn motion(self: Rc<Self>, ev: &wl_pointer::Motion) {
if let Some(window) = self.pointer_focus.get() {
window.motion(self, ev.surface_x, ev.surface_y, false);
window.motion(&self, ev.surface_x, ev.surface_y, false);
}
}
fn button(&self, ev: &wl_pointer::Button) {
fn button(self: Rc<Self>, ev: &wl_pointer::Button) {
if let Some(window) = self.pointer_focus.get() {
window.button(self, ev.button, ev.state);
window.button(&self, ev.button, ev.state);
}
}
}
impl UsrWlRegistryOwner for PortalDisplayPrelude {
fn global(self: Rc<Self>, name: u32, interface: &str, version: u32) {
fn global(self: Rc<Self>, name: GlobalName, interface: &str, version: u32) {
self.globals
.borrow_mut()
.entry(interface.to_string())
@ -237,7 +238,7 @@ impl UsrConOwner for PortalDisplay {
}
impl UsrWlRegistryOwner for PortalDisplay {
fn global(self: Rc<Self>, name: u32, interface: &str, version: u32) {
fn global(self: Rc<Self>, name: GlobalName, interface: &str, version: u32) {
if interface == WlOutput.name() {
add_output(&self, name, version);
} else if interface == WlSeat.name() {
@ -250,7 +251,7 @@ impl UsrWlRegistryOwner for PortalDisplay {
version: Version(version.min(5)),
});
self.con.add_object(ls.clone());
self.registry.request_bind(name, ls.version.0, ls.deref());
self.registry.bind(name, ls.deref());
self.dmabuf.set(Some(ls));
}
}
@ -353,7 +354,7 @@ fn finish_display_connect(dpy: Rc<PortalDisplayPrelude>) {
version: Version(version.min(12)),
});
dpy.con.add_object(jc.clone());
dpy.registry.request_bind(name, jc.version.0, jc.deref());
dpy.registry.bind(name, jc.deref());
jc_opt = Some(jc);
} else if interface == WpFractionalScaleManagerV1.name() {
let ls = Rc::new(UsrWpFractionalScaleManager {
@ -362,7 +363,7 @@ fn finish_display_connect(dpy: Rc<PortalDisplayPrelude>) {
version: Version(version.min(1)),
});
dpy.con.add_object(ls.clone());
dpy.registry.request_bind(name, ls.version.0, ls.deref());
dpy.registry.bind(name, ls.deref());
fsm_opt = Some(ls);
} else if interface == ZwlrLayerShellV1.name() {
let ls = Rc::new(UsrWlrLayerShell {
@ -371,7 +372,7 @@ fn finish_display_connect(dpy: Rc<PortalDisplayPrelude>) {
version: Version(version.min(5)),
});
dpy.con.add_object(ls.clone());
dpy.registry.request_bind(name, ls.version.0, ls.deref());
dpy.registry.bind(name, ls.deref());
ls_opt = Some(ls);
} else if interface == WpViewporter.name() {
let ls = Rc::new(UsrWpViewporter {
@ -380,7 +381,7 @@ fn finish_display_connect(dpy: Rc<PortalDisplayPrelude>) {
version: Version(version.min(1)),
});
dpy.con.add_object(ls.clone());
dpy.registry.request_bind(name, ls.version.0, ls.deref());
dpy.registry.bind(name, ls.deref());
vp_opt = Some(ls);
} else if interface == WlCompositor.name() {
let ls = Rc::new(UsrWlCompositor {
@ -389,7 +390,7 @@ fn finish_display_connect(dpy: Rc<PortalDisplayPrelude>) {
version: Version(version.min(6)),
});
dpy.con.add_object(ls.clone());
dpy.registry.request_bind(name, ls.version.0, ls.deref());
dpy.registry.bind(name, ls.deref());
comp_opt = Some(ls);
} else if interface == ZwpLinuxDmabufV1.name() {
let ls = Rc::new(UsrLinuxDmabuf {
@ -399,7 +400,7 @@ fn finish_display_connect(dpy: Rc<PortalDisplayPrelude>) {
version: Version(version.min(5)),
});
dpy.con.add_object(ls.clone());
dpy.registry.request_bind(name, ls.version.0, ls.deref());
dpy.registry.bind(name, ls.deref());
dmabuf_opt = Some(ls);
} else if interface == WlOutput.name() {
outputs.push((name, version));
@ -465,7 +466,7 @@ fn finish_display_connect(dpy: Rc<PortalDisplayPrelude>) {
log::info!("Display {} initialized", dpy.id);
}
fn add_seat(dpy: &Rc<PortalDisplay>, name: u32, version: u32) {
fn add_seat(dpy: &Rc<PortalDisplay>, name: GlobalName, version: u32) {
let wl = Rc::new(UsrWlSeat {
id: dpy.con.id(),
con: dpy.con.clone(),
@ -473,7 +474,7 @@ fn add_seat(dpy: &Rc<PortalDisplay>, name: u32, version: u32) {
version: Version(version.min(9)),
});
dpy.con.add_object(wl.clone());
dpy.registry.request_bind(name, wl.version.0, wl.deref());
dpy.registry.bind(name, wl.deref());
let jay_pointer = dpy.jc.get_pointer(&wl);
let js = Rc::new(PortalSeat {
global_id: name,
@ -489,7 +490,7 @@ fn add_seat(dpy: &Rc<PortalDisplay>, name: u32, version: u32) {
dpy.seats.set(name, js);
}
fn add_output(dpy: &Rc<PortalDisplay>, name: u32, version: u32) {
fn add_output(dpy: &Rc<PortalDisplay>, name: GlobalName, version: u32) {
let wl = Rc::new(UsrWlOutput {
id: dpy.con.id(),
con: dpy.con.clone(),
@ -498,7 +499,7 @@ fn add_output(dpy: &Rc<PortalDisplay>, name: u32, version: u32) {
name: Default::default(),
});
dpy.con.add_object(wl.clone());
dpy.registry.request_bind(name, wl.version.0, wl.deref());
dpy.registry.bind(name, wl.deref());
let jo = dpy.jc.get_output(&wl);
let po = Rc::new(PortalOutput {
global_id: name,

View file

@ -1,5 +1,6 @@
use {
crate::{
globals::GlobalName,
ifs::wl_seat::{BTN_LEFT, wl_pointer::PRESSED},
portal::{
ptl_display::{PortalDisplay, PortalOutput, PortalSeat},
@ -21,7 +22,7 @@ const V_MARGIN: f32 = 20.0;
pub struct SelectionGui {
remote_desktop_session: Rc<PortalSession>,
dpy: Rc<PortalDisplay>,
surfaces: CopyHashMap<u32, Rc<SelectionGuiSurface>>,
surfaces: CopyHashMap<GlobalName, Rc<SelectionGuiSurface>>,
}
pub struct SelectionGuiSurface {

View file

@ -1,5 +1,6 @@
use {
crate::{
globals::GlobalName,
ifs::wl_seat::{BTN_LEFT, wl_pointer::PRESSED},
portal::{
ptl_display::{PortalDisplay, PortalOutput, PortalSeat},
@ -29,7 +30,7 @@ const V_MARGIN: f32 = 20.0;
pub struct SelectionGui {
screencast_session: Rc<PortalSession>,
dpy: Rc<PortalDisplay>,
surfaces: CopyHashMap<u32, Rc<SelectionGuiSurface>>,
surfaces: CopyHashMap<GlobalName, Rc<SelectionGuiSurface>>,
}
pub struct SelectionGuiSurface {
@ -254,7 +255,7 @@ impl UsrJaySelectToplevelOwner for SelectingWindowScreencast {
}
impl UsrJaySelectWorkspaceOwner for SelectingWorkspaceScreencast {
fn done(&self, output: u32, ws: Option<Rc<UsrJayWorkspace>>) {
fn done(&self, output: GlobalName, ws: Option<Rc<UsrJayWorkspace>>) {
let Some(ws) = ws else {
log::info!("User has aborted the selection");
self.core.session.kill();

View file

@ -10,6 +10,7 @@ use {
AcquireSync, AlphaMode, GfxContext, GfxFramebuffer, GfxTexture, ReleaseSync,
needs_render_usage,
},
globals::GlobalName,
ifs::zwlr_layer_shell_v1::OVERLAY,
portal::{
ptl_display::{PortalDisplay, PortalOutput, PortalSeat},
@ -29,6 +30,7 @@ use {
wl_usr::usr_ifs::{
usr_linux_buffer_params::{UsrLinuxBufferParams, UsrLinuxBufferParamsOwner},
usr_wl_buffer::{UsrWlBuffer, UsrWlBufferOwner},
usr_wl_callback::UsrWlCallbackOwner,
usr_wl_surface::UsrWlSurface,
usr_wlr_layer_surface::{UsrWlrLayerSurface, UsrWlrLayerSurfaceOwner},
usr_wp_fractional_scale::{UsrWpFractionalScale, UsrWpFractionalScaleOwner},
@ -116,7 +118,7 @@ pub struct Button {
pub data: GuiElementData,
pub tex_off_x: Cell<f32>,
pub tex_off_y: Cell<f32>,
pub hover: RefCell<AHashSet<u32>>,
pub hover: RefCell<AHashSet<GlobalName>>,
pub padding: Cell<f32>,
pub border: Cell<f32>,
pub border_color: Cell<Color>,
@ -504,7 +506,7 @@ pub struct WindowData {
pub width: Cell<i32>,
pub height: Cell<i32>,
pub owner: CloneCell<Option<Rc<dyn WindowDataOwner>>>,
pub seats: CopyHashMap<u32, Rc<GuiWindowSeatState>>,
pub seats: CopyHashMap<GlobalName, Rc<GuiWindowSeatState>>,
}
#[derive(Default)]
@ -666,15 +668,7 @@ impl WindowData {
self.frame_missed.set(false);
self.surface.frame({
let slf = self.clone();
move || {
slf.have_frame.set(true);
if slf.frame_missed.get() {
slf.schedule_render();
}
}
});
self.surface.frame().owner.set(Some(self.clone()));
self.have_frame.set(false);
buf.free.set(false);
@ -901,6 +895,15 @@ impl UsrWpFractionalScaleOwner for WindowData {
}
}
impl UsrWlCallbackOwner for WindowData {
fn done(self: Rc<Self>) {
self.have_frame.set(true);
if self.frame_missed.get() {
self.schedule_render();
}
}
}
impl UsrWlrLayerSurfaceOwner for OverlayWindow {
fn configure(&self, _ev: &Configure) {
self.data.schedule_render();

View file

@ -1,8 +1,8 @@
use std::fmt::{Debug, Display, Formatter};
const BASE: u32 = 120;
const BASE64: i64 = BASE as i64;
const BASEF: f64 = BASE as f64;
pub const SCALE_BASE: u32 = 120;
const BASE64: i64 = SCALE_BASE as i64;
pub const SCALE_BASEF: f64 = SCALE_BASE as f64;
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[repr(transparent)]
@ -16,23 +16,23 @@ impl Default for Scale {
impl Scale {
pub const fn from_int(f: u32) -> Self {
Self(f.saturating_mul(BASE))
Self(f.saturating_mul(SCALE_BASE))
}
pub fn from_f64(f: f64) -> Self {
Self((f * BASEF).round() as u32)
Self((f * SCALE_BASEF).round() as u32)
}
pub fn from_f64_as_float(f: f64) -> Self {
Self(((f * (BASEF / 15.0)).round() as u32).saturating_mul(15))
Self(((f * (SCALE_BASEF / 15.0)).round() as u32).saturating_mul(15))
}
pub fn to_f64(self) -> f64 {
self.0 as f64 / BASEF
self.0 as f64 / SCALE_BASEF
}
pub fn round_up(self) -> u32 {
self.0.saturating_add(BASE - 1) / BASE
self.0.saturating_add(SCALE_BASE - 1) / SCALE_BASE
}
pub const fn from_wl(wl: u32) -> Self {
@ -55,7 +55,7 @@ impl Scale {
impl PartialEq<u32> for Scale {
fn eq(&self, other: &u32) -> bool {
self.0 == other * BASE
self.0 == other * SCALE_BASE
}
}

View file

@ -13,7 +13,7 @@ use {
client::{Client, ClientCaps, ClientId, Clients, NUM_CACHED_SERIAL_RANGES, SerialRange},
clientmem::ClientMemOffset,
cmm::{cmm_description::ColorDescription, cmm_manager::ColorManager},
compositor::LIBEI_SOCKET,
compositor::{LIBEI_SOCKET, LogLevel},
config::ConfigProxy,
copy_device::CopyDeviceRegistry,
cpu_worker::CpuWorker,
@ -93,7 +93,7 @@ use {
scale::Scale,
security_context_acceptor::SecurityContextAcceptors,
tagged_acceptor::TaggedAcceptors,
theme::{Color, Theme},
theme::{BarPosition, Color, Theme, ThemeColor, ThemeSized},
time::Time,
tree::{
ContainerNode, ContainerSplit, Direction, DisplayNode, FindTreeUsecase, FloatNode,
@ -110,7 +110,7 @@ use {
clonecell::CloneCell,
copyhashmap::CopyHashMap,
errorfmt::ErrorFmt,
event_listener::{EventListener, EventSource},
event_listener::{EventListener, EventSource, LazyEventSources},
fdcloser::FdCloser,
hash_map_ext::HashMapExt,
linkedlist::LinkedList,
@ -145,10 +145,12 @@ use {
time::Duration,
},
thiserror::Error,
uapi::OwnedFd,
uapi::{OwnedFd, c},
};
pub struct State {
#[expect(dead_code)]
pub pid: c::pid_t,
pub kb_ctx: KbvmContext,
pub backend: CloneCell<Rc<dyn Backend>>,
pub forker: CloneCell<Option<Rc<ForkerProxy>>>,
@ -290,6 +292,7 @@ pub struct State {
pub copy_device_registry: Rc<CopyDeviceRegistry>,
pub supports_presentation_feedback: Cell<bool>,
pub eventfd_cache: Rc<EventfdCache>,
pub lazy_event_sources: Rc<LazyEventSources>,
}
// impl Drop for State {
@ -319,6 +322,8 @@ pub struct XWaylandState {
pub use_wire_scale: Cell<bool>,
pub wire_scale: Cell<Option<i32>>,
pub windows: CopyHashMap<XwindowId, Rc<Xwindow>>,
pub client: CloneCell<Option<Rc<Client>>>,
pub display: CloneCell<Option<Rc<String>>>,
}
pub struct IdleState {
@ -338,31 +343,37 @@ pub struct IdleState {
impl IdleState {
pub fn set_timeout(&self, timeout: Duration) {
self.timeout.set(timeout);
self.timeout_changed.set(true);
self.change.trigger();
self.timeout_changed();
}
pub fn set_grace_period(&self, grace_period: Duration) {
self.grace_period.set(grace_period);
self.timeout_changed();
}
fn timeout_changed(&self) {
self.timeout_changed.set(true);
self.change.trigger();
}
pub fn add_inhibitor(&self, inhibitor: &Rc<ZwpIdleInhibitorV1>) {
self.inhibitors.set(inhibitor.inhibit_id, inhibitor.clone());
self.inhibitors_changed.set(true);
self.change.trigger();
self.inhibitors_changed();
}
pub fn remove_inhibitor(&self, inhibitor: &ZwpIdleInhibitorV1) {
self.inhibitors.remove(&inhibitor.inhibit_id);
self.inhibitors_changed.set(true);
self.change.trigger();
self.inhibitors_changed();
if self.inhibitors.is_empty() {
self.resume_inhibited_notifications();
}
}
fn inhibitors_changed(&self) {
self.inhibitors_changed.set(true);
self.change.trigger();
}
fn resume_inhibited_notifications(&self) {
for notification in self.inhibited_idle_notifications.lock().drain_values() {
notification.resume.trigger();
@ -517,6 +528,14 @@ impl DrmDevData {
);
self.dev.clone().make_render_device();
}
pub fn set_direct_scanout_enabled(&self, enabled: bool) {
self.dev.set_direct_scanout_enabled(enabled);
}
pub fn set_flip_margin(&self, margin: u64) {
self.dev.set_flip_margin(margin);
}
}
struct UpdateTextTexturesVisitor;
@ -984,6 +1003,13 @@ impl State {
}
}
pub fn set_xwayland_use_wire_scale(&self, use_wire_scale: bool) {
if self.xwayland.use_wire_scale.replace(use_wire_scale) == use_wire_scale {
return;
}
self.update_xwayland_wire_scale();
}
pub fn next_serial(&self, client: Option<&Client>) -> u64 {
let serial = self.serial.fetch_add(1);
if let Some(client) = client {
@ -1128,6 +1154,7 @@ impl State {
self.cpu_worker.clear();
self.wait_for_syncobj.clear();
self.xdg_surface_configure_events.clear();
self.lazy_event_sources.clear();
}
pub fn remove_toplevel_id(&self, id: ToplevelIdentifier) {
@ -1688,6 +1715,200 @@ impl State {
self.explicit_sync_enabled.set(enabled);
self.expose_new_singletons();
}
pub fn set_log_level(&self, level: LogLevel) {
if let Some(logger) = &self.logger {
logger.set_level(level);
}
}
fn colors_changed(&self) {
struct V;
impl NodeVisitorBase for V {
fn visit_container(&mut self, node: &Rc<ContainerNode>) {
node.on_colors_changed();
node.node_visit_children(self);
}
fn visit_output(&mut self, node: &Rc<OutputNode>) {
node.on_colors_changed();
node.node_visit_children(self);
}
fn visit_float(&mut self, node: &Rc<FloatNode>) {
node.on_colors_changed();
node.node_visit_children(self);
}
}
self.root.clone().node_visit(&mut V);
self.damage(self.root.extents.get());
self.icons.clear();
}
pub fn reset_colors(&self) {
self.theme.colors.reset();
self.colors_changed();
}
pub fn quit(&self) {
log::info!("Quitting");
self.ring.stop();
}
pub fn reload_config(self: &Rc<Self>) {
log::info!("Reloading config");
let config = match ConfigProxy::from_config_dir(self) {
Ok(c) => c,
Err(e) => {
log::error!("Cannot reload config: {}", ErrorFmt(e));
return;
}
};
if let Some(config) = self.config.take() {
config.destroy();
for seat in self.globals.seats.lock().values() {
seat.clear_shortcuts();
}
}
config.configure(true);
self.config.set(Some(Rc::new(config)));
}
pub fn set_ei_socket_enabled(self: &Rc<Self>, enabled: bool) {
self.enable_ei_acceptor.set(enabled);
self.update_ei_acceptor();
}
pub fn set_workspace_display_order(&self, order: WorkspaceDisplayOrder) {
self.workspace_display_order.set(order);
for output in self.root.outputs.lock().values() {
output.handle_workspace_display_order_update();
}
}
fn spaces_changed(&self) {
struct V;
impl NodeVisitorBase for V {
fn visit_container(&mut self, node: &Rc<ContainerNode>) {
node.on_spaces_changed();
node.node_visit_children(self);
}
fn visit_output(&mut self, node: &Rc<OutputNode>) {
node.on_spaces_changed();
node.node_visit_children(self);
}
fn visit_float(&mut self, node: &Rc<FloatNode>) {
node.on_spaces_changed();
node.node_visit_children(self);
}
}
self.root.clone().node_visit(&mut V);
self.damage(self.root.extents.get());
self.icons.update_sizes(self);
}
pub fn set_show_bar(&self, show: bool) {
self.show_bar.set(show);
self.spaces_changed();
}
pub fn set_show_titles(&self, show: bool) {
self.theme.show_titles.set(show);
self.spaces_changed();
}
pub fn set_ui_drag_enabled(&self, enabled: bool) {
self.ui_drag_enabled.set(enabled);
}
pub fn set_ui_drag_threshold(&self, threshold: i32) {
self.ui_drag_threshold_squared
.set(threshold.saturating_mul(threshold));
}
pub fn set_show_pin_icon(&self, show: bool) {
self.show_pin_icon.set(show);
for stacked in self.root.stacked.iter() {
if let Some(float) = stacked.deref().clone().node_into_float() {
float.schedule_render_titles();
}
}
}
pub fn set_float_above_fullscreen(&self, v: bool) {
self.float_above_fullscreen.set(v);
for seat in self.globals.seats.lock().values() {
seat.emulate_cursor_moved();
seat.trigger_tree_changed(false);
}
self.root.update_visible(self);
}
pub fn reset_sizes(&self) {
self.theme.sizes.reset();
self.spaces_changed();
}
fn fonts_changed(&self) {
struct V;
impl NodeVisitorBase for V {
fn visit_container(&mut self, node: &Rc<ContainerNode>) {
node.schedule_render_titles();
node.node_visit_children(self);
}
fn visit_output(&mut self, node: &Rc<OutputNode>) {
node.schedule_update_render_data();
node.node_visit_children(self);
}
fn visit_float(&mut self, node: &Rc<FloatNode>) {
node.schedule_render_titles();
node.node_visit_children(self);
}
}
self.root.clone().node_visit(&mut V);
}
pub fn reset_fonts(&self) {
let theme = &self.theme;
theme.font.set(self.theme.default_font.clone());
theme.bar_font.set(None);
theme.title_font.set(None);
self.fonts_changed();
}
pub fn set_font(&self, font: &str) {
self.theme.font.set(Arc::new(font.to_string()));
self.fonts_changed();
}
pub fn set_bar_font(&self, font: Option<&str>) {
let font = font.map(|font| Arc::new(font.to_string()));
self.theme.bar_font.set(font);
self.fonts_changed();
}
pub fn set_title_font(&self, font: Option<&str>) {
let font = font.map(|font| Arc::new(font.to_string()));
self.theme.title_font.set(font);
self.fonts_changed();
}
pub fn set_bar_position(&self, p: BarPosition) {
self.theme.bar_position.set(p);
self.spaces_changed();
}
pub fn set_size(&self, sized: ThemeSized, size: i32) {
let field = sized.field(&self.theme);
field.val.set(size);
field.set.set(true);
self.spaces_changed();
}
pub fn set_color(&self, colored: ThemeColor, v: Color) {
colored.field(&self.theme).set(v);
self.colors_changed();
}
}
#[derive(Debug, Error)]

View file

@ -74,6 +74,9 @@ pub fn handle(state: &Rc<State>, connector: &Rc<dyn Connector>) {
eotf: backend_state.eotf,
supported_formats: Default::default(),
brightness: None,
blend_space: BlendSpace::Srgb,
use_native_gamut: false,
vrr_cursor_hz: None,
};
let data = Rc::new(ConnectorData {
id,

View file

@ -4,12 +4,17 @@ use {
crate::{
cmm::cmm_eotf::{Eotf, bt1886_eotf_args, bt1886_inv_eotf_args},
gfx_api::AlphaMode,
utils::clonecell::CloneCell,
utils::{clonecell::CloneCell, static_text::StaticText},
},
jay_config::theme::BarPosition as ConfigBarPosition,
linearize::Linearize,
num_traits::Float,
std::{cell::Cell, cmp::Ordering, ops::Mul, sync::Arc},
std::{
cell::Cell,
cmp::Ordering,
ops::{Add, Div, Mul},
sync::Arc,
},
};
#[derive(Copy, Clone, Debug, PartialEq)]
@ -337,6 +342,32 @@ impl Color {
a: self.a * (1.0 - other.a) + other.a,
}
}
pub fn srgb_to_oklab(self) -> Oklab {
if self.a == 0.0 {
return Oklab {
l: 0.0,
a: 0.0,
b: 0.0,
};
}
let [r, g, b, _] = self.to_array2(Eotf::Linear, Some(1.0 / self.a));
let l = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b;
let m = 0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b;
let s = 0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b;
let l_ = l.cbrt();
let m_ = m.cbrt();
let s_ = s.cbrt();
let l = 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_;
let a = 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_;
let b = 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_;
Oklab { l, a, b }
}
}
impl From<jay_config::theme::Color> for Color {
@ -361,6 +392,25 @@ macro_rules! colors {
)*
}
#[derive(Copy, Clone, Debug, Linearize)]
#[expect(non_camel_case_types)]
pub enum ThemeColor {
$(
$name,
)*
}
impl ThemeColor {
pub fn field(self, theme: &Theme) -> &Cell<Color> {
let colors = &theme.colors;
match self {
$(
Self::$name => &colors.$name,
)*
}
}
}
impl ThemeColors {
pub fn reset(&self) {
let default = Self::default();
@ -406,6 +456,30 @@ colors! {
highlight = (0x9d, 0x28, 0xc6, 0x7f),
}
impl StaticText for ThemeColor {
fn text(&self) -> &'static str {
match self {
ThemeColor::background => "Background",
ThemeColor::unfocused_title_background => "Title Background (unfocused)",
ThemeColor::focused_title_background => "Title Background (focused)",
ThemeColor::captured_unfocused_title_background => {
"Title Background (unfocused, captured)"
}
ThemeColor::captured_focused_title_background => "Title Background (focused, captured)",
ThemeColor::focused_inactive_title_background => "Title Background (focused, inactive)",
ThemeColor::unfocused_title_text => "Title Text (unfocused)",
ThemeColor::focused_title_text => "Title Text (focused)",
ThemeColor::focused_inactive_title_text => "Title Text (focused, inactive)",
ThemeColor::separator => "Separator",
ThemeColor::border => "Border",
ThemeColor::bar_background => "Bar Background",
ThemeColor::bar_text => "Bar Text",
ThemeColor::attention_requested_background => "Attention Requested",
ThemeColor::highlight => "Highlight",
}
}
}
pub struct ThemeSize {
pub val: Cell<i32>,
pub set: Cell<bool>,
@ -425,7 +499,7 @@ macro_rules! sizes {
)*
}
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, Linearize)]
#[expect(non_camel_case_types)]
pub enum ThemeSized {
$(
@ -514,6 +588,17 @@ sizes! {
bar_separator_width = (0, 1000, 1),
}
impl StaticText for ThemeSized {
fn text(&self) -> &'static str {
match self {
ThemeSized::title_height => "Title Height",
ThemeSized::bar_height => "Bar Height",
ThemeSized::border_width => "Border Width",
ThemeSized::bar_separator_width => "Bar Separator Width",
}
}
}
pub const DEFAULT_FONT: &str = "monospace 8";
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Default, Linearize)]
@ -523,6 +608,15 @@ pub enum BarPosition {
Bottom,
}
impl StaticText for BarPosition {
fn text(&self) -> &'static str {
match self {
BarPosition::Top => "Top",
BarPosition::Bottom => "Bottom",
}
}
}
impl TryFrom<ConfigBarPosition> for BarPosition {
type Error = ();
@ -601,3 +695,94 @@ impl Theme {
}
}
}
#[derive(Copy, Clone, Debug)]
pub struct Oklch {
pub l: f32,
pub c: f32,
pub h: f32,
}
#[derive(Copy, Clone, Debug)]
pub struct Oklab {
pub l: f32,
pub a: f32,
pub b: f32,
}
impl Oklab {
pub fn to_srgb(self) -> Color {
let l_ = self.l + 0.3963377774 * self.a + 0.2158037573 * self.b;
let m_ = self.l - 0.1055613458 * self.a - 0.0638541728 * self.b;
let s_ = self.l - 0.0894841775 * self.a - 1.2914855480 * self.b;
let l = l_ * l_ * l_;
let m = m_ * m_ * m_;
let s = s_ * s_ * s_;
let r = 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s;
let g = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s;
let b = -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s;
Color::new(
Eotf::Linear,
AlphaMode::PremultipliedElectrical,
r,
g,
b,
1.0,
)
}
pub fn to_oklch(self) -> Oklch {
let c = (self.a * self.a + self.b * self.b).sqrt();
let h = self.b.atan2(self.a);
Oklch { l: self.l, c, h }
}
}
impl Oklch {
pub fn to_oklab(self) -> Oklab {
let a = self.c * self.h.cos();
let b = self.c * self.h.sin();
Oklab { l: self.l, a, b }
}
}
impl Add for Oklab {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self {
l: self.l + rhs.l,
a: self.a + rhs.a,
b: self.b + rhs.b,
}
}
}
impl Mul<f32> for Oklab {
type Output = Self;
fn mul(self, rhs: f32) -> Self::Output {
Self {
l: self.l * rhs,
a: self.a * rhs,
b: self.b * rhs,
}
}
}
impl Div<f32> for Oklab {
type Output = Self;
fn div(self, rhs: f32) -> Self::Output {
Self {
l: self.l / rhs,
a: self.a / rhs,
b: self.b / rhs,
}
}
}

View file

@ -2,7 +2,7 @@ use {
crate::{
async_engine::{AsyncEngine, SpawnedFuture},
client::{EventFormatter, RequestParser},
compositor::WAYLAND_DISPLAY,
compositor::{LogLevel, WAYLAND_DISPLAY},
io_uring::{IoUring, IoUringError},
logger::Logger,
object::{ObjectId, WL_DISPLAY_ID},
@ -30,7 +30,6 @@ use {
},
},
ahash::AHashMap,
log::Level,
std::{
cell::{Cell, RefCell},
collections::VecDeque,
@ -97,7 +96,7 @@ pub struct ToolClient {
jay_damage_tracking: Cell<Option<Option<JayDamageTrackingId>>>,
}
pub fn with_tool_client<T, F>(level: Level, f: F)
pub fn with_tool_client<T, F>(level: LogLevel, f: F)
where
F: FnOnce(Rc<ToolClient>) -> T + 'static,
T: Future<Output = ()> + 'static,
@ -111,7 +110,7 @@ fn handle_error(e: ToolClientError) -> ! {
fatal!("Could not create a tool client: {}", ErrorFmt(e));
}
fn with_tool_client_<T, F>(level: Level, f: F) -> Result<(), ToolClientError>
fn with_tool_client_<T, F>(level: LogLevel, f: F) -> Result<(), ToolClientError>
where
F: FnOnce(Rc<ToolClient>) -> T + 'static,
T: Future<Output = ()> + 'static,

View file

@ -25,7 +25,7 @@ use {
keyboard::KeyboardState,
rect::Rect,
renderer::Renderer,
utils::{linkedlist::NodeRef, numcell::NumCell},
utils::{linkedlist::NodeRef, numcell::NumCell, static_text::StaticText},
},
jay_config::{
Direction as JayDirection, video::Transform as ConfigTransform,
@ -79,6 +79,15 @@ impl Into<ConfigWorkspaceDisplayOrder> for WorkspaceDisplayOrder {
}
}
impl StaticText for WorkspaceDisplayOrder {
fn text(&self) -> &'static str {
match self {
WorkspaceDisplayOrder::Manual => "Manual",
WorkspaceDisplayOrder::Sorted => "Sorted",
}
}
}
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Default, Linearize)]
pub enum Transform {
#[default]

View file

@ -478,6 +478,10 @@ impl OutputNode {
}
}
pub fn on_colors_changed(self: &Rc<Self>) {
self.schedule_update_render_data();
}
pub fn set_preferred_scale(self: &Rc<Self>, scale: Scale) {
let old_scale = self.global.persistent.scale.replace(scale);
if scale == old_scale {
@ -996,6 +1000,10 @@ impl OutputNode {
.replace(use_native_gamut);
if old != use_native_gamut {
self.update_color_description();
self.global
.connector
.head_managers
.handle_use_native_gamut_change(use_native_gamut);
}
}
@ -1003,6 +1011,10 @@ impl OutputNode {
let old = self.global.persistent.blend_space.replace(blend_space);
if old != blend_space {
self.state.damage(self.global.position());
self.global
.connector
.head_managers
.handle_blend_space_change(blend_space);
}
}
fn find_stacked_at(
@ -1528,6 +1540,10 @@ impl OutputNode {
log::error!("Could not set gamma_lut: {}", ErrorFmt(e));
})
}
pub fn set_flip_margin(&self, margin_ns: u64) {
self.flip_margin_ns.set(Some(margin_ns));
}
}
pub struct OutputTitle {
@ -1904,14 +1920,24 @@ pub enum VrrMode {
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct VrrSurfaceRequirements {
content_type: Option<VrrContentTypeRequirements>,
pub content_type: Option<VrrContentTypeRequirements>,
}
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct VrrContentTypeRequirements {
photo: bool,
video: bool,
game: bool,
pub photo: bool,
pub video: bool,
pub game: bool,
}
impl Default for VrrContentTypeRequirements {
fn default() -> Self {
Self {
photo: true,
video: true,
game: true,
}
}
}
impl VrrMode {
@ -1970,7 +1996,15 @@ pub enum TearingMode {
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct TearingSurfaceRequirements {
tearing_requested: bool,
pub tearing_requested: bool,
}
impl Default for TearingSurfaceRequirements {
fn default() -> Self {
Self {
tearing_requested: true,
}
}
}
impl TearingMode {

View file

@ -34,6 +34,7 @@ use {
array_to_tuple::ArrayToTuple,
clonecell::CloneCell,
copyhashmap::CopyHashMap,
event_listener::LazyEventSource,
hash_map_ext::HashMapExt,
numcell::NumCell,
rc_eq::rc_eq,
@ -48,7 +49,7 @@ use {
jay_config::{window, window::WindowType},
std::{
borrow::Borrow,
cell::{Cell, RefCell},
cell::{Cell, OnceCell, RefCell},
ops::Deref,
rc::{Rc, Weak},
},
@ -404,6 +405,7 @@ pub struct ToplevelData {
pub just_mapped_scheduled: Cell<bool>,
pub seat_foci: CopyHashMap<SeatId, ()>,
pub content_type: Cell<Option<ContentType>>,
pub property_changed_source: OnceCell<Rc<LazyEventSource>>,
}
impl ToplevelData {
@ -457,6 +459,7 @@ impl ToplevelData {
just_mapped_scheduled: Cell::new(false),
seat_foci: Default::default(),
content_type: Default::default(),
property_changed_source: Default::default(),
}
}
@ -497,7 +500,14 @@ impl ToplevelData {
(width, height)
}
fn trigger_property_source(&self) {
if let Some(source) = self.property_changed_source.get() {
source.trigger();
}
}
pub fn property_changed(&self, change: TlMatcherChange) {
self.trigger_property_source();
let mgr = &self.state.tl_matcher_manager;
let props = self.changed_properties.get();
if props.is_none() && mgr.has_no_interest(self, change) {
@ -925,6 +935,12 @@ impl ToplevelData {
};
parent.node_is_workspace()
}
#[expect(dead_code)]
pub fn property_changed_source(&self) -> &Rc<LazyEventSource> {
self.property_changed_source
.get_or_init(|| self.state.lazy_event_sources.create_source())
}
}
impl Drop for ToplevelData {

View file

@ -2,6 +2,7 @@ pub mod activation_token;
pub mod array;
pub mod array_to_tuple;
pub mod asyncevent;
pub mod atomic_enum;
pub mod binary_search_map;
pub mod bindings;
pub mod bitfield;
@ -53,6 +54,7 @@ pub mod run_toplevel;
pub mod scroller;
pub mod smallmap;
pub mod stack;
pub mod static_text;
pub mod string_ext;
pub mod syncqueue;
pub mod threshold_counter;

41
src/utils/atomic_enum.rs Normal file
View file

@ -0,0 +1,41 @@
use {
linearize::Linearize,
std::{
marker::PhantomData,
sync::atomic::{AtomicUsize, Ordering},
},
};
pub struct AtomicEnum<T> {
v: AtomicUsize,
_phantom: PhantomData<T>,
}
impl<T> Default for AtomicEnum<T>
where
T: Default + Linearize + Copy,
{
fn default() -> Self {
Self::new(T::default())
}
}
impl<T> AtomicEnum<T>
where
T: Linearize + Copy,
{
pub fn new(t: T) -> Self {
Self {
v: AtomicUsize::new(t.linearize()),
_phantom: Default::default(),
}
}
pub fn load(&self, ordering: Ordering) -> T {
unsafe { T::from_linear_unchecked(self.v.load(ordering)) }
}
pub fn store(&self, t: T, ordering: Ordering) {
self.v.store(t.linearize(), ordering);
}
}

View file

@ -1,11 +1,28 @@
use {
crate::utils::linkedlist::{LinkedList, LinkedListIter, LinkedNode},
crate::{
state::State,
utils::{
linkedlist::{LinkedList, LinkedListIter, LinkedNode},
queue::AsyncQueue,
},
},
std::{
cell::Cell,
ops::Deref,
rc::{Rc, Weak},
},
};
pub async fn handle_lazy_event_sources(state: Rc<State>) {
loop {
let source = state.lazy_event_sources.queue.pop().await;
source.queued.set(false);
for listener in source.listeners.iter() {
listener.triggered();
}
}
}
pub struct EventSource<T: ?Sized> {
listeners: LinkedList<Weak<T>>,
on_attach: Cell<Option<Box<dyn FnOnce()>>>,
@ -15,6 +32,21 @@ pub struct EventListener<T: ?Sized> {
link: LinkedNode<Weak<T>>,
}
#[derive(Default)]
pub struct LazyEventSources {
queue: AsyncQueue<Rc<LazyEventSource>>,
}
pub trait LazyEventSourceListener {
fn triggered(self: Rc<Self>);
}
pub struct LazyEventSource {
sources: Rc<LazyEventSources>,
queued: Cell<bool>,
listeners: EventSource<dyn LazyEventSourceListener>,
}
impl<T: ?Sized> Default for EventSource<T> {
fn default() -> Self {
Self {
@ -39,6 +71,10 @@ impl<T: ?Sized> EventSource<T> {
self.listeners.is_not_empty()
}
pub fn is_empty(&self) -> bool {
self.listeners.is_empty()
}
pub fn on_attach(&self, f: Box<dyn FnOnce()>) {
self.on_attach.set(Some(f));
}
@ -83,3 +119,37 @@ impl<T: ?Sized> EventListener<T> {
self.link.upgrade()
}
}
impl Deref for LazyEventSource {
type Target = EventSource<dyn LazyEventSourceListener>;
fn deref(&self) -> &Self::Target {
&self.listeners
}
}
impl LazyEventSource {
pub fn trigger(self: &Rc<Self>) {
if self.listeners.is_empty() {
return;
}
if self.queued.replace(true) {
return;
}
self.sources.queue.push(self.clone());
}
}
impl LazyEventSources {
pub fn create_source(self: &Rc<Self>) -> Rc<LazyEventSource> {
Rc::new(LazyEventSource {
sources: self.clone(),
queued: Default::default(),
listeners: Default::default(),
})
}
pub fn clear(&self) {
self.queue.clear();
}
}

3
src/utils/static_text.rs Normal file
View file

@ -0,0 +1,3 @@
pub trait StaticText {
fn text(&self) -> &'static str;
}

View file

@ -32,6 +32,7 @@ use {
pub mod device;
pub mod fence;
pub mod gpu_alloc_ash;
pub mod sync;
pub mod timeline_semaphore;

View file

@ -40,7 +40,7 @@ use {
rc::Rc,
},
thiserror::Error,
uapi::c,
uapi::{OwnedFd, c},
};
#[derive(Debug, Error)]
@ -119,6 +119,24 @@ impl UsrCon {
if let Err(e) = ring.connect(&socket, &addr).await {
return Err(UsrConError::Connect(e));
}
Ok(Self::from_socket(
ring,
wheel,
eng,
dma_buf_ids,
&socket,
server_id,
))
}
pub fn from_socket(
ring: &Rc<IoUring>,
wheel: &Rc<Wheel>,
eng: &Rc<AsyncEngine>,
dma_buf_ids: &Rc<DmaBufIds>,
socket: &Rc<OwnedFd>,
server_id: u32,
) -> Rc<Self> {
let mut obj_ids = Bitfield::default();
obj_ids.take(0);
obj_ids.take(1);
@ -150,7 +168,7 @@ impl UsrCon {
"wl_usr incoming",
Incoming {
con: slf.clone(),
buf: BufFdIn::new(&socket, &slf.ring),
buf: BufFdIn::new(socket, &slf.ring),
data: vec![],
}
.run(),
@ -161,13 +179,13 @@ impl UsrCon {
"wl_usr outgoing",
Outgoing {
con: slf.clone(),
buf: BufFdOut::new(&socket, &slf.ring),
buf: BufFdOut::new(socket, &slf.ring),
buffers: Default::default(),
}
.run(),
),
));
Ok(slf)
slf
}
pub fn kill(&self) {
@ -224,7 +242,8 @@ impl UsrCon {
where
F: FnOnce() + 'static,
{
let callback = Rc::new(UsrWlCallback::new(self, handler));
let callback = Rc::new(UsrWlCallback::new(self));
callback.owner.set(Some(Rc::new(Cell::new(Some(handler)))));
self.request(wl_display::Sync {
self_id: WL_DISPLAY_ID,
callback: callback.id,

View file

@ -15,7 +15,12 @@ pub mod usr_linux_dmabuf;
pub mod usr_wl_buffer;
pub mod usr_wl_callback;
pub mod usr_wl_compositor;
pub mod usr_wl_data_device;
pub mod usr_wl_data_device_manager;
pub mod usr_wl_data_offer;
pub mod usr_wl_data_source;
pub mod usr_wl_display;
pub mod usr_wl_keyboard;
pub mod usr_wl_output;
pub mod usr_wl_pointer;
pub mod usr_wl_registry;
@ -25,9 +30,17 @@ pub mod usr_wl_shm_pool;
pub mod usr_wl_surface;
pub mod usr_wlr_layer_shell;
pub mod usr_wlr_layer_surface;
pub mod usr_wp_cursor_shape_device_v1;
pub mod usr_wp_cursor_shape_manager_v1;
pub mod usr_wp_fractional_scale;
pub mod usr_wp_fractional_scale_manager;
pub mod usr_wp_viewport;
pub mod usr_wp_viewporter;
pub mod usr_xdg_surface;
pub mod usr_xdg_toplevel;
pub mod usr_xdg_wm_base;
pub mod usr_zwlr_screencopy_frame;
pub mod usr_zwlr_screencopy_manager;
pub mod usr_zwp_linux_buffer_params_v1;
pub mod usr_zwp_linux_dmabuf_v1;
pub mod usr_zwp_primary_selection_device_manager;

View file

@ -1,5 +1,6 @@
use {
crate::{
globals::GlobalName,
object::Version,
utils::clonecell::CloneCell,
wire::{JaySelectWorkspaceId, jay_select_workspace::*},
@ -9,7 +10,7 @@ use {
usr_object::UsrObject,
},
},
std::{convert::Infallible, rc::Rc},
std::{cell::Cell, convert::Infallible, rc::Rc},
};
pub struct UsrJaySelectWorkspace {
@ -20,7 +21,7 @@ pub struct UsrJaySelectWorkspace {
}
pub trait UsrJaySelectWorkspaceOwner {
fn done(&self, output: u32, ws: Option<Rc<UsrJayWorkspace>>);
fn done(&self, output: GlobalName, ws: Option<Rc<UsrJayWorkspace>>);
}
impl JaySelectWorkspaceEventHandler for UsrJaySelectWorkspace {
@ -28,7 +29,7 @@ impl JaySelectWorkspaceEventHandler for UsrJaySelectWorkspace {
fn cancelled(&self, _ev: Cancelled, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.done(0, None);
owner.done(GlobalName::from_raw(0), None);
}
self.con.remove_obj(self);
Ok(())
@ -41,7 +42,7 @@ impl JaySelectWorkspaceEventHandler for UsrJaySelectWorkspace {
owner: Default::default(),
version: self.version,
linear_id: Default::default(),
output: Default::default(),
output: Cell::new(GlobalName::from_raw(0)),
name: Default::default(),
});
self.con.add_object(tl.clone());

View file

@ -1,5 +1,6 @@
use {
crate::{
globals::GlobalName,
object::Version,
utils::clonecell::CloneCell,
wire::{JayWorkspaceId, jay_workspace::*},
@ -18,7 +19,7 @@ pub struct UsrJayWorkspace {
pub owner: CloneCell<Option<Rc<dyn UsrJayWorkspaceOwner>>>,
pub version: Version,
pub linear_id: Cell<u32>,
pub output: Cell<u32>,
pub output: Cell<GlobalName>,
pub name: RefCell<Option<String>>,
}
@ -68,7 +69,7 @@ impl JayWorkspaceEventHandler for UsrJayWorkspace {
}
fn output(&self, ev: Output, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.output.set(ev.global_name);
self.output.set(GlobalName::from_raw(ev.global_name));
if let Some(owner) = self.owner.get() {
owner.output(&ev);
}

View file

@ -1,11 +1,12 @@
use {
crate::{
globals::GlobalName,
object::Version,
utils::clonecell::CloneCell,
wire::{JayWorkspaceWatcherId, jay_workspace_watcher::*},
wl_usr::{UsrCon, usr_ifs::usr_jay_workspace::UsrJayWorkspace, usr_object::UsrObject},
},
std::{convert::Infallible, ops::Deref, rc::Rc},
std::{cell::Cell, convert::Infallible, ops::Deref, rc::Rc},
};
pub struct UsrJayWorkspaceWatcher {
@ -32,7 +33,7 @@ impl JayWorkspaceWatcherEventHandler for UsrJayWorkspaceWatcher {
owner: Default::default(),
version: self.version,
linear_id: Default::default(),
output: Default::default(),
output: Cell::new(GlobalName::from_raw(0)),
name: Default::default(),
});
self.con.add_object(jw.clone());

View file

@ -10,19 +10,31 @@ use {
pub struct UsrWlCallback {
pub id: WlCallbackId,
pub con: Rc<UsrCon>,
pub handler: Cell<Option<Box<dyn FnOnce()>>>,
pub owner: Cell<Option<Rc<dyn UsrWlCallbackOwner>>>,
pub version: Version,
}
pub trait UsrWlCallbackOwner {
fn done(self: Rc<Self>);
}
impl<T> UsrWlCallbackOwner for Cell<Option<T>>
where
T: FnOnce() + 'static,
{
fn done(self: Rc<Self>) {
if let Some(slf) = self.take() {
slf();
}
}
}
impl UsrWlCallback {
pub fn new<F>(con: &Rc<UsrCon>, handler: F) -> Self
where
F: FnOnce() + 'static,
{
pub fn new(con: &Rc<UsrCon>) -> Self {
Self {
id: con.id(),
con: con.clone(),
handler: Cell::new(Some(Box::new(handler))),
owner: Default::default(),
version: Version(1),
}
}
@ -32,8 +44,8 @@ impl WlCallbackEventHandler for UsrWlCallback {
type Error = Infallible;
fn done(&self, _ev: Done, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(handler) = self.handler.take() {
handler();
if let Some(handler) = self.owner.take() {
handler.done();
}
self.con.remove_obj(self);
Ok(())
@ -51,6 +63,6 @@ impl UsrObject for UsrWlCallback {
}
fn break_loops(&self) {
self.handler.take();
self.owner.take();
}
}

View file

@ -0,0 +1,99 @@
use {
crate::{
object::Version,
utils::clonecell::CloneCell,
wire::{WlDataDeviceId, wl_data_device::*},
wl_usr::{
UsrCon,
usr_ifs::{usr_wl_data_offer::UsrWlDataOffer, usr_wl_data_source::UsrWlDataSource},
usr_object::UsrObject,
},
},
std::{convert::Infallible, rc::Rc},
};
pub struct UsrWlDataDevice {
pub id: WlDataDeviceId,
pub con: Rc<UsrCon>,
pub version: Version,
pub offer: CloneCell<Option<Rc<UsrWlDataOffer>>>,
pub selection: CloneCell<Option<Rc<UsrWlDataOffer>>>,
}
impl UsrWlDataDevice {
#[expect(dead_code)]
pub fn set_selection(&self, serial: u32, source: &UsrWlDataSource) {
self.con.request(SetSelection {
self_id: self.id,
source: source.id,
serial,
});
}
}
impl WlDataDeviceEventHandler for UsrWlDataDevice {
type Error = Infallible;
fn data_offer(&self, ev: DataOffer, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let obj = Rc::new(UsrWlDataOffer {
id: ev.id,
con: self.con.clone(),
version: self.version,
mime_types: Default::default(),
});
self.con.add_object(obj.clone());
if let Some(offer) = self.offer.set(Some(obj)) {
self.con.remove_obj(&*offer);
}
Ok(())
}
fn enter(&self, ev: Enter, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let _ = ev;
Ok(())
}
fn leave(&self, ev: Leave, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let _ = ev;
Ok(())
}
fn motion(&self, ev: Motion, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let _ = ev;
Ok(())
}
fn drop_(&self, ev: Drop, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let _ = ev;
Ok(())
}
fn selection(&self, ev: Selection, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.selection.take();
if let Some(offer) = self.offer.get()
&& offer.id == ev.id
{
self.selection.set(Some(offer));
}
Ok(())
}
}
usr_object_base! {
self = UsrWlDataDevice = WlDataDevice;
version = self.version;
}
impl UsrObject for UsrWlDataDevice {
fn destroy(&self) {
if let Some(offer) = self.offer.take() {
self.con.remove_obj(&*offer);
}
self.con.request(Release { self_id: self.id });
}
fn break_loops(&self) {
self.selection.take();
self.offer.take();
}
}

View file

@ -0,0 +1,72 @@
use {
crate::{
object::Version,
wire::{WlDataDeviceManagerId, wl_data_device_manager::*},
wl_usr::{
UsrCon,
usr_ifs::{
usr_wl_data_device::UsrWlDataDevice, usr_wl_data_source::UsrWlDataSource,
usr_wl_seat::UsrWlSeat,
},
usr_object::UsrObject,
},
},
std::{convert::Infallible, rc::Rc},
};
pub struct UsrWlDataDeviceManager {
pub id: WlDataDeviceManagerId,
pub con: Rc<UsrCon>,
pub version: Version,
}
impl UsrWlDataDeviceManager {
#[expect(dead_code)]
pub fn create_data_source(&self) -> Rc<UsrWlDataSource> {
let obj = Rc::new(UsrWlDataSource {
id: self.con.id(),
con: self.con.clone(),
owner: Default::default(),
version: self.version,
});
self.con.request(CreateDataSource {
self_id: self.id,
id: obj.id,
});
self.con.add_object(obj.clone());
obj
}
#[expect(dead_code)]
pub fn get_data_device(&self, seat: &UsrWlSeat) -> Rc<UsrWlDataDevice> {
let obj = Rc::new(UsrWlDataDevice {
id: self.con.id(),
con: self.con.clone(),
version: self.version,
offer: Default::default(),
selection: Default::default(),
});
self.con.request(GetDataDevice {
self_id: self.id,
id: obj.id,
seat: seat.id,
});
self.con.add_object(obj.clone());
obj
}
}
impl WlDataDeviceManagerEventHandler for UsrWlDataDeviceManager {
type Error = Infallible;
}
usr_object_base! {
self = UsrWlDataDeviceManager = WlDataDeviceManager;
version = self.version;
}
impl UsrObject for UsrWlDataDeviceManager {
fn destroy(&self) {
self.con.request(Release { self_id: self.id });
}
}

View file

@ -0,0 +1,58 @@
use {
crate::{
object::Version,
wire::{WlDataOfferId, wl_data_offer::*},
wl_usr::{UsrCon, usr_object::UsrObject},
},
ahash::AHashSet,
std::{cell::RefCell, convert::Infallible, rc::Rc},
uapi::OwnedFd,
};
pub struct UsrWlDataOffer {
pub id: WlDataOfferId,
pub con: Rc<UsrCon>,
pub version: Version,
pub mime_types: RefCell<AHashSet<String>>,
}
impl UsrWlDataOffer {
#[expect(dead_code)]
pub fn receive(&self, mime_type: &str, fd: &Rc<OwnedFd>) {
self.con.request(Receive {
self_id: self.id,
mime_type,
fd: fd.clone(),
});
}
}
impl WlDataOfferEventHandler for UsrWlDataOffer {
type Error = Infallible;
fn offer(&self, ev: Offer<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.mime_types
.borrow_mut()
.insert(ev.mime_type.to_string());
Ok(())
}
fn source_actions(&self, _ev: SourceActions, _slf: &Rc<Self>) -> Result<(), Self::Error> {
Ok(())
}
fn action(&self, _ev: Action, _slf: &Rc<Self>) -> Result<(), Self::Error> {
Ok(())
}
}
usr_object_base! {
self = UsrWlDataOffer = WlDataOffer;
version = self.version;
}
impl UsrObject for UsrWlDataOffer {
fn destroy(&self) {
self.con.request(Destroy { self_id: self.id });
}
}

View file

@ -0,0 +1,82 @@
use {
crate::{
object::Version,
utils::clonecell::CloneCell,
wire::{WlDataSourceId, wl_data_source::*},
wl_usr::{UsrCon, usr_object::UsrObject},
},
std::{convert::Infallible, rc::Rc},
uapi::OwnedFd,
};
pub struct UsrWlDataSource {
pub id: WlDataSourceId,
pub con: Rc<UsrCon>,
pub owner: CloneCell<Option<Rc<dyn UsrWlDataSourceOwner>>>,
pub version: Version,
}
pub trait UsrWlDataSourceOwner {
fn send(&self, mime_type: &str, fd: Rc<OwnedFd>);
}
impl UsrWlDataSource {
#[expect(dead_code)]
pub fn offer(&self, mime_type: &str) {
self.con.request(Offer {
self_id: self.id,
mime_type,
});
}
}
impl WlDataSourceEventHandler for UsrWlDataSource {
type Error = Infallible;
fn target(&self, ev: Target<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let _ = ev;
Ok(())
}
fn send(&self, ev: Send<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.send(ev.mime_type, ev.fd);
}
Ok(())
}
fn cancelled(&self, ev: Cancelled, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let _ = ev;
Ok(())
}
fn dnd_drop_performed(&self, ev: DndDropPerformed, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let _ = ev;
Ok(())
}
fn dnd_finished(&self, ev: DndFinished, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let _ = ev;
Ok(())
}
fn action(&self, ev: Action, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let _ = ev;
Ok(())
}
}
usr_object_base! {
self = UsrWlDataSource = WlDataSource;
version = self.version;
}
impl UsrObject for UsrWlDataSource {
fn destroy(&self) {
self.con.request(Destroy { self_id: self.id });
}
fn break_loops(&self) {
self.owner.take();
}
}

View file

@ -0,0 +1,148 @@
use {
crate::{
ifs::wl_seat::wl_keyboard,
object::Version,
utils::{clonecell::CloneCell, mmap::mmap, oserror::OsError},
wire::{WlKeyboardId, WlSurfaceId, wl_keyboard::*},
wl_usr::{UsrCon, usr_object::UsrObject},
},
kbvm::{
Components, Keycode, ModifierMask,
lookup::{Lookup, LookupTable},
xkb::{
Context,
diagnostic::{Diagnostic, WriteToLog},
},
},
std::{cell::RefCell, rc::Rc},
thiserror::Error,
uapi::c,
};
pub struct UsrWlKeyboard {
pub id: WlKeyboardId,
pub con: Rc<UsrCon>,
pub keyboard: RefCell<Option<Keyboard>>,
pub owner: CloneCell<Option<Rc<dyn UsrWlKeyboardOwner>>>,
pub version: Version,
}
pub struct Keyboard {
lookup: LookupTable,
components: Components,
}
pub trait UsrWlKeyboardOwner {
fn focus(self: Rc<Self>, surface: WlSurfaceId, serial: u32);
fn unfocus(self: Rc<Self>);
fn modifiers(self: Rc<Self>, mods: ModifierMask);
fn down(self: Rc<Self>, lookup: Lookup<'_>, serial: u32);
fn repeat(self: Rc<Self>, lookup: Lookup<'_>, serial: u32);
fn up(self: Rc<Self>, lookup: Lookup<'_>, serial: u32);
}
impl WlKeyboardEventHandler for UsrWlKeyboard {
type Error = UsrWlKeyboardError;
fn keymap(&self, ev: Keymap, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let map = mmap(ev.size as _, c::PROT_READ, c::MAP_PRIVATE, ev.fd.raw(), 0)
.map_err(UsrWlKeyboardError::MapKeymap)?;
let mut builder = Context::builder();
builder.enable_default_includes(false);
builder.enable_environment(false);
let keymap = builder
.build()
.keymap_from_bytes(WriteToLog, None, unsafe { &*map.ptr })
.map_err(UsrWlKeyboardError::ParseKeymap)?;
let lookup = keymap.to_builder().build_lookup_table();
let keyboard = Keyboard {
lookup,
components: Default::default(),
};
self.keyboard.replace(Some(keyboard));
Ok(())
}
fn enter(&self, ev: Enter<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let Some(owner) = self.owner.get() else {
return Ok(());
};
owner.focus(ev.surface, ev.serial);
Ok(())
}
fn leave(&self, _ev: Leave, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let Some(owner) = self.owner.get() else {
return Ok(());
};
owner.unfocus();
Ok(())
}
fn key(&self, ev: Key, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let Some(kb) = &*self.keyboard.borrow() else {
return Ok(());
};
let Some(owner) = self.owner.get() else {
return Ok(());
};
let kc = Keycode::from_evdev(ev.key);
let lookup = kb
.lookup
.lookup(kb.components.group, kb.components.mods, kc);
if ev.state == wl_keyboard::PRESSED {
owner.down(lookup, ev.serial);
} else if ev.state == wl_keyboard::REPEATED {
owner.repeat(lookup, ev.serial);
} else if ev.state == wl_keyboard::RELEASED {
owner.up(lookup, ev.serial);
}
Ok(())
}
fn modifiers(&self, ev: Modifiers, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let Some(kb) = &mut *self.keyboard.borrow_mut() else {
return Ok(());
};
kb.components.mods_pressed.0 = ev.mods_depressed;
kb.components.mods_latched.0 = ev.mods_latched;
kb.components.mods_locked.0 = ev.mods_locked;
kb.components.group_locked.0 = ev.group;
let old = kb.components.mods;
kb.components.update_effective();
let new = kb.components.mods;
if old != new
&& let Some(owner) = self.owner.get()
{
owner.modifiers(new);
}
Ok(())
}
fn repeat_info(&self, _ev: RepeatInfo, _slf: &Rc<Self>) -> Result<(), Self::Error> {
Ok(())
}
}
usr_object_base! {
self = UsrWlKeyboard = WlKeyboard;
version = self.version;
}
impl UsrObject for UsrWlKeyboard {
fn destroy(&self) {
self.con.request(Release { self_id: self.id });
}
fn break_loops(&self) {
self.owner.take();
}
}
#[derive(Debug, Error)]
pub enum UsrWlKeyboardError {
#[error("Could not map the keymap")]
MapKeymap(#[source] OsError),
#[error("Could not parse the keymap")]
ParseKeymap(#[source] Diagnostic),
}

View file

@ -3,7 +3,7 @@ use {
ifs::wl_seat::wl_pointer::PendingScroll,
object::Version,
utils::clonecell::CloneCell,
wire::{WlPointerId, wl_pointer::*},
wire::{WlPointerId, WlSurfaceId, wl_pointer::*},
wl_usr::{UsrCon, usr_ifs::usr_wl_surface::UsrWlSurface, usr_object::UsrObject},
},
std::{cell::Cell, convert::Infallible, rc::Rc},
@ -19,34 +19,34 @@ pub struct UsrWlPointer {
}
pub trait UsrWlPointerOwner {
fn enter(&self, ev: &Enter) {
fn enter(self: Rc<Self>, ev: &Enter) {
let _ = ev;
}
fn leave(&self, ev: &Leave) {
fn leave(self: Rc<Self>, ev: &Leave) {
let _ = ev;
}
fn motion(&self, ev: &Motion) {
fn motion(self: Rc<Self>, ev: &Motion) {
let _ = ev;
}
fn button(&self, ev: &Button) {
fn button(self: Rc<Self>, ev: &Button) {
let _ = ev;
}
fn scroll(&self, ps: &PendingScroll) {
fn scroll(self: Rc<Self>, ps: &PendingScroll) {
let _ = ps;
}
}
impl UsrWlPointer {
#[expect(dead_code)]
pub fn set_cursor(&self, serial: u32, cursor: &UsrWlSurface, hot_x: i32, hot_y: i32) {
pub fn set_cursor(&self, serial: u32, cursor: Option<&UsrWlSurface>, hot_x: i32, hot_y: i32) {
self.con.request(SetCursor {
self_id: self.id,
serial,
surface: cursor.id,
surface: cursor.map(|c| c.id).unwrap_or(WlSurfaceId::NONE),
hotspot_x: hot_x,
hotspot_y: hot_y,
});

View file

@ -1,5 +1,6 @@
use {
crate::{
globals::GlobalName,
object::Version,
utils::clonecell::CloneCell,
wire::{WlRegistryId, wl_registry::*},
@ -16,24 +17,24 @@ pub struct UsrWlRegistry {
}
pub trait UsrWlRegistryOwner {
fn global(self: Rc<Self>, name: u32, interface: &str, version: u32) {
fn global(self: Rc<Self>, name: GlobalName, interface: &str, version: u32) {
let _ = name;
let _ = interface;
let _ = version;
}
fn global_remove(&self, name: u32) {
fn global_remove(&self, name: GlobalName) {
let _ = name;
}
}
impl UsrWlRegistry {
pub fn request_bind(&self, name: u32, version: u32, obj: &dyn UsrObject) {
pub fn bind(&self, name: GlobalName, obj: &dyn UsrObject) {
self.con.request(Bind {
self_id: self.id,
name,
name: name.raw(),
interface: obj.interface().name(),
version,
version: obj.version().0,
id: obj.id(),
});
}
@ -44,14 +45,14 @@ impl WlRegistryEventHandler for UsrWlRegistry {
fn global(&self, ev: Global<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.global(ev.name, ev.interface, ev.version);
owner.global(GlobalName::from_raw(ev.name), ev.interface, ev.version);
}
Ok(())
}
fn global_remove(&self, ev: GlobalRemove, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.global_remove(ev.name);
owner.global_remove(GlobalName::from_raw(ev.name));
}
Ok(())
}

View file

@ -3,7 +3,11 @@ use {
object::Version,
utils::clonecell::CloneCell,
wire::{WlSeatId, wl_seat::*},
wl_usr::{UsrCon, usr_ifs::usr_wl_pointer::UsrWlPointer, usr_object::UsrObject},
wl_usr::{
UsrCon,
usr_ifs::{usr_wl_keyboard::UsrWlKeyboard, usr_wl_pointer::UsrWlPointer},
usr_object::UsrObject,
},
},
std::{cell::Cell, convert::Infallible, rc::Rc},
};
@ -42,6 +46,23 @@ impl UsrWlSeat {
});
ptr
}
#[expect(dead_code)]
pub fn get_keyboard(&self) -> Rc<UsrWlKeyboard> {
let kb = Rc::new(UsrWlKeyboard {
id: self.con.id(),
con: self.con.clone(),
keyboard: Default::default(),
owner: Default::default(),
version: self.version,
});
self.con.add_object(kb.clone());
self.con.request(GetKeyboard {
self_id: self.id,
id: kb.id,
});
kb
}
}
impl WlSeatEventHandler for UsrWlSeat {

View file

@ -37,16 +37,14 @@ impl UsrWlSurface {
});
}
pub fn frame<F>(&self, f: F)
where
F: FnOnce() + 'static,
{
let cb = Rc::new(UsrWlCallback::new(&self.con, f));
pub fn frame(&self) -> Rc<UsrWlCallback> {
let cb = Rc::new(UsrWlCallback::new(&self.con));
self.con.request(Frame {
self_id: self.id,
callback: cb.id,
});
self.con.add_object(cb);
self.con.add_object(cb.clone());
cb
}
pub fn commit(&self) {

View file

@ -0,0 +1,41 @@
use {
crate::{
cursor::KnownCursor,
object::Version,
wire::{WpCursorShapeDeviceV1Id, wp_cursor_shape_device_v1::*},
wl_usr::{UsrCon, usr_object::UsrObject},
},
std::{convert::Infallible, rc::Rc},
};
pub struct UsrWpCursorShapeDeviceV1 {
pub id: WpCursorShapeDeviceV1Id,
pub con: Rc<UsrCon>,
pub version: Version,
}
impl UsrWpCursorShapeDeviceV1 {
#[expect(dead_code)]
pub fn set_shape(&self, serial: u32, cursor: KnownCursor) {
self.con.request(SetShape {
self_id: self.id,
serial,
shape: cursor.to_shape(),
});
}
}
impl WpCursorShapeDeviceV1EventHandler for UsrWpCursorShapeDeviceV1 {
type Error = Infallible;
}
usr_object_base! {
self = UsrWpCursorShapeDeviceV1 = WpCursorShapeDeviceV1;
version = self.version;
}
impl UsrObject for UsrWpCursorShapeDeviceV1 {
fn destroy(&self) {
self.con.request(Destroy { self_id: self.id })
}
}

View file

@ -0,0 +1,54 @@
use {
crate::{
object::Version,
wire::{WpCursorShapeManagerV1Id, wp_cursor_shape_manager_v1::*},
wl_usr::{
UsrCon,
usr_ifs::{
usr_wl_pointer::UsrWlPointer,
usr_wp_cursor_shape_device_v1::UsrWpCursorShapeDeviceV1,
},
usr_object::UsrObject,
},
},
std::{convert::Infallible, rc::Rc},
};
pub struct UsrWpCursorShapeManagerV1 {
pub id: WpCursorShapeManagerV1Id,
pub con: Rc<UsrCon>,
pub version: Version,
}
impl UsrWpCursorShapeManagerV1 {
#[expect(dead_code)]
pub fn get_pointer(&self, pointer: &UsrWlPointer) -> Rc<UsrWpCursorShapeDeviceV1> {
let obj = Rc::new(UsrWpCursorShapeDeviceV1 {
id: self.con.id(),
con: self.con.clone(),
version: self.version,
});
self.con.request(GetPointer {
self_id: self.id,
cursor_shape_device: obj.id,
pointer: pointer.id,
});
self.con.add_object(obj.clone());
obj
}
}
impl WpCursorShapeManagerV1EventHandler for UsrWpCursorShapeManagerV1 {
type Error = Infallible;
}
usr_object_base! {
self = UsrWpCursorShapeManagerV1 = WpCursorShapeManagerV1;
version = self.version;
}
impl UsrObject for UsrWpCursorShapeManagerV1 {
fn destroy(&self) {
self.con.request(Destroy { self_id: self.id })
}
}

View file

@ -0,0 +1,70 @@
use {
crate::{
object::Version,
utils::clonecell::CloneCell,
wire::{XdgSurfaceId, xdg_surface::*},
wl_usr::{UsrCon, usr_ifs::usr_xdg_toplevel::UsrXdgToplevel, usr_object::UsrObject},
},
std::{convert::Infallible, rc::Rc},
};
pub struct UsrXdgSurface {
pub id: XdgSurfaceId,
pub con: Rc<UsrCon>,
pub owner: CloneCell<Option<Rc<dyn UsrXdgSurfaceOwner>>>,
pub version: Version,
}
pub trait UsrXdgSurfaceOwner {
fn configure(&self) {
// nothing
}
}
impl UsrXdgSurface {
#[expect(dead_code)]
pub fn get_toplevel(&self) -> Rc<UsrXdgToplevel> {
let obj = Rc::new(UsrXdgToplevel {
id: self.con.id(),
con: self.con.clone(),
owner: Default::default(),
version: self.version,
});
self.con.request(GetToplevel {
self_id: self.id,
id: obj.id,
});
self.con.add_object(obj.clone());
obj
}
}
impl XdgSurfaceEventHandler for UsrXdgSurface {
type Error = Infallible;
fn configure(&self, ev: Configure, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.con.request(AckConfigure {
self_id: self.id,
serial: ev.serial,
});
if let Some(owner) = self.owner.get() {
owner.configure();
}
Ok(())
}
}
usr_object_base! {
self = UsrXdgSurface = XdgSurface;
version = self.version;
}
impl UsrObject for UsrXdgSurface {
fn destroy(&self) {
self.con.request(Destroy { self_id: self.id })
}
fn break_loops(&self) {
self.owner.take();
}
}

View file

@ -0,0 +1,95 @@
use {
crate::{
object::Version,
utils::clonecell::CloneCell,
wire::{WlOutputId, XdgToplevelId, xdg_toplevel::*},
wl_usr::{UsrCon, usr_object::UsrObject},
},
std::{convert::Infallible, rc::Rc},
};
pub struct UsrXdgToplevel {
pub id: XdgToplevelId,
pub con: Rc<UsrCon>,
pub owner: CloneCell<Option<Rc<dyn UsrXdgToplevelOwner>>>,
pub version: Version,
}
impl UsrXdgToplevel {
#[expect(dead_code)]
pub fn set_title(&self, title: &str) {
self.con.request(SetTitle {
self_id: self.id,
title,
});
}
#[expect(dead_code)]
pub fn set_fullscreen(&self, fullscreen: bool) {
match fullscreen {
true => {
self.con.request(SetFullscreen {
self_id: self.id,
output: WlOutputId::NONE,
});
}
false => {
self.con.request(UnsetFullscreen { self_id: self.id });
}
}
}
}
pub trait UsrXdgToplevelOwner {
fn configure(&self, width: i32, height: i32) {
let _ = width;
let _ = height;
}
fn close(&self) {
// nothing
}
}
impl UsrXdgToplevel {}
impl XdgToplevelEventHandler for UsrXdgToplevel {
type Error = Infallible;
fn configure(&self, ev: Configure<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.configure(ev.width, ev.height);
}
Ok(())
}
fn close(&self, _ev: Close, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(owner) = self.owner.get() {
owner.close();
}
Ok(())
}
fn configure_bounds(&self, _ev: ConfigureBounds, _slf: &Rc<Self>) -> Result<(), Self::Error> {
Ok(())
}
fn wm_capabilities(&self, _ev: WmCapabilities<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
Ok(())
}
}
usr_object_base! {
self = UsrXdgToplevel = XdgToplevel;
version = self.version;
}
impl UsrObject for UsrXdgToplevel {
fn destroy(&self) {
self.con.request(Destroy { self_id: self.id })
}
fn break_loops(&self) {
self.owner.take();
}
}

View file

@ -0,0 +1,60 @@
use {
crate::{
object::Version,
wire::{XdgWmBaseId, xdg_wm_base::*},
wl_usr::{
UsrCon,
usr_ifs::{usr_wl_surface::UsrWlSurface, usr_xdg_surface::UsrXdgSurface},
usr_object::UsrObject,
},
},
std::{convert::Infallible, rc::Rc},
};
pub struct UsrXdgWmBase {
pub id: XdgWmBaseId,
pub con: Rc<UsrCon>,
pub version: Version,
}
impl UsrXdgWmBase {
#[expect(dead_code)]
pub fn get_xdg_surface(&self, surface: &UsrWlSurface) -> Rc<UsrXdgSurface> {
let obj = Rc::new(UsrXdgSurface {
id: self.con.id(),
con: self.con.clone(),
owner: Default::default(),
version: self.version,
});
self.con.request(GetXdgSurface {
self_id: self.id,
id: obj.id,
surface: surface.id,
});
self.con.add_object(obj.clone());
obj
}
}
impl XdgWmBaseEventHandler for UsrXdgWmBase {
type Error = Infallible;
fn ping(&self, ev: Ping, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.con.request(Pong {
self_id: self.id,
serial: ev.serial,
});
Ok(())
}
}
usr_object_base! {
self = UsrXdgWmBase = XdgWmBase;
version = self.version;
}
impl UsrObject for UsrXdgWmBase {
fn destroy(&self) {
self.con.request(Destroy { self_id: self.id })
}
}

View file

@ -0,0 +1,79 @@
use {
crate::{
format::Format,
object::Version,
video::Modifier,
wire::{ZwpLinuxBufferParamsV1Id, zwp_linux_buffer_params_v1::*},
wl_usr::{UsrCon, usr_ifs::usr_wl_buffer::UsrWlBuffer, usr_object::UsrObject},
},
std::{convert::Infallible, rc::Rc},
uapi::OwnedFd,
};
pub struct UsrZwpLinuxBufferParamsV1 {
pub id: ZwpLinuxBufferParamsV1Id,
pub con: Rc<UsrCon>,
pub version: Version,
}
impl UsrZwpLinuxBufferParamsV1 {
pub fn add(
&self,
fd: &Rc<OwnedFd>,
plane_idx: usize,
offset: u32,
stride: u32,
modifier: Modifier,
) {
self.con.request(Add {
self_id: self.id,
fd: fd.clone(),
plane_idx: plane_idx as u32,
offset,
stride,
modifier,
});
}
pub fn create_immed(&self, width: i32, height: i32, format: &Format) -> Rc<UsrWlBuffer> {
let obj = Rc::new(UsrWlBuffer {
id: self.con.id(),
con: self.con.clone(),
owner: Default::default(),
version: self.version,
});
self.con.request(CreateImmed {
self_id: self.id,
buffer_id: obj.id,
width,
height,
format: format.drm,
flags: 0,
});
self.con.add_object(obj.clone());
obj
}
}
impl ZwpLinuxBufferParamsV1EventHandler for UsrZwpLinuxBufferParamsV1 {
type Error = Infallible;
fn created(&self, _ev: Created, _slf: &Rc<Self>) -> Result<(), Self::Error> {
Ok(())
}
fn failed(&self, _ev: Failed, _slf: &Rc<Self>) -> Result<(), Self::Error> {
Ok(())
}
}
usr_object_base! {
self = UsrZwpLinuxBufferParamsV1 = ZwpLinuxBufferParamsV1;
version = self.version;
}
impl UsrObject for UsrZwpLinuxBufferParamsV1 {
fn destroy(&self) {
self.con.request(Destroy { self_id: self.id })
}
}

View file

@ -0,0 +1,67 @@
use {
crate::{
object::Version,
video::dmabuf::DmaBuf,
wire::{ZwpLinuxDmabufV1Id, zwp_linux_dmabuf_v1::*},
wl_usr::{
UsrCon,
usr_ifs::{
usr_wl_buffer::UsrWlBuffer,
usr_zwp_linux_buffer_params_v1::UsrZwpLinuxBufferParamsV1,
},
usr_object::UsrObject,
},
},
std::{convert::Infallible, rc::Rc},
};
pub struct UsrZwpLinuxDmabufV1 {
pub id: ZwpLinuxDmabufV1Id,
pub con: Rc<UsrCon>,
pub version: Version,
}
impl UsrZwpLinuxDmabufV1 {
#[expect(dead_code)]
pub fn create_buffer(&self, buffer: &DmaBuf) -> Rc<UsrWlBuffer> {
let params = Rc::new(UsrZwpLinuxBufferParamsV1 {
id: self.con.id(),
con: self.con.clone(),
version: self.version,
});
self.con.request(CreateParams {
self_id: self.id,
params_id: params.id,
});
self.con.add_object(params.clone());
for (idx, plane) in buffer.planes.iter().enumerate() {
params.add(&plane.fd, idx, plane.offset, plane.stride, buffer.modifier);
}
let obj = params.create_immed(buffer.width, buffer.height, &buffer.format);
self.con.remove_obj(&*params);
obj
}
}
impl ZwpLinuxDmabufV1EventHandler for UsrZwpLinuxDmabufV1 {
type Error = Infallible;
fn format(&self, _ev: Format, _slf: &Rc<Self>) -> Result<(), Self::Error> {
Ok(())
}
fn modifier(&self, _ev: Modifier, _slf: &Rc<Self>) -> Result<(), Self::Error> {
Ok(())
}
}
usr_object_base! {
self = UsrZwpLinuxDmabufV1 = ZwpLinuxDmabufV1;
version = self.version;
}
impl UsrObject for UsrZwpLinuxDmabufV1 {
fn destroy(&self) {
self.con.request(Destroy { self_id: self.id })
}
}

View file

@ -0,0 +1,31 @@
use {
crate::{
object::Version,
wire::{ZwpPrimarySelectionDeviceManagerV1Id, zwp_primary_selection_device_manager_v1::*},
wl_usr::{UsrCon, usr_object::UsrObject},
},
std::{convert::Infallible, rc::Rc},
};
pub struct UsrZwpPrimarySelectionDeviceManagerV1 {
pub id: ZwpPrimarySelectionDeviceManagerV1Id,
pub con: Rc<UsrCon>,
pub version: Version,
}
impl UsrZwpPrimarySelectionDeviceManagerV1 {}
impl ZwpPrimarySelectionDeviceManagerV1EventHandler for UsrZwpPrimarySelectionDeviceManagerV1 {
type Error = Infallible;
}
usr_object_base! {
self = UsrZwpPrimarySelectionDeviceManagerV1 = ZwpPrimarySelectionDeviceManagerV1;
version = self.version;
}
impl UsrObject for UsrZwpPrimarySelectionDeviceManagerV1 {
fn destroy(&self) {
self.con.request(Destroy { self_id: self.id })
}
}

View file

@ -114,9 +114,13 @@ pub async fn manage(state: Rc<State>) {
log::error!("Could not listen on the Xwayland socket: {}", ErrorFmt(e));
return;
}
let display = format!(":{}", xsocket.id);
let display = Rc::new(format!(":{}", xsocket.id));
forker.setenv(DISPLAY.as_bytes(), display.as_bytes());
let _unsetenv = on_drop(|| forker.unsetenv(DISPLAY.as_bytes()));
state.xwayland.display.set(Some(display.clone()));
let _unsetenv = on_drop(|| {
forker.unsetenv(DISPLAY.as_bytes());
state.xwayland.display.take();
});
log::info!("Allocated display :{} for Xwayland", xsocket.id);
log::info!("Waiting for connection attempt");
if state.backend.get().import_environment() {
@ -207,8 +211,10 @@ async fn run(
state.ring.readable(&Rc::new(dfdread)).await?;
state.xwayland.queue.clear();
state.xwayland.pidfd.set(Some(pidfd.clone()));
state.xwayland.client.set(Some(client.clone()));
let _remove_pidfd = on_drop(|| {
state.xwayland.pidfd.take();
state.xwayland.client.take();
});
{
let shared = Rc::new(XwmShared::default());