Merge branch 'master_jammy' into applibrary-laucher_jammy

This commit is contained in:
Victoria Brekenfeld 2022-04-28 13:05:00 +02:00 committed by GitHub
commit fa25b9f1a4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 1624 additions and 763 deletions

2
Cargo.lock generated
View file

@ -1311,7 +1311,7 @@ checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
[[package]]
name = "smithay"
version = "0.3.0"
source = "git+https://github.com/pop-os/smithay?branch=main#78cfeb604a0563c2357a2ecd1ee048b71f96e1a4"
source = "git+https://github.com/pop-os/smithay?branch=main#9a2273f4c8fed28d20952a5493627edf9d3cdcfc"
dependencies = [
"appendlist",
"bitflags",

View file

@ -31,9 +31,10 @@
(modifiers: [Logo], key: "j"): Focus(Down),
(modifiers: [Logo], key: "k"): Focus(Up),
(modifiers: [Logo], key: "l"): Focus(Right),
//TODO: automatic orientation with Logo+o toggling
//TODO: automatic orientation with Logo+o toggling
(modifiers: [Logo], key: "v"): Orientation(Vertical),
(modifiers: [Logo], key: "o"): Orientation(Horizontal),
(modifiers: [Logo, Shift], key: "f"): Fullscreen,
//TODO: ability to select default web browser
(modifiers: [Logo], key: "b"): Spawn("firefox"),
//TODO: ability to select default file browser

View file

@ -16,6 +16,7 @@ use smithay::{
allocator::{gbm::GbmDevice, Format},
drm::{DrmDevice, DrmEvent, DrmEventTime, DrmNode, GbmBufferedSurface, NodeType},
egl::{EGLContext, EGLDevice, EGLDisplay},
input::InputEvent,
libinput::{LibinputInputBackend, LibinputSessionInterface},
renderer::{
multigpu::{egl::EglGlesBackend, GpuManager},
@ -34,7 +35,7 @@ use smithay::{
nix::{fcntl::OFlag, sys::stat::dev_t},
wayland_server::protocol::wl_output,
},
utils::signaling::{Linkable, Signaler},
utils::signaling::{Linkable, Signaler, SignalToken},
wayland::output::{Mode as OutputMode, Output, PhysicalProperties},
};
@ -58,7 +59,8 @@ pub struct KmsState {
primary: DrmNode,
session: AutoSession,
signaler: Signaler<Signal>,
tokens: Vec<RegistrationToken>,
_restart_token: SignalToken,
_tokens: Vec<RegistrationToken>,
}
pub struct Device {
@ -86,7 +88,7 @@ pub struct Surface {
fps: Fps,
}
pub fn init_backend(event_loop: &mut EventLoop<State>, state: &mut State) -> Result<()> {
pub fn init_backend(event_loop: &mut EventLoop<'static, State>, state: &mut State) -> Result<()> {
let (session, notifier) = AutoSession::new(None).context("Failed to acquire session")?;
let signaler = notifier.signaler();
@ -101,8 +103,11 @@ pub fn init_backend(event_loop: &mut EventLoop<State>, state: &mut State) -> Res
let libinput_event_source = event_loop
.handle()
.insert_source(libinput_backend, move |event, _, state| {
state.common.process_input_event(event);
.insert_source(libinput_backend, move |mut event, _, state| {
if let &mut InputEvent::DeviceAdded { ref mut device } = &mut event {
state.common.config.read_device(device);
}
state.process_input_event(event);
for output in state.common.shell.outputs() {
state.backend.kms().schedule_render(output);
}
@ -143,51 +148,99 @@ pub fn init_backend(event_loop: &mut EventLoop<State>, state: &mut State) -> Res
};
slog_scope::info!("Using {} as primary gpu for rendering", primary);
let udev_dispatcher = Dispatcher::new(udev_backend, move |event, _, state: &mut State| {
match match event {
UdevEvent::Added { device_id, path } => state
.device_added(device_id, path)
.with_context(|| format!("Failed to add drm device: {}", device_id)),
UdevEvent::Changed { device_id } => state
.device_changed(device_id)
.with_context(|| format!("Failed to update drm device: {}", device_id)),
UdevEvent::Removed { device_id } => state
.device_removed(device_id)
.with_context(|| format!("Failed to remove drm device: {}", device_id)),
} {
Ok(()) => {
slog_scope::debug!("Successfully handled udev event")
}
Err(err) => {
slog_scope::error!("Error while handling udev event: {}", err)
}
}
});
let udev_event_source = event_loop
.handle()
.register_dispatcher(udev_dispatcher.clone())
.unwrap();
let handle = event_loop.handle();
let loop_signal = state.common.event_loop_signal.clone();
let dispatcher = udev_dispatcher.clone();
let _restart_token = signaler.register(move |signal| {
if let Signal::ActivateSession = signal {
let dispatcher = dispatcher.clone();
handle.insert_idle(move |state| {
for (dev, path) in dispatcher.as_source_ref().device_list() {
let drm_node = match DrmNode::from_dev_id(dev) {
Ok(node) => node,
Err(err) => {
slog_scope::error!("Failed to read drm device {}: {}", path.display(), err);
continue
},
};
if state.backend.kms().devices.contains_key(&drm_node) {
if let Err(err) = state.device_changed(dev) {
slog_scope::error!("Failed to update drm device {}: {}", path.display(), err);
}
} else {
if let Err(err) = state.device_added(dev, path.into()) {
slog_scope::error!("Failed to add drm device {}: {}", path.display(), err);
}
}
}
state.common
.output_conf
.update(&mut *state.common.display.borrow_mut());
state.common.config.read_outputs(state.common.output_conf.outputs(), &mut state.backend, &mut state.common.shell);
state.common.shell.refresh_outputs();
state.common.config.write_outputs(state.common.output_conf.outputs());
for surface in state.backend.kms().devices.values_mut().flat_map(|d| d.surfaces.values_mut()) {
surface.pending = false;
}
for output in state.common.shell.outputs() {
state.backend.kms().schedule_render(output);
}
});
loop_signal.wakeup();
}
});
state.backend = BackendData::Kms(KmsState {
api,
tokens: vec![libinput_event_source, session_event_source],
_tokens: vec![libinput_event_source, session_event_source, udev_event_source],
primary,
session,
signaler,
_restart_token,
devices: HashMap::new(),
});
for (dev, path) in udev_backend.device_list() {
for (dev, path) in udev_dispatcher.as_source_ref().device_list() {
state
.device_added(dev, path.into())
.with_context(|| format!("Failed to add drm device: {}", path.display()))?;
}
let udev_event_source = event_loop
.handle()
.insert_source(udev_backend, move |event, _, state| {
match match event {
UdevEvent::Added { device_id, path } => state
.device_added(device_id, path)
.with_context(|| format!("Failed to add drm device: {}", device_id)),
UdevEvent::Changed { device_id } => state
.device_changed(device_id)
.with_context(|| format!("Failed to update drm device: {}", device_id)),
UdevEvent::Removed { device_id } => state
.device_removed(device_id)
.with_context(|| format!("Failed to remove drm device: {}", device_id)),
} {
Ok(()) => {
slog_scope::debug!("Successfully handled udev event")
}
Err(err) => {
slog_scope::error!("Error while handling udev event: {}", err)
}
}
})
.unwrap();
state.backend.kms().tokens.push(udev_event_source);
Ok(())
}
impl State {
fn device_added(&mut self, dev: dev_t, path: PathBuf) -> Result<()> {
if !self.backend.kms().session.is_active() {
return Ok(());
}
let fd = SessionFd::new(
self.backend
.kms()
@ -331,6 +384,10 @@ impl State {
}
fn device_changed(&mut self, dev: dev_t) -> Result<()> {
if !self.backend.kms().session.is_active() {
return Ok(());
}
let drm_node = DrmNode::from_dev_id(dev)?;
let mut outputs_removed = Vec::new();
let mut outputs_added = Vec::new();
@ -400,9 +457,12 @@ impl State {
self.common
.output_conf
.update(&mut *self.common.display.borrow_mut());
self.common.config.read_outputs(self.common.output_conf.outputs(), &mut self.backend, &mut self.common.shell);
self.common.shell.refresh_outputs();
self.common.config.write_outputs(self.common.output_conf.outputs());
if self.backend.kms().session.is_active() {
self.common.config.read_outputs(self.common.output_conf.outputs(), &mut self.backend, &mut self.common.shell);
self.common.shell.refresh_outputs();
self.common.config.write_outputs(self.common.output_conf.outputs());
}
Ok(())
}
@ -557,11 +617,23 @@ impl Surface {
return Ok(());
}
let nodes = state
if render::needs_buffer_reset(&self.output, state) {
self.surface.as_mut().unwrap().reset_buffers();
}
let workspace = state
.shell
.active_space(&self.output)
.space
.windows()
.active_space(&self.output);
let nodes = workspace
.get_fullscreen(&self.output)
.map(|w| vec![w])
.unwrap_or_else(||
workspace
.space
.windows()
.collect::<Vec<_>>()
)
.into_iter()
.flat_map(|w| {
w.toplevel()
.get_surface()?
@ -624,6 +696,13 @@ impl Surface {
}
impl KmsState {
pub fn switch_vt(
&mut self,
num: i32,
) -> Result<(), anyhow::Error> {
self.session.change_vt(num).map_err(Into::into)
}
pub fn apply_config_for_output(
&mut self,
output: &Output,
@ -711,6 +790,7 @@ impl KmsState {
false
};
shell.refresh_outputs();
if recreated {
self.schedule_render(output);
}
@ -729,6 +809,7 @@ impl KmsState {
}
if !surface.pending {
surface.pending = true;
/*
let duration = surface
.last_submit
.as_ref()
@ -739,12 +820,13 @@ impl KmsState {
DrmEventTime::Realtime(time) => time.duration_since(SystemTime::now()).ok(),
})
.unwrap_or(Duration::ZERO); // + Duration::from_secs_f64((1.0 / surface.refresh_rate as f64) - 20.0);
*/
let data = (*device, *crtc);
if surface.vrr {
//if surface.vrr {
surface.render_timer.add_timeout(Duration::ZERO, data);
} else {
surface.render_timer.add_timeout(duration, data);
}
//} else {
// surface.render_timer.add_timeout(duration, data);
//}
}
}
}

View file

@ -12,7 +12,7 @@ pub mod x11;
// TODO
// pub mod wayland; // tbd in smithay
pub fn init_backend_auto(event_loop: &mut EventLoop<State>, state: &mut State) -> Result<()> {
pub fn init_backend_auto(event_loop: &mut EventLoop<'static, State>, state: &mut State) -> Result<()> {
match std::env::var("COSMIC_BACKEND") {
Ok(x) if x == "x11" => x11::init_backend(event_loop, state),
Ok(x) if x == "winit" => winit::init_backend(event_loop, state),

View file

@ -14,12 +14,18 @@ use smithay::{
renderer::{
gles2::{Gles2Renderbuffer, Gles2Renderer, Gles2Texture},
multigpu::{egl::EglGlesBackend, Error as MultiError, MultiFrame, MultiRenderer},
ImportAll, Renderer,
ImportAll, Renderer, Frame, TextureFilter,
},
},
desktop::space::{RenderElement, RenderError, SpaceOutputTuple, SurfaceTree},
utils::{Logical, Point, Rectangle},
wayland::output::Output,
desktop::{
space::{RenderElement, RenderError, SpaceOutputTuple, SurfaceTree},
draw_window, draw_layer_surface, Window, layer_map_for_output, utils::damage_from_surface_tree,
},
utils::{Logical, Point, Rectangle, Transform},
wayland::{
shell::wlr_layer::Layer as WlrLayer,
output::Output,
},
};
mod cursor;
@ -29,6 +35,8 @@ pub type GlMultiRenderer<'a> =
MultiRenderer<'a, 'a, EglGlesBackend, EglGlesBackend, Gles2Renderbuffer>;
pub type GlMultiFrame = MultiFrame<EglGlesBackend, EglGlesBackend>;
static CLEAR_COLOR: [f32; 4] = [0.153, 0.161, 0.165, 1.0];
smithay::custom_elements! {
pub CustomElem<=Gles2Renderer>;
SurfaceTree=SurfaceTree,
@ -96,7 +104,72 @@ impl AsGles2Renderer for GlMultiRenderer<'_> {
}
}
pub fn needs_buffer_reset(output: &Output, state: &Common) -> bool {
use std::sync::atomic::{AtomicBool, Ordering};
struct DidCustomRendering(AtomicBool);
let will_render_custom = {
let workspace = state.shell.active_space(output);
workspace.get_fullscreen(output).is_some()
};
let userdata = output.user_data();
userdata.insert_if_missing(|| DidCustomRendering(AtomicBool::new(false)));
userdata.get::<DidCustomRendering>().unwrap().0.swap(will_render_custom, Ordering::AcqRel) != will_render_custom
}
pub fn render_output<R>(
gpu: Option<&DrmNode>,
renderer: &mut R,
age: u8,
state: &mut Common,
output: &Output,
hardware_cursor: bool,
#[cfg(feature = "debug")] fps: &mut Fps,
) -> Result<Option<Vec<Rectangle<i32, Logical>>>, RenderError<R>>
where
R: Renderer + ImportAll + AsGles2Renderer,
<R as Renderer>::TextureId: Clone + 'static,
CustomElem: RenderElement<R>,
{
renderer.downscale_filter(TextureFilter::Linear).map_err(RenderError::Rendering)?;
#[cfg(feature = "debug")]
{
fps.start();
}
let workspace = state.shell.active_space(output);
let maybe_fullscreen_window = workspace.get_fullscreen(output).cloned();
let res = if let Some(window) = maybe_fullscreen_window {
#[cfg(not(feature = "debug"))]
{
render_fullscreen(gpu, renderer, window, state, output, hardware_cursor)
}
#[cfg(feature = "debug")]
{
render_fullscreen(gpu, renderer, window, state, output, hardware_cursor, fps)
}
} else {
#[cfg(not(feature = "debug"))]
{
render_desktop(gpu, renderer, age, state, output, hardware_cursor)
}
#[cfg(feature = "debug")]
{
render_desktop(gpu, renderer, age, state, output, hardware_cursor, fps)
}
};
#[cfg(feature = "debug")]
{
fps.end();
}
res
}
fn render_desktop<R>(
_gpu: Option<&DrmNode>,
renderer: &mut R,
age: u8,
@ -110,12 +183,6 @@ where
<R as Renderer>::TextureId: Clone + 'static,
CustomElem: RenderElement<R>,
{
#[cfg(feature = "debug")]
{
fps.start();
}
#[allow(unused_mut)]
let mut custom_elements = Vec::<CustomElem>::new();
#[cfg(feature = "debug")]
@ -125,7 +192,7 @@ where
.space
.output_geometry(output)
.unwrap_or(Rectangle::from_loc_and_size((0, 0), (0, 0)));
let scale = workspace.space.output_scale(output).unwrap();
let scale = output.current_scale().fractional_scale();
let fps_overlay = fps_ui(_gpu, state, fps, output_geo, scale);
custom_elements.push(fps_overlay.into());
@ -160,18 +227,115 @@ where
}
}
let res = state.shell.active_space_mut(output).space.render_output(
state.shell.active_space_mut(output).space.render_output(
renderer,
&output,
age as usize,
[0.153, 0.161, 0.165, 1.0],
CLEAR_COLOR,
&*custom_elements,
);
)
}
fn render_fullscreen<R>(
_gpu: Option<&DrmNode>,
renderer: &mut R,
window: Window,
state: &mut Common,
output: &Output,
hardware_cursor: bool,
#[cfg(feature = "debug")] fps: &mut Fps,
) -> Result<Option<Vec<Rectangle<i32, Logical>>>, RenderError<R>>
where
R: Renderer + ImportAll + AsGles2Renderer,
<R as Renderer>::TextureId: Clone + 'static,
CustomElem: RenderElement<R>,
{
let transform = Transform::from(output.current_transform());
let mode = output.current_mode().unwrap();
let scale = output.current_scale().fractional_scale();
let output_geo = state.shell.output_geometry(output);
let mut custom_elements = Vec::<CustomElem>::new();
#[cfg(feature = "debug")]
{
fps.end();
let fps_overlay = fps_ui(_gpu, state, fps, Rectangle::from_loc_and_size((0, 0), output_geo.size), scale);
custom_elements.push(fps_overlay.into());
}
for seat in &state.seats {
let pointer = match seat.get_pointer() {
Some(ptr) => ptr,
None => continue,
};
let location = state
.shell
.space_relative_output_geometry(pointer.current_location().to_i32_round(), output);
if let Some(cursor) = cursor::draw_cursor(
renderer.as_gles2(),
seat,
location,
&state.start_time,
!hardware_cursor,
) {
custom_elements.push(cursor)
}
}
res
renderer
.render(mode.size, transform, |renderer, frame| {
let mut damage = window.accumulated_damage(None);
frame.clear(
CLEAR_COLOR,
&[Rectangle::from_loc_and_size((0, 0), mode.size).to_f64()],
)?;
draw_window(
renderer,
frame,
&window,
scale,
(0, 0),
&[Rectangle::from_loc_and_size(
(0, 0),
mode.size.to_f64().to_logical(scale).to_i32_round(),
)],
&slog_scope::logger(),
)?;
let layer_map = layer_map_for_output(output);
for layer_surface in layer_map.layers_on(WlrLayer::Overlay) {
let geo = layer_map.layer_geometry(&layer_surface).unwrap();
draw_layer_surface(
renderer,
frame,
layer_surface,
scale,
geo.loc,
&[Rectangle::from_loc_and_size((0, 0), geo.size)],
&slog_scope::logger(),
)?;
if let Some(surface) = layer_surface.get_surface() {
damage.extend(damage_from_surface_tree(surface, geo.loc, None));
}
}
for elem in custom_elements {
let geo = elem.geometry();
let elem_damage = elem.accumulated_damage(None);
elem.draw(
renderer,
frame,
scale,
geo.loc,
&[Rectangle::from_loc_and_size((0, 0), geo.size)],
&slog_scope::logger(),
)?;
damage.extend(elem_damage.into_iter().map(|mut rect| {
rect.loc += geo.loc;
rect
}))
}
Ok(Some(damage))
})
.and_then(std::convert::identity)
.map_err(RenderError::<R>::Rendering)
}

View file

@ -35,15 +35,25 @@ pub struct WinitState {
backend: Rc<RefCell<WinitGraphicsBackend>>,
//_global: GlobalDrop<WlOutput>,
output: Output,
age_reset: u8,
#[cfg(feature = "debug")]
fps: Fps,
}
impl WinitState {
pub fn render_output(&mut self, state: &mut Common) -> Result<()> {
if render::needs_buffer_reset(&self.output, state) {
self.reset_buffers();
}
let backend = &mut *self.backend.borrow_mut();
backend.bind().with_context(|| "Failed to bind buffer")?;
let age = backend.buffer_age().unwrap_or(0);
let age = if self.age_reset > 0 {
self.age_reset -= 1;
0
} else {
backend.buffer_age().unwrap_or(0)
};
match render::render_output(
None,
@ -98,6 +108,10 @@ impl WinitState {
Ok(())
}
}
pub fn reset_buffers(&mut self) {
self.age_reset = 3;
}
}
pub fn init_backend(event_loop: &mut EventLoop<State>, state: &mut State) -> Result<()> {
@ -186,6 +200,7 @@ pub fn init_backend(event_loop: &mut EventLoop<State>, state: &mut State) -> Res
output: output.clone(),
#[cfg(feature = "debug")]
fps: Fps::default(),
age_reset: 0,
});
state.common.output_conf.add_heads(std::iter::once(&output));
state
@ -276,7 +291,7 @@ impl State {
render_ping.ping();
}
WinitEvent::Refresh => render_ping.ping(),
WinitEvent::Input(event) => self.common.process_input_event(event),
WinitEvent::Input(event) => self.process_input_event(event),
_ => {}
};
}

View file

@ -179,6 +179,10 @@ impl Surface {
renderer: &mut Gles2Renderer,
state: &mut Common,
) -> Result<()> {
if render::needs_buffer_reset(&self.output, state) {
self.surface.reset_buffers();
}
let (buffer, age) = self
.surface
.buffer()
@ -410,7 +414,7 @@ impl State {
_ => {}
};
self.common.process_input_event(event);
self.process_input_event(event);
// TODO actually figure out the output
for output in self.common.shell.outputs() {
self.backend.schedule_render(output);

View file

@ -1,475 +0,0 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::{
shell::{
Shell,
layout::FocusDirection,
},
state::BackendData,
};
use serde::{Deserialize, Serialize};
pub use smithay::{
backend::input::KeyState,
utils::{Logical, Physical, Point, Size, Transform},
wayland::{
output::{Mode, Output},
seat::{keysyms as KeySyms, Keysym, ModifiersState as KeyModifiers},
},
};
use std::{cell::RefCell, collections::HashMap, fs::OpenOptions, path::PathBuf};
use xkbcommon::xkb;
pub struct Config {
pub static_conf: StaticConfig,
pub dynamic_conf: DynamicConfig,
}
#[derive(Debug, Deserialize)]
pub struct StaticConfig {
pub key_bindings: HashMap<KeyPattern, Action>,
pub workspace_mode: crate::shell::Mode,
}
pub struct DynamicConfig {
outputs: (Option<PathBuf>, OutputsConfig),
}
#[derive(Debug, Deserialize, Serialize)]
pub struct OutputsConfig {
pub config: HashMap<Vec<OutputInfo>, Vec<OutputConfig>>,
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct OutputInfo {
pub connector: String,
pub make: String,
pub model: String,
}
impl From<Output> for OutputInfo {
fn from(o: Output) -> OutputInfo {
let physical = o.physical_properties();
OutputInfo {
connector: o.name(),
make: physical.make,
model: physical.model,
}
}
}
fn default_enabled() -> bool {
true
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
pub struct OutputConfig {
pub mode: ((i32, i32), Option<u32>),
pub vrr: bool,
pub scale: f64,
#[serde(with = "TransformDef")]
pub transform: Transform,
pub position: (i32, i32),
#[serde(default = "default_enabled")]
pub enabled: bool,
}
impl Default for OutputConfig {
fn default() -> OutputConfig {
OutputConfig {
mode: ((0, 0), None),
vrr: false,
scale: 1.0,
transform: Transform::Normal,
position: (0, 0),
enabled: true,
}
}
}
impl OutputConfig {
pub fn mode_size(&self) -> Size<i32, Physical> {
self.mode.0.into()
}
pub fn mode_refresh(&self) -> u32 {
self.mode.1.unwrap_or(60_000)
}
pub fn output_mode(&self) -> Mode {
Mode {
size: self.mode_size(),
refresh: self.mode_refresh() as i32,
}
}
}
#[derive(Serialize, Deserialize)]
#[serde(remote = "Transform")]
enum TransformDef {
Normal,
_90,
_180,
_270,
Flipped,
Flipped90,
Flipped180,
Flipped270,
}
impl Config {
pub fn load() -> Config {
let xdg = xdg::BaseDirectories::new().ok();
Config {
static_conf: Self::load_static(xdg.as_ref()),
dynamic_conf: Self::load_dynamic(xdg.as_ref()),
}
}
fn load_static(xdg: Option<&xdg::BaseDirectories>) -> StaticConfig {
let mut locations = if let Some(base) = xdg {
vec![
base.get_config_file("cosmic-comp.ron"),
base.get_config_file("cosmic-comp/config.ron"),
]
} else {
Vec::with_capacity(3)
};
if cfg!(debug_assertions) {
if let Ok(mut cwd) = std::env::current_dir() {
cwd.push("config.ron");
locations.push(cwd);
}
}
locations.push(PathBuf::from("/etc/cosmic-comp/config.ron"));
locations.push(PathBuf::from("/etc/cosmic-comp.ron"));
for path in locations {
slog_scope::debug!("Trying config location: {}", path.display());
if path.exists() {
slog_scope::info!("Using config at {}", path.display());
return ron::de::from_reader(OpenOptions::new().read(true).open(path).unwrap())
.expect("Malformed config file");
}
}
StaticConfig {
key_bindings: HashMap::new(),
workspace_mode: crate::shell::Mode::global(),
}
}
fn load_dynamic(xdg: Option<&xdg::BaseDirectories>) -> DynamicConfig {
let output_path =
xdg.and_then(|base| base.place_state_file("cosmic-comp/outputs.ron").ok());
let outputs = Self::load_outputs(&output_path);
DynamicConfig {
outputs: (output_path, outputs),
}
}
fn load_outputs(path: &Option<PathBuf>) -> OutputsConfig {
if let Some(path) = path.as_ref() {
if path.exists() {
match ron::de::from_reader(OpenOptions::new().read(true).open(path).unwrap()) {
Ok(config) => return config,
Err(err) => {
slog_scope::warn!("Failed to read output_config ({}), resetting..", err);
if let Err(err) = std::fs::remove_file(path) {
slog_scope::error!("Failed to remove output_config {}", err);
}
}
};
}
}
OutputsConfig {
config: HashMap::new(),
}
}
pub fn read_outputs<'a>(
&mut self,
outputs: impl Iterator<Item=impl std::borrow::Borrow<Output>>,
backend: &mut BackendData,
shell: &mut Shell,
) {
let outputs = outputs.map(|x| x.borrow().clone()).collect::<Vec<_>>();
let mut infos = outputs
.iter()
.cloned()
.map(Into::<crate::config::OutputInfo>::into)
.collect::<Vec<_>>();
infos.sort();
if let Some(configs) = self.dynamic_conf.outputs().config.get(&infos).cloned() {
let mut reset = false;
let known_good_configs = outputs
.iter()
.map(|output| {
output
.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow()
.clone()
})
.collect::<Vec<_>>();
for (name, output_config) in infos.iter().map(|o| &o.connector).zip(configs.into_iter())
{
let output = outputs
.iter()
.find(|o| &o.name() == name)
.unwrap()
.clone();
*output
.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow_mut() = output_config;
if let Err(err) = backend.apply_config_for_output(&output, false, shell) {
slog_scope::warn!(
"Failed to set new config for output {}: {}",
output.name(),
err
);
reset = true;
break;
}
}
if reset {
for (output, output_config) in outputs
.clone()
.into_iter()
.zip(known_good_configs.into_iter())
{
*output
.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow_mut() = output_config;
if let Err(err) = backend.apply_config_for_output(&output, false, shell)
{
slog_scope::error!(
"Failed to reset config for output {}: {}",
output.name(),
err
);
}
}
}
}
}
pub fn write_outputs<'a>(&mut self, outputs: impl Iterator<Item=impl std::borrow::Borrow<Output>>) {
let mut infos = outputs
.map(|o| {
let o = o.borrow();
(
Into::<crate::config::OutputInfo>::into(o.clone()),
o.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow()
.clone(),
)
})
.collect::<Vec<(OutputInfo, OutputConfig)>>();
infos.sort_by(|&(ref a, _), &(ref b, _)| a.cmp(b));
let (infos, configs) = infos.into_iter().unzip();
self
.dynamic_conf
.outputs_mut()
.config
.insert(infos, configs);
}
}
pub struct PersistenceGuard<'a, T: Serialize>(Option<PathBuf>, &'a mut T);
impl<'a, T: Serialize> std::ops::Deref for PersistenceGuard<'a, T> {
type Target = T;
fn deref(&self) -> &T {
&self.1
}
}
impl<'a, T: Serialize> std::ops::DerefMut for PersistenceGuard<'a, T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.1
}
}
impl<'a, T: Serialize> Drop for PersistenceGuard<'a, T> {
fn drop(&mut self) {
if let Some(path) = self.0.as_ref() {
let writer = match OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.open(path)
{
Ok(writer) => writer,
Err(err) => {
slog_scope::warn!("Failed to persist {}: {}", path.display(), err);
return;
}
};
if let Err(err) = ron::ser::to_writer_pretty(writer, &self.1, Default::default()) {
slog_scope::warn!("Failed to persist {}: {}", path.display(), err);
}
}
}
}
impl DynamicConfig {
pub fn outputs(&self) -> &OutputsConfig {
&self.outputs.1
}
pub fn outputs_mut<'a>(&'a mut self) -> PersistenceGuard<'a, OutputsConfig> {
PersistenceGuard(self.outputs.0.clone(), &mut self.outputs.1)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub enum KeyModifier {
Ctrl,
Alt,
Shift,
Logo,
CapsLock,
NumLock,
}
impl std::ops::AddAssign<KeyModifier> for KeyModifiers {
fn add_assign(&mut self, rhs: KeyModifier) {
match rhs {
KeyModifier::Ctrl => self.ctrl = true,
KeyModifier::Alt => self.alt = true,
KeyModifier::Shift => self.shift = true,
KeyModifier::Logo => self.logo = true,
KeyModifier::CapsLock => self.caps_lock = true,
KeyModifier::NumLock => self.num_lock = true,
};
}
}
impl std::ops::BitOr for KeyModifier {
type Output = KeyModifiers;
fn bitor(self, rhs: KeyModifier) -> Self::Output {
let mut modifiers = self.into();
modifiers += rhs;
modifiers
}
}
impl Into<KeyModifiers> for KeyModifier {
fn into(self) -> KeyModifiers {
let mut modifiers = KeyModifiers {
ctrl: false,
alt: false,
shift: false,
caps_lock: false,
logo: false,
num_lock: false,
};
modifiers += self;
modifiers
}
}
#[derive(Deserialize)]
#[serde(transparent)]
struct KeyModifiersDef(Vec<KeyModifier>);
impl From<KeyModifiersDef> for KeyModifiers {
fn from(src: KeyModifiersDef) -> Self {
src.0.into_iter().fold(
KeyModifiers {
ctrl: false,
alt: false,
shift: false,
caps_lock: false,
logo: false,
num_lock: false,
},
|mut modis, modi| {
modis += modi;
modis
},
)
}
}
#[allow(non_snake_case)]
fn deserialize_KeyModifiers<'de, D>(deserializer: D) -> Result<KeyModifiers, D::Error>
where
D: serde::Deserializer<'de>,
{
KeyModifiersDef::deserialize(deserializer).map(Into::into)
}
#[allow(non_snake_case)]
fn deserialize_Keysym<'de, D>(deserializer: D) -> Result<Keysym, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::{Error, Unexpected};
let name = String::deserialize(deserializer)?;
//let name = format!("KEY_{}", code);
match xkb::keysym_from_name(&name, xkb::KEYSYM_NO_FLAGS) {
KeySyms::KEY_NoSymbol => match xkb::keysym_from_name(&name, xkb::KEYSYM_CASE_INSENSITIVE) {
KeySyms::KEY_NoSymbol => Err(<D::Error as Error>::invalid_value(
Unexpected::Str(&name),
&"One of the keysym names of xkbcommon.h without the 'KEY_' prefix",
)),
x => {
slog_scope::warn!(
"Key-Binding '{}' only matched case insensitive for {:?}",
name,
xkb::keysym_get_name(x)
);
Ok(x)
}
},
x => Ok(x),
}
}
/// Describtion of a key combination that might be
/// handled by the compositor.
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Hash)]
#[serde(deny_unknown_fields)]
pub struct KeyPattern {
/// What modifiers are expected to be pressed alongside the key
#[serde(deserialize_with = "deserialize_KeyModifiers")]
pub modifiers: KeyModifiers,
/// The actual key, that was pressed
#[serde(deserialize_with = "deserialize_Keysym")]
pub key: u32,
}
impl KeyPattern {
pub fn new(modifiers: impl Into<KeyModifiers>, key: u32) -> KeyPattern {
KeyPattern {
modifiers: modifiers.into(),
key,
}
}
}
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
pub enum Action {
Terminate,
Debug,
Close,
Workspace(u8),
MoveToWorkspace(u8),
Focus(FocusDirection),
Orientation(crate::shell::layout::Orientation),
Spawn(String),
}

650
src/config/mod.rs Normal file
View file

@ -0,0 +1,650 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::{
shell::{
Shell,
layout::FocusDirection,
},
state::BackendData,
};
use serde::{Deserialize, Serialize};
pub use smithay::{
backend::input::KeyState,
reexports::input::{AccelProfile, ClickMethod, ScrollMethod, SendEventsMode, TapButtonMap, Device as InputDevice},
utils::{Logical, Physical, Point, Size, Transform},
wayland::{
output::{Mode, Output},
seat::{keysyms as KeySyms, Keysym, ModifiersState as KeyModifiers},
},
};
use std::{cell::RefCell, collections::HashMap, fs::OpenOptions, path::PathBuf};
mod types;
pub use self::types::*;
pub struct Config {
pub static_conf: StaticConfig,
pub dynamic_conf: DynamicConfig,
}
#[derive(Debug, Deserialize)]
pub struct StaticConfig {
pub key_bindings: HashMap<KeyPattern, Action>,
pub workspace_mode: crate::shell::Mode,
}
pub struct DynamicConfig {
outputs: (Option<PathBuf>, OutputsConfig),
inputs: (Option<PathBuf>, InputsConfig),
}
#[derive(Debug, Deserialize, Serialize)]
pub struct OutputsConfig {
pub config: HashMap<Vec<OutputInfo>, Vec<OutputConfig>>,
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct OutputInfo {
pub connector: String,
pub make: String,
pub model: String,
}
impl From<Output> for OutputInfo {
fn from(o: Output) -> OutputInfo {
let physical = o.physical_properties();
OutputInfo {
connector: o.name(),
make: physical.make,
model: physical.model,
}
}
}
fn default_enabled() -> bool {
true
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
pub struct OutputConfig {
pub mode: ((i32, i32), Option<u32>),
pub vrr: bool,
pub scale: f64,
#[serde(with = "TransformDef")]
pub transform: Transform,
pub position: (i32, i32),
#[serde(default = "default_enabled")]
pub enabled: bool,
}
impl Default for OutputConfig {
fn default() -> OutputConfig {
OutputConfig {
mode: ((0, 0), None),
vrr: false,
scale: 1.0,
transform: Transform::Normal,
position: (0, 0),
enabled: true,
}
}
}
impl OutputConfig {
pub fn mode_size(&self) -> Size<i32, Physical> {
self.mode.0.into()
}
pub fn mode_refresh(&self) -> u32 {
self.mode.1.unwrap_or(60_000)
}
pub fn output_mode(&self) -> Mode {
Mode {
size: self.mode_size(),
refresh: self.mode_refresh() as i32,
}
}
}
#[derive(Debug, Deserialize, Serialize)]
pub struct InputsConfig {
xkb: XkbConfig,
devices: HashMap<String, InputConfig>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct InputConfig {
state: DeviceState,
#[serde(skip_serializing_if="Option::is_none", default)]
acceleration: Option<AccelConfig>,
#[serde(skip_serializing_if="Option::is_none", default)]
calibration: Option<[f32; 6]>,
#[serde(with = "ClickMethodDef")]
#[serde(skip_serializing_if="Option::is_none", default)]
click_method: Option<ClickMethod>,
#[serde(skip_serializing_if="Option::is_none", default)]
disable_while_typing: Option<bool>,
#[serde(skip_serializing_if="Option::is_none", default)]
left_handed: Option<bool>,
#[serde(skip_serializing_if="Option::is_none", default)]
middle_button_emulation: Option<bool>,
#[serde(skip_serializing_if="Option::is_none", default)]
rotation_angle: Option<u32>,
#[serde(skip_serializing_if="Option::is_none", default)]
scroll_config: Option<ScrollConfig>,
#[serde(skip_serializing_if="Option::is_none", default)]
tap_config: Option<TapConfig>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct AccelConfig {
#[serde(with = "AccelProfileDef")]
profile: Option<AccelProfile>,
speed: f64,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct ScrollConfig {
#[serde(with = "ScrollMethodDef")]
method: Option<ScrollMethod>,
natural_scroll: Option<bool>,
scroll_button: Option<u32>,
}
#[derive(Debug, Serialize, Deserialize)]
pub enum DeviceState {
Enabled,
Disabled,
DisabledOnExternalMouse,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct TapConfig {
enabled: bool,
#[serde(with = "TapButtonMapDef")]
button_map: Option<TapButtonMap>,
drag: bool,
drag_lock: bool,
}
impl Config {
pub fn load() -> Config {
let xdg = xdg::BaseDirectories::new().ok();
Config {
static_conf: Self::load_static(xdg.as_ref()),
dynamic_conf: Self::load_dynamic(xdg.as_ref()),
}
}
fn load_static(xdg: Option<&xdg::BaseDirectories>) -> StaticConfig {
let mut locations = if let Some(base) = xdg {
vec![
base.get_config_file("cosmic-comp.ron"),
base.get_config_file("cosmic-comp/config.ron"),
]
} else {
Vec::with_capacity(3)
};
if cfg!(debug_assertions) {
if let Ok(mut cwd) = std::env::current_dir() {
cwd.push("config.ron");
locations.push(cwd);
}
}
locations.push(PathBuf::from("/etc/cosmic-comp/config.ron"));
locations.push(PathBuf::from("/etc/cosmic-comp.ron"));
for path in locations {
slog_scope::debug!("Trying config location: {}", path.display());
if path.exists() {
slog_scope::info!("Using config at {}", path.display());
return ron::de::from_reader(OpenOptions::new().read(true).open(path).unwrap())
.expect("Malformed config file");
}
}
StaticConfig {
key_bindings: HashMap::new(),
workspace_mode: crate::shell::Mode::global(),
}
}
fn load_dynamic(xdg: Option<&xdg::BaseDirectories>) -> DynamicConfig {
let output_path =
xdg.and_then(|base| base.place_state_file("cosmic-comp/outputs.ron").ok());
let outputs = Self::load_outputs(&output_path);
let input_path =
xdg.and_then(|base| base.place_state_file("cosmic-comp/inputs.ron").ok());
let inputs = Self::load_inputs(&input_path);
DynamicConfig {
outputs: (output_path, outputs),
inputs: (input_path, inputs),
}
}
fn load_outputs(path: &Option<PathBuf>) -> OutputsConfig {
if let Some(path) = path.as_ref() {
if path.exists() {
match ron::de::from_reader(OpenOptions::new().read(true).open(path).unwrap()) {
Ok(config) => return config,
Err(err) => {
slog_scope::warn!("Failed to read output_config ({}), resetting..", err);
if let Err(err) = std::fs::remove_file(path) {
slog_scope::error!("Failed to remove output_config {}", err);
}
}
};
}
}
OutputsConfig {
config: HashMap::new(),
}
}
fn load_inputs(path: &Option<PathBuf>) -> InputsConfig {
if let Some(path) = path.as_ref() {
if path.exists() {
match ron::de::from_reader(OpenOptions::new().read(true).open(path).unwrap()) {
Ok(config) => return config,
Err(err) => {
slog_scope::warn!("Failed to read input_config ({}), resetting..", err);
if let Err(err) = std::fs::remove_file(path) {
slog_scope::error!("Failed to remove input_config {}", err);
}
}
};
}
}
InputsConfig {
xkb: XkbConfig::default(),
devices: HashMap::new(),
}
}
pub fn read_outputs(
&mut self,
outputs: impl Iterator<Item=impl std::borrow::Borrow<Output>>,
backend: &mut BackendData,
shell: &mut Shell,
) {
let outputs = outputs.map(|x| x.borrow().clone()).collect::<Vec<_>>();
let mut infos = outputs
.iter()
.cloned()
.map(Into::<crate::config::OutputInfo>::into)
.collect::<Vec<_>>();
infos.sort();
if let Some(configs) = self.dynamic_conf.outputs().config.get(&infos).cloned() {
let mut reset = false;
let known_good_configs = outputs
.iter()
.map(|output| {
output
.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow()
.clone()
})
.collect::<Vec<_>>();
for (name, output_config) in infos.iter().map(|o| &o.connector).zip(configs.into_iter())
{
let output = outputs
.iter()
.find(|o| &o.name() == name)
.unwrap()
.clone();
*output
.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow_mut() = output_config;
if let Err(err) = backend.apply_config_for_output(&output, false, shell) {
slog_scope::warn!(
"Failed to set new config for output {}: {}",
output.name(),
err
);
reset = true;
break;
}
}
if reset {
for (output, output_config) in outputs
.clone()
.into_iter()
.zip(known_good_configs.into_iter())
{
*output
.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow_mut() = output_config;
if let Err(err) = backend.apply_config_for_output(&output, false, shell)
{
slog_scope::error!(
"Failed to reset config for output {}: {}",
output.name(),
err
);
}
}
}
}
}
pub fn write_outputs(&mut self, outputs: impl Iterator<Item=impl std::borrow::Borrow<Output>>) {
let mut infos = outputs
.map(|o| {
let o = o.borrow();
(
Into::<crate::config::OutputInfo>::into(o.clone()),
o.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow()
.clone(),
)
})
.collect::<Vec<(OutputInfo, OutputConfig)>>();
infos.sort_by(|&(ref a, _), &(ref b, _)| a.cmp(b));
let (infos, configs) = infos.into_iter().unzip();
self
.dynamic_conf
.outputs_mut()
.config
.insert(infos, configs);
}
pub fn read_device(&mut self, device: &mut InputDevice) {
use std::collections::hash_map::Entry;
let mut inputs = self.dynamic_conf.inputs_mut();
match inputs.devices.entry(device.name().into()) {
Entry::Occupied(entry) => {
let config = entry.get();
if let Err(err) = match config.state {
DeviceState::Enabled => device.config_send_events_set_mode(SendEventsMode::ENABLED),
DeviceState::Disabled => device.config_send_events_set_mode(SendEventsMode::DISABLED),
DeviceState::DisabledOnExternalMouse => device.config_send_events_set_mode(SendEventsMode::DISABLED_ON_EXTERNAL_MOUSE),
} {
slog_scope::warn!("Failed to apply mode {:?} for device {:?}: {:?}", config.state, device.name(), err);
}
if let Some(accel) = config.acceleration.as_ref() {
if let Some(profile) = accel.profile {
if let Err(err) = device.config_accel_set_profile(profile) {
slog_scope::warn!("Failed to apply acceleration profile {:?} for device {:?}: {:?}", profile, device.name(), err);
}
}
if let Err(err) = device.config_accel_set_speed(accel.speed) {
slog_scope::warn!("Failed to apply acceleration speed {:?} for device {:?}: {:?}", accel.speed, device.name(), err);
}
}
if let Some(matrix) = config.calibration {
if let Err(err) = device.config_calibration_set_matrix(matrix) {
slog_scope::warn!("Failed to apply calibration matrix {:?} for device {:?}: {:?}", matrix, device.name(), err);
}
}
if let Some(dwt) = config.disable_while_typing {
if let Err(err) = device.config_dwt_set_enabled(dwt) {
slog_scope::warn!("Failed to apply disable-while-typing {:?} for device {:?}: {:?}", dwt, device.name(), err);
}
}
if let Some(left) = config.left_handed {
if let Err(err) = device.config_left_handed_set(left) {
slog_scope::warn!("Failed to apply left-handed {:?} for device {:?}: {:?}", left, device.name(), err);
}
}
if let Some(middle) = config.middle_button_emulation {
if let Err(err) = device.config_middle_emulation_set_enabled(middle) {
slog_scope::warn!("Failed to apply middle-button-emulation {:?} for device {:?}: {:?}", middle, device.name(), err);
}
}
if let Some(angle) = config.rotation_angle {
if let Err(err) = device.config_rotation_set_angle(angle) {
slog_scope::warn!("Failed to apply rotation-angle {:?} for device {:?}: {:?}", angle, device.name(), err);
}
}
if let Some(scroll) = config.scroll_config.as_ref() {
if let Some(method) = scroll.method {
if let Err(err) = device.config_scroll_set_method(method) {
slog_scope::warn!("Failed to apply scroll method {:?} for device {:?}: {:?}", method, device.name(), err);
}
}
if let Some(natural) = scroll.natural_scroll {
if let Err(err) = device.config_scroll_set_natural_scroll_enabled(natural) {
slog_scope::warn!("Failed to apply natural scrolling {:?} for device {:?}: {:?}", natural, device.name(), err);
}
}
if let Some(button) = scroll.scroll_button {
if let Err(err) = device.config_scroll_set_button(button) {
slog_scope::warn!("Failed to apply scroll button {:?} for device {:?}: {:?}", button, device.name(), err);
}
}
}
if let Some(tap) = config.tap_config.as_ref() {
if let Err(err) = device.config_tap_set_enabled(tap.enabled) {
slog_scope::warn!("Failed to apply tap-to-click {:?} for device {:?}: {:?}", tap.enabled, device.name(), err);
}
if let Some(button_map) = tap.button_map {
if let Err(err) = device.config_tap_set_button_map(button_map) {
slog_scope::warn!("Failed to apply button map {:?} for device {:?}: {:?}", button_map, device.name(), err);
}
}
if let Err(err) = device.config_tap_set_drag_enabled(tap.drag) {
slog_scope::warn!("Failed to apply tap-drag {:?} for device {:?}: {:?}", tap.drag, device.name(), err);
}
if let Err(err) = device.config_tap_set_drag_lock_enabled(tap.drag_lock) {
slog_scope::warn!("Failed to apply tap-drag-lock {:?} for device {:?}: {:?}", tap.drag_lock, device.name(), err);
}
}
},
Entry::Vacant(entry) => {
entry.insert(InputConfig {
state: match device.config_send_events_mode() {
x if x.contains(SendEventsMode::ENABLED) => DeviceState::Enabled,
x if x.contains(SendEventsMode::DISABLED_ON_EXTERNAL_MOUSE) => DeviceState::DisabledOnExternalMouse,
x if x.contains(SendEventsMode::DISABLED) => DeviceState::Disabled,
_ => DeviceState::Disabled,
},
acceleration: if device.config_accel_is_available() {
Some(AccelConfig {
profile: device.config_accel_profile(),
speed: device.config_accel_speed(),
})
} else { None },
calibration: device.config_calibration_matrix(),
click_method: device.config_click_method(),
disable_while_typing: if device.config_dwt_is_available() {
Some(device.config_dwt_enabled())
} else {
None
},
left_handed: if device.config_left_handed_is_available() {
Some(device.config_left_handed())
} else {
None
},
middle_button_emulation: if device.config_middle_emulation_is_available() {
Some(device.config_middle_emulation_enabled())
} else {
None
},
rotation_angle: if device.config_rotation_is_available() {
Some(device.config_rotation_angle())
} else {
None
},
scroll_config: if device.config_scroll_methods().iter().any(|x| *x != ScrollMethod::NoScroll) {
Some(ScrollConfig {
method: device.config_scroll_method(),
natural_scroll: if device.config_scroll_has_natural_scroll() {
Some(device.config_scroll_natural_scroll_enabled())
} else {
None
},
scroll_button: if device.config_scroll_method() == Some(ScrollMethod::OnButtonDown) {
Some(device.config_scroll_button())
} else {
None
},
})
} else { None },
tap_config: if device.config_tap_finger_count() > 0 {
Some(TapConfig {
enabled: device.config_tap_enabled(),
button_map: device.config_tap_button_map(),
drag: device.config_tap_drag_enabled(),
drag_lock: device.config_tap_drag_lock_enabled(),
})
} else { None },
});
},
}
}
}
pub struct PersistenceGuard<'a, T: Serialize>(Option<PathBuf>, &'a mut T);
impl<'a, T: Serialize> std::ops::Deref for PersistenceGuard<'a, T> {
type Target = T;
fn deref(&self) -> &T {
&self.1
}
}
impl<'a, T: Serialize> std::ops::DerefMut for PersistenceGuard<'a, T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.1
}
}
impl<'a, T: Serialize> Drop for PersistenceGuard<'a, T> {
fn drop(&mut self) {
if let Some(path) = self.0.as_ref() {
let writer = match OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.open(path)
{
Ok(writer) => writer,
Err(err) => {
slog_scope::warn!("Failed to persist {}: {}", path.display(), err);
return;
}
};
if let Err(err) = ron::ser::to_writer_pretty(writer, &self.1, Default::default()) {
slog_scope::warn!("Failed to persist {}: {}", path.display(), err);
}
}
}
}
impl DynamicConfig {
pub fn outputs(&self) -> &OutputsConfig {
&self.outputs.1
}
pub fn outputs_mut<'a>(&'a mut self) -> PersistenceGuard<'a, OutputsConfig> {
PersistenceGuard(self.outputs.0.clone(), &mut self.outputs.1)
}
pub fn inputs(&self) -> &InputsConfig {
&self.inputs.1
}
pub fn inputs_mut<'a>(&'a mut self) -> PersistenceGuard<'a, InputsConfig> {
PersistenceGuard(self.inputs.0.clone(), &mut self.inputs.1)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub enum KeyModifier {
Ctrl,
Alt,
Shift,
Logo,
CapsLock,
NumLock,
}
impl std::ops::AddAssign<KeyModifier> for KeyModifiers {
fn add_assign(&mut self, rhs: KeyModifier) {
match rhs {
KeyModifier::Ctrl => self.ctrl = true,
KeyModifier::Alt => self.alt = true,
KeyModifier::Shift => self.shift = true,
KeyModifier::Logo => self.logo = true,
KeyModifier::CapsLock => self.caps_lock = true,
KeyModifier::NumLock => self.num_lock = true,
};
}
}
impl std::ops::BitOr for KeyModifier {
type Output = KeyModifiers;
fn bitor(self, rhs: KeyModifier) -> Self::Output {
let mut modifiers = self.into();
modifiers += rhs;
modifiers
}
}
impl Into<KeyModifiers> for KeyModifier {
fn into(self) -> KeyModifiers {
let mut modifiers = KeyModifiers {
ctrl: false,
alt: false,
shift: false,
caps_lock: false,
logo: false,
num_lock: false,
};
modifiers += self;
modifiers
}
}
/// Describtion of a key combination that might be
/// handled by the compositor.
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Hash)]
#[serde(deny_unknown_fields)]
pub struct KeyPattern {
/// What modifiers are expected to be pressed alongside the key
#[serde(deserialize_with = "deserialize_KeyModifiers")]
pub modifiers: KeyModifiers,
/// The actual key, that was pressed
#[serde(deserialize_with = "deserialize_Keysym")]
pub key: u32,
}
impl KeyPattern {
pub fn new(modifiers: impl Into<KeyModifiers>, key: u32) -> KeyPattern {
KeyPattern {
modifiers: modifiers.into(),
key,
}
}
}
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
pub enum Action {
Terminate,
Debug,
Close,
Workspace(u8),
MoveToWorkspace(u8),
Focus(FocusDirection),
Orientation(crate::shell::layout::Orientation),
Fullscreen,
Spawn(String),
}

251
src/config/types.rs Normal file
View file

@ -0,0 +1,251 @@
// SPDX-License-Identifier: GPL-3.0-only
#![allow(non_snake_case)]
use super::KeyModifier;
use serde::{Deserialize, Serialize};
pub use smithay::{
backend::input::KeyState,
reexports::input::{AccelProfile, ClickMethod, ScrollMethod, TapButtonMap},
utils::{Logical, Physical, Point, Size, Transform},
wayland::{
output::{Mode, Output},
seat::{keysyms as KeySyms, Keysym, ModifiersState as KeyModifiers},
},
};
use xkbcommon::xkb;
#[derive(Debug, Deserialize, Serialize)]
pub struct XkbConfig {
pub rules: String,
pub model: String,
pub layout: String,
pub variant: String,
pub options: Option<String>,
}
impl Default for XkbConfig {
fn default() -> XkbConfig {
XkbConfig {
rules: String::new(),
model: String::new(),
layout: String::new(),
variant: String::new(),
options: None,
}
}
}
pub mod ClickMethodDef {
use smithay::reexports::input::ClickMethod as ClickMethodOrig;
use serde::{Deserialize, Serialize, Deserializer, Serializer};
#[derive(Debug, Serialize, Deserialize)]
pub enum ClickMethod {
ButtonAreas,
Clickfinger,
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<ClickMethodOrig>, D::Error>
where
D: Deserializer<'de>,
{
let o = Option::deserialize(deserializer)?;
Ok(o.map(|x| match x {
ClickMethod::ButtonAreas => ClickMethodOrig::ButtonAreas,
ClickMethod::Clickfinger => ClickMethodOrig::Clickfinger,
}))
}
pub fn serialize<S>(arg: &Option<ClickMethodOrig>, ser: S) -> Result<S::Ok, S::Error>
where
S: Serializer
{
let arg = match arg {
Some(ClickMethodOrig::ButtonAreas) => Some(ClickMethod::ButtonAreas),
Some(ClickMethodOrig::Clickfinger) => Some(ClickMethod::Clickfinger),
Some(_) | None => None,
};
Option::serialize(&arg, ser)
}
}
pub mod AccelProfileDef {
use smithay::reexports::input::AccelProfile as AccelProfileOrig;
use serde::{Deserialize, Serialize, Deserializer, Serializer};
#[derive(Debug, Serialize, Deserialize)]
enum AccelProfile {
Flat,
Adaptive,
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<AccelProfileOrig>, D::Error>
where
D: Deserializer<'de>,
{
let o = Option::deserialize(deserializer)?;
Ok(o.map(|x| match x {
AccelProfile::Flat => AccelProfileOrig::Flat,
AccelProfile::Adaptive => AccelProfileOrig::Adaptive,
}))
}
pub fn serialize<S>(arg: &Option<AccelProfileOrig>, ser: S) -> Result<S::Ok, S::Error>
where
S: Serializer
{
let arg = match arg {
Some(AccelProfileOrig::Flat) => Some(AccelProfile::Flat),
Some(AccelProfileOrig::Adaptive) => Some(AccelProfile::Adaptive),
Some(_) | None => None,
};
Option::serialize(&arg, ser)
}
}
pub mod ScrollMethodDef {
use smithay::reexports::input::ScrollMethod as ScrollMethodOrig;
use serde::{Deserialize, Serialize, Deserializer, Serializer};
#[derive(Debug, Serialize, Deserialize)]
pub enum ScrollMethod {
NoScroll,
TwoFinger,
Edge,
OnButtonDown,
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<ScrollMethodOrig>, D::Error>
where
D: Deserializer<'de>,
{
let o = Option::deserialize(deserializer)?;
Ok(o.map(|x| match x {
ScrollMethod::NoScroll => ScrollMethodOrig::NoScroll,
ScrollMethod::TwoFinger => ScrollMethodOrig::TwoFinger,
ScrollMethod::Edge => ScrollMethodOrig::Edge,
ScrollMethod::OnButtonDown => ScrollMethodOrig::OnButtonDown,
}))
}
pub fn serialize<S>(arg: &Option<ScrollMethodOrig>, ser: S) -> Result<S::Ok, S::Error>
where
S: Serializer
{
let arg = match arg {
Some(ScrollMethodOrig::NoScroll) => Some(ScrollMethod::NoScroll),
Some(ScrollMethodOrig::TwoFinger) => Some(ScrollMethod::TwoFinger),
Some(ScrollMethodOrig::Edge) => Some(ScrollMethod::Edge),
Some(ScrollMethodOrig::OnButtonDown) => Some(ScrollMethod::OnButtonDown),
Some(_) | None => None,
};
Option::serialize(&arg, ser)
}
}
#[derive(Serialize, Deserialize)]
#[serde(remote = "Transform")]
pub enum TransformDef {
Normal,
_90,
_180,
_270,
Flipped,
Flipped90,
Flipped180,
Flipped270,
}
pub mod TapButtonMapDef {
use smithay::reexports::input::TapButtonMap as TapButtonMapOrig;
use serde::{Deserialize, Serialize, Deserializer, Serializer};
#[derive(Debug, Serialize, Deserialize)]
pub enum TapButtonMap {
LeftRightMiddle,
LeftMiddleRight,
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<TapButtonMapOrig>, D::Error>
where
D: Deserializer<'de>,
{
let o = Option::deserialize(deserializer)?;
Ok(o.map(|x| match x {
TapButtonMap::LeftRightMiddle => TapButtonMapOrig::LeftRightMiddle,
TapButtonMap::LeftMiddleRight => TapButtonMapOrig::LeftMiddleRight,
}))
}
pub fn serialize<S>(arg: &Option<TapButtonMapOrig>, ser: S) -> Result<S::Ok, S::Error>
where
S: Serializer
{
let arg = match arg {
Some(TapButtonMapOrig::LeftRightMiddle) => Some(TapButtonMap::LeftRightMiddle),
Some(TapButtonMapOrig::LeftMiddleRight) => Some(TapButtonMap::LeftMiddleRight),
Some(_) | None => None,
};
Option::serialize(&arg, ser)
}
}
#[derive(Deserialize)]
#[serde(transparent)]
pub struct KeyModifiersDef(Vec<KeyModifier>);
impl From<KeyModifiersDef> for KeyModifiers {
fn from(src: KeyModifiersDef) -> Self {
src.0.into_iter().fold(
KeyModifiers {
ctrl: false,
alt: false,
shift: false,
caps_lock: false,
logo: false,
num_lock: false,
},
|mut modis, modi: KeyModifier| {
modis += modi;
modis
},
)
}
}
#[allow(non_snake_case)]
pub fn deserialize_KeyModifiers<'de, D>(deserializer: D) -> Result<KeyModifiers, D::Error>
where
D: serde::Deserializer<'de>,
{
KeyModifiersDef::deserialize(deserializer).map(Into::into)
}
#[allow(non_snake_case)]
pub fn deserialize_Keysym<'de, D>(deserializer: D) -> Result<Keysym, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::{Error, Unexpected};
let name = String::deserialize(deserializer)?;
//let name = format!("KEY_{}", code);
match xkb::keysym_from_name(&name, xkb::KEYSYM_NO_FLAGS) {
KeySyms::KEY_NoSymbol => match xkb::keysym_from_name(&name, xkb::KEYSYM_CASE_INSENSITIVE) {
KeySyms::KEY_NoSymbol => Err(<D::Error as Error>::invalid_value(
Unexpected::Str(&name),
&"One of the keysym names of xkbcommon.h without the 'KEY_' prefix",
)),
x => {
slog_scope::warn!(
"Key-Binding '{}' only matched case insensitive for {:?}",
name,
xkb::keysym_get_name(x)
);
Ok(x)
}
},
x => Ok(x),
}
}

View file

@ -1,19 +1,20 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::{config::Action, state::Common};
use crate::{config::Action, state::{Common, State}, shell::Workspace};
use smithay::{
backend::input::{Device, DeviceCapability, InputBackend, InputEvent, KeyState},
desktop::{layer_map_for_output, Kind, Space, WindowSurfaceType},
reexports::wayland_server::{protocol::wl_surface::WlSurface, Display},
utils::{Logical, Point},
utils::{Logical, Point, Rectangle},
wayland::{
data_device::set_data_device_focus,
output::Output,
seat::{CursorImageStatus, FilterResult, KeysymHandle, Seat, XkbConfig},
seat::{CursorImageStatus, FilterResult, KeysymHandle, Seat, XkbConfig, keysyms},
shell::wlr_layer::Layer as WlrLayer,
SERIAL_COUNTER,
},
};
use xkbcommon::xkb::KEY_XF86Switch_VT_12;
use std::{cell::RefCell, collections::HashMap};
pub struct ActiveOutput(pub RefCell<Output>);
@ -114,13 +115,13 @@ pub fn set_active_output(seat: &Seat, output: &Output) {
}
}
impl Common {
impl State {
pub fn process_input_event<B: InputBackend>(&mut self, event: InputEvent<B>) {
use smithay::backend::input::Event;
match event {
InputEvent::DeviceAdded { device } => {
let seat = &mut self.last_active_seat;
let seat = &mut self.common.last_active_seat;
let userdata = seat.user_data();
let devices = userdata.get::<Devices>().unwrap();
for cap in devices.add_device(&device) {
@ -136,6 +137,7 @@ impl Common {
}
DeviceCapability::Pointer => {
let output = self
.common
.shell
.outputs()
.next()
@ -157,12 +159,12 @@ impl Common {
}
#[cfg(feature = "debug")]
{
self.egui.debug_state.handle_device_added(&device);
self.egui.log_state.handle_device_added(&device);
self.common.egui.debug_state.handle_device_added(&device);
self.common.egui.log_state.handle_device_added(&device);
}
}
InputEvent::DeviceRemoved { device } => {
for seat in &mut self.seats {
for seat in &mut self.common.seats {
let userdata = seat.user_data();
let devices = userdata.get::<Devices>().unwrap();
if devices.has_device(&device) {
@ -182,15 +184,15 @@ impl Common {
}
#[cfg(feature = "debug")]
{
self.egui.debug_state.handle_device_added(&device);
self.egui.log_state.handle_device_added(&device);
self.common.egui.debug_state.handle_device_added(&device);
self.common.egui.log_state.handle_device_added(&device);
}
}
InputEvent::Keyboard { event, .. } => {
use smithay::backend::input::KeyboardKeyEvent;
let device = event.device();
for seat in self.seats.clone().iter() {
for seat in self.common.seats.clone().iter() {
let userdata = seat.user_data();
let devices = userdata.get::<Devices>().unwrap();
if devices.has_device(&device) {
@ -212,11 +214,11 @@ impl Common {
#[cfg(feature = "debug")]
{
if self.seats.iter().position(|x| x == seat).unwrap() == 0
&& self.egui.active
if self.common.seats.iter().position(|x| x == seat).unwrap() == 0
&& self.common.egui.active
{
if self.egui.debug_state.wants_keyboard() {
self.egui.debug_state.handle_keyboard(
if self.common.egui.debug_state.wants_keyboard() {
self.common.egui.debug_state.handle_keyboard(
&handle,
state == KeyState::Pressed,
modifiers.clone(),
@ -224,8 +226,8 @@ impl Common {
userdata.get::<SupressedKeys>().unwrap().add(&handle);
return FilterResult::Intercept(None);
}
if self.egui.log_state.wants_keyboard() {
self.egui.log_state.handle_keyboard(
if self.common.egui.log_state.wants_keyboard() {
self.common.egui.log_state.handle_keyboard(
&handle,
state == KeyState::Pressed,
modifiers.clone(),
@ -236,8 +238,16 @@ impl Common {
}
}
if state == KeyState::Pressed && (keysyms::KEY_XF86Switch_VT_1..=KEY_XF86Switch_VT_12).contains(&handle.modified_sym()) {
if let Err(err) = self.backend.kms().switch_vt((handle.modified_sym() - keysyms::KEY_XF86Switch_VT_1 + 1) as i32) {
slog_scope::error!("Failed switching virtual terminal: {}", err);
}
userdata.get::<SupressedKeys>().unwrap().add(&handle);
return FilterResult::Intercept(None);
}
// here we can handle global shortcuts and the like
for (binding, action) in self.config.static_conf.key_bindings.iter()
for (binding, action) in self.common.config.static_conf.key_bindings.iter()
{
if state == KeyState::Pressed
&& binding.modifiers == *modifiers
@ -254,19 +264,19 @@ impl Common {
{
match action {
Action::Terminate => {
self.should_stop = true;
self.common.should_stop = true;
}
#[cfg(feature = "debug")]
Action::Debug => {
self.egui.active = !self.egui.active;
self.common.egui.active = !self.common.egui.active;
}
#[cfg(not(feature = "debug"))]
Action::Debug => {
slog_scope::info!("Debug overlay not included in this version")
}
Action::Close => {
let current_output = active_output(seat, &self);
let workspace = self.shell.active_space_mut(&current_output);
let current_output = active_output(seat, &self.common);
let workspace = self.common.shell.active_space_mut(&current_output);
if let Some(window) = workspace.focus_stack(seat).last() {
#[allow(irrefutable_let_patterns)]
if let Kind::Xdg(xdg) = &window.toplevel() {
@ -275,44 +285,52 @@ impl Common {
}
}
Action::Workspace(key_num) => {
let current_output = active_output(seat, &self);
let current_output = active_output(seat, &self.common);
let workspace = match key_num {
0 => 9,
x => x - 1,
};
self.shell
self.common.shell
.activate(seat, &current_output, workspace as usize);
}
Action::MoveToWorkspace(key_num) => {
let current_output = active_output(seat, &self);
let current_output = active_output(seat, &self.common);
let workspace = match key_num {
0 => 9,
x => x - 1,
};
self.shell.move_current_window(
self.common.shell.move_current_window(
seat,
&current_output,
workspace as usize,
);
}
Action::Focus(focus) => {
let current_output = active_output(seat, &self);
self.shell.move_focus(
let current_output = active_output(seat, &self.common);
self.common.shell.move_focus(
seat,
&current_output,
*focus,
self.seats.iter(),
self.common.seats.iter(),
);
}
Action::Fullscreen => {
let current_output = active_output(seat, &self.common);
let workspace = self.common.shell.active_space_mut(&current_output);
let focused_window = workspace.focus_stack(seat).last();
if let Some(window) = focused_window {
workspace.fullscreen_toggle(&window, &current_output);
}
}
Action::Orientation(orientation) => {
let output = active_output(seat, &self);
self.shell.set_orientation(&seat, &output, *orientation);
let output = active_output(seat, &self.common);
self.common.shell.set_orientation(&seat, &output, *orientation);
}
Action::Spawn(command) => {
if let Err(err) = std::process::Command::new("/bin/sh")
.arg("-c")
.arg(command)
.env("WAYLAND_DISPLAY", &self.socket)
.env("WAYLAND_DISPLAY", &self.common.socket)
.spawn()
{
slog_scope::warn!("Failed to spawn: {}", err);
@ -328,20 +346,22 @@ impl Common {
use smithay::backend::input::PointerMotionEvent;
let device = event.device();
for seat in self.seats.clone().iter() {
for seat in self.common.seats.clone().iter() {
let userdata = seat.user_data();
let devices = userdata.get::<Devices>().unwrap();
if devices.has_device(&device) {
let current_output = active_output(seat, &self);
let current_output = active_output(seat, &self.common);
let mut position = seat.get_pointer().unwrap().current_location();
position += event.delta();
let output = self
.common
.shell
.outputs()
.find(|output| {
self.shell
self.common
.shell
.output_geometry(output)
.to_f64()
.contains(position)
@ -351,7 +371,7 @@ impl Common {
if output != current_output {
set_active_output(seat, &output);
}
let output_geometry = self.shell.output_geometry(&output);
let output_geometry = self.common.shell.output_geometry(&output);
position.x = 0.0f64
.max(position.x)
@ -362,13 +382,14 @@ impl Common {
let serial = SERIAL_COUNTER.next_serial();
let relative_pos =
self.shell.space_relative_output_geometry(position, &output);
let workspace = self.shell.active_space_mut(&output);
let under = Common::surface_under(
self.common.shell.space_relative_output_geometry(position, &output);
let workspace = self.common.shell.active_space_mut(&output);
let under = State::surface_under(
position,
relative_pos,
&output,
&workspace.space,
output_geometry,
&workspace,
);
handle_window_movement(
under.as_ref().map(|(s, _)| s),
@ -379,11 +400,11 @@ impl Common {
.motion(position, under, serial, event.time());
#[cfg(feature = "debug")]
if self.seats.iter().position(|x| x == seat).unwrap() == 0 {
self.egui
if self.common.seats.iter().position(|x| x == seat).unwrap() == 0 {
self.common.egui
.debug_state
.handle_pointer_motion(position.to_i32_round());
self.egui
self.common.egui
.log_state
.handle_pointer_motion(position.to_i32_round());
}
@ -395,23 +416,24 @@ impl Common {
use smithay::backend::input::PointerMotionAbsoluteEvent;
let device = event.device();
for seat in self.seats.clone().iter() {
for seat in self.common.seats.clone().iter() {
let userdata = seat.user_data();
let devices = userdata.get::<Devices>().unwrap();
if devices.has_device(&device) {
let output = active_output(seat, &self);
let geometry = self.shell.output_geometry(&output);
let output = active_output(seat, &self.common);
let geometry = self.common.shell.output_geometry(&output);
let position =
geometry.loc.to_f64() + event.position_transformed(geometry.size);
let relative_pos =
self.shell.space_relative_output_geometry(position, &output);
let workspace = self.shell.active_space_mut(&output);
self.common.shell.space_relative_output_geometry(position, &output);
let workspace = self.common.shell.active_space_mut(&output);
let serial = SERIAL_COUNTER.next_serial();
let under = Common::surface_under(
let under = State::surface_under(
position,
relative_pos,
&output,
&workspace.space,
geometry,
&workspace,
);
handle_window_movement(
under.as_ref().map(|(s, _)| s),
@ -422,11 +444,11 @@ impl Common {
.motion(position, under, serial, event.time());
#[cfg(feature = "debug")]
if self.seats.iter().position(|x| x == seat).unwrap() == 0 {
self.egui
if self.common.seats.iter().position(|x| x == seat).unwrap() == 0 {
self.common.egui
.debug_state
.handle_pointer_motion(position.to_i32_round());
self.egui
self.common.egui
.log_state
.handle_pointer_motion(position.to_i32_round());
}
@ -441,30 +463,30 @@ impl Common {
};
let device = event.device();
for seat in self.seats.clone().iter() {
for seat in self.common.seats.clone().iter() {
let userdata = seat.user_data();
let devices = userdata.get::<Devices>().unwrap();
if devices.has_device(&device) {
#[cfg(feature = "debug")]
if self.seats.iter().position(|x| x == seat).unwrap() == 0
&& self.egui.active
if self.common.seats.iter().position(|x| x == seat).unwrap() == 0
&& self.common.egui.active
{
if self.egui.debug_state.wants_pointer() {
if self.common.egui.debug_state.wants_pointer() {
if let Some(button) = event.button() {
self.egui.debug_state.handle_pointer_button(
self.common.egui.debug_state.handle_pointer_button(
button,
event.state() == ButtonState::Pressed,
self.egui.modifiers.clone(),
self.common.egui.modifiers.clone(),
);
}
break;
}
if self.egui.log_state.wants_pointer() {
if self.common.egui.log_state.wants_pointer() {
if let Some(button) = event.button() {
self.egui.log_state.handle_pointer_button(
self.common.egui.log_state.handle_pointer_button(
button,
event.state() == ButtonState::Pressed,
self.egui.modifiers.clone(),
self.common.egui.modifiers.clone(),
);
}
break;
@ -477,58 +499,76 @@ impl Common {
ButtonState::Pressed => {
// change the keyboard focus unless the pointer is grabbed
if !seat.get_pointer().unwrap().is_grabbed() {
let output = active_output(seat, &self);
let output = active_output(seat, &self.common);
let pos = seat.get_pointer().unwrap().current_location();
let output_geo = self.shell.output_geometry(&output);
let output_geo = self.common.shell.output_geometry(&output);
let relative_pos =
self.shell.space_relative_output_geometry(pos, &output);
let workspace = self.shell.active_space_mut(&output);
self.common.shell.space_relative_output_geometry(pos, &output);
let workspace = self.common.shell.active_space_mut(&output);
let layers = layer_map_for_output(&output);
let mut under = None;
if let Some(layer) = layers
.layer_under(WlrLayer::Overlay, relative_pos)
.or_else(|| layers.layer_under(WlrLayer::Top, relative_pos))
{
if layer.can_receive_keyboard_focus() {
let layer_loc =
layers.layer_geometry(layer).unwrap().loc;
under = layer
.surface_under(
pos - output_geo.loc.to_f64() - layer_loc.to_f64(),
WindowSurfaceType::ALL,
)
.map(|(s, _)| s);
}
} else if let Some(window) =
workspace.space.window_under(relative_pos).cloned()
{
let window_loc =
workspace.space.window_location(&window).unwrap();
under = window
.surface_under(
relative_pos - window_loc.to_f64(),
if let Some(window) = workspace.get_fullscreen(&output) {
if let Some(layer) = layers
.layer_under(WlrLayer::Overlay, relative_pos)
{
if layer.can_receive_keyboard_focus() {
let layer_loc =
layers.layer_geometry(layer).unwrap().loc;
under = layer
.surface_under(
pos - output_geo.loc.to_f64()
- layer_loc.to_f64(),
WindowSurfaceType::ALL,
)
.map(|(s, _)| s);
}
} else {
under = window.surface_under(
pos - output_geo.loc.to_f64(),
WindowSurfaceType::TOPLEVEL
| WindowSurfaceType::SUBSURFACE,
)
.map(|(s, _)| s);
} else if let Some(layer) = layers
.layer_under(WlrLayer::Bottom, pos)
.or_else(|| layers.layer_under(WlrLayer::Background, pos))
{
if layer.can_receive_keyboard_focus() {
let layer_loc =
layers.layer_geometry(layer).unwrap().loc;
under = layer
.surface_under(
pos - output_geo.loc.to_f64() - layer_loc.to_f64(),
WindowSurfaceType::ALL,
)
.map(|(s, _)| s);
| WindowSurfaceType::SUBSURFACE
).map(|(s, _)| s);
}
};
} else {
if let Some(layer) = layers
.layer_under(WlrLayer::Overlay, relative_pos)
.or_else(|| layers.layer_under(WlrLayer::Top, relative_pos))
{
if layer.can_receive_keyboard_focus() {
let layer_loc =
layers.layer_geometry(layer).unwrap().loc;
under = layer
.surface_under(
pos - output_geo.loc.to_f64()
- layer_loc.to_f64(),
WindowSurfaceType::ALL,
)
.map(|(s, _)| s);
}
} else if let Some((_, surface, _)) =
workspace.space.surface_under(relative_pos, WindowSurfaceType::TOPLEVEL | WindowSurfaceType::SUBSURFACE)
{
under = Some(surface);
} else if let Some(layer) = layers
.layer_under(WlrLayer::Bottom, pos)
.or_else(|| layers.layer_under(WlrLayer::Background, pos))
{
if layer.can_receive_keyboard_focus() {
let layer_loc =
layers.layer_geometry(layer).unwrap().loc;
under = layer
.surface_under(
pos - output_geo.loc.to_f64()
- layer_loc.to_f64(),
WindowSurfaceType::ALL,
)
.map(|(s, _)| s);
}
};
}
self.set_focus(under.as_ref(), seat, Some(serial));
self.common.set_focus(under.as_ref(), seat, Some(serial));
}
wl_pointer::ButtonState::Pressed
}
@ -549,11 +589,11 @@ impl Common {
};
let device = event.device();
for seat in self.seats.clone().iter() {
for seat in self.common.seats.clone().iter() {
#[cfg(feature = "debug")]
if self.seats.iter().position(|x| x == seat).unwrap() == 0 && self.egui.active {
if self.egui.debug_state.wants_pointer() {
self.egui.debug_state.handle_pointer_axis(
if self.common.seats.iter().position(|x| x == seat).unwrap() == 0 && self.common.egui.active {
if self.common.egui.debug_state.wants_pointer() {
self.common.egui.debug_state.handle_pointer_axis(
event
.amount_discrete(Axis::Horizontal)
.or_else(|| event.amount(Axis::Horizontal).map(|x| x * 3.0))
@ -565,8 +605,8 @@ impl Common {
);
break;
}
if self.egui.log_state.wants_pointer() {
self.egui.log_state.handle_pointer_axis(
if self.common.egui.log_state.wants_pointer() {
self.common.egui.log_state.handle_pointer_axis(
event
.amount_discrete(Axis::Horizontal)
.or_else(|| event.amount(Axis::Horizontal).map(|x| x * 3.0))
@ -640,55 +680,63 @@ impl Common {
global_pos: Point<f64, Logical>,
relative_pos: Point<f64, Logical>,
output: &Output,
space: &Space,
output_geo: Rectangle<i32, Logical>,
workspace: &Workspace,
) -> Option<(WlSurface, Point<i32, Logical>)> {
let layers = layer_map_for_output(output);
let output_geo = space.output_geometry(output).unwrap();
if let Some(layer) = layers
.layer_under(WlrLayer::Overlay, relative_pos)
.or_else(|| layers.layer_under(WlrLayer::Top, relative_pos))
{
let layer_loc = layers.layer_geometry(layer).unwrap().loc;
layer
.surface_under(
global_pos - output_geo.loc.to_f64() - layer_loc.to_f64(),
WindowSurfaceType::ALL,
)
.map(|(s, loc)| {
(
s,
loc + layer_loc + output_geo.loc,
if let Some(window) = workspace.get_fullscreen(output) {
if let Some(layer) = layers
.layer_under(WlrLayer::Overlay, relative_pos)
.or_else(|| layers.layer_under(WlrLayer::Top, relative_pos))
{
let layer_loc = layers.layer_geometry(layer).unwrap().loc;
layer
.surface_under(
global_pos - output_geo.loc.to_f64() - layer_loc.to_f64(),
WindowSurfaceType::ALL,
)
})
} else if let Some(window) = space.window_under(relative_pos) {
let window_loc = space.window_location(window).unwrap();
window
.surface_under(relative_pos - window_loc.to_f64(), WindowSurfaceType::ALL)
.map(|(s, loc)| {
(
s,
loc + window_loc - (relative_pos - global_pos).to_i32_round(),
)
})
} else if let Some(layer) = layers
.layer_under(WlrLayer::Bottom, relative_pos)
.or_else(|| layers.layer_under(WlrLayer::Background, relative_pos))
{
let layer_loc = layers.layer_geometry(layer).unwrap().loc;
layer
.surface_under(
global_pos - output_geo.loc.to_f64() - layer_loc.to_f64(),
WindowSurfaceType::ALL,
)
.map(|(s, loc)| {
(
s,
loc + layer_loc + output_geo.loc,
)
})
.map(|(s, loc)| (s, loc + layer_loc + output_geo.loc))
} else {
window
.surface_under(global_pos - output_geo.loc.to_f64(), WindowSurfaceType::ALL)
.map(|(s, loc)| {
(
s,
loc + output_geo.loc,
)
})
}
} else {
None
if let Some(layer) = layers
.layer_under(WlrLayer::Overlay, relative_pos)
.or_else(|| layers.layer_under(WlrLayer::Top, relative_pos))
{
let layer_loc = layers.layer_geometry(layer).unwrap().loc;
layer
.surface_under(
global_pos - output_geo.loc.to_f64() - layer_loc.to_f64(),
WindowSurfaceType::ALL,
)
.map(|(s, loc)| (s, loc + layer_loc + output_geo.loc))
} else if let Some((_, surface, loc)) = workspace.space.surface_under(relative_pos, WindowSurfaceType::ALL) {
Some((
surface,
loc + (global_pos - relative_pos).to_i32_round(),
))
} else if let Some(layer) = layers
.layer_under(WlrLayer::Bottom, relative_pos)
.or_else(|| layers.layer_under(WlrLayer::Background, relative_pos))
{
let layer_loc = layers.layer_geometry(layer).unwrap().loc;
layer
.surface_under(
global_pos - output_geo.loc.to_f64() - layer_loc.to_f64(),
WindowSurfaceType::ALL,
)
.map(|(s, loc)| (s, loc + layer_loc + output_geo.loc))
} else {
None
}
}
}
}

View file

@ -30,18 +30,17 @@ fn main() -> Result<()> {
// init wayland
let (display, socket) = init_wayland_display(&mut event_loop)?;
// init state
let mut state = state::State::new(display, socket, event_loop.handle(), log);
let mut state = state::State::new(display, socket, event_loop.handle(), event_loop.get_signal(), log);
// init backend
backend::init_backend_auto(&mut event_loop, &mut state)?;
// run the event loop
let signal = event_loop.get_signal();
event_loop.run(None, &mut state, |state| {
// shall we shut down?
if state.common.shell.outputs().next().is_none() || state.common.should_stop {
slog_scope::info!("Shutting down");
signal.stop();
signal.wakeup();
state.common.event_loop_signal.stop();
state.common.event_loop_signal.wakeup();
return;
}

View file

@ -17,8 +17,7 @@ use smithay::{
seat::{PointerGrabStartData, Seat},
shell::{
wlr_layer::{
wlr_layer_shell_init, KeyboardInteractivity, Layer, LayerShellRequest,
LayerSurfaceAttributes, LayerSurfaceCachedState,
wlr_layer_shell_init, LayerShellRequest, LayerSurfaceAttributes,
},
xdg::{
xdg_shell_init, Configure, XdgPopupSurfaceRoleAttributes, XdgRequest,
@ -26,7 +25,6 @@ use smithay::{
},
},
Serial,
SERIAL_COUNTER,
},
};
use std::{cell::Cell, sync::Mutex};
@ -215,6 +213,31 @@ pub fn init_shell(config: &Config, display: &mut Display) -> super::Shell {
.set(Some(grab));
}
}
XdgRequest::Fullscreen { surface, output } => {
let output = output
.as_ref()
.and_then(Output::from_resource)
.unwrap_or_else(|| {
let seat = &state.last_active_seat;
active_output(seat, &*state)
});
if let Some(surface) = surface.get_surface() {
if let Some(workspace) = state.shell.space_for_surface_mut(surface) {
let window =
workspace.space.window_for_surface(surface).unwrap().clone();
workspace.fullscreen_request(&window, &output)
}
}
}
XdgRequest::UnFullscreen { surface } => {
if let Some(surface) = surface.get_surface() {
if let Some(workspace) = state.shell.space_for_surface_mut(surface) {
let window =
workspace.space.window_for_surface(surface).unwrap().clone();
workspace.unfullscreen_request(&window)
}
}
}
_ => { /*TODO*/ }
}
},
@ -236,26 +259,7 @@ pub fn init_shell(config: &Config, display: &mut Display) -> super::Shell {
.as_ref()
.and_then(Output::from_resource)
.unwrap_or_else(|| active_output(&seat, &*state));
let focus = surface
.get_surface()
.map(|surface| {
with_states(surface, |states| {
let state = states.cached_state.current::<LayerSurfaceCachedState>();
matches!(state.layer, Layer::Top | Layer::Overlay)
&& state.keyboard_interactivity != KeyboardInteractivity::None
})
.unwrap()
})
.unwrap_or(false);
let mut map = layer_map_for_output(&output);
map.map_layer(&LayerSurface::new(surface.clone(), namespace))
.unwrap();
if focus {
state.set_focus(surface.get_surface(), &seat, Some(SERIAL_COUNTER.next_serial()));
}
state.shell.active_space_mut(&output).pending_layer(LayerSurface::new(surface, namespace), &output, &seat);
}
_ => {}
},

View file

@ -529,6 +529,10 @@ impl TilingLayout {
if let Some(geo) = geo {
#[allow(irrefutable_let_patterns)]
if let Kind::Xdg(xdg) = &window.toplevel() {
if xdg.current_state().map(|state| state.states.contains(XdgState::Fullscreen)).unwrap_or(false) ||
xdg.with_pending_state(|pending| pending.states.contains(XdgState::Fullscreen)).unwrap_or(false) {
continue;
}
let ret = xdg.with_pending_state(|state| {
state.size = Some(
(geo.size.w - inner * 2, geo.size.h - inner * 2)
@ -543,12 +547,11 @@ impl TilingLayout {
xdg.send_configure();
}
}
let window_geo = window.geometry();
space.map_window(
&window,
(
geo.loc.x + inner - window_geo.loc.x,
geo.loc.y + inner - window_geo.loc.y,
geo.loc.x + inner,
geo.loc.y + inner,
),
false,
);

View file

@ -6,14 +6,17 @@ use crate::{
state::Common,
};
pub use smithay::{
desktop::{PopupGrab, PopupManager, PopupUngrabStrategy, Space, Window},
desktop::{PopupGrab, PopupManager, PopupUngrabStrategy, Space, Window, layer_map_for_output},
reexports::wayland_server::protocol::wl_surface::WlSurface,
utils::{Logical, Point, Rectangle, Size},
wayland::{
compositor::with_states,
output::{Mode as OutputMode, Output},
output::{Mode as OutputMode, Output, Scale},
seat::Seat,
shell::xdg::XdgToplevelSurfaceRoleAttributes,
shell::{
xdg::XdgToplevelSurfaceRoleAttributes,
wlr_layer::{Layer, KeyboardInteractivity, LayerSurfaceCachedState},
},
Serial, SERIAL_COUNTER,
},
};
@ -103,15 +106,10 @@ impl Shell {
.borrow();
workspace
.space
.map_output(output, config.scale, config.position);
.map_output(output, config.position);
}
} else {
for output in self.outputs.iter() {
let config = output
.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow();
let active = output
.user_data()
.get::<ActiveWorkspace>()
@ -119,7 +117,7 @@ impl Shell {
.get()
.unwrap();
let workspace = &mut self.spaces[active];
workspace.space.map_output(output, config.scale, (0, 0));
workspace.space.map_output(output, (0, 0));
}
}
}
@ -156,16 +154,16 @@ impl Shell {
match self.mode {
Mode::OutputBound => {
let workspace = Self::assign_next_free_output(&mut self.spaces, output);
workspace.space.map_output(output, config.scale, (0, 0));
workspace.space.map_output(output, (0, 0));
}
Mode::Global { active } => {
let workspace = &mut self.spaces[active];
workspace
.space
.map_output(output, config.scale, config.position);
.map_output(output, config.position);
}
}
output.change_current_state(None, None, Some(config.scale.ceil() as i32), None);
output.change_current_state(None, None, Some(Scale::Fractional(config.scale)), None);
}
pub fn remove_output(&mut self, output: &Output) {
@ -271,14 +269,9 @@ impl Shell {
if let Some(old_idx) = active.set(idx) {
self.spaces[old_idx].space.unmap_output(output);
}
let config = output
.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow();
self.spaces[idx]
.space
.map_output(output, config.scale, (0, 0));
.map_output(output, (0, 0));
self.spaces[idx].refresh();
}
}
@ -294,7 +287,7 @@ impl Shell {
.borrow();
self.spaces[*active]
.space
.map_output(output, config.scale, config.position);
.map_output(output, config.position);
}
}
};
@ -353,7 +346,7 @@ impl Shell {
self.spaces[old_active].space.unmap_output(output);
self.spaces[active]
.space
.map_output(output, config.scale, config.position);
.map_output(output, config.position);
self.spaces[active].refresh();
}
@ -367,11 +360,6 @@ impl Shell {
let mut active = Some(active.clone());
self.mode = new;
for output in &self.outputs {
let config = output
.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow();
let workspace = if let Some(a) = active.take() {
output
.user_data()
@ -381,7 +369,7 @@ impl Shell {
} else {
Self::assign_next_free_output(&mut self.spaces, output)
};
workspace.space.map_output(output, config.scale, (0, 0));
workspace.space.map_output(output, (0, 0));
workspace.refresh();
}
}
@ -440,6 +428,7 @@ impl Shell {
}
pub fn refresh(&mut self) {
self.popups.cleanup();
for output in self.outputs.iter() {
let workspace = match &self.mode {
Mode::OutputBound => {
@ -454,6 +443,9 @@ impl Shell {
Mode::Global { active } => &mut self.spaces[*active],
};
workspace.refresh();
let mut map = layer_map_for_output(output);
map.cleanup();
map.arrange();
}
}
@ -480,9 +472,35 @@ impl Shell {
} {
new_focus = Some(seat);
}
workspace.pending_windows.retain(|(w, _)| w != &window);
workspace.pending_windows.retain(|(w, _)| w != &window);
}
workspace.space.commit(surface)
if let Some((layer, output, seat)) = workspace
.pending_layers
.iter()
.find(|(l, _, _)| l.get_surface() == Some(surface))
.cloned()
{
let focus = layer
.get_surface()
.map(|surface| {
with_states(surface, |states| {
let state = states.cached_state.current::<LayerSurfaceCachedState>();
matches!(state.layer, Layer::Top | Layer::Overlay)
&& dbg!(state.keyboard_interactivity) != KeyboardInteractivity::None
})
.unwrap()
})
.unwrap_or(false);
let mut map = layer_map_for_output(&output);
map.map_layer(&layer).unwrap();
if focus {
new_focus = Some(seat);
}
workspace.pending_layers.retain(|(l, _, _)| l != &layer);
}
workspace.space.commit(surface);
}
if let Some(seat) = new_focus {
self.set_focus(Some(surface), &seat, seats, None)

View file

@ -1,8 +1,8 @@
use super::{layout, Layout};
use indexmap::IndexSet;
use smithay::{
desktop::{Space, Window},
reexports::wayland_protocols::xdg_shell::server::xdg_toplevel::ResizeEdge,
desktop::{LayerSurface, Space, Window, Kind},
reexports::wayland_protocols::xdg_shell::server::xdg_toplevel::{self, ResizeEdge},
wayland::{
output::Output,
seat::{PointerGrabStartData, Seat},
@ -52,6 +52,8 @@ pub struct Workspace {
pub space: Space,
pub(super) layout: Box<dyn Layout>,
pub(super) pending_windows: Vec<(Window, Seat)>,
pub(super) pending_layers: Vec<(LayerSurface, Output, Seat)>,
pub fullscreen: HashMap<String, Window>,
}
impl Workspace {
@ -61,6 +63,8 @@ impl Workspace {
space: Space::new(None),
layout: layout::new_default_layout(),
pending_windows: Vec::new(),
pending_layers: Vec::new(),
fullscreen: HashMap::new(),
}
}
@ -89,6 +93,11 @@ impl Workspace {
self.pending_windows.push((window, seat.clone()));
}
pub fn pending_layer(&mut self, layer: LayerSurface, output: &Output, seat: &Seat) {
self.pending_layers
.push((layer, output.clone(), seat.clone()));
}
pub(super) fn map_window(&mut self, window: &Window, seat: &Seat) {
seat.user_data()
.insert_if_missing(|| FocusStackData::new((HashMap::new(), IndexSet::new())));
@ -108,6 +117,19 @@ impl Workspace {
}
pub fn refresh(&mut self) {
let outputs = self.space.outputs().collect::<Vec<_>>();
let dead_output_windows = self.fullscreen
.iter()
.filter(|(name, _)|
!outputs.iter().any(|o| o.name() == **name)
)
.map(|(_, w)| w)
.cloned()
.collect::<Vec<_>>();
for window in dead_output_windows {
self.unfullscreen_request(&window);
}
self.fullscreen.retain(|_, w| w.toplevel().alive());
self.layout.refresh(&mut self.space);
self.space.refresh();
}
@ -141,6 +163,9 @@ impl Workspace {
}
pub fn maximize_request(&mut self, window: &Window, output: &Output) {
if self.fullscreen.values().any(|w| w == window) {
return;
}
self.layout
.maximize_request(&mut self.space, window, output)
}
@ -152,6 +177,9 @@ impl Workspace {
serial: Serial,
start_data: PointerGrabStartData,
) {
if self.fullscreen.values().any(|w| w == window) {
return;
}
self.layout
.move_request(&mut self.space, window, seat, serial, start_data)
}
@ -164,7 +192,73 @@ impl Workspace {
start_data: PointerGrabStartData,
edges: ResizeEdge,
) {
if self.fullscreen.values().any(|w| w == window) {
return;
}
self.layout
.resize_request(&mut self.space, window, seat, serial, start_data, edges)
}
pub fn fullscreen_request(&mut self, window: &Window, output: &Output) {
if self.fullscreen.contains_key(&output.name()) {
return;
}
#[allow(irrefutable_let_patterns)]
if let Kind::Xdg(xdg) = &window.toplevel() {
if xdg.get_surface().is_some() {
let ret = xdg.with_pending_state(|state| {
state.states.set(xdg_toplevel::State::Fullscreen);
state.size = Some(
output
.current_mode()
.map(|m| m.size)
.unwrap_or((0, 0).into())
.to_f64()
.to_logical(output.current_scale().fractional_scale())
.to_i32_round(),
);
});
if ret.is_ok() {
xdg.send_configure();
self.fullscreen.insert(output.name(), window.clone());
}
}
}
}
pub fn unfullscreen_request(&mut self, window: &Window) {
if self.fullscreen.values().any(|w| w == window) {
#[allow(irrefutable_let_patterns)]
if let Kind::Xdg(xdg) = &window.toplevel() {
if xdg.get_surface().is_some() {
let ret = xdg.with_pending_state(|state| {
state.states.unset(xdg_toplevel::State::Fullscreen);
state.size = None;
});
if ret.is_ok() {
self.layout.refresh(&mut self.space);
xdg.send_configure();
}
}
}
self.fullscreen.retain(|_, w| w != window);
}
}
pub fn fullscreen_toggle(&mut self, window: &Window, output: &Output) {
if self.fullscreen.contains_key(&output.name()) {
self.unfullscreen_request(window)
} else {
self.fullscreen_request(window, output)
}
}
pub fn get_fullscreen(&self, output: &Output) -> Option<&Window> {
if !self.space.outputs().any(|o| o == output) {
return None;
}
self.fullscreen.get(&output.name()).filter(|w| w.toplevel().get_surface().is_some())
}
}

View file

@ -8,7 +8,7 @@ use crate::{
};
use smithay::{
reexports::{
calloop::LoopHandle,
calloop::{LoopHandle, LoopSignal},
wayland_server::{protocol::wl_surface::WlSurface, Display},
},
wayland::{
@ -18,7 +18,7 @@ use smithay::{
self, init_wlr_output_configuration, ConfigurationManager, ModeConfiguration,
},
xdg::init_xdg_output_manager,
Mode as OutputMode, Output,
Mode as OutputMode, Output, Scale,
},
seat::Seat,
shell::xdg::ToplevelSurface,
@ -47,6 +47,7 @@ pub struct Common {
pub display: Rc<RefCell<Display>>,
pub socket: OsString,
pub event_loop_handle: LoopHandle<'static, State>,
pub event_loop_signal: LoopSignal,
pub output_conf: ConfigurationManager,
pub shell: Shell,
@ -145,10 +146,10 @@ impl BackendData {
let transform =
Some(final_config.transform.into()).filter(|x| *x != output.current_transform());
let scale =
Some(final_config.scale.ceil() as i32).filter(|x| *x != output.current_scale());
Some(final_config.scale).filter(|x| *x != output.current_scale().fractional_scale());
let location =
Some(final_config.position.into()).filter(|x| *x != output.current_location());
output.change_current_state(mode, transform, scale, location);
output.change_current_state(mode, transform, scale.map(Scale::Fractional), location);
}
result
@ -182,6 +183,7 @@ impl State {
mut display: Display,
socket: OsString,
handle: LoopHandle<'static, State>,
signal: LoopSignal,
log: LogState,
) -> State {
let config = Config::load();
@ -326,6 +328,7 @@ impl State {
display: Rc::new(RefCell::new(display)),
socket,
event_loop_handle: handle,
event_loop_signal: signal,
output_conf,
shell,