diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index dbcd1bac..303af529 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -359,6 +359,10 @@ impl Client { size } + pub fn set_cursor_size(&self, seat: Seat, size: i32) { + self.send(&ClientMessage::SetCursorSize { seat, size }) + } + pub fn set_size(&self, sized: Resizable, size: i32) { self.send(&ClientMessage::SetSize { sized, size }) } @@ -459,6 +463,16 @@ impl Client { connected } + pub fn connector_set_scale(&self, connector: Connector, scale: f64) { + self.send(&ClientMessage::ConnectorSetScale { connector, scale }); + } + + pub fn connector_get_scale(&self, connector: Connector) -> f64 { + let res = self.send_with_response(&ClientMessage::ConnectorGetScale { connector }); + get_response!(res, 1.0, ConnectorGetScale { scale }); + scale + } + pub fn connector_type(&self, connector: Connector) -> ConnectorType { let res = self.send_with_response(&ClientMessage::ConnectorType { connector }); get_response!(res, CON_UNKNOWN, ConnectorType { ty }); @@ -483,6 +497,12 @@ impl Client { } } + pub fn connector_size(&self, connector: Connector) -> (i32, i32) { + let res = self.send_with_response(&ClientMessage::ConnectorSize { connector }); + get_response!(res, (0, 0), ConnectorSize { width, height }); + (width, height) + } + pub fn drm_devices(&self) -> Vec { let res = self.send_with_response(&ClientMessage::GetDrmDevices); get_response!(res, vec![], GetDrmDevices { devices }); diff --git a/jay-config/src/_private/ipc.rs b/jay-config/src/_private/ipc.rs index 67dc351b..cc29025a 100644 --- a/jay-config/src/_private/ipc.rs +++ b/jay-config/src/_private/ipc.rs @@ -274,6 +274,20 @@ pub enum ClientMessage<'a> { device: InputDevice, px: f64, }, + ConnectorSetScale { + connector: Connector, + scale: f64, + }, + ConnectorGetScale { + connector: Connector, + }, + ConnectorSize { + connector: Connector, + }, + SetCursorSize { + seat: Seat, + size: i32, + }, } #[derive(Encode, Decode, Debug)] @@ -360,6 +374,13 @@ pub enum Response { GetFont { font: String, }, + ConnectorGetScale { + scale: f64, + }, + ConnectorSize { + width: i32, + height: i32, + }, } #[derive(Encode, Decode, Debug)] diff --git a/jay-config/src/input.rs b/jay-config/src/input.rs index a3e21a7c..25ba95f1 100644 --- a/jay-config/src/input.rs +++ b/jay-config/src/input.rs @@ -107,6 +107,13 @@ impl Seat { Self(raw) } + /// Sets the size of the cursor theme. + /// + /// Default: 16. + pub fn set_cursor_size(self, size: i32) { + get!().set_cursor_size(self, size) + } + /// Creates a compositor-wide hotkey. /// /// The closure is invoked when the user presses the last key of the modified keysym. diff --git a/jay-config/src/video.rs b/jay-config/src/video.rs index f18c309d..c0647440 100644 --- a/jay-config/src/video.rs +++ b/jay-config/src/video.rs @@ -79,6 +79,23 @@ impl Connector { get!(false).connector_connected(self) } + /// Returns the scale of the currently connected monitor. + pub fn scale(self) -> f64 { + if !self.exists() { + return 1.0; + } + get!(1.0).connector_get_scale(self) + } + + /// Sets the scale to use for the currently connected monitor. + pub fn set_scale(self, scale: f64) { + if !self.exists() { + return; + } + log::info!("setting scale to {}", scale); + get!().connector_set_scale(self, scale); + } + /// Returns the connector type. pub fn ty(self) -> ConnectorType { if !self.exists() { @@ -95,18 +112,18 @@ impl Connector { get!(Mode::zeroed()).connector_mode(self) } - /// Returns the width of the current mode of the connector. + /// Returns the logical width of the connector. /// - /// This is a shortcut for `mode().width()`. + /// The returned value will be different from `mode().width()` if the scale is not 1. pub fn width(self) -> i32 { - self.mode().width + get!().connector_size(self).0 } - /// Returns the height of the current mode of the connector. + /// Returns the logical height of the connector. /// - /// This is a shortcut for `mode().height()`. + /// The returned value will be different from `mode().height()` if the scale is not 1. pub fn height(self) -> i32 { - self.mode().height + get!().connector_size(self).1 } /// Returns the refresh rate in mhz of the current mode of the connector. diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 8569d416..0464b3ab 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -248,6 +248,7 @@ impl MetalConnector { Some(node.global.pos.get()), true, &mut rr, + node.preferred_scale.get(), ); for fr in rr.frame_requests.drain(..) { fr.send_done(); diff --git a/src/backends/x.rs b/src/backends/x.rs index 574027de..d24b387f 100644 --- a/src/backends/x.rs +++ b/src/backends/x.rs @@ -699,6 +699,7 @@ impl XBackend { Some(node.global.pos.get()), true, rr.deref_mut(), + node.preferred_scale.get(), ); for fr in rr.frame_requests.drain(..) { fr.send_done(); diff --git a/src/compositor.rs b/src/compositor.rs index 1d1f64f9..0e81cffd 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -14,6 +14,7 @@ use { clientmem::{self, ClientMemError}, config::ConfigProxy, dbus::Dbus, + fixed::Fixed, forker, globals::Globals, ifs::{wl_output::WlOutputGlobal, wl_surface::NoneSurfaceExt}, @@ -31,7 +32,8 @@ use { user_session::import_environment, utils::{ clonecell::CloneCell, errorfmt::ErrorFmt, fdcloser::FdCloser, numcell::NumCell, - oserror::OsError, queue::AsyncQueue, run_toplevel::RunToplevel, tri::Try, + oserror::OsError, queue::AsyncQueue, refcounted::RefCounted, run_toplevel::RunToplevel, + tri::Try, }, wheel::{Wheel, WheelError}, xkbcommon::XkbContext, @@ -120,6 +122,8 @@ fn start_compositor2( let wheel = Wheel::new(&engine, &ring)?; let (_run_toplevel_future, run_toplevel) = RunToplevel::install(&engine); let node_ids = NodeIds::default(); + let scales = RefCounted::default(); + scales.add(Fixed::from_int(1)); let state = Rc::new(State { xkb_ctx, backend: CloneCell::new(Rc::new(DummyBackend)), @@ -187,6 +191,8 @@ fn start_compositor2( locked: Cell::new(false), lock: Default::default(), }, + scales, + cursor_sizes: Default::default(), }); state.tracker.register(ClientId::from_raw(0)); create_dummy_output(&state); @@ -376,6 +382,7 @@ fn create_dummy_output(state: &Rc) { scroll: Default::default(), pointer_positions: Default::default(), lock_surface: Default::default(), + preferred_scale: Cell::new(Fixed::from_int(1)), }); let dummy_workspace = Rc::new(WorkspaceNode { id: state.node_ids.next(), diff --git a/src/config/handler.rs b/src/config/handler.rs index 2c76aebf..1914ac30 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -7,6 +7,7 @@ use { }, compositor::MAX_EXTENTS, config::ConfigProxy, + fixed::Fixed, ifs::wl_seat::{SeatId, WlSeatGlobal}, state::{ConnectorData, DeviceHandlerData, DrmDevData, OutputData, State}, theme::{Color, ThemeSized, DEFAULT_FONT}, @@ -573,6 +574,47 @@ impl ConfigProxyHandler { Ok(()) } + fn handle_set_cursor_size(&self, seat: Seat, size: i32) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + if size < 0 { + return Err(CphError::NegativeCursorSize); + } + seat.set_cursor_size(size as _); + Ok(()) + } + + fn handle_connector_size(&self, connector: Connector) -> Result<(), CphError> { + let connector = self.get_output(connector)?; + let pos = connector.node.global.pos.get(); + self.respond(Response::ConnectorSize { + width: pos.width(), + height: pos.height(), + }); + Ok(()) + } + + fn handle_connector_get_scale(&self, connector: Connector) -> Result<(), CphError> { + let connector = self.get_output(connector)?; + self.respond(Response::ConnectorGetScale { + scale: connector.node.preferred_scale.get().to_f64(), + }); + Ok(()) + } + + fn handle_connector_set_scale(&self, connector: Connector, scale: f64) -> Result<(), CphError> { + if scale < 0.1 { + return Err(CphError::ScaleTooSmall(scale)); + } + if scale > 1000.0 { + return Err(CphError::ScaleTooLarge(scale)); + } + let scale = Fixed::from_f64(scale); + let connector = self.get_output(connector)?; + connector.node.set_preferred_scale(scale); + self.state.damage(); + Ok(()) + } + fn handle_connector_set_position( &self, connector: Connector, @@ -1086,6 +1128,18 @@ impl ConfigProxyHandler { ClientMessage::SetPxPerWheelScroll { device, px } => self .handle_set_px_per_wheel_scroll(device, px) .wrn("set_px_per_wheel_scroll")?, + ClientMessage::ConnectorSetScale { connector, scale } => self + .handle_connector_set_scale(connector, scale) + .wrn("connector_set_scale")?, + ClientMessage::ConnectorGetScale { connector } => self + .handle_connector_get_scale(connector) + .wrn("connector_get_scale")?, + ClientMessage::ConnectorSize { connector } => self + .handle_connector_size(connector) + .wrn("connector_size")?, + ClientMessage::SetCursorSize { seat, size } => self + .handle_set_cursor_size(seat, size) + .wrn("set_cursor_size")?, } Ok(()) } @@ -1137,6 +1191,12 @@ enum CphError { FailedRequest(&'static str, #[source] Box), #[error(transparent)] TimerError(#[from] TimerError), + #[error("The requested monitor scale {0} is too small")] + ScaleTooSmall(f64), + #[error("The requested monitor scale {0} is too large")] + ScaleTooLarge(f64), + #[error("Tried to set a negative cursor size")] + NegativeCursorSize, } trait WithRequestName { diff --git a/src/cursor.rs b/src/cursor.rs index f7dbab34..c0e631b1 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -1,11 +1,14 @@ use { crate::{ + fixed::Fixed, format::ARGB8888, rect::Rect, render::{RenderContext, RenderError, Renderer, Texture}, - utils::{errorfmt::ErrorFmt, numcell::NumCell}, + state::State, + tree::OutputNode, + utils::{errorfmt::ErrorFmt, numcell::NumCell, smallmap::SmallMapMut}, }, - ahash::AHashSet, + ahash::{AHashMap, AHashSet}, bstr::{BStr, BString, ByteSlice, ByteVec}, byteorder::{LittleEndian, ReadBytesExt}, isnt::std_1::primitive::IsntSliceExt, @@ -34,10 +37,10 @@ const HOME: &str = "HOME"; const HEADER_SIZE: u32 = 16; pub trait Cursor { - fn set_position(&self, x: i32, y: i32); - fn render(&self, renderer: &mut Renderer, x: i32, y: i32); - fn get_hotspot(&self) -> (i32, i32); - fn extents(&self) -> Rect; + fn render(&self, renderer: &mut Renderer, x: Fixed, y: Fixed); + fn set_output(&self, output: &Rc) { + let _ = output; + } fn handle_unset(&self) {} fn tick(&self) {} } @@ -68,11 +71,17 @@ pub enum KnownCursor { } impl ServerCursors { - pub fn load(ctx: &Rc) -> Result { + pub fn load(ctx: &Rc, state: &State) -> Result, CursorError> { let paths = find_cursor_paths(); log::debug!("Trying to load cursors from paths {:?}", paths); - let load = |name: &str| ServerCursorTemplate::load(name, None, 16, &paths, ctx); - Ok(Self { + let sizes = state.cursor_sizes.to_vec(); + let scales = state.scales.to_vec(); + if sizes.is_empty() || scales.is_empty() { + return Ok(None); + } + let load = + |name: &str| ServerCursorTemplate::load(name, None, &scales, &sizes, &paths, ctx); + Ok(Some(Self { default: load("left_ptr")?, // default: load("left_ptr_watch")?, resize_right: load("right_side")?, @@ -85,13 +94,13 @@ impl ServerCursors { resize_top_right: load("top_right_corner")?, resize_bottom_left: load("bottom_left_corner")?, resize_bottom_right: load("bottom_right_corner")?, - }) + })) } } pub struct ServerCursorTemplate { var: ServerCursorTemplateVariant, - pub xcursor: Vec, + pub xcursor: Vec>>, } enum ServerCursorTemplateVariant { @@ -103,45 +112,64 @@ impl ServerCursorTemplate { fn load( name: &str, theme: Option<&BStr>, - size: u32, + scales: &[Fixed], + sizes: &[u32], paths: &[BString], ctx: &Rc, ) -> Result { - match open_cursor(name, theme, size, paths) { + match open_cursor(name, theme, &scales, sizes, paths) { Ok(cs) => { - if cs.len() == 1 { - let c = &cs[0]; - let cursor = CursorImage::from_bytes( - ctx, &c.pixels, 0, c.width, c.height, c.xhot, c.yhot, - )?; + if cs.images.len() == 1 { + let mut sizes = SmallMapMut::new(); + for (k, c) in &cs.images[0] { + sizes.insert( + *k, + CursorImageScaled::from_bytes( + ctx, &c.pixels, c.width, c.height, c.xhot, c.yhot, + )?, + ); + } + let cursor = CursorImage::from_sizes(0, sizes)?; Ok(ServerCursorTemplate { var: ServerCursorTemplateVariant::Static(Rc::new(cursor)), - xcursor: cs, + xcursor: cs.images, }) } else { let mut images = vec![]; - for c in &cs { - let img = CursorImage::from_bytes( - ctx, - &c.pixels, - c.delay as _, - c.width, - c.height, - c.xhot, - c.yhot, - )?; + for image in &cs.images { + let mut sizes = SmallMapMut::new(); + let mut delay_ms = 0; + for (k, c) in image { + delay_ms = c.delay; + sizes.insert( + *k, + CursorImageScaled::from_bytes( + ctx, &c.pixels, c.width, c.height, c.xhot, c.yhot, + )?, + ); + } + let img = CursorImage::from_sizes(delay_ms as _, sizes)?; images.push(img); } Ok(ServerCursorTemplate { var: ServerCursorTemplateVariant::Animated(Rc::new(images)), - xcursor: cs, + xcursor: cs.images, }) } } Err(e) => { log::warn!("Could not load cursor {}: {}", name, ErrorFmt(e)); let empty: [Cell; 4] = unsafe { MaybeUninit::zeroed().assume_init() }; - let cursor = CursorImage::from_bytes(ctx, &empty, 0, 1, 1, 0, 0)?; + let mut img_sizes = SmallMapMut::new(); + for scale in scales { + for size in sizes { + img_sizes.insert( + (*scale, *size), + CursorImageScaled::from_bytes(ctx, &empty, 1, 1, 0, 0)?, + ); + } + } + let cursor = CursorImage::from_sizes(0, img_sizes)?; Ok(ServerCursorTemplate { var: ServerCursorTemplateVariant::Static(Rc::new(cursor)), xcursor: Default::default(), @@ -150,13 +178,10 @@ impl ServerCursorTemplate { } } - pub fn instantiate(&self) -> Rc { + pub fn instantiate(&self, size: u32) -> Rc { match &self.var { ServerCursorTemplateVariant::Static(s) => Rc::new(StaticCursor { - x: Cell::new(0), - y: Cell::new(0), - extents: Cell::new(s.extents), - image: s.clone(), + image: s.for_size(size), }), ServerCursorTemplateVariant::Animated(a) => { let mut start = c::timespec { @@ -168,68 +193,103 @@ impl ServerCursorTemplate { start, next: NumCell::new(a[0].delay_ns), idx: Cell::new(0), - images: a.clone(), - x: Cell::new(0), - y: Cell::new(0), - extents: Cell::new(a[0].extents), + images: a.iter().map(|c| c.for_size(size)).collect(), }) } } } } -struct CursorImage { +struct CursorImageScaled { extents: Rect, - xhot: i32, - yhot: i32, - delay_ns: u64, tex: Rc, } -impl CursorImage { +struct CursorImage { + delay_ns: u64, + sizes: SmallMapMut<(Fixed, u32), Rc, 2>, +} + +struct InstantiatedCursorImage { + delay_ns: u64, + scales: SmallMapMut, 2>, +} + +impl CursorImageScaled { fn from_bytes( ctx: &Rc, data: &[Cell], - delay_ms: u64, width: i32, height: i32, xhot: i32, yhot: i32, + ) -> Result, CursorError> { + Ok(Rc::new(Self { + extents: Rect::new_sized(-xhot, -yhot, width, height).unwrap(), + tex: ctx.shmem_texture(data, ARGB8888, width, height, width * 4)?, + })) + } +} + +impl CursorImage { + fn from_sizes( + delay_ms: u64, + sizes: SmallMapMut<(Fixed, u32), Rc, 2>, ) -> Result { Ok(Self { - extents: Rect::new_sized(-xhot, -yhot, width, height).unwrap(), - xhot, - yhot, delay_ns: delay_ms * 1_000_000, - tex: ctx.shmem_texture(data, ARGB8888, width, height, width * 4)?, + sizes, }) } + + fn for_size(&self, size: u32) -> InstantiatedCursorImage { + let mut sizes = SmallMapMut::new(); + for ((scale, isize), v) in &self.sizes { + if *isize == size { + sizes.insert(*scale, v.clone()); + } + } + InstantiatedCursorImage { + delay_ns: self.delay_ns, + scales: sizes, + } + } } struct StaticCursor { - x: Cell, - y: Cell, - extents: Cell, - image: Rc, + image: InstantiatedCursorImage, +} + +fn render_img(image: &InstantiatedCursorImage, renderer: &mut Renderer, x: Fixed, y: Fixed) { + let scale = renderer.scale(); + let img = match image.scales.get(&scale) { + Some(img) => img, + _ => return, + }; + let extents = if scale != 1 { + let scalef = scale.to_f64(); + let x = (x.to_f64() * scalef).round() as i32; + let y = (y.to_f64() * scalef).round() as i32; + img.extents.move_(x, y) + } else { + img.extents.move_(x.round_down(), y.round_down()) + }; + if extents.intersects(&renderer.physical_extents()) { + renderer.render_texture( + &img.tex, + extents.x1(), + extents.y1(), + ARGB8888, + None, + None, + scale, + ); + } } impl Cursor for StaticCursor { - fn set_position(&self, x: i32, y: i32) { - let dx = x - self.x.replace(x); - let dy = y - self.y.replace(y); - self.extents.set(self.extents.get().move_(dx, dy)); - } - - fn render(&self, renderer: &mut Renderer, x: i32, y: i32) { - renderer.render_texture(&self.image.tex, x, y, ARGB8888, None, None); - } - - fn get_hotspot(&self) -> (i32, i32) { - (self.image.xhot, self.image.yhot) - } - - fn extents(&self) -> Rect { - self.extents.get() + fn render(&self, renderer: &mut Renderer, x: Fixed, y: Fixed) { + render_img(&self.image, renderer, x, y); } } @@ -237,31 +297,13 @@ struct AnimatedCursor { start: c::timespec, next: NumCell, idx: Cell, - images: Rc>, - x: Cell, - y: Cell, - extents: Cell, + images: Vec, } impl Cursor for AnimatedCursor { - fn set_position(&self, x: i32, y: i32) { - let dx = x - self.x.replace(x); - let dy = y - self.y.replace(y); - self.extents.set(self.extents.get().move_(dx, dy)); - } - - fn render(&self, renderer: &mut Renderer, x: i32, y: i32) { + fn render(&self, renderer: &mut Renderer, x: Fixed, y: Fixed) { let img = &self.images[self.idx.get()]; - renderer.render_texture(&img.tex, x, y, ARGB8888, None, None); - } - - fn get_hotspot(&self) -> (i32, i32) { - let img = &self.images[self.idx.get()]; - (img.xhot, img.yhot) - } - - fn extents(&self) -> Rect { - self.extents.get() + render_img(img, renderer, x, y); } fn tick(&self) { @@ -278,25 +320,21 @@ impl Cursor for AnimatedCursor { let idx = (self.idx.get() + 1) % self.images.len(); self.idx.set(idx); let image = &self.images[idx]; - self.extents.set( - Rect::new_sized( - self.x.get() - image.xhot, - self.y.get() - image.yhot, - image.extents.width(), - image.extents.height(), - ) - .unwrap(), - ); self.next.fetch_add(image.delay_ns); } } +struct OpenCursorResult { + images: Vec>>, +} + fn open_cursor( name: &str, theme: Option<&BStr>, - size: u32, + scales: &[Fixed], + sizes: &[u32], paths: &[BString], -) -> Result, CursorError> { +) -> Result { let name = name.as_bytes().as_bstr(); let mut file = None; let mut themes_tested = AHashSet::new(); @@ -311,11 +349,7 @@ fn open_cursor( _ => return Err(CursorError::NotFound), }; let mut file = BufReader::new(file); - let images = parser_cursor_file(&mut file, size)?; - if images.is_empty() { - return Err(CursorError::EmptyXcursorFile); - } - Ok(images) + parser_cursor_file(&mut file, scales, sizes) } fn open_cursor_file( @@ -461,8 +495,9 @@ impl Debug for XCursorImage { fn parser_cursor_file( r: &mut R, - target: u32, -) -> Result, CursorError> { + scales: &[Fixed], + sizes: &[u32], +) -> Result { let [magic, header] = read_u32_n(r)?; if magic != XCURSOR_MAGIC || header < HEADER_SIZE { return Err(CursorError::NotAnXcursorFile); @@ -472,24 +507,54 @@ fn parser_cursor_file( if ntoc > 0x10000 { return Err(CursorError::OversizedXcursorFile); } - let mut images_positions = vec![]; - let mut best_fit = i64::MAX; + struct Target { + positions: Vec, + effective_size: u32, + size: u32, + scale: Fixed, + best_fit: i64, + } + let mut targets = Vec::new(); + for scale in scales { + let scalef = scale.to_f64(); + for size in sizes { + let effective_size = (*size as f64 * scalef).round() as _; + targets.push(Target { + positions: vec![], + effective_size, + size: *size, + scale: *scale, + best_fit: i64::MAX, + }); + } + } + let mut sizes = AHashSet::new(); for _ in 0..ntoc { let [type_, size, position] = read_u32_n(r)?; if type_ != XCURSOR_IMAGE_TYPE { continue; } - let fit = (size as i64 - target as i64).abs(); - if fit < best_fit { - best_fit = fit; - images_positions.clear(); - } - if fit == best_fit { - images_positions.push(position); + sizes.insert(size); + for target in &mut targets { + let fit = (size as i64 - target.effective_size as i64).abs(); + if fit < target.best_fit { + target.best_fit = fit; + target.positions.clear(); + } + if fit == target.best_fit { + target.positions.push(position); + } } } - let mut images = Vec::with_capacity(images_positions.len()); - for position in images_positions { + let positions: AHashSet<_> = targets + .iter() + .flat_map(|t| t.positions.iter().copied()) + .collect(); + if positions.is_empty() { + return Err(CursorError::EmptyXcursorFile); + } + let mut images = AHashMap::new(); + for position in positions { r.seek(SeekFrom::Start(position as u64))?; let [_chunk_header, _type_, _size, _version, width, height, xhot, yhot, delay] = read_u32_n(r)?; @@ -511,9 +576,23 @@ fn parser_cursor_file( num_bytes, ))?; } - images.push(image); + images.insert(position, Rc::new(image)); } - Ok(images) + let mut num = targets[0].positions.len(); + if num > 1 && targets.iter().any(|t| t.positions.len() != num) { + log::warn!("Cursor file contains animated cursor but not all scales have the same number of images"); + num = 1; + } + let mut res = vec![]; + for i in 0..num { + let mut idx_images = AHashMap::new(); + for target in &targets { + let image = images.get(&target.positions[i]).unwrap(); + idx_images.insert((target.scale, target.size), image.clone()); + } + res.push(idx_images); + } + Ok(OpenCursorResult { images: res }) } fn read_u32_n(r: &mut R) -> Result<[u32; N], io::Error> { diff --git a/src/fixed.rs b/src/fixed.rs index 7e7afe1d..d1511453 100644 --- a/src/fixed.rs +++ b/src/fixed.rs @@ -4,7 +4,7 @@ use std::{ ops::{Add, AddAssign, Sub, SubAssign}, }; -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] #[repr(transparent)] pub struct Fixed(pub i32); @@ -37,6 +37,10 @@ impl Fixed { self.0 >> 8 } + pub fn round_up(self) -> i32 { + (self.0 + 255) >> 8 + } + pub fn apply_fract(self, i: i32) -> Self { Self((i << 8) | (self.0 & 255)) } diff --git a/src/globals.rs b/src/globals.rs index f015d728..f5503dc9 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -18,6 +18,7 @@ use { }, wl_shm::WlShmGlobal, wl_subcompositor::WlSubcompositorGlobal, + wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1Global, wp_presentation::WpPresentationGlobal, wp_viewporter::WpViewporterGlobal, xdg_wm_base::XdgWmBaseGlobal, @@ -143,6 +144,7 @@ impl Globals { add_singleton!(ZwpRelativePointerManagerV1Global); add_singleton!(ExtSessionLockManagerV1Global); add_singleton!(WpViewporterGlobal); + add_singleton!(WpFractionalScaleManagerV1Global); if backend.supports_idle() { add_singleton!(ZwpIdleInhibitManagerV1Global); diff --git a/src/ifs.rs b/src/ifs.rs index 18621aba..ef546dae 100644 --- a/src/ifs.rs +++ b/src/ifs.rs @@ -20,6 +20,7 @@ pub mod wl_shm; pub mod wl_shm_pool; pub mod wl_subcompositor; pub mod wl_surface; +pub mod wp_fractional_scale_manager_v1; pub mod wp_presentation; pub mod wp_presentation_feedback; pub mod wp_viewporter; diff --git a/src/ifs/wl_output.rs b/src/ifs/wl_output.rs index fc4e43fa..66163441 100644 --- a/src/ifs/wl_output.rs +++ b/src/ifs/wl_output.rs @@ -5,8 +5,8 @@ use { format::XRGB8888, globals::{Global, GlobalName}, ifs::{ - wl_buffer::WlBufferStorage, zwlr_screencopy_frame_v1::ZwlrScreencopyFrameV1, - zxdg_output_v1::ZxdgOutputV1, + wl_buffer::WlBufferStorage, wl_surface::WlSurface, + zwlr_screencopy_frame_v1::ZwlrScreencopyFrameV1, zxdg_output_v1::ZxdgOutputV1, }, leaks::Tracker, object::Object, @@ -72,6 +72,7 @@ pub struct WlOutputGlobal { pub unused_captures: LinkedList>, pub pending_captures: LinkedList>, pub destroyed: Cell, + pub legacy_scale: Cell, } #[derive(Eq, PartialEq)] @@ -117,6 +118,7 @@ impl WlOutputGlobal { unused_captures: Default::default(), pending_captures: Default::default(), destroyed: Cell::new(false), + legacy_scale: Cell::new(1), } } @@ -124,6 +126,27 @@ impl WlOutputGlobal { self.pos.get() } + pub fn for_each_binding)>(&self, client: ClientId, mut f: F) { + let bindings = self.bindings.borrow_mut(); + if let Some(bindings) = bindings.get(&client) { + for binding in bindings.values() { + f(binding); + } + } + } + + pub fn send_enter(&self, surface: &WlSurface) { + self.for_each_binding(surface.client.id, |b| { + surface.send_enter(b.id); + }) + } + + pub fn send_leave(&self, surface: &WlSurface) { + self.for_each_binding(surface.client.id, |b| { + surface.send_leave(b.id); + }) + } + pub fn send_mode(&self) { let bindings = self.bindings.borrow_mut(); for binding in bindings.values() { @@ -293,7 +316,7 @@ impl WlOutput { fn send_scale(self: &Rc) { let event = Scale { self_id: self.id, - factor: 1, + factor: self.global.legacy_scale.get(), }; self.client.event(event); } diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index 1ba0d716..ab3ea5a6 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -149,11 +149,20 @@ pub struct WlSeatGlobal { output: CloneCell>, desired_known_cursor: Cell>, changes: NumCell, + cursor_size: Cell, } const CHANGE_CURSOR_MOVED: u32 = 1 << 0; const CHANGE_TREE: u32 = 1 << 1; +const DEFAULT_CURSOR_SIZE: u32 = 16; + +impl Drop for WlSeatGlobal { + fn drop(&mut self) { + self.state.remove_cursor_size(self.cursor_size.get()); + } +} + impl WlSeatGlobal { pub fn new(name: GlobalName, seat_name: &str, state: &Rc) -> Rc { let slf = Rc::new(Self { @@ -192,7 +201,9 @@ impl WlSeatGlobal { output: CloneCell::new(state.dummy_output.get().unwrap()), desired_known_cursor: Cell::new(None), changes: NumCell::new(CHANGE_CURSOR_MOVED | CHANGE_TREE), + cursor_size: Cell::new(DEFAULT_CURSOR_SIZE), }); + state.add_cursor_size(DEFAULT_CURSOR_SIZE); let seat = slf.clone(); let future = state.eng.spawn(async move { loop { @@ -207,6 +218,15 @@ impl WlSeatGlobal { slf } + pub fn set_cursor_size(&self, size: u32) { + let old = self.cursor_size.replace(size); + if size != old { + self.state.remove_cursor_size(old); + self.state.add_cursor_size(size); + self.reload_known_cursor(); + } + } + pub fn add_data_device(&self, device: &Rc) { let mut dd = self.data_devices.borrow_mut(); dd.entry(device.client.id) @@ -342,15 +362,25 @@ impl WlSeatGlobal { pub fn set_position(&self, x: i32, y: i32) { self.pos.set((Fixed::from_int(x), Fixed::from_int(y))); self.trigger_tree_changed(); - 'set_output: { + let output = 'set_output: { let outputs = self.state.outputs.lock(); for output in outputs.values() { if output.node.global.pos.get().contains(x, y) { - self.output.set(output.node.clone()); - break 'set_output; + break 'set_output output.node.clone(); } } - self.output.set(self.state.dummy_output.get().unwrap()); + self.state.dummy_output.get().unwrap() + }; + self.set_output(&output); + } + + fn set_output(&self, output: &Rc) { + self.output.set(output.clone()); + if let Some(cursor) = self.cursor.get() { + cursor.set_output(output); + } + if let Some(dnd) = self.pointer_owner.dnd_icon() { + dnd.set_output(output); } } @@ -547,6 +577,9 @@ impl WlSeatGlobal { icon: Option>, serial: u32, ) -> Result<(), WlSeatError> { + if let Some(icon) = &icon { + icon.set_output(&self.output.get()); + } self.pointer_owner .start_drag(self, origin, source, icon, serial) } @@ -604,6 +637,12 @@ impl WlSeatGlobal { self.set_selection_::(&self.primary_selection, selection) } + pub fn reload_known_cursor(&self) { + if let Some(kc) = self.desired_known_cursor.get() { + self.set_known_cursor(kc); + } + } + pub fn set_known_cursor(&self, cursor: KnownCursor) { self.desired_known_cursor.set(Some(cursor)); let cursors = match self.state.cursors.get() { @@ -622,7 +661,7 @@ impl WlSeatGlobal { KnownCursor::ResizeBottomLeft => &cursors.resize_bottom_left, KnownCursor::ResizeBottomRight => &cursors.resize_bottom_right, }; - self.set_cursor2(Some(tpl.instantiate())); + self.set_cursor2(Some(tpl.instantiate(self.cursor_size.get()))); } pub fn set_app_cursor(&self, cursor: Option>) { @@ -640,8 +679,7 @@ impl WlSeatGlobal { old.handle_unset(); } if let Some(cursor) = cursor.as_ref() { - let (x, y) = self.pos.get(); - cursor.set_position(x.round_down(), y.round_down()); + cursor.set_output(&self.output.get()); } self.cursor.set(cursor); } @@ -654,6 +692,10 @@ impl WlSeatGlobal { self.pointer_owner.remove_dnd_icon(); } + pub fn get_position(&self) -> (Fixed, Fixed) { + self.pos.get() + } + pub fn get_cursor(&self) -> Option> { self.cursor.get() } diff --git a/src/ifs/wl_seat/event_handling.rs b/src/ifs/wl_seat/event_handling.rs index 279f6497..09470f6e 100644 --- a/src/ifs/wl_seat/event_handling.rs +++ b/src/ifs/wl_seat/event_handling.rs @@ -212,7 +212,7 @@ impl WlSeatGlobal { Some(o) => o, _ => return, }; - self.output.set(output.node.clone()); + self.set_output(&output.node); let pos = output.node.global.pos.get(); x += Fixed::from_int(pos.x1()); y += Fixed::from_int(pos.y1()); @@ -247,7 +247,7 @@ impl WlSeatGlobal { let outputs = self.state.outputs.lock(); for output in outputs.values() { if output.node.global.pos.get().contains(x_int, y_int) { - self.output.set(output.node.clone()); + self.set_output(&output.node); break 'warp; } } @@ -261,8 +261,8 @@ impl WlSeatGlobal { } else if y_int >= pos.y2() { y_int = pos.y2() - 1; } - x = x.apply_fract(x_int); - y = y.apply_fract(y_int); + x = Fixed::from_int(x_int); + y = Fixed::from_int(y_int); } } self.set_new_position(time_usec, x, y); @@ -480,9 +480,6 @@ impl WlSeatGlobal { fn set_new_position(self: &Rc, time_usec: u64, x: Fixed, y: Fixed) { self.pos_time_usec.set(time_usec); self.pos.set((x, y)); - if let Some(cursor) = self.cursor.get() { - cursor.set_position(x.round_down(), y.round_down()); - } self.changes.or_assign(CHANGE_CURSOR_MOVED); self.apply_changes(); } diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index b57edfa0..c43abb52 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -1,6 +1,7 @@ pub mod cursor; pub mod ext_session_lock_surface_v1; pub mod wl_subsurface; +pub mod wp_fractional_scale_v1; pub mod wp_viewport; pub mod xdg_surface; pub mod xwindow; @@ -21,7 +22,8 @@ use { }, wl_seat::{wl_pointer::PendingScroll, Dnd, NodeSeatState, SeatId, WlSeatGlobal}, wl_surface::{ - cursor::CursorSurface, wl_subsurface::WlSubsurface, wp_viewport::WpViewport, + cursor::CursorSurface, wl_subsurface::WlSubsurface, + wp_fractional_scale_v1::WpFractionalScaleV1, wp_viewport::WpViewport, xdg_surface::XdgSurfaceError, zwlr_layer_surface_v1::ZwlrLayerSurfaceV1Error, }, wp_presentation_feedback::WpPresentationFeedback, @@ -31,7 +33,10 @@ use { rect::{Rect, Region}, render::Renderer, state::DeviceHandlerData, - tree::{FindTreeResult, FoundNode, Node, NodeId, NodeVisitor, ToplevelNode}, + tree::{ + FindTreeResult, FoundNode, Node, NodeId, NodeVisitor, NodeVisitorBase, OutputNode, + ToplevelNode, + }, utils::{ buffd::{MsgParser, MsgParserError}, clonecell::CloneCell, @@ -207,6 +212,14 @@ impl SurfaceRole { } } +pub struct SurfaceSendPreferredScaleVisitor(pub Fixed); +impl NodeVisitorBase for SurfaceSendPreferredScaleVisitor { + fn visit_surface(&mut self, node: &Rc) { + node.send_preferred_scale(); + node.node_visit_children(self); + } +} + pub struct WlSurface { pub id: WlSurfaceId, pub node_id: SurfaceNodeId, @@ -239,6 +252,8 @@ pub struct WlSurface { pub tracker: Tracker, idle_inhibitors: CopyHashMap>, viewporter: CloneCell>>, + output: CloneCell>, + fractional_scale: CloneCell>>, } impl Debug for WlSurface { @@ -370,6 +385,34 @@ impl WlSurface { tracker: Default::default(), idle_inhibitors: Default::default(), viewporter: Default::default(), + output: CloneCell::new(client.state.dummy_output.get().unwrap()), + fractional_scale: Default::default(), + } + } + + pub fn set_output(&self, output: &Rc) { + let old = self.output.set(output.clone()); + if old.id == output.id { + return; + } + output.global.send_enter(self); + old.global.send_leave(self); + if old.preferred_scale.get() != output.preferred_scale.get() { + if let Some(fs) = self.fractional_scale.get() { + fs.send_preferred_scale(); + } + } + let children = self.children.borrow_mut(); + if let Some(children) = &*children { + for ss in children.subsurfaces.values() { + ss.surface.set_output(output); + } + } + } + + pub fn send_preferred_scale(&self) { + if let Some(fs) = self.fractional_scale.get() { + fs.send_preferred_scale(); } } @@ -423,13 +466,20 @@ impl WlSurface { } } - fn send_enter(&self, output: WlOutputId) { + pub fn send_enter(&self, output: WlOutputId) { self.client.event(Enter { self_id: self.id, output, }) } + pub fn send_leave(&self, output: WlOutputId) { + self.client.event(Leave { + self_id: self.id, + output, + }) + } + fn set_toplevel(&self, tl: Option>) { let ch = self.children.borrow(); if let Some(ch) = &*ch { @@ -967,6 +1017,7 @@ impl Object for WlSurface { self.pending.presentation_feedback.borrow_mut().clear(); self.presentation_feedback.borrow_mut().clear(); self.viewporter.take(); + self.fractional_scale.take(); } } diff --git a/src/ifs/wl_surface/cursor.rs b/src/ifs/wl_surface/cursor.rs index 223ca2ca..253e04b1 100644 --- a/src/ifs/wl_surface/cursor.rs +++ b/src/ifs/wl_surface/cursor.rs @@ -1,10 +1,12 @@ use { crate::{ cursor::Cursor, + fixed::Fixed, ifs::{wl_seat::WlSeatGlobal, wl_surface::WlSurface}, leaks::Tracker, rect::Rect, render::Renderer, + tree::OutputNode, }, std::{cell::Cell, rc::Rc}, }; @@ -13,7 +15,6 @@ pub struct CursorSurface { seat: Rc, surface: Rc, hotspot: Cell<(i32, i32)>, - pos: Cell<(i32, i32)>, extents: Cell, pub tracker: Tracker, } @@ -24,25 +25,16 @@ impl CursorSurface { seat: seat.clone(), surface: surface.clone(), hotspot: Cell::new((0, 0)), - pos: Cell::new((0, 0)), extents: Cell::new(Default::default()), tracker: Default::default(), } } fn update_extents(&self) { - let (pos_x, pos_y) = self.pos.get(); let extents = self.extents.get(); let (hot_x, hot_y) = self.hotspot.get(); - self.extents.set( - Rect::new_sized( - pos_x - hot_x, - pos_y - hot_y, - extents.width(), - extents.height(), - ) - .unwrap(), - ); + self.extents + .set(Rect::new_sized(-hot_x, -hot_y, extents.width(), extents.height()).unwrap()); } pub fn handle_surface_destroy(&self) { @@ -69,21 +61,25 @@ impl CursorSurface { } impl Cursor for CursorSurface { - fn set_position(&self, x: i32, y: i32) { - self.pos.set((x, y)); - self.update_extents(); + fn render(&self, renderer: &mut Renderer, x: Fixed, y: Fixed) { + let extents = self.extents.get().move_(x.round_down(), y.round_down()); + if extents.intersects(&renderer.logical_extents()) { + let scale = renderer.scale(); + if scale != 1 { + let scale = scale.to_f64(); + let (hot_x, hot_y) = self.hotspot.get(); + let (hot_x, hot_y) = (Fixed::from_int(hot_x), Fixed::from_int(hot_y)); + let x = ((x - hot_x).to_f64() * scale).round() as _; + let y = ((y - hot_y).to_f64() * scale).round() as _; + renderer.render_surface_scaled(&self.surface, x, y, None); + } else { + renderer.render_surface(&self.surface, extents.x1(), extents.y1()); + } + } } - fn render(&self, renderer: &mut Renderer, x: i32, y: i32) { - renderer.render_surface(&self.surface, x, y); - } - - fn get_hotspot(&self) -> (i32, i32) { - self.hotspot.get() - } - - fn extents(&self) -> Rect { - self.extents.get() + fn set_output(&self, output: &Rc) { + self.surface.set_output(output); } fn handle_unset(&self) { diff --git a/src/ifs/wl_surface/wp_fractional_scale_v1.rs b/src/ifs/wl_surface/wp_fractional_scale_v1.rs new file mode 100644 index 00000000..b22baf9a --- /dev/null +++ b/src/ifs/wl_surface/wp_fractional_scale_v1.rs @@ -0,0 +1,78 @@ +use { + crate::{ + client::{Client, ClientError}, + ifs::wl_surface::WlSurface, + leaks::Tracker, + object::Object, + utils::buffd::{MsgParser, MsgParserError}, + wire::{wp_fractional_scale_v1::*, WpFractionalScaleV1Id}, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct WpFractionalScaleV1 { + pub id: WpFractionalScaleV1Id, + pub client: Rc, + pub surface: Rc, + pub tracker: Tracker, +} + +impl WpFractionalScaleV1 { + pub fn new(id: WpFractionalScaleV1Id, surface: &Rc) -> Self { + Self { + id, + client: surface.client.clone(), + surface: surface.clone(), + tracker: Default::default(), + } + } + + pub fn install(self: &Rc) -> Result<(), WpFractionalScaleError> { + if self.surface.fractional_scale.get().is_some() { + return Err(WpFractionalScaleError::Exists); + } + self.surface.fractional_scale.set(Some(self.clone())); + Ok(()) + } + + pub fn send_preferred_scale(&self) { + self.client.event(PreferredScale { + self_id: self.id, + scale: self.surface.output.get().preferred_scale.get(), + }); + } + + fn destroy(&self, msg: MsgParser<'_, '_>) -> Result<(), WpFractionalScaleError> { + let _req: Destroy = self.client.parse(self, msg)?; + self.surface.fractional_scale.take(); + self.client.remove_obj(self)?; + Ok(()) + } +} + +object_base! { + WpFractionalScaleV1; + + DESTROY => destroy, +} + +impl Object for WpFractionalScaleV1 { + fn num_requests(&self) -> u32 { + DESTROY + 1 + } +} + +simple_add_obj!(WpFractionalScaleV1); + +#[derive(Debug, Error)] +pub enum WpFractionalScaleError { + #[error("Parsing failed")] + MsgParserError(#[source] Box), + #[error(transparent)] + ClientError(Box), + #[error("The surface already has a fractional scale extension attached")] + Exists, +} +efrom!(WpFractionalScaleError, MsgParserError); +efrom!(WpFractionalScaleError, ClientError); diff --git a/src/ifs/wl_surface/xdg_surface.rs b/src/ifs/wl_surface/xdg_surface.rs index 7031d189..55580ab3 100644 --- a/src/ifs/wl_surface/xdg_surface.rs +++ b/src/ifs/wl_surface/xdg_surface.rs @@ -124,6 +124,7 @@ impl XdgSurface { fn set_workspace(&self, ws: &Rc) { self.workspace.set(Some(ws.clone())); + self.surface.set_output(&ws.output.get()); let pu = self.popups.lock(); for pu in pu.values() { pu.xdg.set_workspace(ws); diff --git a/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs b/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs index adc5aabc..4fec5444 100644 --- a/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs +++ b/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs @@ -456,8 +456,7 @@ impl ToplevelNode for XdgToplevel { Some(self.xdg.surface.clone()) } - fn tl_set_workspace(self: Rc, ws: &Rc) { - self.toplevel_data.workspace.set(Some(ws.clone())); + fn tl_set_workspace_ext(self: Rc, ws: &Rc) { self.xdg.set_workspace(ws); } @@ -548,12 +547,7 @@ impl XdgSurfaceExt for XdgToplevel { self.extents_changed(); if let Some(workspace) = self.xdg.workspace.get() { let output = workspace.output.get(); - let bindings = output.global.bindings.borrow_mut(); - if let Some(binding) = bindings.get(&self.xdg.surface.client.id) { - for binding in binding.values() { - self.xdg.surface.send_enter(binding.id); - } - } + surface.set_output(&output); } // { // let seats = surface.client.state.globals.lock_seats(); diff --git a/src/ifs/wl_surface/xwindow.rs b/src/ifs/wl_surface/xwindow.rs index abae97a3..97440e5f 100644 --- a/src/ifs/wl_surface/xwindow.rs +++ b/src/ifs/wl_surface/xwindow.rs @@ -12,7 +12,7 @@ use { state::State, tree::{ Direction, FindTreeResult, FoundNode, Node, NodeId, NodeVisitor, StackedNode, - ToplevelData, ToplevelNode, + ToplevelData, ToplevelNode, WorkspaceNode, }, utils::{clonecell::CloneCell, copyhashmap::CopyHashMap, linkedlist::LinkedNode}, wire::WlSurfaceId, @@ -401,6 +401,10 @@ impl ToplevelNode for Xwindow { Some(self.surface.clone()) } + fn tl_set_workspace_ext(self: Rc, ws: &Rc) { + self.surface.set_output(&ws.output.get()); + } + fn tl_change_extents(self: Rc, rect: &Rect) { // log::info!("xwin {} change_extents {:?}", self.data.window_id, rect); let old = self.data.info.extents.replace(*rect); diff --git a/src/ifs/wp_fractional_scale_manager_v1.rs b/src/ifs/wp_fractional_scale_manager_v1.rs new file mode 100644 index 00000000..91367423 --- /dev/null +++ b/src/ifs/wp_fractional_scale_manager_v1.rs @@ -0,0 +1,112 @@ +use { + crate::{ + client::{Client, ClientError}, + globals::{Global, GlobalName}, + ifs::wl_surface::wp_fractional_scale_v1::{WpFractionalScaleError, WpFractionalScaleV1}, + leaks::Tracker, + object::Object, + utils::buffd::{MsgParser, MsgParserError}, + wire::{wp_fractional_scale_manager_v1::*, WpFractionalScaleManagerV1Id}, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct WpFractionalScaleManagerV1Global { + pub name: GlobalName, +} + +pub struct WpFractionalScaleManagerV1 { + pub id: WpFractionalScaleManagerV1Id, + pub client: Rc, + pub tracker: Tracker, +} + +impl WpFractionalScaleManagerV1Global { + pub fn new(name: GlobalName) -> Self { + Self { name } + } + + fn bind_( + self: Rc, + id: WpFractionalScaleManagerV1Id, + client: &Rc, + _version: u32, + ) -> Result<(), WpFractionalScaleManagerError> { + let obj = Rc::new(WpFractionalScaleManagerV1 { + id, + client: client.clone(), + tracker: Default::default(), + }); + track!(client, obj); + client.add_client_obj(&obj)?; + Ok(()) + } +} + +global_base!( + WpFractionalScaleManagerV1Global, + WpFractionalScaleManagerV1, + WpFractionalScaleManagerError +); + +impl Global for WpFractionalScaleManagerV1Global { + fn singleton(&self) -> bool { + true + } + + fn version(&self) -> u32 { + 1 + } +} + +simple_add_global!(WpFractionalScaleManagerV1Global); + +impl WpFractionalScaleManagerV1 { + fn destroy(&self, msg: MsgParser<'_, '_>) -> Result<(), WpFractionalScaleManagerError> { + let _req: Destroy = self.client.parse(self, msg)?; + self.client.remove_obj(self)?; + Ok(()) + } + + fn get_fractional_scale( + &self, + msg: MsgParser<'_, '_>, + ) -> Result<(), WpFractionalScaleManagerError> { + let req: GetFractionalScale = self.client.parse(self, msg)?; + let surface = self.client.lookup(req.surface)?; + let fs = Rc::new(WpFractionalScaleV1::new(req.id, &surface)); + track!(self.client, fs); + fs.install()?; + self.client.add_client_obj(&fs)?; + fs.send_preferred_scale(); + Ok(()) + } +} + +object_base! { + WpFractionalScaleManagerV1; + + DESTROY => destroy, + GET_FRACTIONAL_SCALE => get_fractional_scale, +} + +impl Object for WpFractionalScaleManagerV1 { + fn num_requests(&self) -> u32 { + GET_FRACTIONAL_SCALE + 1 + } +} + +simple_add_obj!(WpFractionalScaleManagerV1); + +#[derive(Debug, Error)] +pub enum WpFractionalScaleManagerError { + #[error("Parsing failed")] + MsgParserError(#[source] Box), + #[error(transparent)] + ClientError(Box), + #[error(transparent)] + WpFractionalScaleError(#[from] WpFractionalScaleError), +} +efrom!(WpFractionalScaleManagerError, MsgParserError); +efrom!(WpFractionalScaleManagerError, ClientError); diff --git a/src/pango.rs b/src/pango.rs index a4e97dd6..8219a216 100644 --- a/src/pango.rs +++ b/src/pango.rs @@ -63,6 +63,8 @@ extern "C" { fn pango_font_description_from_string(str: *const c::c_char) -> *mut PangoFontDescription_; fn pango_font_description_free(desc: *mut PangoFontDescription_); + fn pango_font_description_get_size(desc: *mut PangoFontDescription_) -> c::c_int; + fn pango_font_description_set_size(desc: *mut PangoFontDescription_, size: c::c_int); fn pango_layout_new(context: *mut PangoContext_) -> *mut PangoLayout_; fn pango_layout_set_width(layout: *mut PangoLayout_, width: c::c_int); @@ -256,6 +258,16 @@ impl PangoFontDescription { s: unsafe { pango_font_description_from_string(s.as_ptr()) }, } } + + pub fn size(&self) -> i32 { + unsafe { pango_font_description_get_size(self.s) as _ } + } + + pub fn set_size(&mut self, size: i32) { + unsafe { + pango_font_description_set_size(self.s, size); + } + } } impl Drop for PangoFontDescription { diff --git a/src/render/renderer/framebuffer.rs b/src/render/renderer/framebuffer.rs index a4fe1aad..1549a14e 100644 --- a/src/render/renderer/framebuffer.rs +++ b/src/render/renderer/framebuffer.rs @@ -1,5 +1,6 @@ use { crate::{ + fixed::Fixed, format::{Format, XRGB8888}, rect::Rect, render::{ @@ -55,14 +56,19 @@ impl Framebuffer { glViewport(0, 0, self.gl.width, self.gl.height); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); } + let scale = Fixed::from_int(1); let mut renderer = Renderer { ctx: &self.ctx, fb: &self.gl, state, on_output: false, result: &mut RenderResult::default(), + scaled: false, + scale, + scalef: 1.0, + logical_extents: Rect::new_sized(0, 0, self.gl.width, self.gl.height).unwrap(), }; - renderer.render_texture(texture, x, y, XRGB8888, None, None); + renderer.render_texture(texture, x, y, XRGB8888, None, None, scale); unsafe { glFlush(); } @@ -106,6 +112,7 @@ impl Framebuffer { cursor_rect: Option, on_output: bool, result: &mut RenderResult, + scale: Fixed, ) { let _ = self.ctx.ctx.with_current(|| { let c = state.theme.colors.background.get(); @@ -122,29 +129,31 @@ impl Framebuffer { state, on_output, result, + scaled: scale != 1, + scale, + scalef: scale.to_f64(), + logical_extents: node.node_absolute_position().at_point(0, 0), }; node.node_render(&mut renderer, 0, 0); if let Some(rect) = cursor_rect { let seats = state.globals.lock_seats(); for seat in seats.values() { if let Some(cursor) = seat.get_cursor() { - cursor.tick(); - let extents = cursor.extents(); + let (mut x, mut y) = seat.get_position(); if let Some(dnd_icon) = seat.dnd_icon() { - let (x_hot, y_hot) = cursor.get_hotspot(); let extents = dnd_icon.extents.get().move_( - extents.x1() + x_hot + dnd_icon.buf_x.get(), - extents.y1() + y_hot + dnd_icon.buf_y.get(), + x.round_down() + dnd_icon.buf_x.get(), + y.round_down() + dnd_icon.buf_y.get(), ); if extents.intersects(&rect) { let (x, y) = rect.translate(extents.x1(), extents.y1()); renderer.render_surface(&dnd_icon, x, y); } } - if extents.intersects(&rect) { - let (x, y) = rect.translate(extents.x1(), extents.y1()); - cursor.render(&mut renderer, x, y); - } + cursor.tick(); + x -= Fixed::from_int(rect.x1()); + y -= Fixed::from_int(rect.y1()); + cursor.render(&mut renderer, x, y); } } } diff --git a/src/render/renderer/renderer.rs b/src/render/renderer/renderer.rs index 64ee16ae..9584635c 100644 --- a/src/render/renderer/renderer.rs +++ b/src/render/renderer/renderer.rs @@ -1,5 +1,6 @@ use { crate::{ + fixed::Fixed, format::{Format, ARGB8888}, ifs::{ wl_buffer::WlBuffer, @@ -59,9 +60,44 @@ pub struct Renderer<'a> { pub(super) state: &'a State, pub(super) on_output: bool, pub(super) result: &'a mut RenderResult, + pub(super) scaled: bool, + pub(super) scale: Fixed, + pub(super) scalef: f64, + pub(super) logical_extents: Rect, } impl Renderer<'_> { + pub fn scale(&self) -> Fixed { + self.scale + } + + pub fn physical_extents(&self) -> Rect { + Rect::new_sized(0, 0, self.fb.width, self.fb.height).unwrap() + } + + pub fn logical_extents(&self) -> Rect { + self.logical_extents + } + + pub(super) fn scale_point(&self, mut x: i32, mut y: i32) -> (i32, i32) { + if self.scaled { + x = (x as f64 * self.scalef).round() as _; + y = (y as f64 * self.scalef).round() as _; + } + (x, y) + } + + fn scale_rect(&self, mut rect: Rect) -> Rect { + if self.scaled { + let x1 = (rect.x1() as f64 * self.scalef).round() as _; + let y1 = (rect.y1() as f64 * self.scalef).round() as _; + let x2 = (rect.x2() as f64 * self.scalef).round() as _; + let y2 = (rect.y2() as f64 * self.scalef).round() as _; + rect = Rect::new(x1, y1, x2, y2).unwrap(); + } + rect + } + pub fn render_display(&mut self, display: &DisplayNode, x: i32, y: i32) { let ext = display.extents.get(); let outputs = display.outputs.lock(); @@ -121,25 +157,14 @@ impl Renderer<'_> { self.fill_boxes2(slice::from_ref(&rd.underline), &c, x, y); let c = theme.colors.unfocused_title_background.get(); self.fill_boxes2(&rd.inactive_workspaces, &c, x, y); + let scale = output.preferred_scale.get(); for title in &rd.titles { - self.render_texture( - &title.tex, - x + title.tex_x, - y + title.tex_y, - ARGB8888, - None, - None, - ); + let (x, y) = self.scale_point(x + title.tex_x, y + title.tex_y); + self.render_texture(&title.tex, x, y, ARGB8888, None, None, scale); } if let Some(status) = &rd.status { - self.render_texture( - &status.tex, - x + status.tex_x, - y + status.tex_y, - ARGB8888, - None, - None, - ); + let (x, y) = self.scale_point(x + status.tex_x, y + status.tex_y); + self.render_texture(&status.tex, x, y, ARGB8888, None, None, scale); } } if let Some(ws) = output.workspace.get() { @@ -180,8 +205,10 @@ impl Renderer<'_> { if boxes.is_empty() { return; } + let (dx, dy) = self.scale_point(dx, dy); let mut pos = Vec::with_capacity(boxes.len() * 12); for bx in boxes { + let bx = self.scale_rect(*bx); let x1 = self.x_to_f(bx.x1() + dx); let y1 = self.y_to_f(bx.y1() + dy); let x2 = self.x_to_f(bx.x2() + dx); @@ -220,10 +247,10 @@ impl Renderer<'_> { std::slice::from_ref(&pos.at_point(x, y)), &Color::from_rgba_straight(20, 20, 20, 255), ); - if let Some(tex) = placeholder.texture.get() { + if let Some(tex) = placeholder.textures.get(&self.scale) { let x = x + (pos.width() - tex.width()) / 2; let y = y + (pos.height() - tex.height()) / 2; - self.render_texture(&tex, x, y, &ARGB8888, None, None); + self.render_texture(&tex, x, y, &ARGB8888, None, None, self.scale); } } @@ -247,13 +274,17 @@ impl Renderer<'_> { .get(); self.fill_boxes2(std::slice::from_ref(lar), &c, x, y); } - for title in &rd.titles { - self.render_texture(&title.tex, x + title.x, y + title.y, ARGB8888, None, None); + if let Some(titles) = rd.titles.get(&self.scale) { + for title in titles { + let (x, y) = self.scale_point(x + title.x, y + title.y); + self.render_texture(&title.tex, x, y, ARGB8888, None, None, self.scale); + } } } if let Some(child) = container.mono_child.get() { unsafe { let body = container.mono_body.get().move_(x, y); + let body = self.scale_rect(body); with_scissor(&body, || { let content = container.mono_content.get(); child @@ -268,6 +299,7 @@ impl Renderer<'_> { break; } let body = body.move_(x, y); + let body = self.scale_rect(body); unsafe { with_scissor(&body, || { let content = child.content.get(); @@ -291,6 +323,17 @@ impl Renderer<'_> { } pub fn render_surface(&mut self, surface: &WlSurface, x: i32, y: i32) { + let (x, y) = self.scale_point(x, y); + self.render_surface_scaled(surface, x, y, None); + } + + pub fn render_surface_scaled( + &mut self, + surface: &WlSurface, + x: i32, + y: i32, + pos_rel: Option<(i32, i32)>, + ) { let children = surface.children.borrow(); let buffer = match surface.buffer.get() { Some(b) => b, @@ -302,7 +345,14 @@ impl Renderer<'_> { } }; let tpoints = surface.buffer_points_norm.borrow_mut(); - let size = surface.buffer_abs_pos.get().size(); + let mut size = surface.buffer_abs_pos.get().size(); + if let Some((x_rel, y_rel)) = pos_rel { + let (x, y) = self.scale_point(x_rel, y_rel); + let (w, h) = self.scale_point(x_rel + size.0, y_rel + size.1); + size = (w - x, h - y); + } else { + size = self.scale_point(size.0, size.1); + } if let Some(children) = children.deref() { macro_rules! render { ($children:expr) => { @@ -311,7 +361,13 @@ impl Renderer<'_> { continue; } let pos = child.sub_surface.position.get(); - self.render_surface(&child.sub_surface.surface, x + pos.x1(), y + pos.y1()); + let (x1, y1) = self.scale_point(pos.x1(), pos.y1()); + self.render_surface_scaled( + &child.sub_surface.surface, + x + x1, + y + y1, + Some((pos.x1(), pos.y1())), + ); } }; } @@ -342,7 +398,15 @@ impl Renderer<'_> { tsize: (i32, i32), ) { if let Some(tex) = buffer.texture.get() { - self.render_texture(&tex, x, y, buffer.format, Some(tpoints), Some(tsize)); + self.render_texture( + &tex, + x, + y, + buffer.format, + Some(tpoints), + Some(tsize), + self.scale, + ); } } @@ -354,6 +418,7 @@ impl Renderer<'_> { format: &Format, tpoints: Option<&[f32; 8]>, tsize: Option<(i32, i32)>, + tscale: Fixed, ) { assert!(rc_eq(&self.ctx.ctx, &texture.ctx.ctx)); unsafe { @@ -402,7 +467,13 @@ impl Renderer<'_> { let (twidth, theight) = if let Some(size) = tsize { size } else { - (texture.gl.width, texture.gl.height) + let (mut w, mut h) = (texture.gl.width, texture.gl.height); + if tscale != self.scale { + let tscale = tscale.to_f64(); + w = (w as f64 * self.scalef / tscale).round() as _; + h = (h as f64 * self.scalef / tscale).round() as _; + } + (w, h) }; let x1 = 2.0 * (x as f32 / f_width) - 1.0; @@ -466,8 +537,9 @@ impl Renderer<'_> { let title_underline = [Rect::new_sized(x + bw, y + bw + th, pos.width() - 2 * bw, 1).unwrap()]; self.fill_boxes(&title_underline, &uc); - if let Some(title) = floating.title_texture.get() { - self.render_texture(&title, x + bw, y + bw, ARGB8888, None, None); + if let Some(title) = floating.title_textures.get(&self.scale) { + let (x, y) = self.scale_point(x + bw, y + bw); + self.render_texture(&title, x, y, ARGB8888, None, None, self.scale); } let body = Rect::new_sized( x + bw, @@ -476,8 +548,9 @@ impl Renderer<'_> { pos.height() - 2 * bw - th - 1, ) .unwrap(); + let scissor_body = self.scale_rect(body); unsafe { - with_scissor(&body, || { + with_scissor(&scissor_body, || { child.node_render(self, body.x1(), body.y1()); }); } @@ -486,6 +559,7 @@ impl Renderer<'_> { pub fn render_layer_surface(&mut self, surface: &ZwlrLayerSurfaceV1, x: i32, y: i32) { unsafe { let body = surface.position().at_point(x, y); + let body = self.scale_rect(body); with_scissor(&body, || { self.render_surface(&surface.surface, x, y); }); diff --git a/src/screenshoter.rs b/src/screenshoter.rs index b6b73fe0..de4f86ba 100644 --- a/src/screenshoter.rs +++ b/src/screenshoter.rs @@ -1,5 +1,6 @@ use { crate::{ + fixed::Fixed, format::XRGB8888, render::RenderError, state::State, @@ -59,6 +60,7 @@ pub fn take_screenshot(state: &State) -> Result Some(state.root.extents.get()), false, &mut Default::default(), + Fixed::from_int(1), ); let drm = ctx.gbm.drm.dup_render()?.fd().clone(); Ok(Screenshot { drm, bo }) diff --git a/src/state.rs b/src/state.rs index 1aa9a048..f96ca385 100644 --- a/src/state.rs +++ b/src/state.rs @@ -12,6 +12,7 @@ use { config::ConfigProxy, cursor::ServerCursors, dbus::Dbus, + fixed::Fixed, forker::ForkerProxy, globals::{Globals, GlobalsError, WaylandGlobal}, ifs::{ @@ -37,7 +38,7 @@ use { utils::{ asyncevent::AsyncEvent, clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, fdcloser::FdCloser, linkedlist::LinkedList, numcell::NumCell, - queue::AsyncQueue, run_toplevel::RunToplevel, + queue::AsyncQueue, refcounted::RefCounted, run_toplevel::RunToplevel, }, wheel::Wheel, xkbcommon::{XkbContext, XkbKeymap}, @@ -110,6 +111,8 @@ pub struct State { pub data_offer_ids: NumCell, pub ring: Rc, pub lock: ScreenlockState, + pub scales: RefCounted, + pub cursor_sizes: RefCounted, } // impl Drop for State { @@ -202,10 +205,64 @@ pub struct DrmDevData { pub pci_id: Option, } +struct UpdateTextTexturesVisitor; +impl NodeVisitorBase for UpdateTextTexturesVisitor { + fn visit_container(&mut self, node: &Rc) { + node.schedule_compute_render_data(); + node.node_visit_children(self); + } + fn visit_output(&mut self, node: &Rc) { + node.update_render_data(); + node.node_visit_children(self); + } + fn visit_float(&mut self, node: &Rc) { + node.schedule_render_titles(); + node.node_visit_children(self); + } + fn visit_placeholder(&mut self, node: &Rc) { + node.update_texture(); + node.node_visit_children(self); + } +} + impl State { + pub fn add_output_scale(&self, scale: Fixed) { + if self.scales.add(scale) { + self.output_scales_changed(); + } + } + + pub fn remove_output_scale(&self, scale: Fixed) { + if self.scales.remove(&scale) { + self.output_scales_changed(); + } + } + + pub fn add_cursor_size(&self, size: u32) { + if self.cursor_sizes.add(size) { + self.cursor_sizes_changed(); + } + } + + pub fn remove_cursor_size(&self, size: u32) { + if self.cursor_sizes.remove(&size) { + self.cursor_sizes_changed(); + } + } + + fn output_scales_changed(&self) { + UpdateTextTexturesVisitor.visit_display(&self.root); + self.reload_cursors(); + } + + fn cursor_sizes_changed(&self) { + self.reload_cursors(); + } + pub fn set_render_ctx(&self, ctx: Option<&Rc>) { self.render_ctx.set(ctx.cloned()); self.render_ctx_version.fetch_add(1); + self.cursors.set(None); { struct Walker; @@ -220,11 +277,11 @@ impl State { node.node_visit_children(self); } fn visit_float(&mut self, node: &Rc) { - node.title_texture.set(None); + node.title_textures.clear(); node.node_visit_children(self); } fn visit_placeholder(&mut self, node: &Rc) { - node.texture.set(None); + node.textures.clear(); node.node_visit_children(self); } fn visit_surface(&mut self, node: &Rc) { @@ -242,36 +299,9 @@ impl State { } } - if let Some(ctx) = ctx { - let cursors = match ServerCursors::load(ctx) { - Ok(c) => Some(Rc::new(c)), - Err(e) => { - log::error!("Could not load the cursors: {}", ErrorFmt(e)); - None - } - }; - self.cursors.set(cursors); - - struct Walker; - impl NodeVisitorBase for Walker { - fn visit_container(&mut self, node: &Rc) { - node.schedule_compute_render_data(); - node.node_visit_children(self); - } - fn visit_output(&mut self, node: &Rc) { - node.update_render_data(); - node.node_visit_children(self); - } - fn visit_float(&mut self, node: &Rc) { - node.schedule_render_titles(); - node.node_visit_children(self); - } - fn visit_placeholder(&mut self, node: &Rc) { - node.update_texture(); - node.node_visit_children(self); - } - } - Walker.visit_display(&self.root); + if ctx.is_some() { + self.reload_cursors(); + UpdateTextTexturesVisitor.visit_display(&self.root); } let seats = self.globals.seats.lock(); @@ -288,6 +318,22 @@ impl State { } } + fn reload_cursors(&self) { + if let Some(ctx) = self.render_ctx.get() { + let cursors = match ServerCursors::load(&ctx, self) { + Ok(c) => c.map(Rc::new), + Err(e) => { + log::error!("Could not load the cursors: {}", ErrorFmt(e)); + None + } + }; + self.cursors.set(cursors); + for seat in self.globals.seats.lock().values() { + seat.reload_known_cursor(); + } + } + } + pub fn add_global(&self, global: &Rc) { self.globals.add_global(self, global) } diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index 01cb7fa5..3428beef 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -1,6 +1,7 @@ use { crate::{ backend::{Connector, ConnectorEvent, ConnectorId, MonitorInfo}, + fixed::Fixed, ifs::wl_output::WlOutputGlobal, state::{ConnectorData, OutputData, State}, tree::{OutputNode, OutputRenderData}, @@ -121,7 +122,9 @@ impl ConnectorHandler { scroll: Default::default(), pointer_positions: Default::default(), lock_surface: Default::default(), + preferred_scale: Cell::new(Fixed::from_int(1)), }); + self.state.add_output_scale(on.preferred_scale.get()); let mode = info.initial_mode; let output_data = Rc::new(OutputData { connector: self.data.clone(), @@ -154,7 +157,7 @@ impl ConnectorHandler { } } for ws in ws_to_move { - ws.output.set(on.clone()); + ws.set_output(&on); on.workspaces.add_last_existing(&ws); if ws.visible_on_desired_output.get() && on.workspace.get().is_none() { on.show_workspace(&ws); @@ -220,7 +223,7 @@ impl ConnectorHandler { let is_visible = !target_is_dummy && target.workspaces.is_empty() && ws.visible.get(); ws.visible_on_desired_output.set(ws.visible.get()); - ws.output.set(target.clone()); + ws.set_output(&target); target.workspaces.add_last_existing(&ws); if is_visible { target.show_workspace(&ws); @@ -243,5 +246,6 @@ impl ConnectorHandler { if let Some(dev) = &self.data.drm_dev { dev.connectors.remove(&self.id); } + self.state.remove_output_scale(on.preferred_scale.get()); } } diff --git a/src/text.rs b/src/text.rs index de3db234..87c97c58 100644 --- a/src/text.rs +++ b/src/text.rs @@ -40,7 +40,7 @@ struct Data { layout: PangoLayout, } -fn create_data(font: &str, width: i32, height: i32) -> Result { +fn create_data(font: &str, width: i32, height: i32, scale: Option) -> Result { let image = match CairoImageSurface::new_image_surface(CAIRO_FORMAT_ARGB32, width, height) { Ok(s) => s, Err(e) => return Err(TextError::CreateImage(e)), @@ -53,7 +53,10 @@ fn create_data(font: &str, width: i32, height: i32) -> Result { Ok(c) => c, Err(e) => return Err(TextError::PangoContext(e)), }; - let fd = PangoFontDescription::from_string(font); + let mut fd = PangoFontDescription::from_string(font); + if let Some(scale) = scale { + fd.set_size((fd.size() as f64 * scale).round() as _); + } let layout = match pctx.create_layout() { Ok(l) => l, Err(e) => return Err(TextError::CreateLayout(e)), @@ -68,8 +71,13 @@ fn create_data(font: &str, width: i32, height: i32) -> Result { }) } -pub fn measure(font: &str, text: &str, markup: bool) -> Result { - let data = create_data(font, 1, 1)?; +pub fn measure( + font: &str, + text: &str, + markup: bool, + scale: Option, +) -> Result { + let data = create_data(font, 1, 1, scale)?; if markup { data.layout.set_markup(text); } else { @@ -85,8 +93,11 @@ pub fn render( font: &str, text: &str, color: Color, + scale: Option, ) -> Result, TextError> { - render2(ctx, 1, width, height, 1, font, text, color, true, false) + render2( + ctx, 1, width, height, 1, font, text, color, true, false, scale, + ) } fn render2( @@ -100,8 +111,9 @@ fn render2( color: Color, ellipsize: bool, markup: bool, + scale: Option, ) -> Result, TextError> { - let data = create_data(font, width, height)?; + let data = create_data(font, width, height, scale)?; if ellipsize { data.layout .set_width((width - 2 * padding).max(0) * PANGO_SCALE); @@ -137,8 +149,9 @@ pub fn render_fitting( text: &str, color: Color, markup: bool, + scale: Option, ) -> Result, TextError> { - let rect = measure(font, text, markup)?; + let rect = measure(font, text, markup, scale)?; render2( ctx, rect.x1().neg(), @@ -150,5 +163,6 @@ pub fn render_fitting( color, false, markup, + scale, ) } diff --git a/src/tree/container.rs b/src/tree/container.rs index 2601cbe9..9fd9d64c 100644 --- a/src/tree/container.rs +++ b/src/tree/container.rs @@ -22,6 +22,7 @@ use { numcell::NumCell, rc_eq::rc_eq, scroller::Scroller, + smallmap::SmallMapMut, }, }, ahash::AHashMap, @@ -84,7 +85,7 @@ pub struct ContainerRenderData { pub last_active_rect: Option, pub border_rects: Vec, pub underline_rects: Vec, - pub titles: Vec, + pub titles: SmallMapMut, 2>, } pub struct ContainerNode { @@ -634,7 +635,9 @@ impl ContainerNode { let cwidth = self.width.get(); let cheight = self.height.get(); let ctx = self.state.render_ctx.get(); - rd.titles.clear(); + for (_, v) in rd.titles.iter_mut() { + v.clear(); + } rd.title_rects.clear(); rd.active_title_rects.clear(); rd.border_rects.clear(); @@ -644,6 +647,7 @@ impl ContainerNode { let mono = self.mono_child.get().is_some(); let split = self.split.get(); let have_active = self.children.iter().any(|c| c.active.get()); + let scales = self.state.scales.lock(); for (i, child) in self.children.iter().enumerate() { let rect = child.title_rect.get(); if i > 0 { @@ -670,20 +674,32 @@ impl ContainerNode { let rect = Rect::new_sized(rect.x1(), rect.y2(), rect.width(), 1).unwrap(); rd.underline_rects.push(rect); } - 'render_title: { - let title = child.title.borrow_mut(); - if th == 0 || rect.width() == 0 || title.is_empty() { - break 'render_title; - } - if let Some(ctx) = &ctx { - match text::render(ctx, rect.width(), th, &font, title.deref(), color) { - Ok(t) => rd.titles.push(ContainerTitle { - x: rect.x1(), - y: rect.y1(), - tex: t, - }), - Err(e) => { - log::error!("Could not render title {}: {}", title, ErrorFmt(e)); + let title = child.title.borrow_mut(); + for (scale, _) in scales.iter() { + let titles = rd.titles.get_or_default_mut(*scale); + 'render_title: { + let mut th = th; + let mut scalef = None; + let mut width = rect.width(); + if *scale != 1 { + let scale = scale.to_f64(); + th = (th as f64 * scale).round() as _; + width = (width as f64 * scale).round() as _; + scalef = Some(scale); + } + if th == 0 || width == 0 || title.is_empty() { + break 'render_title; + } + if let Some(ctx) = &ctx { + match text::render(ctx, width, th, &font, title.deref(), color, scalef) { + Ok(t) => titles.push(ContainerTitle { + x: rect.x1(), + y: rect.y1(), + tex: t, + }), + Err(e) => { + log::error!("Could not render title {}: {}", title, ErrorFmt(e)); + } } } } @@ -693,6 +709,7 @@ impl ContainerNode { rd.underline_rects .push(Rect::new_sized(0, th, cwidth, 1).unwrap()); } + rd.titles.remove_if(|_, v| v.is_empty()); } fn activate_child(self: &Rc, child: &NodeRef) { @@ -1349,8 +1366,7 @@ impl ToplevelNode for ContainerNode { self.parent.set(parent); } - fn tl_set_workspace(self: Rc, ws: &Rc) { - self.toplevel_data.workspace.set(Some(ws.clone())); + fn tl_set_workspace_ext(self: Rc, ws: &Rc) { for child in self.children.iter() { child.node.clone().tl_set_workspace(ws); } diff --git a/src/tree/float.rs b/src/tree/float.rs index c439c97c..52aeeb8a 100644 --- a/src/tree/float.rs +++ b/src/tree/float.rs @@ -12,7 +12,10 @@ use { walker::NodeVisitor, ContainingNode, FindTreeResult, FoundNode, Node, NodeId, StackedNode, ToplevelNode, WorkspaceNode, }, - utils::{clonecell::CloneCell, errorfmt::ErrorFmt, linkedlist::LinkedNode}, + utils::{ + clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, + linkedlist::LinkedNode, + }, }, ahash::AHashMap, std::{ @@ -39,7 +42,7 @@ pub struct FloatNode { pub layout_scheduled: Cell, pub render_titles_scheduled: Cell, pub title: RefCell, - pub title_texture: CloneCell>>, + pub title_textures: CopyHashMap>, seats: RefCell>, } @@ -106,7 +109,7 @@ impl FloatNode { layout_scheduled: Cell::new(false), render_titles_scheduled: Cell::new(false), title: Default::default(), - title_texture: Default::default(), + title_textures: Default::default(), seats: Default::default(), }); floater @@ -174,23 +177,38 @@ impl FloatNode { let bw = theme.sizes.border_width.get(); let font = theme.font.borrow_mut(); let title = self.title.borrow_mut(); - self.title_texture.set(None); + self.title_textures.clear(); let pos = self.position.get(); - if pos.width() <= 2 * bw || th == 0 || title.is_empty() { + if pos.width() <= 2 * bw || title.is_empty() { return; } let ctx = match self.state.render_ctx.get() { Some(c) => c, _ => return, }; - let texture = match text::render(&ctx, pos.width() - 2 * bw, th, &font, &title, tc) { - Ok(t) => t, - Err(e) => { - log::error!("Could not render title {}: {}", title, ErrorFmt(e)); - return; + let scales = self.state.scales.lock(); + for (scale, _) in scales.iter() { + let mut th = th; + let mut scalef = None; + let mut width = pos.width() - 2 * bw; + if *scale != 1 { + let scale = scale.to_f64(); + th = (th as f64 * scale).round() as _; + width = (width as f64 * scale).round() as _; + scalef = Some(scale); } - }; - self.title_texture.set(Some(texture)); + if th == 0 || width == 0 { + continue; + } + let texture = match text::render(&ctx, width, th, &font, &title, tc, scalef) { + Ok(t) => t, + Err(e) => { + log::error!("Could not render title {}: {}", title, ErrorFmt(e)); + return; + } + }; + self.title_textures.set(*scale, texture); + } } fn pointer_move(self: &Rc, seat: &Rc, x: i32, y: i32) { diff --git a/src/tree/output.rs b/src/tree/output.rs index eb96fab2..df6dab6a 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -11,7 +11,7 @@ use { }, wl_surface::{ ext_session_lock_surface_v1::ExtSessionLockSurfaceV1, - zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, + zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, SurfaceSendPreferredScaleVisitor, }, zwlr_layer_shell_v1::{BACKGROUND, BOTTOM, OVERLAY, TOP}, }, @@ -51,6 +51,7 @@ pub struct OutputNode { pub scroll: Scroller, pub pointer_positions: CopyHashMap, pub lock_surface: CloneCell>>, + pub preferred_scale: Cell, } impl OutputNode { @@ -72,6 +73,24 @@ impl OutputNode { } } + pub fn set_preferred_scale(&self, scale: Fixed) { + let old_scale = self.preferred_scale.replace(scale); + if scale == old_scale { + return; + } + let legacy_scale = scale.round_up(); + if self.global.legacy_scale.replace(legacy_scale) != legacy_scale { + self.global.send_mode(); + } + self.state.remove_output_scale(old_scale); + self.state.add_output_scale(scale); + let rect = self.calculate_extents(); + self.change_extents_(&rect); + let mut visitor = SurfaceSendPreferredScaleVisitor(scale); + self.node_visit_children(&mut visitor); + self.update_render_data(); + } + pub fn update_render_data(&self) { let mut rd = self.render_data.borrow_mut(); rd.titles.clear(); @@ -82,9 +101,19 @@ impl OutputNode { let font = self.state.theme.font.borrow_mut(); let theme = &self.state.theme; let th = theme.sizes.title_height.get(); + let scale = self.preferred_scale.get(); + let scale = if scale != 1 { + Some(scale.to_f64()) + } else { + None + }; + let mut texture_height = th; + if let Some(scale) = scale { + texture_height = (th as f64 * scale).round() as _; + } let active_id = self.workspace.get().map(|w| w.id); - let width = self.global.pos.get().width(); - rd.underline = Rect::new_sized(0, th, width, 1).unwrap(); + let output_width = self.global.pos.get().width(); + rd.underline = Rect::new_sized(0, th, output_width, 1).unwrap(); for ws in self.workspaces.iter() { let mut title_width = th; 'create_texture: { @@ -96,7 +125,15 @@ impl OutputNode { true => theme.colors.focused_title_text.get(), false => theme.colors.unfocused_title_text.get(), }; - let title = match text::render_fitting(&ctx, th, &font, &ws.name, tc, false) { + let title = match text::render_fitting( + &ctx, + texture_height, + &font, + &ws.name, + tc, + false, + scale, + ) { Ok(t) => t, Err(e) => { log::error!("Could not render title {}: {}", ws.name, ErrorFmt(e)); @@ -104,10 +141,14 @@ impl OutputNode { } }; let mut x = pos + 1; - if title.width() + 2 > title_width { - title_width = title.width() + 2; + let mut width = title.width(); + if let Some(scale) = scale { + width = (width as f64 / scale).round() as _; + } + if width + 2 > title_width { + title_width = width + 2; } else { - x = pos + (title_width - title.width()) / 2; + x = pos + (title_width - width) / 2; } rd.titles.push(OutputTitle { x1: pos, @@ -137,14 +178,19 @@ impl OutputNode { break 'set_status; } let tc = self.state.theme.colors.bar_text.get(); - let title = match text::render_fitting(&ctx, th, &font, &status, tc, true) { - Ok(t) => t, - Err(e) => { - log::error!("Could not render status {}: {}", status, ErrorFmt(e)); - break 'set_status; - } - }; - let pos = width - title.width() - 1; + let title = + match text::render_fitting(&ctx, texture_height, &font, &status, tc, true, scale) { + Ok(t) => t, + Err(e) => { + log::error!("Could not render status {}: {}", status, ErrorFmt(e)); + break 'set_status; + } + }; + let mut width = title.width(); + if let Some(scale) = scale { + width = (width as f64 / scale).round() as _; + } + let pos = output_width - width - 1; rd.status = Some(OutputStatus { tex_x: pos, tex_y: 0, @@ -266,11 +312,24 @@ impl OutputNode { return; } self.global.mode.set(mode); - let pos = self.global.pos.get(); - let rect = Rect::new_sized(pos.x1(), pos.y1(), mode.width, mode.height).unwrap(); + let rect = self.calculate_extents(); self.change_extents_(&rect); } + fn calculate_extents(&self) -> Rect { + let mode = self.global.mode.get(); + let mut width = mode.width; + let mut height = mode.height; + let scale = self.preferred_scale.get(); + if scale != 1 { + let scale = scale.to_f64(); + width = (width as f64 / scale).round() as _; + height = (height as f64 / scale).round() as _; + } + let pos = self.global.pos.get(); + pos.with_size(width, height).unwrap() + } + fn change_extents_(&self, rect: &Rect) { self.global.pos.set(*rect); self.state.root.update_extents(); diff --git a/src/tree/placeholder.rs b/src/tree/placeholder.rs index 02f5c3bc..dc41a8e4 100644 --- a/src/tree/placeholder.rs +++ b/src/tree/placeholder.rs @@ -12,7 +12,7 @@ use { Direction, FindTreeResult, FoundNode, Node, NodeId, NodeVisitor, ToplevelData, ToplevelNode, }, - utils::{clonecell::CloneCell, errorfmt::ErrorFmt}, + utils::{errorfmt::ErrorFmt, smallmap::SmallMap}, }, std::{cell::Cell, ops::Deref, rc::Rc}, }; @@ -23,7 +23,7 @@ pub struct PlaceholderNode { id: PlaceholderNodeId, toplevel: ToplevelData, destroyed: Cell, - pub texture: CloneCell>>, + pub textures: SmallMap, 2>, } impl PlaceholderNode { @@ -36,7 +36,7 @@ impl PlaceholderNode { node.node_client(), ), destroyed: Default::default(), - texture: Default::default(), + textures: Default::default(), } } @@ -45,24 +45,35 @@ impl PlaceholderNode { } pub fn update_texture(&self) { - self.texture.set(None); + self.textures.clear(); if let Some(ctx) = self.toplevel.state.render_ctx.get() { + let scales = self.toplevel.state.scales.lock(); let rect = self.toplevel.pos.get(); - if rect.width() != 0 && rect.height() != 0 { - let font = format!("monospace {}", rect.width() / 10); - match text::render_fitting( - &ctx, - rect.height(), - &font, - "Fullscreen", - self.toplevel.state.theme.colors.unfocused_title_text.get(), - false, - ) { - Ok(t) => { - self.texture.set(Some(t)); - } - Err(e) => { - log::warn!("Could not render fullscreen texture: {}", ErrorFmt(e)); + for (scale, _) in scales.iter() { + let mut width = rect.width(); + let mut height = rect.height(); + if *scale != 1 { + let scale = scale.to_f64(); + width = (width as f64 * scale).round() as _; + height = (height as f64 * scale).round() as _; + } + if width != 0 && height != 0 { + let font = format!("monospace {}", width / 10); + match text::render_fitting( + &ctx, + height, + &font, + "Fullscreen", + self.toplevel.state.theme.colors.unfocused_title_text.get(), + false, + None, + ) { + Ok(t) => { + self.textures.insert(*scale, t); + } + Err(e) => { + log::warn!("Could not render fullscreen texture: {}", ErrorFmt(e)); + } } } } diff --git a/src/tree/toplevel.rs b/src/tree/toplevel.rs index 77338322..dea198cb 100644 --- a/src/tree/toplevel.rs +++ b/src/tree/toplevel.rs @@ -129,6 +129,11 @@ pub trait ToplevelNode: Node { fn tl_set_workspace(self: Rc, ws: &Rc) { let data = self.tl_data(); data.workspace.set(Some(ws.clone())); + self.tl_set_workspace_ext(ws); + } + + fn tl_set_workspace_ext(self: Rc, ws: &Rc) { + let _ = ws; } fn tl_change_extents(self: Rc, rect: &Rect) { diff --git a/src/tree/workspace.rs b/src/tree/workspace.rs index a15e77bf..11c38d74 100644 --- a/src/tree/workspace.rs +++ b/src/tree/workspace.rs @@ -4,6 +4,7 @@ use { ifs::{ wl_output::OutputId, wl_seat::{NodeSeatState, WlSeatGlobal}, + wl_surface::SurfaceSendPreferredScaleVisitor, }, rect::Rect, render::Renderer, @@ -16,7 +17,7 @@ use { linkedlist::{LinkedList, LinkedNode}, }, }, - std::{cell::Cell, fmt::Debug, rc::Rc}, + std::{cell::Cell, fmt::Debug, ops::Deref, rc::Rc}, }; tree_id!(WorkspaceNodeId); @@ -44,6 +45,17 @@ impl WorkspaceNode { self.fullscreen.set(None); } + pub fn set_output(&self, output: &Rc) { + let old = self.output.set(output.clone()); + if old.preferred_scale.get() != output.preferred_scale.get() { + let mut visitor = SurfaceSendPreferredScaleVisitor(output.preferred_scale.get()); + self.node_visit_children(&mut visitor); + for stacked in self.stacked.iter() { + stacked.deref().clone().node_visit(&mut visitor); + } + } + } + pub fn set_container(self: &Rc, container: &Rc) { let pos = self.position.get(); container.clone().tl_change_extents(&pos); diff --git a/src/utils.rs b/src/utils.rs index 34410108..e851cf03 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -22,6 +22,7 @@ pub mod oserror; pub mod ptr_ext; pub mod queue; pub mod rc_eq; +pub mod refcounted; pub mod run_toplevel; pub mod scroller; pub mod smallmap; diff --git a/src/utils/refcounted.rs b/src/utils/refcounted.rs new file mode 100644 index 00000000..fd3dbb2c --- /dev/null +++ b/src/utils/refcounted.rs @@ -0,0 +1,90 @@ +use { + crate::utils::ptr_ext::{MutPtrExt, PtrExt}, + std::{cell::UnsafeCell, mem, ops::Deref}, +}; + +pub struct RefCounted { + map: UnsafeCell>, +} + +impl Default for RefCounted { + fn default() -> Self { + Self { + map: UnsafeCell::new(vec![]), + } + } +} + +impl RefCounted { + pub fn add(&self, t: T) -> bool { + unsafe { + let map = self.map.get().deref_mut(); + for (k, v) in &mut *map { + if k == &t { + *v += 1; + return false; + } + } + map.push((t, 1)); + true + } + } + + pub fn remove(&self, t: &T) -> bool { + unsafe { + let map = self.map.get().deref_mut(); + let idx = 'idx: { + for (idx, (k, v)) in map.iter_mut().enumerate() { + if k == t { + *v -= 1; + if *v > 0 { + return false; + } else { + break 'idx idx; + } + } + } + return false; + }; + let _v = map.swap_remove(idx); + true + } + } + + pub fn to_vec(&self) -> Vec + where + T: Copy, + { + unsafe { self.map.get().deref().iter().map(|k| k.0).collect() } + } + + pub fn lock(&self) -> Locked { + unsafe { + Locked { + vec: mem::take(self.map.get().deref_mut()), + rc: self, + } + } + } +} + +pub struct Locked<'a, T> { + rc: &'a RefCounted, + vec: Vec<(T, usize)>, +} + +impl<'a, T> Deref for Locked<'a, T> { + type Target = [(T, usize)]; + + fn deref(&self) -> &Self::Target { + &self.vec + } +} + +impl<'a, T> Drop for Locked<'a, T> { + fn drop(&mut self) { + unsafe { + *self.rc.map.get() = mem::take(&mut self.vec); + } + } +} diff --git a/src/utils/smallmap.rs b/src/utils/smallmap.rs index 2fe67a90..bc9e98b1 100644 --- a/src/utils/smallmap.rs +++ b/src/utils/smallmap.rs @@ -12,16 +12,12 @@ use { }; pub struct SmallMap { - m: UnsafeCell>, + m: UnsafeCell>, } impl Debug for SmallMap { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - unsafe { - f.debug_map() - .entries(self.m.get().deref().iter().map(|e| (&e.0, &e.1))) - .finish() - } + unsafe { self.m.get().deref().fmt(f) } } } @@ -35,16 +31,14 @@ impl Default for SmallMap { impl SmallMap { pub fn new_with(k: K, v: V) -> Self { - let mut sv = SmallVec::new(); - sv.push((k, v)); Self { - m: UnsafeCell::new(sv), + m: UnsafeCell::new(SmallMapMut::new_with(k, v)), } } pub fn new() -> Self { Self { - m: UnsafeCell::new(SmallVec::new_const()), + m: UnsafeCell::new(SmallMapMut::new()), } } @@ -53,42 +47,25 @@ impl SmallMap { } pub fn insert(&self, k: K, v: V) -> Option { - unsafe { - let m = self.m.get().deref_mut(); - for (ek, ev) in &mut *m { - if ek == &k { - return Some(mem::replace(ev, v)); - } - } - m.push((k, v)); - None - } + unsafe { self.m.get().deref_mut().insert(k, v) } } pub fn is_empty(&self) -> bool { - unsafe { self.m.get().deref_mut().is_empty() } + unsafe { self.m.get().deref().is_empty() } } pub fn remove(&self, k: &K) -> Option { - unsafe { - let m = self.m.get().deref_mut(); - for (idx, (ek, _)) in m.iter_mut().enumerate() { - if ek == k { - return Some(m.swap_remove(idx).1); - } - } - None - } + unsafe { self.m.get().deref_mut().remove(k) } } pub fn clear(&self) { unsafe { - let _v = mem::replace(self.m.get().deref_mut(), SmallVec::new()); + let _v = self.m.get().deref_mut().clear(); } } pub fn take(&self) -> SmallVec<[(K, V); N]> { - unsafe { mem::take(self.m.get().deref_mut()) } + unsafe { self.m.get().deref_mut().take() } } pub fn pop(&self) -> Option<(K, V)> { @@ -103,7 +80,7 @@ impl SmallMap { impl SmallMap { pub fn get(&self, k: &K) -> Option { unsafe { - let m = self.m.get().deref(); + let m = &self.m.get().deref().m; for (ek, ev) in m { if ek == k { return Some(ev.clone()); @@ -133,7 +110,7 @@ impl<'a, K: Copy, V: UnsafeCellCloneSafe, const N: usize> Iterator for SmallMapI fn next(&mut self) -> Option { unsafe { - let v = self.map.m.get().deref(); + let v = &self.map.m.get().deref().m; if self.pos >= v.len() { return None; } @@ -143,3 +120,163 @@ impl<'a, K: Copy, V: UnsafeCellCloneSafe, const N: usize> Iterator for SmallMapI } } } + +pub struct SmallMapMut { + m: SmallVec<[(K, V); N]>, +} + +impl Debug for SmallMapMut { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_map() + .entries(self.m.iter().map(|e| (&e.0, &e.1))) + .finish() + } +} + +impl Default for SmallMapMut { + fn default() -> Self { + Self { + m: Default::default(), + } + } +} + +impl SmallMapMut { + pub fn new_with(k: K, v: V) -> Self { + let mut sv = SmallVec::new(); + sv.push((k, v)); + Self { m: sv } + } + + pub fn new() -> Self { + Self { + m: SmallVec::new_const(), + } + } + + pub fn len(&self) -> usize { + self.m.len() + } + + pub fn insert(&mut self, k: K, v: V) -> Option { + for (ek, ev) in &mut self.m { + if ek == &k { + return Some(mem::replace(ev, v)); + } + } + self.m.push((k, v)); + None + } + + pub fn get(&self, k: &K) -> Option<&V> { + for (ek, ev) in &self.m { + if ek == k { + return Some(ev); + } + } + None + } + + pub fn get_or_default_mut(&mut self, k: K) -> &mut V + where + V: Default, + { + for (ek, ev) in &mut self.m { + if ek == &k { + return unsafe { (ev as *mut V).deref_mut() }; + } + } + self.m.push((k, V::default())); + &mut self.m.last_mut().unwrap().1 + } + + pub fn is_empty(&self) -> bool { + self.m.is_empty() + } + + pub fn remove(&mut self, k: &K) -> Option { + for (idx, (ek, _)) in self.m.iter_mut().enumerate() { + if ek == k { + return Some(self.m.swap_remove(idx).1); + } + } + None + } + + pub fn clear(&mut self) { + let _v = mem::replace(&mut self.m, SmallVec::new()); + } + + pub fn take(&mut self) -> SmallVec<[(K, V); N]> { + mem::take(&mut self.m) + } + + pub fn pop(&mut self) -> Option<(K, V)> { + self.m.pop() + } + + pub fn iter<'a>(&'a self) -> SmallMapMutIter<'a, K, V, N> { + SmallMapMutIter { pos: 0, map: self } + } + + pub fn iter_mut<'a>(&'a mut self) -> SmallMapMutIterMut<'a, K, V, N> { + SmallMapMutIterMut { pos: 0, map: self } + } + + pub fn remove_if bool>(&mut self, mut f: F) { + let mut i = 0; + while i < self.m.len() { + let (k, v) = &self.m[i]; + if f(k, v) { + self.m.swap_remove(i); + } else { + i += 1; + } + } + } +} + +impl<'a, K: Copy, V, const N: usize> IntoIterator for &'a SmallMapMut { + type Item = (&'a K, &'a V); + type IntoIter = SmallMapMutIter<'a, K, V, N>; + + fn into_iter(self) -> Self::IntoIter { + SmallMapMutIter { pos: 0, map: self } + } +} + +pub struct SmallMapMutIter<'a, K, V, const N: usize> { + pos: usize, + map: &'a SmallMapMut, +} + +impl<'a, K, V, const N: usize> Iterator for SmallMapMutIter<'a, K, V, N> { + type Item = (&'a K, &'a V); + + fn next(&mut self) -> Option { + if self.pos >= self.map.m.len() { + return None; + } + let (k, v) = &self.map.m[self.pos]; + self.pos += 1; + Some((k, v)) + } +} + +pub struct SmallMapMutIterMut<'a, K, V, const N: usize> { + pos: usize, + map: &'a mut SmallMapMut, +} + +impl<'a, K, V, const N: usize> Iterator for SmallMapMutIterMut<'a, K, V, N> { + type Item = (&'a mut K, &'a mut V); + + fn next(&mut self) -> Option { + if self.pos >= self.map.m.len() { + return None; + } + let (k, v) = &mut self.map.m[self.pos]; + self.pos += 1; + unsafe { Some(((k as *mut K).deref_mut(), (v as *mut V).deref_mut())) } + } +} diff --git a/src/xwayland/xwm.rs b/src/xwayland/xwm.rs index 77157afe..c26f58a1 100644 --- a/src/xwayland/xwm.rs +++ b/src/xwayland/xwm.rs @@ -424,6 +424,10 @@ impl Wm { Some(f) => f, _ => break 'set_root_cursor, }; + let first = match first.iter().find(|i| i.0 .0 == 1) { + Some(f) => f.1, + _ => break 'set_root_cursor, + }; let cursor = match c .create_cursor( &first.pixels, diff --git a/wire/wp_fractional_scale_manager_v1.txt b/wire/wp_fractional_scale_manager_v1.txt new file mode 100644 index 00000000..ebc3f450 --- /dev/null +++ b/wire/wp_fractional_scale_manager_v1.txt @@ -0,0 +1,10 @@ +# requests + +msg destroy = 0 { + +} + +msg get_fractional_scale = 1 { + id: id(wp_fractional_scale_v1), + surface: id(wl_surface), +} diff --git a/wire/wp_fractional_scale_v1.txt b/wire/wp_fractional_scale_v1.txt new file mode 100644 index 00000000..01e44fe1 --- /dev/null +++ b/wire/wp_fractional_scale_v1.txt @@ -0,0 +1,11 @@ +# requests + +msg destroy = 0 { + +} + +# events + +msg preferred_scale = 0 { + scale: fixed, +}