Merge pull request #194 from pop-os/ws-refactor_jammy

Overhaul of workspace code
This commit is contained in:
Victoria Brekenfeld 2023-10-26 19:25:26 +02:00 committed by GitHub
commit 7300e23ce8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 3626 additions and 3398 deletions

574
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -36,10 +36,10 @@ libsystemd = { version = "0.6", optional = true }
wayland-backend = "0.3.2"
wayland-scanner = "0.31.0"
cosmic-comp-config = { path = "cosmic-comp-config" }
cosmic-config = { git = "https://github.com/pop-os/libcosmic/", rev = "f91287d", features = ["calloop"] }
cosmic-config = { git = "https://github.com/pop-os/libcosmic/", features = ["calloop", "macro"] }
cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols", branch = "main", default-features = false, features = ["server"] }
libcosmic = { git = "https://github.com/pop-os/libcosmic/", rev = "f91287d", default-features = false }
iced_tiny_skia = { git = "https://github.com/pop-os/libcosmic/", rev = "f91287d" }
libcosmic = { git = "https://github.com/pop-os/libcosmic/", default-features = false }
iced_tiny_skia = { git = "https://github.com/pop-os/libcosmic/" }
tiny-skia = "0.10"
ordered-float = "4.0"
glow = "0.12.0"
@ -53,6 +53,7 @@ once_cell = "1.18.0"
i18n-embed = { version = "0.14", features = ["fluent-system", "desktop-requester"] }
i18n-embed-fl = "0.7"
rust-embed = "8.0"
libc = "0.2.149"
[dependencies.id_tree]
git = "https://github.com/Drakulix/id-tree.git"
@ -87,7 +88,4 @@ debug = true
lto = "fat"
[patch."https://github.com/Smithay/smithay.git"]
smithay = { git = "https://github.com/smithay//smithay", rev = "d60b1b83e" }
[patch.crates-io]
calloop = { git = "https://github.com/Smithay/calloop", rev = "71b6e633b1" }
smithay = { git = "https://github.com/pop-os/smithay", branch = "x11_fixes" }

View file

@ -16,6 +16,7 @@ use crate::{
use anyhow::{Context, Result};
use cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_session_v1::FailureReason;
use libc::dev_t;
use smithay::{
backend::{
allocator::{
@ -25,7 +26,7 @@ use smithay::{
Allocator, Format, Fourcc,
},
drm::{
compositor::{BlitFrameResultError, DrmCompositor, FrameError},
compositor::{BlitFrameResultError, DrmCompositor, FrameError, PrimaryPlaneElement},
DrmDevice, DrmDeviceFd, DrmEvent, DrmEventTime, DrmNode, NodeType,
},
egl::{EGLContext, EGLDevice, EGLDisplay},
@ -58,7 +59,7 @@ use smithay::{
Device as _,
},
input::{self, Libinput},
nix::{fcntl::OFlag, sys::stat::dev_t},
rustix::fs::OFlags,
wayland_protocols::wp::{
linux_dmabuf::zv1::server::zwp_linux_dmabuf_feedback_v1,
presentation_time::server::wp_presentation_feedback,
@ -82,7 +83,6 @@ use std::{
collections::{HashMap, HashSet},
ffi::CStr,
fmt,
os::unix::io::FromRawFd,
path::PathBuf,
time::Duration,
};
@ -418,23 +418,21 @@ impl State {
return Ok(());
}
let fd = DrmDeviceFd::new(unsafe {
DeviceFd::from_raw_fd(
self.backend
.kms()
.session
.open(
&path,
OFlag::O_RDWR | OFlag::O_CLOEXEC | OFlag::O_NOCTTY | OFlag::O_NONBLOCK,
let fd = DrmDeviceFd::new(DeviceFd::from(
self.backend
.kms()
.session
.open(
&path,
OFlags::RDWR | OFlags::CLOEXEC | OFlags::NOCTTY | OFlags::NONBLOCK,
)
.with_context(|| {
format!(
"Failed to optain file descriptor for drm device: {}",
path.display()
)
.with_context(|| {
format!(
"Failed to optain file descriptor for drm device: {}",
path.display()
)
})?,
)
});
})?,
));
let (drm, notifier) = DrmDevice::new(fd.clone(), false)
.with_context(|| format!("Failed to initialize drm device for: {}", path.display()))?;
let drm_node = DrmNode::from_dev_id(dev)?;
@ -1014,8 +1012,14 @@ impl Device {
interface,
PhysicalProperties {
size: (phys_w as i32, phys_h as i32).into(),
// TODO: We need to read that from the connector properties
subpixel: Subpixel::Unknown,
subpixel: match conn_info.subpixel() {
connector::SubPixel::HorizontalRgb => Subpixel::HorizontalRgb,
connector::SubPixel::HorizontalBgr => Subpixel::HorizontalBgr,
connector::SubPixel::VerticalRgb => Subpixel::VerticalRgb,
connector::SubPixel::VerticalBgr => Subpixel::VerticalBgr,
connector::SubPixel::None => Subpixel::None,
_ => Subpixel::Unknown,
},
make: edid_info
.as_ref()
.map(|info| info.manufacturer.clone())
@ -1093,8 +1097,8 @@ fn render_node_for_output(
) -> DrmNode {
let workspace = shell.active_space(output);
let nodes = workspace
.get_fullscreen(output)
.map(|w| vec![w.surface()])
.get_fullscreen()
.map(|w| vec![w.clone()])
.unwrap_or_else(|| workspace.windows().collect::<Vec<_>>())
.into_iter()
.flat_map(|w| w.wl_surface().and_then(|s| source_node_for_surface(&s, dh)))
@ -1235,8 +1239,11 @@ impl Surface {
})?;
self.fps.elements();
let res =
compositor.render_frame::<_, _, GlesTexture>(&mut renderer, &elements, CLEAR_COLOR);
let res = compositor.render_frame::<_, _, GlesTexture>(
&mut renderer,
&elements,
CLEAR_COLOR, // TODO use a theme neutral color
);
self.fps.render();
match res {
@ -1247,6 +1254,11 @@ impl Surface {
None
};
if frame_result.needs_sync() {
if let PrimaryPlaneElement::Swapchain(elem) = &frame_result.primary_element {
elem.sync.wait();
}
}
match compositor.queue_frame(feedback) {
Ok(()) | Err(FrameError::EmptyFrame) => {}
Err(err) => {
@ -1507,7 +1519,6 @@ impl KmsState {
false
};
shell.refresh_outputs();
if recreated {
let sessions = output.pending_buffers().collect::<Vec<_>>();
if let Err(err) = self.schedule_render(

View file

@ -17,7 +17,7 @@ use crate::{
CosmicMapped, CosmicMappedRenderElement, OverviewMode, Trigger, WorkspaceRenderElement,
},
state::{Common, Fps},
utils::prelude::{OutputExt, SeatExt},
utils::prelude::*,
wayland::{
handlers::{
data_device::get_dnd_icon,
@ -79,10 +79,6 @@ pub type GlMultiFrame<'a, 'b, 'frame> =
pub type GlMultiError = MultiError<GbmGlesBackend<GlowRenderer>, GbmGlesBackend<GlowRenderer>>;
pub static CLEAR_COLOR: [f32; 4] = [0.153, 0.161, 0.165, 1.0];
pub static GROUP_COLOR: [f32; 3] = [0.788, 0.788, 0.788];
pub static ACTIVE_GROUP_COLOR: [f32; 3] = [0.58, 0.922, 0.922];
pub static FOCUS_INDICATOR_COLOR: [f32; 3] = [0.580, 0.921, 0.921];
pub static OUTLINE_SHADER: &str = include_str!("./shaders/rounded_outline.frag");
pub static RECTANGLE_SHADER: &str = include_str!("./shaders/rounded_rectangle.frag");
@ -160,10 +156,11 @@ impl IndicatorShader {
pub fn focus_element<R: AsGlowRenderer>(
renderer: &R,
key: impl Into<Key>,
mut element_geo: Rectangle<i32, Logical>,
mut element_geo: Rectangle<i32, Local>,
thickness: u8,
scale: f64,
alpha: f32,
active_window_hint: [f32; 3],
) -> PixelShaderElement {
let t = thickness as i32;
element_geo.loc -= (t, t).into();
@ -177,14 +174,14 @@ impl IndicatorShader {
thickness * 2,
alpha,
scale,
FOCUS_INDICATOR_COLOR,
active_window_hint,
)
}
pub fn element<R: AsGlowRenderer>(
renderer: &R,
key: impl Into<Key>,
geo: Rectangle<i32, Logical>,
geo: Rectangle<i32, Local>,
thickness: u8,
radius: u8,
alpha: f32,
@ -223,7 +220,7 @@ impl IndicatorShader {
let elem = PixelShaderElement::new(
shader,
geo,
geo.as_logical(),
None, //TODO
alpha,
vec![
@ -240,8 +237,8 @@ impl IndicatorShader {
}
let elem = &mut cache.get_mut(&key).unwrap().1;
if elem.geometry(1.0.into()).to_logical(1) != geo {
elem.resize(geo, None);
if elem.geometry(1.0.into()).to_logical(1) != geo.as_logical() {
elem.resize(geo.as_logical(), None);
}
elem.clone()
}
@ -271,7 +268,7 @@ impl BackdropShader {
pub fn element<R: AsGlowRenderer>(
renderer: &R,
key: impl Into<Key>,
geo: Rectangle<i32, Logical>,
geo: Rectangle<i32, Local>,
radius: f32,
alpha: f32,
color: [f32; 3],
@ -304,7 +301,7 @@ impl BackdropShader {
let elem = PixelShaderElement::new(
shader,
geo,
geo.as_logical(),
None, // TODO
alpha,
vec![
@ -320,8 +317,8 @@ impl BackdropShader {
}
let elem = &mut cache.get_mut(&key).unwrap().1;
if elem.geometry(1.0.into()).to_logical(1) != geo {
elem.resize(geo, None);
if elem.geometry(1.0.into()).to_logical(1) != geo.as_logical() {
elem.resize(geo.as_logical(), None);
}
elem.clone()
}
@ -413,13 +410,14 @@ where
);
}
let theme = state.theme.cosmic();
if let Some(grab_elements) = seat
.user_data()
.get::<SeatMoveGrabState>()
.unwrap()
.borrow()
.as_ref()
.map(|state| state.render::<E, R>(renderer, seat, output))
.map(|state| state.render::<E, R>(renderer, seat, output, theme))
{
elements.extend(grab_elements);
}
@ -449,6 +447,7 @@ where
#[cfg(feature = "debug")]
puffin::profile_function!();
let theme = state.theme.cosmic();
let mut elements = cursor_elements(renderer, state, output, cursor_mode);
#[cfg(feature = "debug")]
@ -492,15 +491,11 @@ where
let (resize_mode, resize_indicator) = state.shell.resize_mode();
let resize_indicator = resize_indicator.map(|indicator| (resize_mode, indicator));
let swap_tree = if let OverviewMode::Started(Trigger::KeyboardSwap(_, desc), _) = &overview.0 {
if let Some(desc_output) = desc.output.upgrade() {
if output != &desc_output || current.0 != desc.handle {
state
.shell
.space_for_handle(&desc.handle)
.and_then(|w| w.tiling_layer.tree_for_output(&desc_output))
} else {
None
}
if current.0 != desc.handle {
state
.shell
.space_for_handle(&desc.handle)
.map(|w| w.tiling_layer.tree())
} else {
None
}
@ -530,9 +525,9 @@ where
let has_fullscreen = workspace
.fullscreen
.get(output)
.as_ref()
.filter(|f| !f.is_animating())
.map(|f| f.exclusive);
.is_some();
let (overlay_elements, overlay_popups) =
split_layer_elements(renderer, output, Layer::Overlay, exclude_workspace_overview);
@ -540,7 +535,7 @@ where
elements.extend(overlay_popups.into_iter().map(Into::into));
elements.extend(overlay_elements.into_iter().map(Into::into));
let mut window_elements = if !has_fullscreen.unwrap_or(false) {
let mut window_elements = if !has_fullscreen {
let (top_elements, top_popups) =
split_layer_elements(renderer, output, Layer::Top, exclude_workspace_overview);
elements.extend(top_popups.into_iter().map(Into::into));
@ -548,6 +543,7 @@ where
} else {
Vec::new()
};
let active_hint = theme.active_hint as u8;
let offset = match previous.as_ref() {
Some((previous, previous_idx, start)) => {
@ -557,7 +553,7 @@ where
.shell
.space_for_handle(&previous)
.ok_or(OutputNoMode)?;
let has_fullscreen = workspace.fullscreen.contains_key(output);
let has_fullscreen = workspace.fullscreen.is_some();
let is_active_space = workspace.outputs().any(|o| o == &active_output);
let percentage = {
@ -581,15 +577,15 @@ where
});
let (w_elements, p_elements) = workspace
.render_output::<R>(
.render::<R>(
renderer,
output,
&state.shell.override_redirect_windows,
state.xwayland_state.as_mut(),
(!move_active && is_active_space).then_some(&last_active_seat),
overview.clone(),
resize_indicator.clone(),
state.config.static_conf.active_hint,
active_hint,
theme,
)
.map_err(|_| OutputNoMode)?;
elements.extend(p_elements.into_iter().map(|p_element| {
@ -639,15 +635,15 @@ where
let is_active_space = workspace.outputs().any(|o| o == &active_output);
let (w_elements, p_elements) = workspace
.render_output::<R>(
.render::<R>(
renderer,
output,
&state.shell.override_redirect_windows,
state.xwayland_state.as_mut(),
(!move_active && is_active_space).then_some(&last_active_seat),
overview,
resize_indicator,
state.config.static_conf.active_hint,
active_hint,
theme,
)
.map_err(|_| OutputNoMode)?;
elements.extend(p_elements.into_iter().map(|p_element| {
@ -665,7 +661,7 @@ where
))
}));
if has_fullscreen.is_none() {
if !has_fullscreen {
let (w_elements, p_elements) =
background_layer_elements(renderer, output, exclude_workspace_overview);
@ -919,7 +915,12 @@ where
}
renderer.bind(target).map_err(RenderError::Rendering)?;
let res = damage_tracker.render_output(renderer, age, &elements, CLEAR_COLOR);
let res = damage_tracker.render_output(
renderer,
age,
&elements,
CLEAR_COLOR, // TODO use a theme neutral color
);
if let Some(fps) = fps.as_mut() {
fps.render();

View file

@ -26,6 +26,7 @@ use smithay::{
calloop::{ping, EventLoop},
wayland_protocols::wp::presentation_time::server::wp_presentation_feedback,
wayland_server::DisplayHandle,
winit::platform::pump_events::PumpStatus,
},
utils::Transform,
wayland::dmabuf::DmabufFeedbackBuilder,
@ -125,12 +126,9 @@ impl WinitState {
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow_mut();
if dbg!(config.mode.0) != dbg!((size.physical_size.w as i32, size.physical_size.h as i32)) {
if dbg!(config.mode.0) != dbg!((size.w as i32, size.h as i32)) {
if !test_only {
config.mode = (
(size.physical_size.w as i32, size.physical_size.h as i32),
None,
);
config.mode = ((size.w as i32, size.h as i32), None);
}
Err(anyhow::anyhow!("Cannot set window size"))
} else {
@ -165,7 +163,7 @@ pub fn init_backend(
model: name.clone(),
};
let mode = Mode {
size: (size.physical_size.w as i32, size.physical_size.h as i32).into(),
size: (size.w as i32, size.h as i32).into(),
refresh: 60_000,
};
let output = Output::new(name, props);
@ -179,10 +177,7 @@ pub fn init_backend(
);
output.user_data().insert_if_missing(|| {
RefCell::new(OutputConfig {
mode: (
(size.physical_size.w as i32, size.physical_size.h as i32),
None,
),
mode: ((size.w as i32, size.h as i32), None),
transform: Transform::Flipped180.into(),
..Default::default()
})
@ -212,11 +207,11 @@ pub fn init_backend(
match input
.dispatch_new_events(|event| state.process_winit_event(event, &render_ping_handle))
{
Ok(_) => {
PumpStatus::Continue => {
event_ping_handle.ping();
render_ping_handle.ping();
}
Err(winit::WinitError::WindowClosed) => {
PumpStatus::Exit(_) => {
let output = state.backend.winit().output.clone();
let seats = state.common.seats().cloned().collect::<Vec<_>>();
state.common.shell.remove_output(&output, seats.into_iter());
@ -349,10 +344,9 @@ impl State {
output.change_current_state(Some(mode), None, None, None);
layer_map_for_output(output).arrange();
self.common.output_configuration_state.update();
self.common.shell.refresh_outputs();
render_ping.ping();
}
WinitEvent::Refresh => render_ping.ping(),
WinitEvent::Redraw => render_ping.ping(),
WinitEvent::Input(event) => self.process_input_event(event, false),
_ => {}
};

View file

@ -448,7 +448,6 @@ pub fn init_backend(
output.set_preferred(mode);
layer_map_for_output(output).arrange();
state.common.output_configuration_state.update();
state.common.shell.refresh_outputs();
surface.dirty = true;
if !surface.pending {
surface.render.ping();

View file

@ -50,10 +50,6 @@ pub struct StaticConfig {
#[serde(default = "default_workspace_layout")]
pub workspace_layout: WorkspaceLayout,
pub tiling_enabled: bool,
#[serde(default = "default_active_hint")]
pub active_hint: u8,
#[serde(default = "default_gaps")]
pub gaps: (u8, u8),
}
#[derive(Debug, Deserialize, Clone, Copy, PartialEq, Eq)]
@ -100,14 +96,6 @@ fn default_enabled() -> bool {
true
}
fn default_active_hint() -> u8 {
4
}
fn default_gaps() -> (u8, u8) {
(0, 4)
}
fn default_workspace_layout() -> WorkspaceLayout {
WorkspaceLayout::Vertical
}
@ -219,8 +207,6 @@ impl Config {
workspace_amount: WorkspaceAmount::Dynamic,
workspace_layout: WorkspaceLayout::Vertical,
tiling_enabled: false,
active_hint: default_active_hint(),
gaps: default_gaps(),
}
}

View file

@ -6,7 +6,10 @@ use crate::{
shell::{
focus::{target::PointerFocusTarget, FocusDirection},
grabs::{ResizeEdge, SeatMoveGrabState},
layout::tiling::{SwapWindowGrab, TilingLayout},
layout::{
floating::ResizeGrabMarker,
tiling::{SwapWindowGrab, TilingLayout},
},
Direction, FocusResult, MoveResult, OverviewMode, ResizeDirection, ResizeMode, Trigger,
Workspace,
},
@ -39,7 +42,7 @@ use smithay::{
input::event::pointer::PointerAxisEvent as LibinputPointerAxisEvent,
wayland_server::DisplayHandle,
},
utils::{Logical, Point, Rectangle, Serial, SERIAL_COUNTER},
utils::{Point, Serial, SERIAL_COUNTER},
wayland::{
keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitorSeat,
pointer_constraints::{with_pointer_constraint, PointerConstraint},
@ -214,7 +217,6 @@ impl State {
<B as InputBackend>::PointerAxisEvent: 'static,
{
use smithay::backend::input::Event;
match event {
InputEvent::DeviceAdded { device } => {
let seat = &mut self.common.last_active_seat();
@ -350,7 +352,7 @@ impl State {
if let Some(old_workspace) = old_w.get_mut(0) {
if let Some(new_workspace) = other_w.iter_mut().find(|w| w.handle == new_workspace) {
if new_workspace.tiling_layer.windows().next().is_none() {
if let Some(focus) = TilingLayout::move_tree(&mut old_workspace.tiling_layer, &mut new_workspace.tiling_layer, &current_output, &new_workspace.handle, &seat, new_workspace.focus_stack.get(&seat).iter(), old_descriptor, &mut data.common.shell.toplevel_info_state) {
if let Some(focus) = TilingLayout::move_tree(&mut old_workspace.tiling_layer, &mut new_workspace.tiling_layer, &new_workspace.handle, &seat, new_workspace.focus_stack.get(&seat).iter(), old_descriptor, &mut data.common.shell.toplevel_info_state) {
let seat = seat.clone();
data.common.event_loop_handle.insert_idle(move |state| {
Common::set_focus(state, Some(&focus), &seat, None);
@ -553,24 +555,18 @@ impl State {
if let Some(seat) = self.common.seat_with_device(&event.device()).cloned() {
let current_output = seat.active_output();
let mut position = seat.get_pointer().unwrap().current_location();
let mut position = seat.get_pointer().unwrap().current_location().as_global();
let relative_pos = self
.common
.shell
.map_global_to_space(position, &current_output);
let overview = self.common.shell.overview_mode();
let output_geometry = current_output.geometry();
let workspace = self.common.shell.workspaces.active_mut(&current_output);
let under = State::surface_under(
position,
relative_pos,
&current_output,
output_geometry,
&self.common.shell.override_redirect_windows,
overview.0.clone(),
workspace,
);
)
.map(|(target, pos)| (target, pos.as_logical()));
let ptr = seat.get_pointer().unwrap();
@ -618,7 +614,7 @@ impl State {
return;
}
position += event.delta();
position += event.delta().as_global();
let output = self
.common
@ -628,17 +624,30 @@ impl State {
.cloned()
.unwrap_or(current_output.clone());
let workspace = self.common.shell.workspaces.active_mut(&output);
if ptr.is_grabbed()
&& seat
.user_data()
.get::<ResizeGrabMarker>()
.map(|marker| marker.get())
.unwrap_or(false)
{
if output != current_output {
ptr.frame(self);
return;
}
}
let output_geometry = output.geometry();
let workspace = self.common.shell.workspaces.active_mut(&output);
let new_under = State::surface_under(
position,
relative_pos,
&output,
output_geometry,
&self.common.shell.override_redirect_windows,
overview.0,
workspace,
);
)
.map(|(target, pos)| (target, pos.as_logical()));
position.x = position.x.clamp(
output_geometry.loc.x as f64,
@ -660,13 +669,17 @@ impl State {
}
if let PointerFocusTarget::Element(element) = surface {
//if !element.is_in_input_region(&(position.to_i32_round() - *surface_loc).to_f64()) {
if !element.is_in_input_region(&(position - surface_loc.to_f64())) {
if !element.is_in_input_region(
&(position.as_logical() - surface_loc.to_f64()),
) {
ptr.frame(self);
return;
}
}
if let Some(region) = confine_region {
if !region.contains(position.to_i32_round() - *surface_loc) {
if !region
.contains(position.as_logical().to_i32_round() - *surface_loc)
{
ptr.frame(self);
return;
}
@ -679,7 +692,7 @@ impl State {
self,
under,
&MotionEvent {
location: position,
location: position.as_logical(),
serial,
time: event.time_msec(),
},
@ -720,17 +733,16 @@ impl State {
for session in sessions_for_output(&self.common, &output) {
if let Some((geometry, offset)) = seat.cursor_geometry(
position.to_buffer(
position.as_logical().to_buffer(
output.current_scale().fractional_scale(),
output.current_transform(),
&output.geometry().size.to_f64(),
&output_geometry.size.to_f64().as_logical(),
),
self.common.clock.now(),
) {
session.cursor_info(&seat, InputType::Pointer, geometry, offset);
}
}
#[cfg(feature = "debug")]
if self.common.seats().position(|x| x == &seat).unwrap() == 0 {
let location = if let Some(output) = self.common.shell.outputs.first() {
@ -752,28 +764,27 @@ impl State {
let position = geometry.loc.to_f64()
+ smithay::backend::input::AbsolutePositionEvent::position_transformed(
&event,
geometry.size,
);
let relative_pos = self.common.shell.map_global_to_space(position, &output);
geometry.size.as_logical(),
)
.as_global();
let overview = self.common.shell.overview_mode();
let workspace = self.common.shell.workspaces.active_mut(&output);
let serial = SERIAL_COUNTER.next_serial();
let under = State::surface_under(
position,
relative_pos,
&output,
geometry,
&self.common.shell.override_redirect_windows,
overview.0,
workspace,
);
)
.map(|(target, pos)| (target, pos.as_logical()));
for session in sessions_for_output(&self.common, &output) {
if let Some((geometry, offset)) = seat.cursor_geometry(
position.to_buffer(
position.as_logical().to_buffer(
output.current_scale().fractional_scale(),
output.current_transform(),
&output.geometry().size.to_f64(),
&geometry.size.to_f64().as_logical(),
),
self.common.clock.now(),
) {
@ -785,7 +796,7 @@ impl State {
self,
under,
&MotionEvent {
location: position,
location: position.as_logical(),
serial,
time: event.time_msec(),
},
@ -835,22 +846,22 @@ impl State {
&& !seat.get_keyboard().map(|k| k.is_grabbed()).unwrap_or(false)
{
let output = seat.active_output();
let pos = seat.get_pointer().unwrap().current_location();
let relative_pos = self.common.shell.map_global_to_space(pos, &output);
let pos = seat.get_pointer().unwrap().current_location().as_global();
let relative_pos = pos.to_local(&output);
let overview = self.common.shell.overview_mode();
let workspace = self.common.shell.active_space_mut(&output);
let mut under = None;
if let Some(window) = workspace.get_fullscreen(&output) {
if let Some(window) = workspace.get_fullscreen() {
let layers = layer_map_for_output(&output);
if let Some(layer) =
layers.layer_under(WlrLayer::Overlay, relative_pos)
layers.layer_under(WlrLayer::Overlay, relative_pos.as_logical())
{
let layer_loc = layers.layer_geometry(layer).unwrap().loc;
if layer.can_receive_keyboard_focus()
&& layer
.surface_under(
relative_pos - layer_loc.to_f64(),
relative_pos.as_logical() - layer_loc.to_f64(),
WindowSurfaceType::ALL,
)
.is_some()
@ -864,14 +875,19 @@ impl State {
let done = {
let layers = layer_map_for_output(&output);
if let Some(layer) = layers
.layer_under(WlrLayer::Overlay, relative_pos)
.or_else(|| layers.layer_under(WlrLayer::Top, relative_pos))
.layer_under(WlrLayer::Overlay, relative_pos.as_logical())
.or_else(|| {
layers.layer_under(
WlrLayer::Top,
relative_pos.as_logical(),
)
})
{
let layer_loc = layers.layer_geometry(layer).unwrap().loc;
if layer.can_receive_keyboard_focus()
&& layer
.surface_under(
relative_pos - layer_loc.to_f64(),
relative_pos.as_logical() - layer_loc.to_f64(),
WindowSurfaceType::ALL,
)
.is_some()
@ -884,35 +900,38 @@ impl State {
}
};
if !done {
if let Some(surface) = workspace.get_maximized(&output) {
under = Some(surface.clone().into());
if let Some((target, _)) =
workspace.element_under(pos, overview.0)
{
under = Some(target);
} else {
if let Some((target, _)) =
workspace.element_under(relative_pos, overview.0)
let layers = layer_map_for_output(&output);
if let Some(layer) = layers
.layer_under(
WlrLayer::Bottom,
relative_pos.as_logical(),
)
.or_else(|| {
layers.layer_under(
WlrLayer::Background,
relative_pos.as_logical(),
)
})
{
under = Some(target);
} else {
let layers = layer_map_for_output(&output);
if let Some(layer) = layers
.layer_under(WlrLayer::Bottom, pos)
.or_else(|| {
layers.layer_under(WlrLayer::Background, pos)
})
let layer_loc =
layers.layer_geometry(layer).unwrap().loc;
if layer.can_receive_keyboard_focus()
&& layer
.surface_under(
relative_pos.as_logical()
- layer_loc.to_f64(),
WindowSurfaceType::ALL,
)
.is_some()
{
let layer_loc =
layers.layer_geometry(layer).unwrap().loc;
if layer.can_receive_keyboard_focus()
&& layer
.surface_under(
relative_pos - layer_loc.to_f64(),
WindowSurfaceType::ALL,
)
.is_some()
{
under = Some(layer.clone().into());
}
};
}
under = Some(layer.clone().into());
}
};
}
}
}
@ -1330,16 +1349,15 @@ impl State {
}
Action::NextOutput => {
let current_output = seat.active_output();
if let Some(next_output) = self
let next_output = self
.common
.shell
.outputs
.iter()
.outputs()
.skip_while(|o| *o != &current_output)
.skip(1)
.next()
.cloned()
{
.cloned();
if let Some(next_output) = next_output {
let idx = self.common.shell.workspaces.active_num(&next_output).1;
match self.common.shell.activate(&next_output, idx) {
Ok(Some(new_pos)) => {
@ -1349,7 +1367,7 @@ impl State {
self,
None,
&MotionEvent {
location: new_pos.to_f64(),
location: new_pos.to_f64().as_logical(),
serial,
time,
},
@ -1366,17 +1384,16 @@ impl State {
}
Action::PreviousOutput => {
let current_output = seat.active_output();
if let Some(prev_output) = self
let prev_output = self
.common
.shell
.outputs
.iter()
.outputs()
.rev()
.skip_while(|o| *o != &current_output)
.skip(1)
.next()
.cloned()
{
.cloned();
if let Some(prev_output) = prev_output {
let idx = self.common.shell.workspaces.active_num(&prev_output).1;
match self.common.shell.activate(&prev_output, idx) {
Ok(Some(new_pos)) => {
@ -1386,7 +1403,7 @@ impl State {
self,
None,
&MotionEvent {
location: new_pos.to_f64(),
location: new_pos.to_f64().as_logical(),
serial,
time,
},
@ -1403,16 +1420,15 @@ impl State {
}
x @ Action::MoveToNextOutput | x @ Action::SendToNextOutput => {
let current_output = seat.active_output();
if let Some(next_output) = self
let next_output = self
.common
.shell
.outputs
.iter()
.outputs()
.skip_while(|o| *o != &current_output)
.skip(1)
.next()
.cloned()
{
.cloned();
if let Some(next_output) = next_output {
if let Ok(Some(new_pos)) = Shell::move_current_window(
self,
seat,
@ -1426,7 +1442,7 @@ impl State {
self,
None,
&MotionEvent {
location: new_pos.to_f64(),
location: new_pos.to_f64().as_logical(),
serial,
time,
},
@ -1438,17 +1454,16 @@ impl State {
}
x @ Action::MoveToPreviousOutput | x @ Action::SendToPreviousOutput => {
let current_output = seat.active_output();
if let Some(prev_output) = self
let prev_output = self
.common
.shell
.outputs
.iter()
.outputs()
.rev()
.skip_while(|o| *o != &current_output)
.skip(1)
.next()
.cloned()
{
.cloned();
if let Some(prev_output) = prev_output {
if let Ok(Some(new_pos)) = Shell::move_current_window(
self,
seat,
@ -1462,7 +1477,7 @@ impl State {
self,
None,
&MotionEvent {
location: new_pos.to_f64(),
location: new_pos.to_f64().as_logical(),
serial,
time,
},
@ -1540,9 +1555,6 @@ impl State {
Action::Move(direction) => {
let current_output = seat.active_output();
let workspace = self.common.shell.active_space_mut(&current_output);
if workspace.get_fullscreen(&current_output).is_some() {
return; // TODO, is this what we want? How do we indicate the switch?
}
match workspace.move_current_element(direction, seat) {
MoveResult::MoveFurther(_move_further) => {
@ -1603,7 +1615,7 @@ impl State {
Action::SwapWindow => {
let current_output = seat.active_output();
let workspace = self.common.shell.active_space_mut(&current_output);
if workspace.get_fullscreen(&current_output).is_some() {
if workspace.get_fullscreen().is_some() {
return; // TODO, is this what we want? Maybe disengage fullscreen instead?
}
@ -1625,11 +1637,7 @@ impl State {
let focus_stack = workspace.focus_stack.get(seat);
let focused_window = focus_stack.last();
if let Some(window) = focused_window.map(|f| f.active_window()) {
workspace.maximize_toggle(
&window,
&current_output,
self.common.event_loop_handle.clone(),
);
workspace.maximize_toggle(&window);
}
}
Action::Resizing(direction) => self.common.shell.set_resize_mode(
@ -1705,77 +1713,83 @@ impl State {
}
pub fn surface_under(
global_pos: Point<f64, Logical>,
relative_pos: Point<f64, Logical>,
global_pos: Point<f64, Global>,
output: &Output,
output_geo: Rectangle<i32, Logical>,
override_redirect_windows: &[X11Surface],
overview: OverviewMode,
workspace: &mut Workspace,
) -> Option<(PointerFocusTarget, Point<i32, Logical>)> {
if let Some(window) = workspace.get_fullscreen(output) {
) -> Option<(PointerFocusTarget, Point<i32, Global>)> {
let relative_pos = global_pos.to_local(output);
let output_geo = output.geometry();
if let Some(window) = workspace.get_fullscreen() {
let layers = layer_map_for_output(output);
if let Some(layer) = layers.layer_under(WlrLayer::Overlay, relative_pos) {
if let Some(layer) = layers.layer_under(WlrLayer::Overlay, relative_pos.as_logical()) {
let layer_loc = layers.layer_geometry(layer).unwrap().loc;
if layer
.surface_under(relative_pos - layer_loc.to_f64(), WindowSurfaceType::ALL)
.surface_under(
relative_pos.as_logical() - layer_loc.to_f64(),
WindowSurfaceType::ALL,
)
.is_some()
{
return Some((layer.clone().into(), output_geo.loc + layer_loc));
return Some((layer.clone().into(), output_geo.loc + layer_loc.as_global()));
}
}
if let Some(or) = override_redirect_windows
.iter()
.find(|or| or.is_in_input_region(&(global_pos - or.geometry().loc.to_f64())))
{
return Some((or.clone().into(), or.geometry().loc));
if let Some(or) = override_redirect_windows.iter().find(|or| {
or.is_in_input_region(&(global_pos.as_logical() - or.geometry().loc.to_f64()))
}) {
return Some((or.clone().into(), or.geometry().loc.as_global()));
}
Some((window.clone().into(), output_geo.loc))
} else {
{
let layers = layer_map_for_output(output);
if let Some(layer) = layers
.layer_under(WlrLayer::Overlay, relative_pos)
.or_else(|| layers.layer_under(WlrLayer::Top, relative_pos))
.layer_under(WlrLayer::Overlay, relative_pos.as_logical())
.or_else(|| layers.layer_under(WlrLayer::Top, relative_pos.as_logical()))
{
let layer_loc = layers.layer_geometry(layer).unwrap().loc;
if layer
.surface_under(relative_pos - layer_loc.to_f64(), WindowSurfaceType::ALL)
.surface_under(
relative_pos.as_logical() - layer_loc.to_f64(),
WindowSurfaceType::ALL,
)
.is_some()
{
return Some((layer.clone().into(), output_geo.loc + layer_loc));
return Some((
layer.clone().into(),
output_geo.loc + layer_loc.as_global(),
));
}
}
}
if let Some(or) = override_redirect_windows
.iter()
.find(|or| or.is_in_input_region(&(global_pos - or.geometry().loc.to_f64())))
{
return Some((or.clone().into(), or.geometry().loc));
if let Some(or) = override_redirect_windows.iter().find(|or| {
or.is_in_input_region(&(global_pos.as_logical() - or.geometry().loc.to_f64()))
}) {
return Some((or.clone().into(), or.geometry().loc.as_global()));
}
if let Some(surface) = workspace.get_maximized(output) {
let offset = layer_map_for_output(output).non_exclusive_zone().loc;
return Some((surface.clone().into(), output_geo.loc + offset));
} else {
if let Some((target, loc)) = workspace.element_under(relative_pos, overview) {
return Some((target, loc + (global_pos - relative_pos).to_i32_round()));
}
if let Some((target, loc)) = workspace.element_under(global_pos, overview) {
return Some((target, loc));
}
{
let layers = layer_map_for_output(output);
if let Some(layer) = layers
.layer_under(WlrLayer::Bottom, relative_pos.as_logical())
.or_else(|| layers.layer_under(WlrLayer::Background, relative_pos.as_logical()))
{
let layers = layer_map_for_output(output);
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;
if layer
.surface_under(
relative_pos.as_logical() - layer_loc.to_f64(),
WindowSurfaceType::ALL,
)
.is_some()
{
let layer_loc = layers.layer_geometry(layer).unwrap().loc;
if layer
.surface_under(
relative_pos - layer_loc.to_f64(),
WindowSurfaceType::ALL,
)
.is_some()
{
return Some((layer.clone().into(), output_geo.loc + layer_loc));
}
return Some((
layer.clone().into(),
output_geo.loc + layer_loc.as_global(),
));
}
}
}
@ -1786,7 +1800,7 @@ impl State {
fn sessions_for_output(state: &Common, output: &Output) -> impl Iterator<Item = Session> {
let workspace = state.shell.active_space(&output);
let maybe_fullscreen = workspace.get_fullscreen(&output);
let maybe_fullscreen = workspace.get_fullscreen();
workspace
.screencopy_sessions
.iter()
@ -1795,7 +1809,7 @@ fn sessions_for_output(state: &Common, output: &Output) -> impl Iterator<Item =
maybe_fullscreen
.as_ref()
.and_then(|w| {
if let Some(sessions) = w.surface().user_data().get::<ScreencopySessions>() {
if let Some(sessions) = w.user_data().get::<ScreencopySessions>() {
Some(
sessions
.0

View file

@ -25,6 +25,7 @@ pub mod shell;
pub mod state;
#[cfg(feature = "systemd")]
pub mod systemd;
pub mod theme;
pub mod utils;
pub mod wayland;
pub mod xwayland;
@ -55,6 +56,10 @@ fn main() -> Result<()> {
// potentially tell the session we are setup now
session::setup_socket(event_loop.handle(), &state)?;
if let Err(err) = theme::watch_theme(event_loop.handle()) {
warn!(?err, "Failed to watch theme");
}
// run the event loop
event_loop.run(None, &mut state, |state| {
// shall we shut down?

View file

@ -2,7 +2,7 @@
use smithay::reexports::{
calloop::{generic::Generic, Interest, LoopHandle, Mode, PostAction},
nix::{fcntl, unistd},
rustix,
};
use anyhow::{anyhow, Context, Result};
@ -50,20 +50,24 @@ impl From<UnixStream> for StreamWrapper {
}
}
unsafe fn set_cloexec(fd: RawFd) -> rustix::io::Result<()> {
if fd == -1 {
return Err(rustix::io::Errno::BADF);
}
let fd = BorrowedFd::borrow_raw(fd);
let flags = rustix::io::fcntl_getfd(fd)?;
rustix::io::fcntl_setfd(fd, flags | rustix::io::FdFlags::CLOEXEC)
}
pub fn setup_socket(handle: LoopHandle<State>, state: &State) -> Result<()> {
if let Ok(fd_num) = std::env::var("COSMIC_SESSION_SOCK") {
if let Ok(fd) = fd_num.parse::<RawFd>() {
// set CLOEXEC
let flags = fcntl::fcntl(fd, fcntl::FcntlArg::F_GETFD);
let result = flags
.map(|f| fcntl::FdFlag::from_bits(f).unwrap() | fcntl::FdFlag::FD_CLOEXEC)
.and_then(|f| fcntl::fcntl(fd, fcntl::FcntlArg::F_SETFD(f)));
let mut session_socket = match result {
let mut session_socket = match unsafe { set_cloexec(fd) } {
// CLOEXEC worked and we can startup with session IPC
Ok(_) => unsafe { UnixStream::from_raw_fd(fd) },
// CLOEXEC didn't work, something is wrong with the fd, just close it
Err(err) => {
let _ = unistd::close(fd);
unsafe { rustix::io::close(fd) };
return Err(err).with_context(|| "Failed to setup session socket");
}
};

View file

@ -4,7 +4,7 @@ use crate::{
GlMultiError, GlMultiFrame, GlMultiRenderer,
},
state::State,
utils::prelude::SeatExt,
utils::prelude::*,
};
use calloop::LoopHandle;
use id_tree::NodeId;
@ -74,7 +74,7 @@ use tracing::debug;
use super::{
focus::FocusDirection,
layout::{floating::ResizeState, tiling::NodeDesc},
Direction,
Direction, ManagedLayer,
};
space_elements! {
@ -84,18 +84,25 @@ space_elements! {
Stack=CosmicStack,
}
#[derive(Debug, Clone)]
pub struct MaximizedState {
pub original_geometry: Rectangle<i32, Local>,
pub original_layer: ManagedLayer,
}
#[derive(Clone)]
pub struct CosmicMapped {
element: CosmicMappedInternal,
// associated data
last_cursor_position: Arc<Mutex<HashMap<usize, Point<f64, Logical>>>>,
pub maximized_state: Arc<Mutex<Option<MaximizedState>>>,
//tiling
pub tiling_node_id: Arc<Mutex<Option<NodeId>>>,
//floating
pub(super) last_geometry: Arc<Mutex<Option<Rectangle<i32, Logical>>>>,
pub(super) resize_state: Arc<Mutex<Option<ResizeState>>>,
pub last_geometry: Arc<Mutex<Option<Rectangle<i32, Local>>>>,
#[cfg(feature = "debug")]
debug: Arc<Mutex<Option<smithay_egui::EguiState>>>,
@ -106,8 +113,10 @@ impl fmt::Debug for CosmicMapped {
f.debug_struct("CosmicMapped")
.field("element", &self.element)
.field("last_cursor_position", &self.last_cursor_position)
.field("maximized_state", &self.maximized_state)
.field("tiling_node_id", &self.tiling_node_id)
.field("resize_state", &self.resize_state)
.field("last_geometry", &self.last_geometry)
.finish()
}
}
@ -361,7 +370,15 @@ impl CosmicMapped {
window.is_activated(pending)
}
pub fn set_geometry(&self, geo: Rectangle<i32, Logical>) {
pub fn pending_size(&self) -> Option<Size<i32, Logical>> {
match &self.element {
CosmicMappedInternal::Stack(s) => s.pending_size(),
CosmicMappedInternal::Window(w) => w.pending_size(),
_ => unreachable!(),
}
}
pub fn set_geometry(&self, geo: Rectangle<i32, Global>) {
match &self.element {
CosmicMappedInternal::Stack(s) => s.set_geometry(geo),
CosmicMappedInternal::Window(w) => w.set_geometry(geo),
@ -491,7 +508,8 @@ impl CosmicMapped {
pub fn convert_to_stack<'a>(
&mut self,
outputs: impl Iterator<Item = (&'a Output, Rectangle<i32, Logical>)>,
(output, overlap): (&'a Output, Rectangle<i32, Logical>),
theme: cosmic::Theme,
) {
match &self.element {
CosmicMappedInternal::Window(window) => {
@ -499,13 +517,11 @@ impl CosmicMapped {
let activated = surface.is_activated(true);
let handle = window.loop_handle();
let stack = CosmicStack::new(std::iter::once(surface), handle);
let stack = CosmicStack::new(std::iter::once(surface), handle, theme);
if let Some(geo) = self.last_geometry.lock().unwrap().clone() {
stack.set_geometry(geo);
}
for (output, overlap) in outputs {
stack.output_enter(output, overlap);
stack.set_geometry(geo.to_global(&output));
}
stack.output_enter(output, overlap);
stack.set_activate(activated);
stack.active().send_configure();
stack.refresh();
@ -519,19 +535,18 @@ impl CosmicMapped {
pub fn convert_to_surface<'a>(
&mut self,
surface: CosmicSurface,
outputs: impl Iterator<Item = (&'a Output, Rectangle<i32, Logical>)>,
(output, overlap): (&'a Output, Rectangle<i32, Logical>),
theme: cosmic::Theme,
) {
let handle = self.loop_handle();
surface.try_force_undecorated(false);
surface.set_tiled(false);
let window = CosmicWindow::new(surface, handle);
let window = CosmicWindow::new(surface, handle, theme);
if let Some(geo) = self.last_geometry.lock().unwrap().clone() {
window.set_geometry(geo);
}
for (output, overlap) in outputs {
window.output_enter(output, overlap);
window.set_geometry(geo.to_global(&output));
}
window.output_enter(output, overlap);
window.set_activate(self.is_activated(true));
window.surface().send_configure();
window.refresh();
@ -759,6 +774,22 @@ impl CosmicMapped {
popup_elements.into_iter().map(C::from).collect(),
)
}
pub(crate) fn update_theme(&self, theme: cosmic::Theme) {
match &self.element {
CosmicMappedInternal::Window(w) => w.set_theme(theme),
CosmicMappedInternal::Stack(s) => s.set_theme(theme),
CosmicMappedInternal::_GenericCatcher(_) => {}
}
}
pub(crate) fn force_redraw(&self) {
match &self.element {
CosmicMappedInternal::Window(w) => w.force_redraw(),
CosmicMappedInternal::Stack(s) => s.force_redraw(),
CosmicMappedInternal::_GenericCatcher(_) => {}
}
}
}
impl IsAlive for CosmicMapped {
@ -1067,9 +1098,10 @@ impl From<CosmicWindow> for CosmicMapped {
CosmicMapped {
element: CosmicMappedInternal::Window(w),
last_cursor_position: Arc::new(Mutex::new(HashMap::new())),
maximized_state: Arc::new(Mutex::new(None)),
tiling_node_id: Arc::new(Mutex::new(None)),
last_geometry: Arc::new(Mutex::new(None)),
resize_state: Arc::new(Mutex::new(None)),
last_geometry: Arc::new(Mutex::new(None)),
#[cfg(feature = "debug")]
debug: Arc::new(Mutex::new(None)),
}
@ -1081,9 +1113,10 @@ impl From<CosmicStack> for CosmicMapped {
CosmicMapped {
element: CosmicMappedInternal::Stack(s),
last_cursor_position: Arc::new(Mutex::new(HashMap::new())),
maximized_state: Arc::new(Mutex::new(None)),
tiling_node_id: Arc::new(Mutex::new(None)),
last_geometry: Arc::new(Mutex::new(None)),
resize_state: Arc::new(Mutex::new(None)),
last_geometry: Arc::new(Mutex::new(None)),
#[cfg(feature = "debug")]
debug: Arc::new(Mutex::new(None)),
}

View file

@ -23,6 +23,7 @@ pub fn resize_indicator(
direction: ResizeDirection,
config: &Config,
evlh: LoopHandle<'static, crate::state::State>,
theme: cosmic::Theme,
) -> ResizeIndicator {
ResizeIndicator::new(
ResizeIndicatorInternal {
@ -49,6 +50,7 @@ pub fn resize_indicator(
},
Size::from((1, 1)),
evlh,
theme,
)
}

View file

@ -5,7 +5,7 @@ use crate::{
},
state::State,
utils::iced::{IcedElement, Program},
utils::prelude::SeatExt,
utils::prelude::*,
wayland::handlers::screencopy::ScreencopySessions,
};
use calloop::LoopHandle;
@ -91,7 +91,7 @@ pub struct CosmicStackInternal {
override_alive: Arc<AtomicBool>,
last_seat: Arc<Mutex<Option<(Seat<State>, Serial)>>>,
last_location: Arc<Mutex<Option<(Point<f64, Logical>, Serial, u32)>>>,
geometry: Arc<Mutex<Option<Rectangle<i32, Logical>>>>,
geometry: Arc<Mutex<Option<Rectangle<i32, Global>>>>,
mask: Arc<Mutex<Option<tiny_skia::Mask>>>,
}
@ -129,6 +129,7 @@ impl CosmicStack {
pub fn new<I: Into<CosmicSurface>>(
windows: impl Iterator<Item = I>,
handle: LoopHandle<'static, crate::state::State>,
theme: cosmic::Theme,
) -> CosmicStack {
let windows = windows.map(Into::into).collect::<Vec<_>>();
assert!(!windows.is_empty());
@ -160,6 +161,7 @@ impl CosmicStack {
},
(width, TAB_HEIGHT),
handle,
theme,
))
}
@ -406,7 +408,17 @@ impl CosmicStack {
Point::from((0, TAB_HEIGHT))
}
pub fn set_geometry(&self, geo: Rectangle<i32, Logical>) {
pub fn pending_size(&self) -> Option<Size<i32, Logical>> {
self.0.with_program(|p| {
p.geometry
.lock()
.unwrap()
.clone()
.map(|geo| geo.size.as_logical())
})
}
pub fn set_geometry(&self, geo: Rectangle<i32, Global>) {
self.0.with_program(|p| {
let loc = (geo.loc.x, geo.loc.y + TAB_HEIGHT);
let size = (geo.size.w, geo.size.h - TAB_HEIGHT);
@ -544,6 +556,14 @@ impl CosmicStack {
popup_elements.into_iter().map(C::from).collect(),
)
}
pub(crate) fn set_theme(&self, theme: cosmic::Theme) {
self.0.set_theme(theme);
}
pub(crate) fn force_redraw(&self) {
self.0.force_redraw();
}
}
#[derive(Debug, Clone, Copy)]
@ -1047,20 +1067,21 @@ impl PointerTarget<State> for CosmicStack {
if let Some(workspace) = data.common.shell.space_for(stack_mapped) {
// TODO: Unify this somehow with Shell::move_request/Workspace::move_request
let button = 0x110; // BTN_LEFT
let pos = event.location;
let pos = event.location.as_global();
let start_data = PointerGrabStartData {
focus: None,
button,
location: pos,
location: pos.as_logical(),
};
let mapped = CosmicMapped::from(CosmicWindow::new(
surface,
self.0.loop_handle(),
data.common.theme.clone(),
));
let elem_geo =
workspace.element_geometry(stack_mapped).unwrap();
let indicator_thickness =
data.common.config.static_conf.active_hint;
data.common.theme.cosmic().active_hint as u8;
let was_tiled = workspace.is_tiled(stack_mapped);
self.remove_idx(dragged_out);

View file

@ -248,6 +248,14 @@ where
}
impl State {
fn next_tab_animation(&self) -> Option<&TabAnimationState> {
let now = Instant::now();
self.tab_animations
.iter()
.find(|anim| now.duration_since(anim.start_time) <= TAB_ANIMATION_DURATION)
}
pub fn offset(&self, bounds: Rectangle, content_bounds: Size) -> Vector {
if let Some(animation) = self.scroll_animation {
let percentage = {
@ -275,19 +283,41 @@ impl State {
}
pub fn cleanup_old_animations(&mut self) {
let start_time = Instant::now();
if let Some(animation) = self.scroll_animation.as_ref() {
if Instant::now().duration_since(animation.start_time) > SCROLL_ANIMATION_DURATION {
if start_time.duration_since(animation.start_time) > SCROLL_ANIMATION_DURATION {
self.scroll_animation.take();
}
}
if let Some(animation) = self.tab_animations.front() {
if Instant::now().duration_since(animation.start_time) > TAB_ANIMATION_DURATION {
self.tab_animations.pop_front();
if let Some(next_animation) = self.tab_animations.front_mut() {
next_animation.start_time = Instant::now();
Self::discard_expired_tab_animations(&mut self.tab_animations, start_time);
}
/// Remove expired tab animations from the queue.
fn discard_expired_tab_animations(
tab_animations: &mut VecDeque<TabAnimationState>,
start_time: Instant,
) {
if let Some(mut animation) = tab_animations.pop_front() {
let mut set_next_start = false;
while start_time.duration_since(animation.start_time) > TAB_ANIMATION_DURATION {
set_next_start = true;
if let Some(next) = tab_animations.pop_front() {
animation = next;
continue;
}
return;
}
if set_next_start {
animation.start_time = start_time;
}
tab_animations.push_front(animation);
}
}
}
@ -532,7 +562,9 @@ where
renderer.with_layer(bounds, |renderer| {
renderer.with_translation(Vector::new(-offset.x, -offset.y), |renderer| {
let percentage = if let Some(animation) = state.tab_animations.front() {
let tab_animation = state.next_tab_animation();
let percentage = if let Some(animation) = tab_animation {
let percentage = Instant::now()
.duration_since(animation.start_time)
.as_millis() as f32
@ -547,7 +579,7 @@ where
.zip(tree.children.iter().skip(2))
.zip(layout.children().skip(2))
{
let bounds = if let Some(animation) = state.tab_animations.front() {
let bounds = if let Some(animation) = tab_animation {
let id = tab.as_widget().id().unwrap();
let previous =
animation
@ -755,11 +787,15 @@ where
if unknown_keys || changes.is_some() {
if !scrolling || !matches!(changes, Some(Difference::Focus)) {
let start_time = Instant::now();
State::discard_expired_tab_animations(&mut state.tab_animations, start_time);
// new tab_animation
state.tab_animations.push_back(TabAnimationState {
previous_bounds: last_state.clone(),
next_bounds: current_state.clone(),
start_time: Instant::now(),
start_time,
});
}

View file

@ -18,8 +18,9 @@ pub type StackHover = IcedElement<StackHoverInternal>;
pub fn stack_hover(
evlh: LoopHandle<'static, crate::state::State>,
size: Size<i32, Logical>,
theme: cosmic::Theme,
) -> StackHover {
StackHover::new(StackHoverInternal, size, evlh)
StackHover::new(StackHoverInternal, size, evlh, theme)
}
pub struct StackHoverInternal;

View file

@ -51,6 +51,7 @@ use smithay::{
use crate::{
state::{State, SurfaceDmabufFeedback},
utils::prelude::*,
wayland::handlers::decoration::PreferredDecorationMode,
};
@ -122,13 +123,23 @@ impl CosmicSurface {
}
}
pub fn set_geometry(&self, geo: Rectangle<i32, Logical>) {
pub fn pending_size(&self) -> Option<Size<i32, Logical>> {
match self {
CosmicSurface::Wayland(window) => {
window.toplevel().with_pending_state(|state| state.size)
}
CosmicSurface::X11(surface) => Some(surface.geometry().size),
_ => unreachable!(),
}
}
pub fn set_geometry(&self, geo: Rectangle<i32, Global>) {
match self {
CosmicSurface::Wayland(window) => window
.toplevel()
.with_pending_state(|state| state.size = Some(geo.size)),
.with_pending_state(|state| state.size = Some(geo.size.as_logical())),
CosmicSurface::X11(surface) => {
let _ = surface.configure(geo);
let _ = surface.configure(geo.as_logical());
}
_ => {}
}

View file

@ -15,8 +15,11 @@ use smithay::utils::Size;
pub type SwapIndicator = IcedElement<SwapIndicatorInternal>;
pub fn swap_indicator(evlh: LoopHandle<'static, crate::state::State>) -> SwapIndicator {
SwapIndicator::new(SwapIndicatorInternal, Size::from((1, 1)), evlh)
pub fn swap_indicator(
evlh: LoopHandle<'static, crate::state::State>,
theme: cosmic::Theme,
) -> SwapIndicator {
SwapIndicator::new(SwapIndicatorInternal, Size::from((1, 1)), evlh, theme)
}
pub struct SwapIndicatorInternal;

View file

@ -3,7 +3,7 @@ use crate::{
state::State,
utils::{
iced::{IcedElement, Program},
prelude::SeatExt,
prelude::*,
},
wayland::handlers::screencopy::ScreencopySessions,
};
@ -114,6 +114,7 @@ impl CosmicWindow {
pub fn new(
window: impl Into<CosmicSurface>,
handle: LoopHandle<'static, crate::state::State>,
theme: cosmic::Theme,
) -> CosmicWindow {
let window = window.into();
let width = window.geometry().size.w;
@ -129,10 +130,21 @@ impl CosmicWindow {
},
(width, SSD_HEIGHT),
handle,
theme,
))
}
pub fn set_geometry(&self, geo: Rectangle<i32, Logical>) {
pub fn pending_size(&self) -> Option<Size<i32, Logical>> {
self.0.with_program(|p| {
let mut size = p.window.pending_size()?;
if p.has_ssd(true) {
size.h += SSD_HEIGHT;
}
Some(size)
})
}
pub fn set_geometry(&self, geo: Rectangle<i32, Global>) {
self.0.with_program(|p| {
let loc = (
geo.loc.x,
@ -213,6 +225,14 @@ impl CosmicWindow {
popup_elements.into_iter().map(C::from).collect(),
)
}
pub(crate) fn set_theme(&self, theme: cosmic::Theme) {
self.0.set_theme(theme);
}
pub(crate) fn force_redraw(&self) {
self.0.force_redraw();
}
}
#[derive(Debug, Clone, Copy)]
@ -241,27 +261,20 @@ impl Program for CosmicWindowInternal {
}
}
Message::Maximize => {
if let Some((seat, _serial)) = self.last_seat.lock().unwrap().clone() {
if let Some(surface) = self.window.wl_surface() {
loop_handle.insert_idle(move |state| {
if let Some(mapped) =
state.common.shell.element_for_wl_surface(&surface).cloned()
{
if let Some(workspace) = state.common.shell.space_for_mut(&mapped) {
let output = seat.active_output();
let (window, _) = mapped
.windows()
.find(|(w, _)| w.wl_surface().as_ref() == Some(&surface))
.unwrap();
workspace.maximize_toggle(
&window,
&output,
state.common.event_loop_handle.clone(),
)
}
if let Some(surface) = self.window.wl_surface() {
loop_handle.insert_idle(move |state| {
if let Some(mapped) =
state.common.shell.element_for_wl_surface(&surface).cloned()
{
if let Some(workspace) = state.common.shell.space_for_mut(&mapped) {
let (window, _) = mapped
.windows()
.find(|(w, _)| w.wl_surface().as_ref() == Some(&surface))
.unwrap();
workspace.maximize_toggle(&window)
}
});
}
}
});
}
}
Message::Close => self.window.close(),

View file

@ -3,7 +3,6 @@ use crate::{
state::Common,
utils::prelude::*,
wayland::handlers::xdg_shell::PopupGrabData,
xwayland::XWaylandState,
};
use indexmap::IndexSet;
use smithay::{
@ -100,11 +99,9 @@ impl Shell {
// update FocusStack and notify layouts about new focus (if any window)
let element = match target {
Some(KeyboardFocusTarget::Element(mapped)) => Some(mapped.clone()),
Some(KeyboardFocusTarget::Fullscreen(window)) => state
.common
.shell
.element_for_surface(&window.surface())
.cloned(),
Some(KeyboardFocusTarget::Fullscreen(window)) => {
state.common.shell.element_for_surface(window).cloned()
}
_ => None,
};
@ -139,11 +136,7 @@ impl Shell {
}
}
fn update_active<'a, 'b>(
&mut self,
seats: impl Iterator<Item = &'a Seat<State>>,
mut xwm: Option<&'b mut XWaylandState>,
) {
fn update_active<'a, 'b>(&mut self, seats: impl Iterator<Item = &'a Seat<State>>) {
// update activate status
let focused_windows = seats
.flat_map(|seat| {
@ -154,7 +147,7 @@ impl Shell {
return None;
}
Some(self.outputs.iter().flat_map(|o| {
Some(self.outputs().flat_map(|o| {
let space = self.active_space(o);
let stack = space.focus_stack.get(seat);
stack.last().cloned()
@ -163,14 +156,10 @@ impl Shell {
.flatten()
.collect::<Vec<_>>();
for output in self.outputs.iter() {
let workspace = self.workspaces.active_mut(output);
for output in self.outputs().cloned().collect::<Vec<_>>().into_iter() {
// TODO: Add self.workspaces.active_workspaces()
let workspace = self.workspaces.active_mut(&output);
for focused in focused_windows.iter() {
if let CosmicSurface::X11(window) = focused.active_window() {
if let Some(xwm) = xwm.as_mut().and_then(|state| state.xwm.as_mut()) {
let _ = xwm.raise_window(&window);
}
}
raise_with_children(&mut workspace.floating_layer, focused);
}
for window in workspace.mapped() {
@ -213,18 +202,15 @@ impl Common {
) {
Shell::set_focus(state, target, active_seat, serial);
let seats = state.common.seats().cloned().collect::<Vec<_>>();
state
.common
.shell
.update_active(seats.iter(), state.common.xwayland_state.as_mut());
state.common.shell.update_active(seats.iter());
}
pub fn refresh_focus(state: &mut State) {
let seats = state.common.seats().cloned().collect::<Vec<_>>();
for seat in seats {
let output = seat.active_output();
if !state.common.shell.outputs.contains(&output) {
seat.set_active_output(&state.common.shell.outputs[0]);
if !state.common.shell.outputs().any(|o| o == &output) {
seat.set_active_output(&state.common.shell.outputs().next().unwrap());
continue;
}
let last_known_focus = ActiveFocus::get(&seat);
@ -236,7 +222,7 @@ impl Common {
let workspace = state.common.shell.active_space(&output);
let focus_stack = workspace.focus_stack.get(&seat);
if focus_stack.last().map(|m| m == &mapped).unwrap_or(false)
&& workspace.get_fullscreen(&output).is_none()
&& workspace.get_fullscreen().is_none()
{
continue; // Focus is valid
} else {
@ -248,11 +234,16 @@ impl Common {
continue; // Focus is valid
}
}
KeyboardFocusTarget::Group(WindowGroup {
output: weak_output,
..
}) => {
if weak_output == output {
KeyboardFocusTarget::Group(WindowGroup { node, .. }) => {
if state
.common
.shell
.workspaces
.active(&output)
.1
.tiling_layer
.has_node(&node)
{
continue; // Focus is valid,
}
}
@ -262,9 +253,9 @@ impl Common {
if focus_stack
.last()
.map(|m| m.has_active_window(&window.surface()))
.map(|m| m.has_active_window(&window))
.unwrap_or(false)
&& workspace.get_fullscreen(&output).is_some()
&& workspace.get_fullscreen().is_some()
{
continue; // Focus is valid
} else {
@ -276,6 +267,31 @@ impl Common {
}
};
} else {
if let KeyboardFocusTarget::Popup(_) = target {
if let Some(popup_grab) = seat
.user_data()
.get::<PopupGrabData>()
.and_then(|x| x.take())
{
if !popup_grab.has_ended() {
if let Some(new) = popup_grab.current_grab() {
trace!("restore focus to previous popup grab");
if let Some(keyboard) = seat.get_keyboard() {
keyboard.set_focus(
state,
Some(new.clone()),
SERIAL_COUNTER.next_serial(),
);
}
ActiveFocus::set(&seat, Some(new));
seat.user_data()
.get_or_insert::<PopupGrabData, _>(PopupGrabData::default)
.set(Some(popup_grab));
continue;
}
}
}
}
trace!("Surface dead, focus fixup");
}
} else {
@ -307,7 +323,7 @@ impl Common {
.common
.shell
.active_space(&output)
.get_fullscreen(&output)
.get_fullscreen()
.cloned()
.map(KeyboardFocusTarget::Fullscreen)
.or_else(|| {
@ -330,9 +346,6 @@ impl Common {
}
let seats = state.common.seats().cloned().collect::<Vec<_>>();
state
.common
.shell
.update_active(seats.iter(), state.common.xwayland_state.as_mut())
state.common.shell.update_active(seats.iter())
}
}

View file

@ -1,10 +1,7 @@
use std::sync::Weak;
use crate::{
shell::{
element::{CosmicMapped, CosmicWindow},
layout::tiling::ResizeForkTarget,
},
shell::{element::CosmicMapped, layout::tiling::ResizeForkTarget, CosmicSurface},
utils::prelude::*,
wayland::handlers::xdg_shell::popup::get_popup_toplevel,
};
@ -22,7 +19,6 @@ use smithay::{
},
Seat,
},
output::WeakOutput,
reexports::wayland_server::{backend::ObjectId, protocol::wl_surface::WlSurface, Resource},
utils::{IsAlive, Serial},
wayland::seat::WaylandFocus,
@ -32,7 +28,7 @@ use smithay::{
#[derive(Debug, Clone, PartialEq)]
pub enum PointerFocusTarget {
Element(CosmicMapped),
Fullscreen(CosmicWindow),
Fullscreen(CosmicSurface),
LayerSurface(LayerSurface),
Popup(PopupKind),
OverrideRedirect(X11Surface),
@ -42,7 +38,7 @@ pub enum PointerFocusTarget {
#[derive(Debug, Clone, PartialEq)]
pub enum KeyboardFocusTarget {
Element(CosmicMapped),
Fullscreen(CosmicWindow),
Fullscreen(CosmicSurface),
Group(WindowGroup),
LayerSurface(LayerSurface),
Popup(PopupKind),
@ -87,16 +83,13 @@ impl KeyboardFocusTarget {
#[derive(Debug, Clone)]
pub struct WindowGroup {
pub node: NodeId,
pub output: WeakOutput,
pub alive: Weak<()>,
pub focus_stack: Vec<NodeId>,
}
impl PartialEq for WindowGroup {
fn eq(&self, other: &Self) -> bool {
self.node == other.node
&& self.output == other.output
&& Weak::ptr_eq(&self.alive, &other.alive)
self.node == other.node && Weak::ptr_eq(&self.alive, &other.alive)
}
}
@ -564,8 +557,8 @@ impl From<CosmicMapped> for PointerFocusTarget {
}
}
impl From<CosmicWindow> for PointerFocusTarget {
fn from(s: CosmicWindow) -> Self {
impl From<CosmicSurface> for PointerFocusTarget {
fn from(s: CosmicSurface) -> Self {
PointerFocusTarget::Fullscreen(s)
}
}
@ -600,8 +593,8 @@ impl From<CosmicMapped> for KeyboardFocusTarget {
}
}
impl From<CosmicWindow> for KeyboardFocusTarget {
fn from(s: CosmicWindow) -> Self {
impl From<CosmicSurface> for KeyboardFocusTarget {
fn from(s: CosmicSurface) -> Self {
KeyboardFocusTarget::Fullscreen(s)
}
}

View file

@ -17,6 +17,7 @@ use crate::{
utils::prelude::*,
};
use cosmic::theme::CosmicTheme;
use smithay::{
backend::renderer::{
element::{utils::RescaleRenderElement, AsRenderElements, RenderElement},
@ -58,7 +59,13 @@ pub struct MoveGrabState {
}
impl MoveGrabState {
pub fn render<I, R>(&self, renderer: &mut R, seat: &Seat<State>, output: &Output) -> Vec<I>
pub fn render<I, R>(
&self,
renderer: &mut R,
seat: &Seat<State>,
output: &Output,
theme: &CosmicTheme,
) -> Vec<I>
where
R: Renderer + ImportAll + ImportMem + AsGlowRenderer,
<R as Renderer>::TextureId: 'static,
@ -77,21 +84,33 @@ impl MoveGrabState {
} else {
1.0
};
let alpha = if &seat.active_output() == output {
1.0
} else {
0.4
};
let cursor_at = seat.get_pointer().unwrap().current_location();
let mut window_geo = self.window.geometry();
window_geo.loc += cursor_at.to_i32_round() + self.window_offset;
if !output.geometry().intersection(window_geo).is_some() {
if !output
.geometry()
.as_logical()
.intersection(window_geo)
.is_some()
{
return Vec::new();
}
let output_scale: Scale<f64> = output.current_scale().fractional_scale().into();
let scaling_offset =
self.window_offset - self.window_offset.to_f64().upscale(scale).to_i32_round();
let render_location =
cursor_at.to_i32_round() - output.geometry().loc + self.window_offset - scaling_offset;
let render_location = cursor_at.to_i32_round() - output.geometry().loc.as_logical()
+ self.window_offset
- scaling_offset;
let active_window_hint = crate::theme::active_window_hint(theme);
let focus_element = if self.indicator_thickness > 0 {
Some(
CosmicMappedRenderElement::from(IndicatorShader::focus_element(
@ -105,10 +124,16 @@ impl MoveGrabState {
.to_f64()
.upscale(scale)
.to_i32_round(),
),
)
.as_local(),
self.indicator_thickness,
output_scale.x,
1.0,
alpha,
[
active_window_hint.red,
active_window_hint.green,
active_window_hint.blue,
],
))
.into(),
)
@ -123,7 +148,7 @@ impl MoveGrabState {
(render_location - self.window.geometry().loc)
.to_physical_precise_round(output_scale),
output_scale,
1.0,
alpha,
);
self.stacking_indicator
@ -208,6 +233,7 @@ impl PointerGrab<State> for MoveGrab {
.find(|output| {
output
.geometry()
.as_logical()
.contains(handle.current_location().to_i32_round())
})
.cloned()
@ -221,7 +247,7 @@ impl PointerGrab<State> for MoveGrab {
.workspaces
.active_mut(&self.cursor_output)
.tiling_layer
.cleanup_drag(&self.cursor_output);
.cleanup_drag();
self.cursor_output = current_output.clone();
}
@ -233,8 +259,8 @@ impl PointerGrab<State> for MoveGrab {
if let Some(grab_state) = borrow.as_mut().and_then(|s| s.as_mut()) {
let mut window_geo = self.window.geometry();
window_geo.loc += event.location.to_i32_round() + grab_state.window_offset;
for output in &state.common.shell.outputs {
if let Some(overlap) = output.geometry().intersection(window_geo) {
for output in state.common.shell.outputs() {
if let Some(overlap) = output.geometry().as_logical().intersection(window_geo) {
if self.window_outputs.insert(output.clone()) {
self.window.output_enter(output, overlap);
if let Some(indicator) =
@ -261,11 +287,21 @@ impl PointerGrab<State> for MoveGrab {
if indicator_location.is_some() != grab_state.stacking_indicator.is_some() {
grab_state.stacking_indicator = indicator_location.map(|geo| {
let element = stack_hover(state.common.event_loop_handle.clone(), geo.size);
let element = stack_hover(
state.common.event_loop_handle.clone(),
geo.size.as_logical(),
state.common.theme.clone(),
);
for output in &self.window_outputs {
element.output_enter(output, output.geometry());
element.output_enter(
output,
Rectangle::from_loc_and_size(
(0, 0),
output.geometry().size.as_logical(),
),
);
}
(element, geo.loc)
(element, geo.loc.as_logical())
});
}
}
@ -397,8 +433,8 @@ impl MoveGrab {
start_data: PointerGrabStartData<State>,
window: CosmicMapped,
seat: &Seat<State>,
initial_cursor_location: Point<f64, Logical>,
initial_window_location: Point<i32, Logical>,
initial_cursor_location: Point<f64, Global>,
initial_window_location: Point<i32, Global>,
indicator_thickness: u8,
was_tiled: bool,
) -> MoveGrab {
@ -409,7 +445,8 @@ impl MoveGrab {
let grab_state = MoveGrabState {
window: window.clone(),
window_offset: initial_window_location - initial_cursor_location.to_i32_round(),
window_offset: (initial_window_location - initial_cursor_location.to_i32_round())
.as_logical(),
indicator_thickness,
start: Instant::now(),
stacking_indicator: None,
@ -451,16 +488,16 @@ impl MoveGrab {
// No more buttons are pressed, release the grab.
let output = self.seat.active_output();
let position = if let Some(grab_state) = self
let position: Option<(CosmicMapped, Point<i32, Global>)> = if let Some(grab_state) = self
.seat
.user_data()
.get::<SeatMoveGrabState>()
.and_then(|s| s.borrow_mut().take())
{
if grab_state.window.alive() {
let window_location = handle.current_location().to_i32_round()
- output.geometry().loc
+ grab_state.window_offset;
let window_location = (handle.current_location().to_i32_round()
+ grab_state.window_offset)
.as_global();
let workspace_handle = state.common.shell.active_space(&output).handle;
for old_output in self.window_outputs.iter().filter(|o| *o != &output) {
@ -480,41 +517,26 @@ impl MoveGrab {
}
if self.tiling {
Some(
state
.common
.shell
.active_space_mut(&output)
.tiling_layer
.drop_window(grab_state.window, &output, handle.current_location()),
)
} else {
let offset = state
.common
.shell
.active_space(&output)
.floating_layer
.space
.output_geometry(&output)
.unwrap()
.loc;
grab_state.window.set_geometry(Rectangle::from_loc_and_size(
window_location + offset,
grab_state.window.geometry().size,
));
state
let (window, location) = state
.common
.shell
.active_space_mut(&output)
.floating_layer
.map_internal(grab_state.window, &output, Some(window_location + offset));
.tiling_layer
.drop_window(grab_state.window);
Some((window, location.to_global(&output)))
} else {
grab_state.window.set_geometry(Rectangle::from_loc_and_size(
window_location,
grab_state.window.geometry().size.as_global(),
));
let workspace = state.common.shell.active_space_mut(&output);
workspace.floating_layer.map_internal(
grab_state.window,
Some(window_location.to_local(&workspace.output)),
None,
);
let pointer_pos = handle.current_location();
let relative_pos = state.common.shell.map_global_to_space(pointer_pos, &output);
Some((
self.window.clone(),
window_location + offset + (pointer_pos - relative_pos).to_i32_round(),
))
Some((self.window.clone(), window_location))
}
} else {
None
@ -523,7 +545,7 @@ impl MoveGrab {
None
};
handle.unset_grab(state, serial, time);
handle.unset_grab(state, serial, time, true);
{
let cursor_state = self.seat.user_data().get::<CursorState>().unwrap();
@ -535,7 +557,7 @@ impl MoveGrab {
state,
Some((
PointerFocusTarget::from(mapped.clone()),
position - self.window.geometry().loc,
position.as_logical() - self.window.geometry().loc,
)),
&MotionEvent {
location: handle.current_location(),

View file

@ -1,5 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only
use std::sync::atomic::{AtomicBool, Ordering};
use crate::{
shell::{
element::CosmicMapped, focus::target::PointerFocusTarget, grabs::ResizeEdge, CosmicSurface,
@ -8,11 +10,15 @@ use crate::{
};
use smithay::{
desktop::space::SpaceElement,
input::pointer::{
AxisFrame, ButtonEvent, GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent,
GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData as PointerGrabStartData,
MotionEvent, PointerGrab, PointerInnerHandle, RelativeMotionEvent,
input::{
pointer::{
AxisFrame, ButtonEvent, GestureHoldBeginEvent, GestureHoldEndEvent,
GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent,
GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent,
GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab, PointerInnerHandle,
RelativeMotionEvent,
},
Seat,
},
utils::{IsAlive, Logical, Point, Rectangle, Size},
};
@ -39,6 +45,7 @@ pub enum ResizeState {
pub struct ResizeSurfaceGrab {
start_data: PointerGrabStartData<State>,
seat: Seat<State>,
window: CosmicMapped,
edges: ResizeEdge,
initial_window_size: Size<i32, Logical>,
@ -58,7 +65,13 @@ impl PointerGrab<State> for ResizeSurfaceGrab {
// It is impossible to get `min_size` and `max_size` of dead toplevel, so we return early.
if !self.window.alive() {
handle.unset_grab(data, event.serial, event.time);
self.seat
.user_data()
.get::<ResizeGrabMarker>()
.unwrap()
.0
.store(false, Ordering::SeqCst);
handle.unset_grab(data, event.serial, event.time, true);
return;
}
@ -101,10 +114,10 @@ impl PointerGrab<State> for ResizeSurfaceGrab {
self.window.set_resizing(true);
self.window.set_geometry(Rectangle::from_loc_and_size(
match self.window.active_window() {
CosmicSurface::X11(s) => s.geometry().loc,
CosmicSurface::X11(s) => s.geometry().loc.as_global(),
_ => (0, 0).into(),
},
self.last_window_size,
self.last_window_size.as_global(),
));
self.window.configure();
}
@ -129,7 +142,13 @@ impl PointerGrab<State> for ResizeSurfaceGrab {
handle.button(data, event);
if handle.current_pressed().is_empty() {
// No more buttons are pressed, release the grab.
handle.unset_grab(data, event.serial, event.time);
self.seat
.user_data()
.get::<ResizeGrabMarker>()
.unwrap()
.0
.store(false, Ordering::SeqCst);
handle.unset_grab(data, event.serial, event.time, true);
// If toplevel is dead, we can't resize it, so we return early.
if !self.window.alive() {
@ -139,10 +158,10 @@ impl PointerGrab<State> for ResizeSurfaceGrab {
self.window.set_resizing(false);
self.window.set_geometry(Rectangle::from_loc_and_size(
match self.window.active_window() {
CosmicSurface::X11(s) => s.geometry().loc,
CosmicSurface::X11(s) => s.geometry().loc.as_global(),
_ => (0, 0).into(),
},
self.last_window_size,
self.last_window_size.as_global(),
));
self.window.configure();
@ -245,6 +264,14 @@ impl PointerGrab<State> for ResizeSurfaceGrab {
}
}
pub struct ResizeGrabMarker(AtomicBool);
impl ResizeGrabMarker {
pub fn get(&self) -> bool {
self.0.load(Ordering::SeqCst)
}
}
impl ResizeSurfaceGrab {
pub fn new(
start_data: PointerGrabStartData<State>,
@ -252,6 +279,7 @@ impl ResizeSurfaceGrab {
edges: ResizeEdge,
initial_window_location: Point<i32, Logical>,
initial_window_size: Size<i32, Logical>,
seat: &Seat<State>,
) -> ResizeSurfaceGrab {
let resize_state = ResizeState::Resizing(ResizeData {
edges,
@ -260,9 +288,14 @@ impl ResizeSurfaceGrab {
});
*mapped.resize_state.lock().unwrap() = Some(resize_state);
seat.user_data()
.get_or_insert::<ResizeGrabMarker, _>(|| ResizeGrabMarker(AtomicBool::new(true)))
.0
.store(true, Ordering::SeqCst);
ResizeSurfaceGrab {
start_data,
seat: seat.clone(),
window: mapped,
edges,
initial_window_size,
@ -271,7 +304,13 @@ impl ResizeSurfaceGrab {
}
pub fn apply_resize_to_location(window: CosmicMapped, space: &mut Workspace) {
if let Some(location) = space.floating_layer.space.element_location(&window) {
if let Some(location) = space
.floating_layer
.space
.element_location(&window)
.map(PointExt::as_local)
.map(|p| p.to_global(space.output()))
{
let mut new_location = None;
let mut resize_state = window.resize_state.lock().unwrap();
@ -316,13 +355,13 @@ impl ResizeSurfaceGrab {
CosmicSurface::Wayland(window) => {
update_reactive_popups(
&window,
new_location + offset,
new_location + offset.as_global(),
space.floating_layer.space.outputs(),
);
}
CosmicSurface::X11(surface) => {
let mut geometry = surface.geometry();
geometry.loc += location - new_location;
geometry.loc += (location - new_location).as_logical();
let _ = surface.configure(geometry);
}
_ => unreachable!(),
@ -331,7 +370,7 @@ impl ResizeSurfaceGrab {
space
.floating_layer
.space
.map_element(window, new_location, false);
.map_element(window, new_location.as_logical(), false);
}
}
}

View file

@ -11,7 +11,6 @@ use smithay::{
utils::{Logical, Point, Rectangle, Size},
wayland::seat::WaylandFocus,
};
use std::collections::HashMap;
use crate::{
backend::render::{element::AsGlowRenderer, IndicatorShader, Key, Usage},
@ -28,9 +27,7 @@ use crate::{
},
state::State,
utils::prelude::*,
wayland::{
handlers::xdg_shell::popup::get_popup_toplevel, protocols::toplevel_info::ToplevelInfoState,
},
wayland::handlers::xdg_shell::popup::get_popup_toplevel,
};
mod grabs;
@ -42,67 +39,68 @@ pub struct FloatingLayout {
}
impl FloatingLayout {
pub fn new() -> FloatingLayout {
Default::default()
pub fn new(output: &Output) -> FloatingLayout {
let mut layout = Self::default();
layout.space.map_output(output, (0, 0));
layout
}
pub fn map_output(&mut self, output: &Output, location: Point<i32, Logical>) {
self.space.map_output(output, location)
}
pub fn set_output(&mut self, output: &Output) {
let old_output = self.space.outputs().next().unwrap().clone();
self.space.unmap_output(&old_output);
self.space.map_output(output, (0, 0));
/*
TODO: rescale all positions? (evem rescale windows?)
*/
pub fn unmap_output(
&mut self,
output: &Output,
toplevel_info: &mut ToplevelInfoState<State, CosmicSurface>,
) {
let windows = self
.space
.elements_for_output(output)
.cloned()
.collect::<Vec<_>>();
for window in &windows {
for (toplevel, _) in window.windows() {
toplevel_info.toplevel_leave_output(&toplevel, output);
}
}
self.space.unmap_output(output);
self.refresh();
for window in &windows {
for output in self.space.outputs_for_element(&window) {
for (toplevel, _) in window.windows() {
toplevel_info.toplevel_enter_output(&toplevel, &output);
}
}
}
}
pub fn map(
&mut self,
mapped: impl Into<CosmicMapped>,
seat: &Seat<State>,
position: impl Into<Option<Point<i32, Logical>>>,
position: impl Into<Option<Point<i32, Local>>>,
) {
let mapped = mapped.into();
let output = seat.active_output();
let position = position.into();
self.map_internal(mapped, &output, position)
self.map_internal(mapped, position, None)
}
pub fn map_maximized(&mut self, mapped: CosmicMapped) {
let output = self.space.outputs().next().unwrap().clone();
let layers = layer_map_for_output(&output);
let geometry = layers.non_exclusive_zone().as_local();
mapped.set_bounds(geometry.size.as_logical());
mapped.set_tiled(true);
mapped.set_maximized(true);
mapped.set_geometry(geometry.to_global(&output));
mapped.configure();
self.space
.map_element(mapped, geometry.loc.as_logical(), true);
}
pub(in crate::shell) fn map_internal(
&mut self,
mapped: CosmicMapped,
output: &Output,
position: Option<Point<i32, Logical>>,
position: Option<Point<i32, Local>>,
size: Option<Size<i32, Logical>>,
) {
let mut win_geo = mapped.geometry();
let mut win_geo = mapped.geometry().as_local();
let output = self.space.outputs().next().unwrap().clone();
let layers = layer_map_for_output(&output);
let geometry = layers.non_exclusive_zone();
mapped.set_bounds(geometry.size);
let last_geometry = mapped.last_geometry.lock().unwrap().clone();
if let Some(size) = last_geometry.map(|g| g.size) {
if let Some(size) = size
.map(SizeExt::as_local)
.or(last_geometry.map(|g| g.size))
{
win_geo.size = size;
} else {
let (min_size, max_size) = (
@ -150,30 +148,24 @@ impl FloatingLayout {
});
mapped.set_tiled(false);
let offset = output.geometry().loc
- self
.space
.output_geometry(output)
.map(|g| g.loc)
.unwrap_or_default();
mapped.set_geometry(Rectangle::from_loc_and_size(
position + offset,
win_geo.size,
));
mapped
.set_geometry(Rectangle::from_loc_and_size(position, win_geo.size).to_global(&output));
mapped.configure();
self.space.map_element(mapped, position, false);
self.space.map_element(mapped, position.as_logical(), false);
}
pub fn unmap(&mut self, window: &CosmicMapped) -> bool {
#[allow(irrefutable_let_patterns)]
let is_maximized = window.is_maximized(true);
if !is_maximized {
if !window.is_maximized(true) || !window.is_fullscreen(true) {
if let Some(location) = self.space.element_location(window) {
*window.last_geometry.lock().unwrap() = Some(Rectangle::from_loc_and_size(
location,
window.geometry().size,
));
*window.last_geometry.lock().unwrap() = Some(
Rectangle::from_loc_and_size(
location,
window
.pending_size()
.unwrap_or_else(|| window.geometry().size),
)
.as_local(),
)
}
}
@ -182,56 +174,8 @@ impl FloatingLayout {
was_unmaped
}
pub fn element_geometry(&self, elem: &CosmicMapped) -> Option<Rectangle<i32, Logical>> {
self.space.element_geometry(elem)
}
pub fn maximize_request(&mut self, window: &CosmicSurface) {
if let Some(mapped) = self
.space
.elements()
.find(|m| m.windows().any(|(w, _)| &w == window))
{
if let Some(location) = self.space.element_location(mapped) {
*mapped.last_geometry.lock().unwrap() = Some(Rectangle::from_loc_and_size(
location,
mapped.geometry().size,
));
}
}
}
pub fn unmaximize_request(&mut self, window: &CosmicSurface) -> Option<Size<i32, Logical>> {
let maybe_mapped = self
.space
.elements()
.find(|m| m.windows().any(|(w, _)| &w == window))
.cloned();
if let Some(mapped) = maybe_mapped {
let last_geometry = mapped.last_geometry.lock().unwrap().clone();
let last_size = last_geometry.map(|g| g.size).expect("No previous size?");
let last_location = last_geometry.map(|g| g.loc).expect("No previous location?");
let output = self
.space
.output_under(last_location.to_f64())
.next()
.unwrap_or(self.space.outputs().next().unwrap());
let offset = output.geometry().loc
- self
.space
.output_geometry(output)
.map(|g| g.loc)
.unwrap_or_default();
mapped.set_geometry(Rectangle::from_loc_and_size(
last_location + offset,
last_size,
));
self.space.map_element(mapped, last_location, true);
Some(last_size)
} else {
None
}
pub fn element_geometry(&self, elem: &CosmicMapped) -> Option<Rectangle<i32, Local>> {
self.space.element_geometry(elem).map(RectExt::as_local)
}
pub fn resize_request(
@ -251,6 +195,7 @@ impl FloatingLayout {
edges,
location,
size,
seat,
))
} else {
None
@ -274,6 +219,9 @@ impl FloatingLayout {
else {
return false;
};
if mapped.is_maximized(true) {
return false;
}
let Some(original_geo) = self.space.element_geometry(mapped) else {
return false; // we don't have that window
@ -339,13 +287,10 @@ impl FloatingLayout {
}));
mapped.set_resizing(true);
mapped.set_geometry(Rectangle::from_loc_and_size(
match mapped.active_window() {
CosmicSurface::X11(s) => s.geometry().loc,
_ => (0, 0).into(),
},
geo.size,
));
mapped.set_geometry(
geo.as_local()
.to_global(self.space.outputs().next().unwrap()),
);
mapped.configure();
true
@ -427,6 +372,7 @@ impl FloatingLayout {
&mut self,
direction: Direction,
seat: &Seat<State>,
theme: cosmic::Theme,
) -> MoveResult {
let Some(target) = seat.get_keyboard().unwrap().current_focus() else {
return MoveResult::None
@ -451,7 +397,7 @@ impl FloatingLayout {
match focused.handle_move(direction) {
StackMoveResult::Handled => return MoveResult::Done,
StackMoveResult::MoveOut(surface, loop_handle) => {
let mapped: CosmicMapped = CosmicWindow::new(surface, loop_handle).into();
let mapped: CosmicMapped = CosmicWindow::new(surface, loop_handle, theme).into();
let output = seat.active_output();
let pos = self.space.element_geometry(focused).unwrap().loc
+ match direction {
@ -471,7 +417,7 @@ impl FloatingLayout {
})
.then_some(pos);
self.map_internal(mapped.clone(), &output, position);
self.map_internal(mapped.clone(), position.map(PointExt::as_local), None);
return MoveResult::ShiftFocus(KeyboardFocusTarget::Element(mapped));
}
StackMoveResult::Default => {}
@ -503,87 +449,31 @@ impl FloatingLayout {
{
// TODO what about windows leaving to the top with no headerbar to drag? can that happen? (Probably if the user is moving outputs down)
*element.last_geometry.lock().unwrap() = None;
let output = self.space.outputs().next().unwrap().clone();
self.map_internal(element, &output, None);
self.map_internal(element, None, None);
}
}
pub fn most_overlapped_output_for_element(&self, elem: &CosmicMapped) -> Option<Output> {
let elem_geo = self.space.element_geometry(elem)?;
if self.space.outputs().nth(1).is_none() {
return self.space.outputs().next().cloned();
}
Some(
self.space
.outputs_for_element(elem)
.into_iter()
.max_by_key(|o| {
let output_geo = self.space.output_geometry(o).unwrap();
if let Some(intersection) = output_geo.intersection(elem_geo) {
intersection.size.w * intersection.size.h
} else {
0
}
})
.unwrap_or(self.space.outputs().next().unwrap().clone()),
)
}
pub fn merge(&mut self, other: FloatingLayout) {
let mut output_pos_map = HashMap::new();
for output in self.space.outputs() {
output_pos_map.insert(
output.clone(),
self.space.output_geometry(output).unwrap().loc
- other
.space
.output_geometry(output)
.map(|geo| geo.loc)
.unwrap_or_else(|| (0, 0).into()),
);
}
for element in other.space.elements() {
let mut elem_geo = other.space.element_geometry(element).unwrap();
let output = other
let elem_loc = other
.space
.outputs_for_element(element)
.into_iter()
.filter(|o| self.space.outputs().any(|o2| o == o2))
.max_by_key(|o| {
let output_geo = other.space.output_geometry(o).unwrap();
let intersection = output_geo.intersection(elem_geo).unwrap();
intersection.size.w * intersection.size.h
})
.unwrap_or(self.space.outputs().next().unwrap().clone());
elem_geo.loc += output_pos_map
.get(&output)
.copied()
.unwrap_or_else(|| (0, 0).into());
let offset = output.geometry().loc
- self
.space
.output_geometry(&output)
.map(|g| g.loc)
.unwrap_or_default();
element.set_geometry(Rectangle::from_loc_and_size(
elem_geo.loc + offset,
elem_geo.size,
));
self.space.map_element(element.clone(), elem_geo.loc, false);
.element_geometry(element)
.unwrap()
.loc
.as_local();
self.map_internal(element.clone(), Some(elem_loc), None);
}
self.refresh(); //fixup any out of bounds elements
}
pub fn render_output<R>(
pub fn render<R>(
&self,
renderer: &mut R,
output: &Output,
focused: Option<&CosmicMapped>,
mut resize_indicator: Option<(ResizeMode, ResizeIndicator)>,
indicator_thickness: u8,
alpha: f32,
theme: &cosmic::theme::CosmicTheme,
) -> (
Vec<CosmicMappedRenderElement<R>>,
Vec<CosmicMappedRenderElement<R>>,
@ -598,68 +488,72 @@ impl FloatingLayout {
#[cfg(feature = "debug")]
puffin::profile_function!();
let output = self.space.outputs().next().unwrap();
let output_scale = output.current_scale().fractional_scale();
let output_geo = self.space.output_geometry(output).unwrap();
let mut window_elements = Vec::new();
let mut popup_elements = Vec::new();
self.space
.elements_for_output(output)
.rev()
.for_each(|elem| {
let render_location = self.space.element_location(elem).unwrap()
- output_geo.loc
- elem.geometry().loc;
let (w_elements, p_elements) = elem.split_render_elements(
renderer,
render_location.to_physical_precise_round(output_scale),
output_scale.into(),
alpha,
);
self.space.elements().rev().for_each(|elem| {
let render_location = self.space.element_location(elem).unwrap() - elem.geometry().loc;
let (w_elements, p_elements) = elem.split_render_elements(
renderer,
render_location.to_physical_precise_round(output_scale),
output_scale.into(),
alpha,
);
if focused == Some(elem) {
let mut indicator_geometry = Rectangle::from_loc_and_size(
self.space.element_location(elem).unwrap() - output_geo.loc,
elem.geometry().size,
if focused == Some(elem) && !elem.is_maximized(false) {
let mut indicator_geometry = Rectangle::from_loc_and_size(
self.space.element_location(elem).unwrap(),
elem.geometry().size,
)
.as_local();
if let Some((mode, resize)) = resize_indicator.as_mut() {
indicator_geometry.loc -= (18, 18).into();
indicator_geometry.size += (36, 36).into();
resize.resize(indicator_geometry.size.as_logical());
resize.output_enter(output, Rectangle::default() /* unused */);
window_elements.extend(
resize
.render_elements::<CosmicWindowRenderElement<R>>(
renderer,
indicator_geometry
.loc
.as_logical()
.to_physical_precise_round(output_scale),
output_scale.into(),
alpha * mode.alpha().unwrap_or(1.0),
)
.into_iter()
.map(CosmicMappedRenderElement::Window),
);
if let Some((mode, resize)) = resize_indicator.as_mut() {
indicator_geometry.loc -= (18, 18).into();
indicator_geometry.size += (36, 36).into();
resize.resize(indicator_geometry.size);
resize.output_enter(output, output_geo);
window_elements.extend(
resize
.render_elements::<CosmicWindowRenderElement<R>>(
renderer,
indicator_geometry
.loc
.to_physical_precise_round(output_scale),
output_scale.into(),
alpha * mode.alpha().unwrap_or(1.0),
)
.into_iter()
.map(CosmicMappedRenderElement::Window),
);
}
if indicator_thickness > 0 {
let element = IndicatorShader::focus_element(
renderer,
Key::Window(Usage::FocusIndicator, elem.clone()),
indicator_geometry,
indicator_thickness,
output_scale,
alpha,
);
window_elements.push(element.into());
}
}
window_elements.extend(w_elements);
popup_elements.extend(p_elements);
});
let active_window_hint = crate::theme::active_window_hint(theme);
if indicator_thickness > 0 {
let element = IndicatorShader::focus_element(
renderer,
Key::Window(Usage::FocusIndicator, elem.clone()),
indicator_geometry,
indicator_thickness,
output_scale,
alpha,
[
active_window_hint.red,
active_window_hint.green,
active_window_hint.blue,
],
);
window_elements.push(element.into());
}
}
window_elements.extend(w_elements);
popup_elements.extend(p_elements);
});
(window_elements, popup_elements)
}

View file

@ -5,7 +5,7 @@ use crate::{
shell::{focus::target::PointerFocusTarget, layout::Orientation},
utils::prelude::*,
};
use id_tree::NodeId;
use id_tree::{NodeId, Tree};
use smithay::{
backend::input::ButtonState,
input::{
@ -80,6 +80,8 @@ impl PointerTarget<State> for ResizeForkTarget {
button,
location,
},
old_tree: None,
accumulated_delta: 0.0,
last_loc: location,
node,
output,
@ -116,6 +118,8 @@ impl PointerTarget<State> for ResizeForkTarget {
pub struct ResizeForkGrab {
start_data: PointerGrabStartData<State>,
last_loc: Point<f64, Logical>,
old_tree: Option<Tree<Data>>,
accumulated_delta: f64,
node: NodeId,
output: WeakOutput,
left_up_idx: usize,
@ -137,67 +141,104 @@ impl PointerGrab<State> for ResizeForkGrab {
if let Some(output) = self.output.upgrade() {
let tiling_layer = &mut data.common.shell.active_space_mut(&output).tiling_layer;
if let Some(queue) = tiling_layer.queues.get_mut(&output) {
let tree = &mut queue.trees.back_mut().unwrap().0;
if tree.get(&self.node).is_ok() {
let delta = match self.orientation {
Orientation::Vertical => delta.x,
Orientation::Horizontal => delta.y,
}
.round() as i32;
let gaps = tiling_layer.gaps();
// check that we are still alive
let mut iter = tree
.children_ids(&self.node)
.unwrap()
.skip(self.left_up_idx);
let first_elem = iter.next();
let second_elem = iter.next();
if first_elem.is_none() || second_elem.is_none() {
return handle.unset_grab(data, event.serial, event.time);
};
let tree = &mut tiling_layer.queue.trees.back_mut().unwrap().0;
match &mut self.old_tree {
Some(old_tree) => {
// it would be so nice to just `zip` here, but `zip` just returns `None` once either returns `None`.
let mut iter_a = old_tree
.root_node_id()
.into_iter()
.flat_map(|root_id| old_tree.traverse_pre_order_ids(root_id).unwrap());
let mut iter_b = tree
.root_node_id()
.into_iter()
.flat_map(|root_id| tree.traverse_pre_order_ids(root_id).unwrap());
match tree.get_mut(&self.node).unwrap().data_mut() {
Data::Group {
sizes, orientation, ..
} => {
if sizes[self.left_up_idx] + sizes[self.left_up_idx + 1]
< match orientation {
Orientation::Vertical => 720,
Orientation::Horizontal => 480,
}
{
return;
};
let old_size = sizes[self.left_up_idx];
sizes[self.left_up_idx] = (old_size + delta).max(
if self.orientation == Orientation::Vertical {
360
} else {
240
},
);
let diff = old_size - sizes[self.left_up_idx];
let next_size = sizes[self.left_up_idx + 1] + diff;
sizes[self.left_up_idx + 1] =
next_size.max(if self.orientation == Orientation::Vertical {
360
} else {
240
});
let next_diff = next_size - sizes[self.left_up_idx + 1];
sizes[self.left_up_idx] += next_diff;
// so lets do it manually
let mut equal = true;
let mut a = iter_a.next();
let mut b = iter_b.next();
while a.is_some() || b.is_some() {
equal = a == b;
if !equal {
break;
}
_ => unreachable!(),
a = iter_a.next();
b = iter_b.next();
}
self.last_loc = event.location;
let blocker = TilingLayout::update_positions(&output, tree, tiling_layer.gaps);
tiling_layer.pending_blockers.extend(blocker);
} else {
handle.unset_grab(data, event.serial, event.time);
if !equal {
*old_tree = tree.copy_clone();
self.accumulated_delta = 0.0;
} else {
*tree = old_tree.copy_clone();
}
}
x @ None => {
*x = Some(tree.copy_clone());
}
};
if tree.get(&self.node).is_ok() {
let delta = match self.orientation {
Orientation::Vertical => delta.x,
Orientation::Horizontal => delta.y,
}
.round();
self.accumulated_delta += delta;
// check that we are still alive
let mut iter = tree
.children_ids(&self.node)
.unwrap()
.skip(self.left_up_idx);
let first_elem = iter.next();
let second_elem = iter.next();
if first_elem.is_none() || second_elem.is_none() {
return handle.unset_grab(data, event.serial, event.time, true);
};
match tree.get_mut(&self.node).unwrap().data_mut() {
Data::Group {
sizes, orientation, ..
} => {
if sizes[self.left_up_idx] + sizes[self.left_up_idx + 1]
< match orientation {
Orientation::Vertical => 720,
Orientation::Horizontal => 480,
}
{
return;
};
let old_size = sizes[self.left_up_idx];
sizes[self.left_up_idx] = (old_size
+ self.accumulated_delta.round() as i32)
.max(if self.orientation == Orientation::Vertical {
360
} else {
240
});
let diff = old_size - sizes[self.left_up_idx];
let next_size = sizes[self.left_up_idx + 1] + diff;
sizes[self.left_up_idx + 1] =
next_size.max(if self.orientation == Orientation::Vertical {
360
} else {
240
});
let next_diff = next_size - sizes[self.left_up_idx + 1];
sizes[self.left_up_idx] += next_diff;
}
_ => unreachable!(),
}
self.last_loc = event.location;
let blocker = TilingLayout::update_positions(&output, tree, gaps);
tiling_layer.pending_blockers.extend(blocker);
} else {
handle.unset_grab(data, event.serial, event.time, true);
}
}
}
@ -222,7 +263,7 @@ impl PointerGrab<State> for ResizeForkGrab {
handle.button(data, event);
if handle.current_pressed().is_empty() {
// No more buttons are pressed, release the grab.
handle.unset_grab(data, event.serial, event.time);
handle.unset_grab(data, event.serial, event.time, true);
}
}

View file

@ -39,8 +39,7 @@ impl KeyboardGrab<State> for SwapWindowGrab {
serial: Serial,
time: u32,
) {
if self.desc.output.upgrade().is_none()
|| !matches!(&data.common.shell.overview_mode, OverviewMode::Started(Trigger::KeyboardSwap(_, d), _) if d == &self.desc)
if !matches!(&data.common.shell.overview_mode, OverviewMode::Started(Trigger::KeyboardSwap(_, d), _) if d == &self.desc)
{
handle.unset_grab(data, serial, false);
return;

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -21,7 +21,7 @@ use crate::{
xwayland::XWaylandState,
};
use calloop::LoopHandle;
use cosmic::theme::CosmicTheme;
use id_tree::Tree;
use indexmap::IndexSet;
use keyframe::{ease, functions::EaseInOutCubic};
@ -47,7 +47,7 @@ use smithay::{
xwayland::X11Surface,
};
use std::{
collections::HashMap,
collections::{HashMap, VecDeque},
sync::{
atomic::{AtomicBool, Ordering},
Arc,
@ -61,7 +61,7 @@ use super::{
element::{
resize_indicator::ResizeIndicator, stack::CosmicStackRenderElement,
swap_indicator::SwapIndicator, window::CosmicWindowRenderElement, CosmicMapped,
CosmicWindow,
MaximizedState,
},
focus::{
target::{KeyboardFocusTarget, PointerFocusTarget, WindowGroup},
@ -76,28 +76,37 @@ const FULLSCREEN_ANIMATION_DURATION: Duration = Duration::from_millis(200);
#[derive(Debug)]
pub struct Workspace {
pub output: Output,
pub tiling_layer: TilingLayout,
pub floating_layer: FloatingLayout,
pub tiling_enabled: bool,
pub fullscreen: HashMap<Output, FullscreenSurface>,
pub fullscreen: Option<FullscreenSurface>,
pub handle: WorkspaceHandle,
pub focus_stack: FocusStacks,
pub pending_buffers: Vec<(ScreencopySession, BufferParams)>,
pub screencopy_sessions: Vec<DropableSession>,
pub output_stack: VecDeque<String>,
pub(super) backdrop_id: Id,
pub dirty: AtomicBool,
}
#[derive(Debug, Clone)]
pub struct FullscreenSurface {
pub window: CosmicWindow,
pub exclusive: bool,
original_size: Size<i32, Logical>,
pub surface: CosmicSurface,
pub previously: Option<(ManagedLayer, WorkspaceHandle)>,
original_geometry: Rectangle<i32, Global>,
start_at: Option<Instant>,
ended_at: Option<Instant>,
animation_signal: Option<Arc<AtomicBool>>,
}
impl PartialEq for FullscreenSurface {
fn eq(&self, other: &Self) -> bool {
self.surface == other.surface
}
}
struct FullscreenBlocker {
signal: Arc<AtomicBool>,
}
@ -120,17 +129,17 @@ impl FullscreenSurface {
impl IsAlive for FullscreenSurface {
fn alive(&self) -> bool {
self.window.alive()
self.surface.alive()
}
}
#[derive(Debug, Default)]
pub struct FocusStacks(HashMap<Seat<State>, IndexSet<CosmicMapped>>);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, PartialEq)]
pub struct ManagedState {
pub layer: ManagedLayer,
pub was_fullscreen: Option<bool>,
pub was_fullscreen: Option<FullscreenSurface>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ManagedLayer {
@ -198,16 +207,26 @@ impl MoveResult {
}
impl Workspace {
pub fn new(handle: WorkspaceHandle, tiling_enabled: bool, gaps: (u8, u8)) -> Workspace {
pub fn new(
handle: WorkspaceHandle,
output: Output,
tiling_enabled: bool,
theme: cosmic::Theme,
) -> Workspace {
let tiling_layer = TilingLayout::new(theme, &output);
let floating_layer = FloatingLayout::new(&output);
Workspace {
tiling_layer: TilingLayout::new(gaps),
floating_layer: FloatingLayout::new(),
output,
tiling_layer,
floating_layer,
tiling_enabled,
fullscreen: HashMap::new(),
fullscreen: None,
handle,
focus_stack: FocusStacks::default(),
pending_buffers: Vec::new(),
screencopy_sessions: Vec::new(),
output_stack: VecDeque::new(),
backdrop_id: Id::new(),
dirty: AtomicBool::new(false),
}
@ -217,7 +236,11 @@ impl Workspace {
#[cfg(feature = "debug")]
puffin::profile_function!();
self.fullscreen.retain(|_, w| w.alive());
// TODO: `Option::take_if` once stabilitized
if self.fullscreen.as_ref().is_some_and(|w| !w.alive()) {
let _ = self.fullscreen.take();
};
self.floating_layer.refresh();
self.tiling_layer.refresh();
}
@ -233,15 +256,15 @@ impl Workspace {
self.tiling_layer.animations_going()
|| self
.fullscreen
.values()
.any(|f| f.start_at.is_some() || f.ended_at.is_some())
.as_ref()
.is_some_and(|f| f.start_at.is_some() || f.ended_at.is_some())
|| self.dirty.swap(false, Ordering::SeqCst)
}
pub fn update_animations(&mut self) -> HashMap<ClientId, Client> {
let mut clients = HashMap::new();
for f in self.fullscreen.values_mut() {
if let Some(f) = self.fullscreen.as_mut() {
if let Some(start) = f.start_at.as_ref() {
let duration_since = Instant::now().duration_since(*start);
if duration_since > FULLSCREEN_ANIMATION_DURATION {
@ -252,36 +275,32 @@ impl Workspace {
if let Some(signal) = f.animation_signal.take() {
signal.store(true, Ordering::SeqCst);
if let Some(client) =
f.window.wl_surface().as_ref().and_then(Resource::client)
f.surface.wl_surface().as_ref().and_then(Resource::client)
{
clients.insert(client.id(), client);
}
}
}
}
}
let len = self.fullscreen.len();
self.fullscreen.retain(|_, f| match f.ended_at {
None => true,
Some(instant) => {
let duration_since = Instant::now().duration_since(instant);
if let Some(end) = f.ended_at {
let duration_since = Instant::now().duration_since(end);
if duration_since * 2 > FULLSCREEN_ANIMATION_DURATION {
if let Some(signal) = f.animation_signal.take() {
signal.store(true, Ordering::SeqCst);
if let Some(client) =
f.window.wl_surface().as_ref().and_then(Resource::client)
f.surface.wl_surface().as_ref().and_then(Resource::client)
{
clients.insert(client.id(), client);
}
}
}
duration_since < FULLSCREEN_ANIMATION_DURATION
if duration_since >= FULLSCREEN_ANIMATION_DURATION {
let _ = self.fullscreen.take();
self.dirty.store(true, Ordering::SeqCst);
}
}
});
if len != self.fullscreen.len() {
self.dirty.store(true, Ordering::SeqCst);
}
clients.extend(self.tiling_layer.update_animation_state());
@ -299,35 +318,44 @@ impl Workspace {
}
}
pub fn map_output(&mut self, output: &Output, position: Point<i32, Logical>) {
self.tiling_layer.map_output(output, position);
self.floating_layer.map_output(output, position);
pub fn output(&self) -> &Output {
&self.output
}
pub fn unmap_output(
pub fn set_output(
&mut self,
output: &Output,
toplevel_info: &mut ToplevelInfoState<State, CosmicSurface>,
) {
if let Some(dead_output_fullscreen) = self.fullscreen.remove(output) {
self.unfullscreen_request(&dead_output_fullscreen.window.surface());
self.tiling_layer.set_output(output);
self.floating_layer.set_output(output);
for mapped in self.mapped() {
for (surface, _) in mapped.windows() {
toplevel_info.toplevel_leave_output(&surface, &self.output);
toplevel_info.toplevel_enter_output(&surface, output);
}
}
self.tiling_layer.unmap_output(output, toplevel_info);
self.floating_layer.unmap_output(output, toplevel_info);
self.refresh();
self.output = output.clone();
}
pub fn unmap(&mut self, mapped: &CosmicMapped) -> Option<ManagedState> {
let was_floating = self.floating_layer.unmap(&mapped);
let was_tiling = self.tiling_layer.unmap(&mapped).is_some();
if was_floating || was_tiling {
assert!(was_floating != was_tiling);
let was_fullscreen = self
.fullscreen
.as_ref()
.filter(|f| f.ended_at.is_none())
.map(|f| mapped.windows().any(|(w, _)| w == f.surface))
.unwrap_or(false)
.then(|| self.fullscreen.take().unwrap());
if mapped.maximized_state.lock().unwrap().is_some() {
// If surface is maximized then unmaximize it, so it is assigned to only one layer
let _ = self.unmaximize_request(&mapped.active_window());
}
let was_maximized = mapped.is_maximized(true);
let was_fullscreen = mapped.is_fullscreen(true);
if was_maximized || was_fullscreen {
self.unmaximize_request(&mapped.active_window());
let was_floating = self.floating_layer.unmap(&mapped);
let was_tiling = self.tiling_layer.unmap(&mapped);
if was_floating || was_tiling {
assert!(was_floating != was_tiling);
}
self.focus_stack
@ -337,12 +365,12 @@ impl Workspace {
if was_floating {
Some(ManagedState {
layer: ManagedLayer::Floating,
was_fullscreen: (was_fullscreen || was_maximized).then_some(was_fullscreen),
was_fullscreen,
})
} else if was_tiling {
Some(ManagedState {
layer: ManagedLayer::Tiling,
was_fullscreen: (was_fullscreen || was_maximized).then_some(was_fullscreen),
was_fullscreen,
})
} else {
None
@ -366,140 +394,103 @@ impl Workspace {
})
}
pub fn outputs_for_element(&self, elem: &CosmicMapped) -> impl Iterator<Item = Output> {
self.floating_layer
.space
.outputs_for_element(elem)
.into_iter()
.chain(self.tiling_layer.output_for_element(elem).cloned())
}
pub fn output_under(&self, point: Point<i32, Logical>) -> Option<&Output> {
let space = &self.floating_layer.space;
space.outputs().find(|o| {
let internal_output_geo = space.output_geometry(o).unwrap();
let external_output_geo = o.geometry();
internal_output_geo.contains(point - external_output_geo.loc + internal_output_geo.loc)
})
}
pub fn element_under(
&mut self,
location: Point<f64, Logical>,
location: Point<f64, Global>,
overview: OverviewMode,
) -> Option<(PointerFocusTarget, Point<i32, Logical>)> {
) -> Option<(PointerFocusTarget, Point<i32, Global>)> {
let location = location.to_local(&self.output);
self.floating_layer
.space
.element_under(location)
.map(|(mapped, p)| (mapped.clone().into(), p))
.element_under(location.as_logical())
.map(|(mapped, p)| (mapped.clone().into(), p.as_local()))
.or_else(|| self.tiling_layer.element_under(location, overview))
.map(|(m, p)| (m, p.to_global(&self.output)))
}
pub fn element_geometry(&self, elem: &CosmicMapped) -> Option<Rectangle<i32, Logical>> {
let space = &self.floating_layer.space;
let outputs = space.outputs().collect::<Vec<_>>();
let offset = if outputs.len() == 1
&& space.output_geometry(&outputs[0]).unwrap().loc == Point::from((0, 0))
{
outputs[0].geometry().loc
} else {
(0, 0).into()
};
pub fn element_geometry(&self, elem: &CosmicMapped) -> Option<Rectangle<i32, Local>> {
self.floating_layer
.space
.element_geometry(elem)
.or_else(|| self.tiling_layer.element_geometry(elem))
.map(|mut geo| {
geo.loc += offset;
geo
})
}
pub fn recalculate(&mut self, output: &Output) {
if let Some(f) = self.fullscreen.get(output) {
if !f.exclusive {
f.window
.set_geometry(layer_map_for_output(output).non_exclusive_zone());
}
}
self.tiling_layer.recalculate(output);
pub fn recalculate(&mut self) {
self.tiling_layer.recalculate();
self.floating_layer.refresh();
}
pub fn maximize_request(
&mut self,
window: &CosmicSurface,
output: &Output,
evlh: LoopHandle<'static, crate::state::State>,
) {
if self.fullscreen.contains_key(output) {
pub fn maximize_request(&mut self, window: &CosmicSurface) {
if self.fullscreen.is_some() {
return;
}
self.floating_layer.maximize_request(window);
window.set_fullscreen(false);
window.set_maximized(true);
self.set_fullscreen(window, output, false, evlh)
if let Some(elem) = self.element_for_surface(window).cloned() {
let mut state = elem.maximized_state.lock().unwrap();
if state.is_none() {
*state = Some(MaximizedState {
original_geometry: self.element_geometry(&elem).unwrap(),
original_layer: if self.is_floating(&elem) {
ManagedLayer::Floating
} else {
ManagedLayer::Tiling
},
});
std::mem::drop(state);
self.floating_layer.map_maximized(elem);
}
}
}
pub fn unmaximize_request(&mut self, window: &CosmicSurface) -> Option<Size<i32, Logical>> {
if self
.fullscreen
.values()
.any(|f| &f.window.surface() == window)
{
self.unfullscreen_request(window)
} else {
None
if let Some(elem) = self.element_for_surface(window).cloned() {
let mut state = elem.maximized_state.lock().unwrap();
if let Some(state) = state.take() {
match state.original_layer {
ManagedLayer::Tiling => {
// should still be mapped in tiling
self.floating_layer.unmap(&elem);
elem.output_enter(&self.output, elem.bbox());
elem.set_maximized(false);
elem.set_geometry(state.original_geometry.to_global(&self.output));
elem.configure();
self.tiling_layer.recalculate();
return self
.tiling_layer
.element_geometry(&elem)
.map(|geo| geo.size.as_logical());
}
ManagedLayer::Floating => {
elem.set_maximized(false);
self.floating_layer.map_internal(
elem.clone(),
Some(state.original_geometry.loc),
Some(state.original_geometry.size.as_logical()),
);
return Some(state.original_geometry.size.as_logical());
}
}
}
}
None
}
pub fn fullscreen_request(
&mut self,
window: &CosmicSurface,
output: &Output,
evlh: LoopHandle<'static, crate::state::State>,
previously: Option<(ManagedLayer, WorkspaceHandle)>,
) {
if self
.fullscreen
.get(output)
.map(|f| &f.window.surface() != window)
.unwrap_or(false)
.as_ref()
.filter(|f| f.ended_at.is_none())
.is_some()
{
return;
}
if !window.is_maximized(true) {
self.floating_layer.maximize_request(window);
}
window.set_maximized(false);
window.set_fullscreen(true);
self.set_fullscreen(window, output, true, evlh)
}
fn set_fullscreen<'a>(
&mut self,
window: &'a CosmicSurface,
output: &Output,
exclusive: bool,
evlh: LoopHandle<'static, crate::state::State>,
) {
if let Some(mapped) = self
.mapped()
.find(|m| m.windows().any(|(w, _)| &w == window))
{
mapped.set_active(window);
}
let window = CosmicWindow::new(window.clone(), evlh);
let geo = if exclusive {
output.geometry()
} else {
layer_map_for_output(output).non_exclusive_zone()
};
let original_size = window.geometry().size;
let geo = self.output.geometry();
let original_geometry = window.geometry().as_global();
let signal = if let Some(surface) = window.wl_surface() {
let signal = Arc::new(AtomicBool::new(false));
add_blocker(
@ -513,35 +504,35 @@ impl Workspace {
None
};
window.set_geometry(geo);
window.output_enter(output, Rectangle::from_loc_and_size((0, 0), geo.size));
window.surface().send_configure();
window.send_configure();
self.fullscreen.insert(
output.clone(),
FullscreenSurface {
window: window.clone(),
exclusive,
original_size,
start_at: Some(Instant::now()),
ended_at: None,
animation_signal: signal,
},
);
self.fullscreen = Some(FullscreenSurface {
surface: window.clone(),
previously,
original_geometry,
start_at: Some(Instant::now()),
ended_at: None,
animation_signal: signal,
});
}
pub fn unfullscreen_request(&mut self, window: &CosmicSurface) -> Option<Size<i32, Logical>> {
if let Some((output, f)) = self
#[must_use]
pub fn unfullscreen_request(
&mut self,
window: &CosmicSurface,
) -> Option<(ManagedLayer, WorkspaceHandle)> {
if let Some(f) = self
.fullscreen
.iter_mut()
.find(|(_, f)| &f.window.surface() == window)
.as_mut()
.filter(|f| &f.surface == window && f.ended_at.is_none())
{
f.window.output_leave(output);
window.set_maximized(false);
window.set_fullscreen(false);
let result = self.floating_layer.unmaximize_request(window);
window.set_geometry(f.original_geometry);
self.floating_layer.refresh();
self.tiling_layer.recalculate(output);
self.tiling_layer.recalculate();
self.tiling_layer.refresh();
let signal = if let Some(surface) = window.wl_surface() {
let signal = Arc::new(AtomicBool::new(false));
add_blocker(
@ -574,90 +565,36 @@ impl Workspace {
}
}
result
f.previously
} else {
None
}
}
pub fn remove_fullscreen(&mut self, output: &Output) {
if let Some(FullscreenSurface {
window,
ended_at,
start_at,
animation_signal,
..
}) = self.fullscreen.get_mut(output)
{
window.output_leave(output);
let surface = window.surface();
surface.set_maximized(false);
surface.set_fullscreen(false);
self.floating_layer.unmaximize_request(&surface);
self.floating_layer.refresh();
self.tiling_layer.recalculate(output);
self.tiling_layer.refresh();
let signal = if let Some(surface) = surface.wl_surface() {
let signal = Arc::new(AtomicBool::new(false));
add_blocker(
&surface,
FullscreenBlocker {
signal: signal.clone(),
},
);
Some(signal)
} else {
None
};
surface.send_configure();
*ended_at = Some(
Instant::now()
- (FULLSCREEN_ANIMATION_DURATION
- start_at
.take()
.map(|earlier| {
Instant::now()
.duration_since(earlier)
.min(FULLSCREEN_ANIMATION_DURATION)
})
.unwrap_or(FULLSCREEN_ANIMATION_DURATION)),
);
if let Some(new_signal) = signal {
if let Some(old_signal) = animation_signal.replace(new_signal) {
old_signal.store(true, Ordering::SeqCst);
}
}
#[must_use]
pub fn remove_fullscreen(&mut self) -> Option<(CosmicMapped, ManagedLayer, WorkspaceHandle)> {
if let Some(surface) = self.fullscreen.as_ref().map(|f| f.surface.clone()) {
self.unfullscreen_request(&surface)
.map(|(l, h)| (self.element_for_surface(&surface).unwrap().clone(), l, h))
} else {
None
}
}
pub fn maximize_toggle(
&mut self,
window: &CosmicSurface,
output: &Output,
evlh: LoopHandle<'static, crate::state::State>,
) {
if self.fullscreen.contains_key(output) {
pub fn maximize_toggle(&mut self, window: &CosmicSurface) {
if window.is_maximized(true) {
self.unmaximize_request(window);
} else {
self.maximize_request(window, output, evlh);
self.maximize_request(window);
}
}
pub fn get_fullscreen(&self, output: &Output) -> Option<&CosmicWindow> {
pub fn get_fullscreen(&self) -> Option<&CosmicSurface> {
self.fullscreen
.get(output)
.filter(|f| f.alive() && f.exclusive)
.as_ref()
.filter(|f| f.alive())
.filter(|f| f.ended_at.is_none() && f.start_at.is_none())
.map(|f| &f.window)
}
pub fn get_maximized(&self, output: &Output) -> Option<&CosmicWindow> {
self.fullscreen
.get(output)
.filter(|f| f.alive() && !f.exclusive)
.filter(|f| f.ended_at.is_none() && f.start_at.is_none())
.map(|f| &f.window)
.map(|f| &f.surface)
}
pub fn resize_request(
@ -690,8 +627,8 @@ impl Workspace {
if let Some(toplevel) = focused.toplevel() {
if self
.fullscreen
.values()
.any(|f| f.window.surface().wl_surface().as_ref() == Some(&toplevel))
.as_ref()
.is_some_and(|f| f.surface.wl_surface().as_ref() == Some(&toplevel))
{
return false;
}
@ -713,15 +650,27 @@ impl Workspace {
indicator_thickness: u8,
) -> Option<MoveGrab> {
let pointer = seat.get_pointer().unwrap();
let pos = pointer.current_location();
let pos = pointer.current_location().as_global();
if self
.fullscreen
.as_ref()
.is_some_and(|f| &f.surface == window)
{
let _ = self.remove_fullscreen(); // We are moving this window, we don't need to send it back to it's original workspace
}
let mapped = self.element_for_surface(&window)?.clone();
let mut initial_window_location = self.element_geometry(&mapped).unwrap().loc;
let mut initial_window_location = self
.element_geometry(&mapped)
.unwrap()
.loc
.to_global(&self.output);
if mapped.is_fullscreen(true) || mapped.is_maximized(true) {
if mapped.maximized_state.lock().unwrap().is_some() {
// If surface is maximized then unmaximize it
let new_size = self.unmaximize_request(window);
let ratio = pos.x / output.geometry().size.w as f64;
let ratio = pos.to_local(&self.output).x / output.geometry().size.w as f64;
initial_window_location = new_size
.map(|size| (pos.x - (size.w as f64 * ratio), pos.y).into())
@ -754,7 +703,7 @@ impl Workspace {
.into_iter()
{
self.tiling_layer.unmap(&window);
self.floating_layer.map(window, seat, None);
self.floating_layer.map(window, None);
}
self.tiling_enabled = false;
} else {
@ -768,7 +717,7 @@ impl Workspace {
{
self.floating_layer.unmap(&window);
self.tiling_layer
.map(window, seat, focus_stack.iter(), None)
.map(window, Some(focus_stack.iter()), None)
}
self.tiling_enabled = true;
}
@ -779,12 +728,12 @@ impl Workspace {
if let Some(window) = self.focus_stack.get(seat).iter().next().cloned() {
if self.tiling_layer.mapped().any(|(_, m, _)| m == &window) {
self.tiling_layer.unmap(&window);
self.floating_layer.map(window, seat, None);
self.floating_layer.map(window, None);
} else if self.floating_layer.mapped().any(|w| w == &window) {
let focus_stack = self.focus_stack.get(seat);
self.floating_layer.unmap(&window);
self.tiling_layer
.map(window, seat, focus_stack.iter(), None)
.map(window, Some(focus_stack.iter()), None)
}
}
}
@ -808,37 +757,23 @@ impl Workspace {
pub fn is_fullscreen(&self, mapped: &CosmicMapped) -> bool {
self.fullscreen
.values()
.any(|f| f.exclusive && f.window.surface() == mapped.active_window())
}
pub fn is_maximized(&self, mapped: &CosmicMapped) -> bool {
self.fullscreen
.values()
.any(|f| !f.exclusive && f.window.surface() == mapped.active_window())
.as_ref()
.is_some_and(|f| f.surface == mapped.active_window())
}
pub fn is_floating(&self, mapped: &CosmicMapped) -> bool {
!self
.fullscreen
.values()
.any(|f| f.window.surface() == mapped.active_window())
&& self.floating_layer.mapped().any(|m| m == mapped)
!self.is_fullscreen(mapped) && self.floating_layer.mapped().any(|m| m == mapped)
}
pub fn is_tiled(&self, mapped: &CosmicMapped) -> bool {
!self
.fullscreen
.values()
.any(|f| f.window.surface() == mapped.active_window())
&& self.tiling_layer.mapped().any(|(_, m, _)| m == mapped)
!self.is_fullscreen(mapped) && self.tiling_layer.mapped().any(|(_, m, _)| m == mapped)
}
pub fn node_desc(&self, focus: KeyboardFocusTarget) -> Option<NodeDesc> {
match focus {
KeyboardFocusTarget::Element(mapped) => {
self.tiling_layer.mapped().find_map(|(output, m, _)| {
(m == &mapped).then_some(output.clone()).and_then(|output| {
self.tiling_layer.mapped().find_map(|(_, m, _)| {
if m == &mapped {
mapped
.tiling_node_id
.lock()
@ -846,7 +781,6 @@ impl Workspace {
.clone()
.map(|node_id| NodeDesc {
handle: self.handle.clone(),
output: output.downgrade(),
node: node_id,
stack_window: if mapped
.stack_ref()
@ -858,12 +792,13 @@ impl Workspace {
None
},
})
})
} else {
None
}
})
}
KeyboardFocusTarget::Group(WindowGroup { output, node, .. }) => Some(NodeDesc {
KeyboardFocusTarget::Group(WindowGroup { node, .. }) => Some(NodeDesc {
handle: self.handle.clone(),
output,
node,
stack_window: None,
}),
@ -877,7 +812,7 @@ impl Workspace {
seat: &Seat<State>,
swap_desc: Option<NodeDesc>,
) -> FocusResult {
if self.fullscreen.contains_key(&seat.active_output()) {
if self.fullscreen.is_some() {
return FocusResult::None;
}
@ -895,25 +830,25 @@ impl Workspace {
direction: Direction,
seat: &Seat<State>,
) -> MoveResult {
if let Some(f) = self.fullscreen.get(&seat.active_output()) {
MoveResult::MoveFurther(KeyboardFocusTarget::Fullscreen(f.window.clone()))
if let Some(f) = self.fullscreen.as_ref() {
MoveResult::MoveFurther(KeyboardFocusTarget::Fullscreen(f.surface.clone()))
} else {
self.floating_layer
.move_current_element(direction, seat)
.move_current_element(direction, seat, self.tiling_layer.theme.clone())
.or_else(|| self.tiling_layer.move_current_node(direction, seat))
}
}
pub fn render_output<'a, R>(
pub fn render<'a, R>(
&self,
renderer: &mut R,
output: &Output,
override_redirect_windows: &[X11Surface],
xwm_state: Option<&'a mut XWaylandState>,
draw_focus_indicator: Option<&Seat<State>>,
overview: (OverviewMode, Option<(SwapIndicator, Option<&Tree<Data>>)>),
resize_indicator: Option<(ResizeMode, ResizeIndicator)>,
indicator_thickness: u8,
theme: &CosmicTheme,
) -> Result<
(
Vec<WorkspaceRenderElement<R>>,
@ -935,20 +870,28 @@ impl Workspace {
let mut window_elements = Vec::new();
let mut popup_elements = Vec::new();
let output_scale = output.current_scale().fractional_scale();
let layer_map = layer_map_for_output(output);
let zone = layer_map.non_exclusive_zone();
let output_scale = self.output.current_scale().fractional_scale();
let zone = {
let layer_map = layer_map_for_output(&self.output);
layer_map.non_exclusive_zone().as_local()
};
// OR windows above all
popup_elements.extend(
override_redirect_windows
.iter()
.filter(|or| (*or).geometry().intersection(output.geometry()).is_some())
.filter(|or| {
(*or)
.geometry()
.as_global()
.intersection(self.output.geometry())
.is_some()
})
.flat_map(|or| {
AsRenderElements::<R>::render_elements::<WorkspaceRenderElement<R>>(
or,
renderer,
(or.geometry().loc - output.geometry().loc)
(or.geometry().loc - self.output.geometry().loc.as_logical())
.to_physical_precise_round(output_scale),
Scale::from(output_scale),
1.0,
@ -956,32 +899,27 @@ impl Workspace {
}),
);
if let Some(fullscreen) = self.fullscreen.get(output) {
if let Some(fullscreen) = self.fullscreen.as_ref() {
// fullscreen window
let bbox = fullscreen.window.bbox();
let bbox = fullscreen.surface.bbox().as_local();
let element_geo = Rectangle::from_loc_and_size(
self.element_for_surface(&fullscreen.window.surface())
self.element_for_surface(&fullscreen.surface)
.and_then(|elem| {
self.floating_layer
.space
.element_geometry(elem)
.or_else(|| self.tiling_layer.element_geometry(elem))
.map(|mut geo| {
geo.loc -= elem.geometry().loc;
geo.loc -= elem.geometry().loc.as_local();
geo
})
})
.unwrap_or(bbox)
.loc,
fullscreen.original_size,
fullscreen.original_geometry.size.as_local(),
);
let mut full_geo = if fullscreen.exclusive {
Rectangle::from_loc_and_size((0, 0), output.geometry().size)
} else {
layer_map.non_exclusive_zone()
};
let mut full_geo =
Rectangle::from_loc_and_size((0, 0), self.output.geometry().size.as_local());
if fullscreen.start_at.is_none() {
if bbox != full_geo {
if bbox.size.w < full_geo.size.w {
@ -1027,14 +965,17 @@ impl Workspace {
(None, None) => (full_geo, 1.0),
};
let render_loc = target_geo.loc.to_physical_precise_round(output_scale);
let render_loc = target_geo
.loc
.as_logical()
.to_physical_precise_round(output_scale);
let scale = Scale {
x: target_geo.size.w as f64 / bbox.size.w as f64,
y: target_geo.size.h as f64 / bbox.size.h as f64,
};
let (w_elements, p_elements) = fullscreen
.window
.surface
.split_render_elements::<R, CosmicWindowRenderElement<R>>(
renderer,
render_loc,
@ -1052,12 +993,12 @@ impl Workspace {
if self
.fullscreen
.get(output)
.as_ref()
.map(|f| f.start_at.is_some() || f.ended_at.is_some())
.unwrap_or(true)
{
let focused = draw_focus_indicator
.filter(|_| !self.fullscreen.contains_key(output))
.filter(|_| !self.fullscreen.is_some())
.and_then(|seat| self.focus_stack.get(seat).last().cloned());
// floating surfaces
@ -1078,13 +1019,13 @@ impl Workspace {
OverviewMode::None => 1.0,
};
let (w_elements, p_elements) = self.floating_layer.render_output::<R>(
let (w_elements, p_elements) = self.floating_layer.render::<R>(
renderer,
output,
focused.as_ref(),
resize_indicator.clone(),
indicator_thickness,
alpha,
theme,
);
popup_elements.extend(p_elements.into_iter().map(WorkspaceRenderElement::from));
window_elements.extend(w_elements.into_iter().map(WorkspaceRenderElement::from));
@ -1102,14 +1043,14 @@ impl Workspace {
};
//tiling surfaces
let (w_elements, p_elements) = self.tiling_layer.render_output::<R>(
let (w_elements, p_elements) = self.tiling_layer.render::<R>(
renderer,
output,
draw_focus_indicator,
layer_map.non_exclusive_zone(),
zone,
overview,
resize_indicator,
indicator_thickness,
theme,
)?;
popup_elements.extend(p_elements.into_iter().map(WorkspaceRenderElement::from));
window_elements.extend(w_elements.into_iter().map(WorkspaceRenderElement::from));
@ -1131,7 +1072,7 @@ impl Workspace {
if let Some(xwm) = xwm_state.and_then(|state| state.xwm.as_mut()) {
if let Err(err) =
xwm.update_stacking_order_upwards(window_elements.iter().map(|e| e.id()))
xwm.update_stacking_order_upwards(window_elements.iter().rev().map(|e| e.id()))
{
warn!(
wm_id = ?xwm.id(),

View file

@ -137,6 +137,8 @@ pub struct Common {
pub clock: Clock<Monotonic>,
pub should_stop: bool,
pub theme: cosmic::Theme,
#[cfg(feature = "debug")]
pub egui: Egui,
@ -283,7 +285,7 @@ impl State {
.with_context(|| "Failed to load languages")
.unwrap();
let clock = Clock::new().expect("Failed to initialize clock");
let clock = Clock::new();
let config = Config::load(&handle);
let compositor_state = CompositorState::new::<Self>(dh);
let data_device_state = DataDeviceState::new::<Self>(dh);
@ -330,6 +332,8 @@ impl State {
clock,
should_stop: false,
theme: cosmic::theme::system_preference(),
#[cfg(feature = "debug")]
egui: Egui {
active: false,
@ -511,50 +515,34 @@ impl Common {
let active = self.shell.active_space(output);
active.mapped().for_each(|mapped| {
let outputs_for_element: Vec<_> = active.outputs_for_element(mapped).collect();
if outputs_for_element.contains(&output) {
let window = mapped.active_window();
window.with_surfaces(|surface, states| {
let primary_scanout_output = update_surface_primary_scanout_output(
surface,
output,
states,
render_element_states,
|current_output, current_state, next_output, next_state| {
if outputs_for_element.contains(current_output) {
default_primary_scanout_output_compare(
current_output,
current_state,
next_output,
next_state,
)
} else {
next_output
}
},
);
if let Some(output) = primary_scanout_output {
with_fractional_scale(states, |fraction_scale| {
fraction_scale
.set_preferred_scale(output.current_scale().fractional_scale());
});
}
});
window.send_frame(output, time, throttle, surface_primary_scanout_output);
if let Some(feedback) = window
.wl_surface()
.and_then(|wl_surface| {
source_node_for_surface(&wl_surface, &self.display_handle)
})
.and_then(|source| dmabuf_feedback(source))
{
window.send_dmabuf_feedback(
output,
&feedback,
render_element_states,
surface_primary_scanout_output,
);
let window = mapped.active_window();
window.with_surfaces(|surface, states| {
let primary_scanout_output = update_surface_primary_scanout_output(
surface,
output,
states,
render_element_states,
|_current_output, _current_state, next_output, _next_state| next_output,
);
if let Some(output) = primary_scanout_output {
with_fractional_scale(states, |fraction_scale| {
fraction_scale
.set_preferred_scale(output.current_scale().fractional_scale());
});
}
});
window.send_frame(output, time, throttle, surface_primary_scanout_output);
if let Some(feedback) = window
.wl_surface()
.and_then(|wl_surface| source_node_for_surface(&wl_surface, &self.display_handle))
.and_then(|source| dmabuf_feedback(source))
{
window.send_dmabuf_feedback(
output,
&feedback,
render_element_states,
surface_primary_scanout_output,
);
}
});
@ -565,10 +553,8 @@ impl Common {
.filter(|w| w.handle != active.handle)
{
space.mapped().for_each(|mapped| {
if space.outputs_for_element(mapped).any(|o| &o == output) {
let window = mapped.active_window();
window.send_frame(output, time, throttle, |_, _| None);
}
let window = mapped.active_window();
window.send_frame(space.output(), time, throttle, |_, _| None);
});
}

67
src/theme.rs Normal file
View file

@ -0,0 +1,67 @@
// insert into the event loop, a watcher for the theme & theme mode for changes
// update a Arc<Mutex<Theme>> in the state on change of the theme and mark all interfaces for a redraw.
use calloop::LoopHandle;
use cosmic::cosmic_theme::{
palette::{self, Srgba},
Theme, ThemeMode,
};
use crate::state::State;
pub(crate) fn group_color(theme: &Theme<Srgba>) -> [f32; 3] {
let neutral_8 = theme.palette.neutral_8;
[neutral_8.red, neutral_8.green, neutral_8.blue]
}
pub(crate) fn active_window_hint(theme: &Theme<Srgba>) -> palette::Srgba {
if let Some(hint) = theme.window_hint {
palette::Srgba::from(hint)
} else {
theme.accent_color()
}
}
pub fn watch_theme(handle: LoopHandle<'_, State>) -> Result<(), cosmic_config::Error> {
let (ping_tx, ping_rx) = calloop::ping::make_ping().unwrap();
let config_mode_helper = ThemeMode::config()?;
let config_dark_helper = Theme::<palette::Srgba>::dark_config()?;
let config_light_helper = Theme::<palette::Srgba>::light_config()?;
if let Err(e) = handle.insert_source(ping_rx, move |_, _, state| {
let new_theme = cosmic::theme::system_preference();
let theme = &mut state.common.theme;
if theme.theme_type != new_theme.theme_type {
*theme = new_theme;
state.common.shell.set_theme(theme.clone());
state.common.shell.workspaces.spaces().for_each(|s| {
s.mapped().for_each(|m| {
m.update_theme(theme.clone());
m.force_redraw();
})
});
}
}) {
tracing::error!("{e}");
};
let ping_tx_clone = ping_tx.clone();
let theme_watcher_mode = config_mode_helper.watch(move |_, _keys| {
ping_tx_clone.ping();
})?;
let ping_tx_clone = ping_tx.clone();
let theme_watcher_light = config_light_helper.watch(move |_, _keys| {
ping_tx_clone.ping();
})?;
let theme_watcher_dark = config_dark_helper.watch(move |_, _keys| {
ping_tx.ping();
})?;
std::mem::forget(theme_watcher_dark);
std::mem::forget(theme_watcher_light);
std::mem::forget(theme_watcher_mode);
Ok(())
}

149
src/utils/geometry.rs Normal file
View file

@ -0,0 +1,149 @@
use smithay::{
output::Output,
utils::{Coordinate, Logical, Point, Rectangle, Size},
};
use super::prelude::OutputExt;
/// Marker type for coordinates in global space
#[derive(Debug)]
pub struct Global;
/// Marker type for coordinates in workspace local space
#[derive(Debug)]
pub struct Local;
pub trait PointExt<C: Coordinate> {
fn as_global(self) -> Point<C, Global>;
fn as_local(self) -> Point<C, Local>;
}
pub trait PointGlobalExt<C: Coordinate> {
fn to_local(self, output: &Output) -> Point<C, Local>;
fn as_logical(self) -> Point<C, Logical>;
}
pub trait PointLocalExt<C: Coordinate> {
fn to_global(self, output: &Output) -> Point<C, Global>;
fn as_logical(self) -> Point<C, Logical>;
}
pub trait SizeExt<C: Coordinate> {
fn as_logical(self) -> Size<C, Logical>;
fn as_local(self) -> Size<C, Local>;
fn as_global(self) -> Size<C, Global>;
}
pub trait RectExt<C: Coordinate> {
fn as_global(self) -> Rectangle<C, Global>;
fn as_local(self) -> Rectangle<C, Local>;
}
pub trait RectGlobalExt<C: Coordinate> {
fn to_local(self, output: &Output) -> Rectangle<C, Local>;
fn as_logical(self) -> Rectangle<C, Logical>;
}
pub trait RectLocalExt<C: Coordinate> {
fn to_global(self, output: &Output) -> Rectangle<C, Global>;
fn as_logical(self) -> Rectangle<C, Logical>;
}
impl<C: Coordinate> PointExt<C> for Point<C, Logical> {
fn as_global(self) -> Point<C, Global> {
(self.x, self.y).into()
}
fn as_local(self) -> Point<C, Local> {
(self.x, self.y).into()
}
}
impl<C: Coordinate> PointGlobalExt<C> for Point<C, Global> {
fn to_local(self, output: &Output) -> Point<C, Local> {
let point = (self.to_f64() - output.geometry().loc.to_f64()).as_logical();
(C::from_f64(point.x), C::from_f64(point.y)).into()
}
fn as_logical(self) -> Point<C, Logical> {
(self.x, self.y).into()
}
}
impl<C: Coordinate> PointLocalExt<C> for Point<C, Local> {
fn to_global(self, output: &Output) -> Point<C, Global> {
let point =
(self.to_f64().as_logical() + output.geometry().loc.to_f64().as_logical()).as_global();
(C::from_f64(point.x), C::from_f64(point.y)).into()
}
fn as_logical(self) -> Point<C, Logical> {
(self.x, self.y).into()
}
}
impl<C: Coordinate> SizeExt<C> for Size<C, Global> {
fn as_logical(self) -> Size<C, Logical> {
(self.w, self.h).into()
}
fn as_global(self) -> Size<C, Global> {
self
}
fn as_local(self) -> Size<C, Local> {
(self.w, self.h).into()
}
}
impl<C: Coordinate> SizeExt<C> for Size<C, Local> {
fn as_logical(self) -> Size<C, Logical> {
(self.w, self.h).into()
}
fn as_global(self) -> Size<C, Global> {
(self.w, self.h).into()
}
fn as_local(self) -> Size<C, Local> {
self
}
}
impl<C: Coordinate> SizeExt<C> for Size<C, Logical> {
fn as_logical(self) -> Size<C, Logical> {
self
}
fn as_global(self) -> Size<C, Global> {
(self.w, self.h).into()
}
fn as_local(self) -> Size<C, Local> {
(self.w, self.h).into()
}
}
impl<C: Coordinate> RectExt<C> for Rectangle<C, Logical> {
fn as_global(self) -> Rectangle<C, Global> {
Rectangle::from_loc_and_size(self.loc.as_global(), (self.size.w, self.size.h))
}
fn as_local(self) -> Rectangle<C, Local> {
Rectangle::from_loc_and_size(self.loc.as_local(), (self.size.w, self.size.h))
}
}
impl<C: Coordinate> RectGlobalExt<C> for Rectangle<C, Global> {
fn to_local(self, output: &Output) -> Rectangle<C, Local> {
Rectangle::from_loc_and_size(self.loc.to_local(output), (self.size.w, self.size.h))
}
fn as_logical(self) -> Rectangle<C, Logical> {
Rectangle::from_loc_and_size(self.loc.as_logical(), self.size.as_logical())
}
}
impl<C: Coordinate> RectLocalExt<C> for Rectangle<C, Local> {
fn to_global(self, output: &Output) -> Rectangle<C, Global> {
Rectangle::from_loc_and_size(self.loc.to_global(output), (self.size.w, self.size.h))
}
fn as_logical(self) -> Rectangle<C, Logical> {
Rectangle::from_loc_and_size(self.loc.as_logical(), self.size.as_logical())
}
}

View file

@ -229,6 +229,7 @@ impl<P: Program + Send + 'static> IcedElement<P> {
program: P,
size: impl Into<Size<i32, Logical>>,
handle: LoopHandle<'static, crate::state::State>,
theme: cosmic::Theme,
) -> IcedElement<P> {
let size = size.into();
let mut renderer =
@ -257,7 +258,7 @@ impl<P: Program + Send + 'static> IcedElement<P> {
pending_update: None,
size,
cursor_pos: None,
theme: Theme::dark(), // TODO
theme,
renderer,
state,
debug,
@ -308,6 +309,11 @@ impl<P: Program + Send + 'static> IcedElement<P> {
self.0.lock().unwrap().update(true);
}
pub fn set_theme(&self, theme: cosmic::Theme) {
let mut guard = self.0.lock().unwrap();
guard.theme = theme.clone();
}
pub fn force_redraw(&self) {
let mut internal = self.0.lock().unwrap();
for (_buffer, ref mut old_primitives) in internal.buffers.values_mut() {

View file

@ -2,6 +2,7 @@
mod ids;
pub(crate) use self::ids::id_gen;
pub mod geometry;
pub mod iced;
pub mod prelude;
pub mod tween;

View file

@ -11,20 +11,21 @@ use smithay::{
Seat,
},
output::Output,
utils::{Buffer, IsAlive, Logical, Monotonic, Point, Rectangle, Time, Transform},
utils::{Buffer, IsAlive, Monotonic, Point, Rectangle, Time, Transform},
wayland::compositor::with_states,
};
pub use super::geometry::*;
pub use crate::shell::{Shell, Workspace};
pub use crate::state::{Common, State};
pub use crate::wayland::handlers::xdg_shell::popup::update_reactive_popups;
pub trait OutputExt {
fn geometry(&self) -> Rectangle<i32, Logical>;
fn geometry(&self) -> Rectangle<i32, Global>;
}
impl OutputExt for Output {
fn geometry(&self) -> Rectangle<i32, Logical> {
fn geometry(&self) -> Rectangle<i32, Global> {
Rectangle::from_loc_and_size(self.current_location(), {
Transform::from(self.current_transform())
.transform_size(
@ -36,6 +37,7 @@ impl OutputExt for Output {
.to_logical(self.current_scale().fractional_scale())
.to_i32_round()
})
.as_global()
}
}

View file

@ -171,12 +171,12 @@ impl CompositorHandler for State {
on_commit_buffer_handler::<Self>(surface);
// then handle initial configure events and map windows if necessary
if let Some((window, seat)) = self
if let Some((window, _, _)) = self
.common
.shell
.pending_windows
.iter()
.find(|(window, _)| window.wl_surface().as_ref() == Some(surface))
.find(|(window, _, _)| window.wl_surface().as_ref() == Some(surface))
.cloned()
{
match window {
@ -185,9 +185,8 @@ impl CompositorHandler for State {
if self.toplevel_ensure_initial_configure(&toplevel)
&& with_renderer_surface_state(&surface, |state| state.buffer().is_some())
{
let output = seat.active_output();
window.on_commit();
Shell::map_window(self, &window, &output);
Shell::map_window(self, &window);
} else {
return;
}
@ -254,7 +253,7 @@ impl CompositorHandler for State {
let changed = layer_map_for_output(&output).arrange();
if changed {
for workspace in self.common.shell.workspaces.spaces_mut() {
workspace.recalculate(&output);
workspace.recalculate();
}
}
}

View file

@ -41,8 +41,7 @@ impl WlrLayerShellHandler for State {
}
fn new_popup(&mut self, _parent: WlrLayerSurface, popup: PopupSurface) {
let positioner = popup.with_pending_state(|state| state.positioner);
self.common.shell.unconstrain_popup(&popup, &positioner);
self.common.shell.unconstrain_popup(&popup);
if popup.send_configure().is_ok() {
self.common
@ -76,7 +75,7 @@ impl WlrLayerShellHandler for State {
}
for workspace in self.common.shell.workspaces.spaces_mut() {
workspace.recalculate(&output);
workspace.recalculate();
}
// collect screencopy sessions needing an update

View file

@ -47,7 +47,7 @@ use crate::{
},
shell::{CosmicMappedRenderElement, CosmicSurface, WorkspaceRenderElement},
state::{BackendData, ClientState, Common, State},
utils::prelude::OutputExt,
utils::prelude::{OutputExt, PointExt},
wayland::protocols::{
screencopy::{
delegate_screencopy, BufferInfo, BufferParams, CursorMode as ScreencopyCursorMode,
@ -106,7 +106,7 @@ impl ScreencopyHandler for State {
if let Some(pointer) = seat.get_pointer() {
if output
.geometry()
.contains(pointer.current_location().to_i32_round())
.contains(pointer.current_location().to_i32_round().as_global())
{
session.cursor_enter(seat, InputType::Pointer);
}
@ -1015,7 +1015,12 @@ pub fn render_window_to_buffer(
renderer.bind(render_buffer).map_err(DTError::Rendering)?;
}
dt.render_output(renderer, age, &elements, CLEAR_COLOR)
dt.render_output(
renderer,
age,
&elements,
CLEAR_COLOR, // TODO use a theme neutral color
)
}
let node = node_from_params(&params, &mut state.backend, None);
@ -1296,7 +1301,7 @@ pub fn schedule_offscreen_workspace_session(
if !session.alive() {
return;
}
if !state.common.shell.outputs.contains(&output) {
if !state.common.shell.outputs().any(|o| o == &output) {
return;
}
match render_workspace_to_buffer(

View file

@ -1,7 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::{
shell::WorkspaceMode,
state::ClientState,
utils::prelude::*,
wayland::protocols::workspace::{
@ -30,19 +29,12 @@ impl WorkspaceHandler for State {
for request in requests.into_iter() {
match request {
Request::Activate(handle) => {
let maybe = match &self.common.shell.workspaces {
WorkspaceMode::Global(set) => set
.workspaces
let maybe = self.common.shell.workspaces.iter().find_map(|(o, set)| {
set.workspaces
.iter()
.position(|w| w.handle == handle)
.map(|i| (self.common.last_active_seat().active_output(), i)),
WorkspaceMode::OutputBound(sets, _) => sets.iter().find_map(|(o, set)| {
set.workspaces
.iter()
.position(|w| w.handle == handle)
.map(|i| (o.clone(), i))
}),
};
.map(|i| (o.clone(), i))
});
if let Some((output, idx)) = maybe {
let _ = self.common.shell.activate(&output, idx); // TODO: move cursor?

View file

@ -1,6 +1,10 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::{shell::CosmicSurface, utils::prelude::*, wayland::protocols::screencopy::SessionType};
use crate::{
shell::{element::CosmicWindow, CosmicMapped, CosmicSurface, ManagedLayer},
utils::prelude::*,
wayland::protocols::screencopy::SessionType,
};
use smithay::{
delegate_xdg_shell,
desktop::{
@ -38,7 +42,7 @@ impl XdgShellHandler for State {
fn new_toplevel(&mut self, surface: ToplevelSurface) {
let seat = self.common.last_active_seat().clone();
let window = CosmicSurface::Wayland(Window::new(surface));
self.common.shell.pending_windows.push((window, seat));
self.common.shell.pending_windows.push((window, seat, None));
// We will position the window after the first commit, when we know its size hints
}
@ -50,7 +54,7 @@ impl XdgShellHandler for State {
if surface.get_parent_surface().is_some() {
// let other shells deal with their popups
self.common.shell.unconstrain_popup(&surface, &positioner);
self.common.shell.unconstrain_popup(&surface);
if surface.send_configure().is_ok() {
self.common
@ -123,7 +127,7 @@ impl XdgShellHandler for State {
state.positioner = positioner;
});
self.common.shell.unconstrain_popup(&surface, &positioner);
self.common.shell.unconstrain_popup(&surface);
surface.send_repositioned(token);
if let Err(err) = surface.send_configure() {
warn!(
@ -150,9 +154,6 @@ impl XdgShellHandler for State {
}
fn maximize_request(&mut self, surface: ToplevelSurface) {
let seat = self.common.last_active_seat();
let output = seat.active_output();
if let Some(mapped) = self
.common
.shell
@ -164,7 +165,7 @@ impl XdgShellHandler for State {
.windows()
.find(|(w, _)| w.wl_surface().as_ref() == Some(surface.wl_surface()))
.unwrap();
workspace.maximize_request(&window, &output, self.common.event_loop_handle.clone())
workspace.maximize_request(&window)
}
}
}
@ -187,13 +188,14 @@ impl XdgShellHandler for State {
}
fn fullscreen_request(&mut self, surface: ToplevelSurface, output: Option<WlOutput>) {
let active_output = {
let seat = self.common.last_active_seat();
seat.active_output()
};
let output = output
.as_ref()
.and_then(Output::from_resource)
.unwrap_or_else(|| {
let seat = self.common.last_active_seat();
seat.active_output()
});
.unwrap_or_else(|| active_output.clone());
if let Some(mapped) = self
.common
@ -202,15 +204,70 @@ impl XdgShellHandler for State {
.cloned()
{
if let Some(workspace) = self.common.shell.space_for_mut(&mapped) {
let (window, _) = mapped
.windows()
.find(|(w, _)| w.wl_surface().as_ref() == Some(surface.wl_surface()))
.unwrap();
workspace.fullscreen_request(
&window,
&output,
self.common.event_loop_handle.clone(),
)
if workspace.output != output {
let (mapped, layer) = if mapped
.stack_ref()
.map(|stack| stack.len() > 1)
.unwrap_or(false)
{
let stack = mapped.stack_ref().unwrap();
let surface = stack
.surfaces()
.find(|s| s.wl_surface().as_ref() == Some(surface.wl_surface()))
.unwrap();
stack.remove_window(&surface);
(
CosmicMapped::from(CosmicWindow::new(
surface,
self.common.event_loop_handle.clone(),
self.common.theme.clone(),
)),
if workspace.is_tiled(&mapped) {
ManagedLayer::Tiling
} else {
ManagedLayer::Floating
},
)
} else {
let layer = workspace.unmap(&mapped).unwrap().layer;
(mapped, layer)
};
let handle = workspace.handle.clone();
std::mem::drop(workspace);
let workspace_handle = self.common.shell.active_space(&output).handle.clone();
for (window, _) in mapped.windows() {
self.common
.shell
.toplevel_info_state
.toplevel_enter_output(&window, &output);
self.common
.shell
.toplevel_info_state
.toplevel_enter_workspace(&window, &workspace_handle);
}
let workspace = self.common.shell.active_space_mut(&output);
workspace.floating_layer.map(mapped.clone(), None);
workspace.fullscreen_request(&mapped.active_window(), Some((layer, handle)));
} else {
let (window, _) = mapped
.windows()
.find(|(w, _)| w.wl_surface().as_ref() == Some(surface.wl_surface()))
.unwrap();
workspace.fullscreen_request(&window, None)
}
}
} else {
if let Some(o) = self
.common
.shell
.pending_windows
.iter_mut()
.find(|(s, _, _)| s.wl_surface().as_ref() == Some(surface.wl_surface()))
.map(|(_, _, o)| o)
{
*o = Some(output);
}
}
}
@ -227,7 +284,23 @@ impl XdgShellHandler for State {
.windows()
.find(|(w, _)| w.wl_surface().as_ref() == Some(surface.wl_surface()))
.unwrap();
workspace.unfullscreen_request(&window);
if let Some((layer, previous_workspace)) = workspace.unfullscreen_request(&window) {
let old_handle = workspace.handle.clone();
let new_workspace_handle = self
.common
.shell
.space_for_handle(&previous_workspace)
.is_some()
.then_some(previous_workspace)
.unwrap_or(old_handle); // if the workspace doesn't exist anymore, we can still remap on the right layer
self.common.shell.remap_unfullscreened_window(
mapped,
&old_handle,
&new_workspace_handle,
layer,
);
}
}
}
}

View file

@ -27,27 +27,28 @@ use std::sync::Mutex;
use tracing::{trace, warn};
impl Shell {
pub fn unconstrain_popup(&self, surface: &PopupSurface, positioner: &PositionerState) {
pub fn unconstrain_popup(&self, surface: &PopupSurface) {
if let Some(parent) = get_popup_toplevel(&surface) {
if let Some(elem) = self.element_for_wl_surface(&parent) {
let workspace = self.space_for(elem).unwrap();
let mut element_geo = workspace.element_geometry(elem).unwrap();
let mut element_geo = workspace
.element_geometry(elem)
.unwrap()
.to_global(workspace.output());
let (window, offset) = elem
.windows()
.find(|(w, _)| w.wl_surface().as_ref() == Some(&parent))
.unwrap();
let window_geo_offset = window.geometry().loc;
let window_loc = element_geo.loc + offset + window_geo_offset;
let anchor_point = get_anchor_point(&positioner) + window_loc;
let window_loc: Point<i32, Global> =
element_geo.loc + offset.as_global() + window_geo_offset.as_global();
if workspace.is_tiled(elem) {
element_geo.loc = (0, 0).into(); //-= window_loc;
if !unconstrain_xdg_popup_tile(surface, element_geo) {
if let Some(output) = workspace.output_under(anchor_point) {
unconstrain_xdg_popup(surface, window_loc, output.geometry());
}
element_geo.loc = (0, 0).into();
if !unconstrain_xdg_popup_tile(surface, element_geo.as_logical()) {
unconstrain_xdg_popup(surface, window_loc, workspace.output().geometry());
}
} else if let Some(output) = workspace.output_under(anchor_point) {
unconstrain_xdg_popup(surface, window_loc, output.geometry());
} else {
unconstrain_xdg_popup(surface, window_loc, workspace.output().geometry());
}
} else if let Some((output, layer_surface)) = self.outputs().find_map(|o| {
let map = layer_map_for_output(o);
@ -62,7 +63,7 @@ impl Shell {
pub fn update_reactive_popups<'a>(
window: &Window,
loc: Point<i32, Logical>,
loc: Point<i32, Global>,
outputs: impl Iterator<Item = &'a Output>,
) {
let output_geo = outputs.map(|o| o.geometry()).collect::<Vec<_>>();
@ -79,7 +80,7 @@ pub fn update_reactive_popups<'a>(
attributes.current.positioner.clone()
});
if positioner.reactive {
let anchor_point = get_anchor_point(&positioner) + loc;
let anchor_point = loc + get_anchor_point(&positioner).as_global();
if let Some(rect) = output_geo
.iter()
.find(|geo| geo.contains(anchor_point))
@ -101,7 +102,9 @@ pub fn update_reactive_popups<'a>(
}
fn unconstrain_xdg_popup_tile(surface: &PopupSurface, rect: Rectangle<i32, Logical>) -> bool {
let geometry = surface.with_pending_state(|state| state.positioner.get_geometry());
let toplevel_offset = get_popup_toplevel_coords(surface);
let mut geometry = surface.with_pending_state(|state| state.positioner.get_geometry());
geometry.loc += toplevel_offset;
let offset = check_constrained(geometry, rect);
if offset.x != 0 || offset.y != 0 {
@ -116,12 +119,14 @@ fn unconstrain_xdg_popup_tile(surface: &PopupSurface, rect: Rectangle<i32, Logic
fn unconstrain_xdg_popup(
surface: &PopupSurface,
window_loc: Point<i32, Logical>,
rect: Rectangle<i32, Logical>,
window_loc: Point<i32, Global>,
mut rect: Rectangle<i32, Global>,
) {
let mut relative = rect;
relative.loc -= window_loc;
let geometry = surface.with_pending_state(|state| state.positioner.get_geometry());
rect.loc -= window_loc;
let relative = rect.as_logical();
let toplevel_offset = get_popup_toplevel_coords(surface);
let mut geometry = surface.with_pending_state(|state| state.positioner.get_geometry());
geometry.loc += toplevel_offset;
let offset = check_constrained(geometry, relative);
if offset.x != 0 || offset.y != 0 {
@ -139,9 +144,11 @@ fn unconstrain_layer_popup(surface: &PopupSurface, output: &Output, layer_surfac
let layer_geo = map.layer_geometry(layer_surface).unwrap();
// the output_rect represented relative to the parents coordinate system
let mut relative = Rectangle::from_loc_and_size((0, 0), output.geometry().size);
let mut relative = Rectangle::from_loc_and_size((0, 0), output.geometry().size).as_logical();
relative.loc -= layer_geo.loc;
let geometry = surface.with_pending_state(|state| state.positioner.get_geometry());
let toplevel_offset = get_popup_toplevel_coords(surface);
let mut geometry = surface.with_pending_state(|state| state.positioner.get_geometry());
geometry.loc += toplevel_offset;
let offset = check_constrained(geometry, relative);
if offset.x != 0 || offset.y != 0 {
@ -155,8 +162,11 @@ fn unconstrain_layer_popup(surface: &PopupSurface, output: &Output, layer_surfac
}
fn unconstrain_flip(popup: &PopupSurface, toplevel_box: Rectangle<i32, Logical>) -> bool {
let toplevel_offset = get_popup_toplevel_coords(popup);
let positioner = popup.with_pending_state(|state| state.positioner.clone());
let offset = check_constrained(positioner.get_geometry(), toplevel_box);
let mut geometry = positioner.get_geometry();
geometry.loc += toplevel_offset;
let offset = check_constrained(geometry, toplevel_box);
if offset.x == 0 && offset.y == 0 {
return true;
}
@ -176,7 +186,9 @@ fn unconstrain_flip(popup: &PopupSurface, toplevel_box: Rectangle<i32, Logical>)
let old_positioner = positioner.clone();
positioner.anchor_edges = invert_anchor_x(positioner.anchor_edges);
positioner.gravity = invert_gravity_x(positioner.gravity);
let new_offset = check_constrained(positioner.get_geometry(), toplevel_box);
geometry = positioner.get_geometry();
geometry.loc += toplevel_offset;
let new_offset = check_constrained(geometry, toplevel_box);
if !(new_offset.x.abs() < offset.x.abs()) {
positioner = old_positioner;
}
@ -185,13 +197,17 @@ fn unconstrain_flip(popup: &PopupSurface, toplevel_box: Rectangle<i32, Logical>)
let old_positioner = positioner.clone();
positioner.anchor_edges = invert_anchor_y(positioner.anchor_edges);
positioner.gravity = invert_gravity_y(positioner.gravity);
let new_offset = check_constrained(positioner.get_geometry(), toplevel_box);
geometry = positioner.get_geometry();
geometry.loc += toplevel_offset;
let new_offset = check_constrained(geometry, toplevel_box);
if !(new_offset.y.abs() < offset.y.abs()) {
positioner = old_positioner;
}
}
let new_offset = check_constrained(positioner.get_geometry(), toplevel_box);
geometry = positioner.get_geometry();
geometry.loc += toplevel_offset;
let new_offset = check_constrained(geometry, toplevel_box);
if new_offset.x.abs() < offset.x.abs() || new_offset.y.abs() < offset.y.abs() {
popup.with_pending_state(|state| {
state.geometry = positioner.get_geometry();
@ -203,8 +219,11 @@ fn unconstrain_flip(popup: &PopupSurface, toplevel_box: Rectangle<i32, Logical>)
}
fn unconstrain_slide(popup: &PopupSurface, toplevel_box: Rectangle<i32, Logical>) -> bool {
let toplevel_offset = get_popup_toplevel_coords(popup);
let positioner = popup.with_pending_state(|state| state.positioner.clone());
let offset = check_constrained(positioner.get_geometry(), toplevel_box);
let mut geometry = positioner.get_geometry();
geometry.loc += toplevel_offset;
let offset = check_constrained(geometry, toplevel_box);
if offset.x == 0 && offset.y == 0 {
return true;
}
@ -234,7 +253,9 @@ fn unconstrain_slide(popup: &PopupSurface, toplevel_box: Rectangle<i32, Logical>
geometry.loc.y += toplevel_box.loc.y - toplevel.y;
}
let new_offset = check_constrained(geometry, toplevel_box);
let mut check_geometry = geometry.clone();
check_geometry.loc += toplevel;
let new_offset = check_constrained(check_geometry, toplevel_box);
if new_offset.x.abs() < offset.x.abs() || new_offset.y.abs() < offset.y.abs() {
popup.with_pending_state(|state| {
state.geometry = geometry;
@ -245,8 +266,11 @@ fn unconstrain_slide(popup: &PopupSurface, toplevel_box: Rectangle<i32, Logical>
}
fn unconstrain_resize(popup: &PopupSurface, toplevel_box: Rectangle<i32, Logical>) -> bool {
let toplevel_offset = get_popup_toplevel_coords(popup);
let positioner = popup.with_pending_state(|state| state.positioner.clone());
let offset = check_constrained(positioner.get_geometry(), toplevel_box);
let mut geometry = positioner.get_geometry();
geometry.loc += toplevel_offset;
let offset = check_constrained(geometry, toplevel_box);
if offset.x == 0 && offset.y == 0 {
return true;
}
@ -268,6 +292,8 @@ fn unconstrain_resize(popup: &PopupSurface, toplevel_box: Rectangle<i32, Logical
geometry.size.h -= offset.y;
}
let mut check_geometry = geometry.clone();
check_geometry.loc += toplevel_offset;
let offset = check_constrained(geometry, toplevel_box);
if offset.x == 0 && offset.y == 0 {
// no longer constrained

View file

@ -151,16 +151,19 @@ impl XwmHandler for State {
}
let seat = self.common.last_active_seat().clone();
self.common.shell.pending_windows.push((surface, seat));
self.common
.shell
.pending_windows
.push((surface, seat, None));
}
fn map_window_notify(&mut self, _xwm: XwmId, surface: X11Surface) {
if let Some((window, seat)) = self
if let Some((window, _, _)) = self
.common
.shell
.pending_windows
.iter()
.find(|(window, _)| {
.find(|(window, _, _)| {
if let CosmicSurface::X11(window) = window {
window == &surface
} else {
@ -169,8 +172,7 @@ impl XwmHandler for State {
})
.cloned()
{
let output = seat.active_output();
Shell::map_window(self, &window, &output);
Shell::map_window(self, &window);
}
}
@ -277,13 +279,16 @@ impl XwmHandler for State {
{
let space = self.common.shell.space_for(mapped).unwrap();
if space.is_floating(mapped) {
mapped.set_geometry(Rectangle::from_loc_and_size(
current_geo.loc,
(
w.map(|w| w as i32).unwrap_or(current_geo.size.w),
h.map(|h| h as i32).unwrap_or(current_geo.size.h),
),
))
mapped.set_geometry(
Rectangle::from_loc_and_size(
current_geo.loc,
(
w.map(|w| w as i32).unwrap_or(current_geo.size.w),
h.map(|h| h as i32).unwrap_or(current_geo.size.h),
),
)
.as_global(),
)
}
} else {
if let Some(x) = x {
@ -325,13 +330,13 @@ impl XwmHandler for State {
}
}
let geo = window.geometry();
let geo = window.geometry().as_global();
for (output, overlap) in self.common.shell.outputs().cloned().map(|o| {
let intersection = o.geometry().intersection(geo);
(o, intersection)
}) {
if let Some(overlap) = overlap {
window.output_enter(&output, overlap);
window.output_enter(&output, overlap.as_logical());
} else {
window.output_leave(&output);
}
@ -360,14 +365,11 @@ impl XwmHandler for State {
}
fn maximize_request(&mut self, _xwm: XwmId, window: X11Surface) {
let seat = self.common.last_active_seat();
let output = seat.active_output();
let surface = CosmicSurface::X11(window);
if let Some(mapped) = self.common.shell.element_for_surface(&surface).cloned() {
if let Some(workspace) = self.common.shell.space_for_mut(&mapped) {
let (window, _) = mapped.windows().find(|(w, _)| w == &surface).unwrap();
workspace.maximize_request(&window, &output, self.common.event_loop_handle.clone())
workspace.maximize_request(&window);
}
}
}
@ -383,18 +385,10 @@ impl XwmHandler for State {
}
fn fullscreen_request(&mut self, _xwm: XwmId, window: X11Surface) {
let seat = self.common.last_active_seat();
let output = seat.active_output();
let surface = CosmicSurface::X11(window);
if let Some(mapped) = self.common.shell.element_for_surface(&surface).cloned() {
if let Some(workspace) = self.common.shell.space_for_mut(&mapped) {
let (window, _) = mapped.windows().find(|(w, _)| w == &surface).unwrap();
workspace.fullscreen_request(
&window,
&output,
self.common.event_loop_handle.clone(),
)
workspace.fullscreen_request(&surface, None)
}
}
}
@ -404,7 +398,8 @@ impl XwmHandler for State {
if let Some(mapped) = self.common.shell.element_for_surface(&surface).cloned() {
if let Some(workspace) = self.common.shell.space_for_mut(&mapped) {
let (window, _) = mapped.windows().find(|(w, _)| w == &surface).unwrap();
workspace.unfullscreen_request(&window);
let previous = workspace.unfullscreen_request(&window);
assert!(previous.is_none());
}
}
}