fmt commit
This commit is contained in:
parent
352c526e9e
commit
9e0a6e1b5f
25 changed files with 787 additions and 499 deletions
|
|
@ -40,7 +40,10 @@ use smithay::{
|
||||||
DisplayHandle, Resource,
|
DisplayHandle, Resource,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
utils::{Size, signaling::{Linkable, SignalToken, Signaler}},
|
utils::{
|
||||||
|
signaling::{Linkable, SignalToken, Signaler},
|
||||||
|
Size,
|
||||||
|
},
|
||||||
wayland::{
|
wayland::{
|
||||||
dmabuf::DmabufGlobal,
|
dmabuf::DmabufGlobal,
|
||||||
output::{Mode as OutputMode, Output, PhysicalProperties},
|
output::{Mode as OutputMode, Output, PhysicalProperties},
|
||||||
|
|
@ -1023,12 +1026,15 @@ impl KmsState {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn capture_output(&self, output: &Output) -> Option<(DrmNode, Dmabuf, Instant)> {
|
pub fn capture_output(&self, output: &Output) -> Option<(DrmNode, Dmabuf, Instant)> {
|
||||||
self.devices
|
self.devices.values().find_map(|dev| {
|
||||||
.values()
|
dev.surfaces
|
||||||
.find_map(|dev| dev.surfaces.values().find(|s| &s.output == output)
|
.values()
|
||||||
.and_then(|s| s.last_render.clone()
|
.find(|s| &s.output == output)
|
||||||
.map(|(buf, time)| (dev.render_node.clone(), buf, time))
|
.and_then(|s| {
|
||||||
)
|
s.last_render
|
||||||
)
|
.clone()
|
||||||
|
.map(|(buf, time)| (dev.render_node.clone(), buf, time))
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ pub fn init_backend_auto(
|
||||||
.with_context(|| "Backend initialized without output")?
|
.with_context(|| "Backend initialized without output")?
|
||||||
.clone();
|
.clone();
|
||||||
seat.user_data()
|
seat.user_data()
|
||||||
.insert_if_missing(|| crate::input::ActiveOutput(std::cell::RefCell::new(output)));
|
.insert_if_missing(|| crate::input::ActiveOutput(std::cell::RefCell::new(output)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
res
|
res
|
||||||
|
|
|
||||||
|
|
@ -242,10 +242,14 @@ where
|
||||||
1,
|
1,
|
||||||
scale,
|
scale,
|
||||||
Transform::Normal,
|
Transform::Normal,
|
||||||
&damage.iter().copied().map(|mut rect| {
|
&damage
|
||||||
rect.loc -= self.position.to_physical(scale).to_i32_round();
|
.iter()
|
||||||
rect
|
.copied()
|
||||||
}).collect::<Vec<_>>(),
|
.map(|mut rect| {
|
||||||
|
rect.loc -= self.position.to_physical(scale).to_i32_round();
|
||||||
|
rect
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
1.0,
|
1.0,
|
||||||
)?;
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -335,7 +339,10 @@ where
|
||||||
Point::<i32, Logical>::from((frame.xhot as i32, frame.yhot as i32)).to_f64();
|
Point::<i32, Logical>::from((frame.xhot as i32, frame.yhot as i32)).to_f64();
|
||||||
*state.current_image.borrow_mut() = Some(frame);
|
*state.current_image.borrow_mut() = Some(frame);
|
||||||
|
|
||||||
Some(PointerElement::new(seat, pointer_image.clone(), location - hotspot, new_frame).into())
|
Some(
|
||||||
|
PointerElement::new(seat, pointer_image.clone(), location - hotspot, new_frame)
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,16 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
use crate::{
|
|
||||||
state::Common,
|
|
||||||
shell::grabs::{
|
|
||||||
SeatMoveGrabState,
|
|
||||||
MoveGrabRenderElement,
|
|
||||||
},
|
|
||||||
wayland::handlers::data_device::get_dnd_icon,
|
|
||||||
};
|
|
||||||
#[cfg(feature = "debug")]
|
#[cfg(feature = "debug")]
|
||||||
use crate::{
|
use crate::{
|
||||||
debug::{debug_ui, fps_ui, log_ui, EguiFrame},
|
debug::{debug_ui, fps_ui, log_ui, EguiFrame},
|
||||||
state::Fps,
|
state::Fps,
|
||||||
utils::prelude::*,
|
utils::prelude::*,
|
||||||
};
|
};
|
||||||
|
use crate::{
|
||||||
|
shell::grabs::{MoveGrabRenderElement, SeatMoveGrabState},
|
||||||
|
state::Common,
|
||||||
|
wayland::handlers::data_device::get_dnd_icon,
|
||||||
|
};
|
||||||
|
|
||||||
use slog::Logger;
|
use slog::Logger;
|
||||||
use smithay::{
|
use smithay::{
|
||||||
|
|
@ -151,7 +148,7 @@ pub fn cursor_custom_elements<R>(
|
||||||
hardware_cursor: bool,
|
hardware_cursor: bool,
|
||||||
) -> Vec<CustomElem>
|
) -> Vec<CustomElem>
|
||||||
where
|
where
|
||||||
R: AsGles2Renderer
|
R: AsGles2Renderer,
|
||||||
{
|
{
|
||||||
let mut custom_elements = Vec::new();
|
let mut custom_elements = Vec::new();
|
||||||
|
|
||||||
|
|
@ -164,12 +161,17 @@ where
|
||||||
.shell
|
.shell
|
||||||
.space_relative_output_geometry(pointer.current_location().to_i32_round(), output);
|
.space_relative_output_geometry(pointer.current_location().to_i32_round(), output);
|
||||||
|
|
||||||
if let Some(grab) = seat.user_data().get::<SeatMoveGrabState>().unwrap().borrow()
|
if let Some(grab) = seat
|
||||||
.as_ref().and_then(|state| state.render(seat, output))
|
.user_data()
|
||||||
|
.get::<SeatMoveGrabState>()
|
||||||
|
.unwrap()
|
||||||
|
.borrow()
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|state| state.render(seat, output))
|
||||||
{
|
{
|
||||||
custom_elements.push(grab);
|
custom_elements.push(grab);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(wl_surface) = get_dnd_icon(seat) {
|
if let Some(wl_surface) = get_dnd_icon(seat) {
|
||||||
custom_elements.push(cursor::draw_dnd_icon(wl_surface, location.to_i32_round()).into());
|
custom_elements.push(cursor::draw_dnd_icon(wl_surface, location.to_i32_round()).into());
|
||||||
}
|
}
|
||||||
|
|
@ -245,16 +247,41 @@ where
|
||||||
}
|
}
|
||||||
#[cfg(feature = "debug")]
|
#[cfg(feature = "debug")]
|
||||||
{
|
{
|
||||||
render_fullscreen(gpu, renderer, window, state, output, hardware_cursor, fps.as_deref_mut())
|
render_fullscreen(
|
||||||
|
gpu,
|
||||||
|
renderer,
|
||||||
|
window,
|
||||||
|
state,
|
||||||
|
output,
|
||||||
|
hardware_cursor,
|
||||||
|
fps.as_deref_mut(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
#[cfg(not(feature = "debug"))]
|
#[cfg(not(feature = "debug"))]
|
||||||
{
|
{
|
||||||
render_desktop(gpu, renderer, age, state, space_idx, output, hardware_cursor)
|
render_desktop(
|
||||||
|
gpu,
|
||||||
|
renderer,
|
||||||
|
age,
|
||||||
|
state,
|
||||||
|
space_idx,
|
||||||
|
output,
|
||||||
|
hardware_cursor,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
#[cfg(feature = "debug")]
|
#[cfg(feature = "debug")]
|
||||||
{
|
{
|
||||||
render_desktop(gpu, renderer, age, state, space_idx, output, hardware_cursor, fps.as_deref_mut())
|
render_desktop(
|
||||||
|
gpu,
|
||||||
|
renderer,
|
||||||
|
age,
|
||||||
|
state,
|
||||||
|
space_idx,
|
||||||
|
output,
|
||||||
|
hardware_cursor,
|
||||||
|
fps.as_deref_mut(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -318,7 +345,12 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
custom_elements.extend(cursor_custom_elements(renderer, state, output, hardware_cursor));
|
custom_elements.extend(cursor_custom_elements(
|
||||||
|
renderer,
|
||||||
|
state,
|
||||||
|
output,
|
||||||
|
hardware_cursor,
|
||||||
|
));
|
||||||
|
|
||||||
state.shell.spaces[space_idx].space.render_output(
|
state.shell.spaces[space_idx].space.render_output(
|
||||||
renderer,
|
renderer,
|
||||||
|
|
@ -364,7 +396,12 @@ where
|
||||||
custom_elements.push(fps_overlay.into());
|
custom_elements.push(fps_overlay.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
custom_elements.extend(cursor_custom_elements(renderer, state, output, hardware_cursor));
|
custom_elements.extend(cursor_custom_elements(
|
||||||
|
renderer,
|
||||||
|
state,
|
||||||
|
output,
|
||||||
|
hardware_cursor,
|
||||||
|
));
|
||||||
|
|
||||||
renderer
|
renderer
|
||||||
.render(mode.size, transform, |renderer, frame| {
|
.render(mode.size, transform, |renderer, frame| {
|
||||||
|
|
@ -429,14 +466,7 @@ where
|
||||||
let loc = elem.location(scale);
|
let loc = elem.location(scale);
|
||||||
let geo = elem.geometry(scale);
|
let geo = elem.geometry(scale);
|
||||||
let elem_damage = elem.accumulated_damage(scale, None);
|
let elem_damage = elem.accumulated_damage(scale, None);
|
||||||
elem.draw(
|
elem.draw(renderer, frame, scale, loc, &[geo], &slog_scope::logger())?;
|
||||||
renderer,
|
|
||||||
frame,
|
|
||||||
scale,
|
|
||||||
loc,
|
|
||||||
&[geo],
|
|
||||||
&slog_scope::logger(),
|
|
||||||
)?;
|
|
||||||
damage.extend(elem_damage)
|
damage.extend(elem_damage)
|
||||||
}
|
}
|
||||||
Ok(Some(damage))
|
Ok(Some(damage))
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,9 @@ pub use smithay::{
|
||||||
utils::{Logical, Physical, Point, Size, Transform},
|
utils::{Logical, Physical, Point, Size, Transform},
|
||||||
wayland::{
|
wayland::{
|
||||||
output::{Mode, Output},
|
output::{Mode, Output},
|
||||||
seat::{keysyms as KeySyms, Keysym, ModifiersState as KeyModifiers, XkbConfig as WlXkbConfig},
|
seat::{
|
||||||
|
keysyms as KeySyms, Keysym, ModifiersState as KeyModifiers, XkbConfig as WlXkbConfig,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use xkbcommon::xkb;
|
use xkbcommon::xkb;
|
||||||
|
|
|
||||||
41
src/debug.rs
41
src/debug.rs
|
|
@ -5,7 +5,7 @@ use smithay::{
|
||||||
backend::drm::DrmNode,
|
backend::drm::DrmNode,
|
||||||
desktop::layer_map_for_output,
|
desktop::layer_map_for_output,
|
||||||
reexports::wayland_server::Resource,
|
reexports::wayland_server::Resource,
|
||||||
utils::{Physical, Rectangle, IsAlive},
|
utils::{IsAlive, Physical, Rectangle},
|
||||||
};
|
};
|
||||||
pub use smithay_egui::EguiFrame;
|
pub use smithay_egui::EguiFrame;
|
||||||
|
|
||||||
|
|
@ -252,15 +252,36 @@ pub fn debug_ui(
|
||||||
ui.collapsing("Layers:", |ui| {
|
ui.collapsing("Layers:", |ui| {
|
||||||
let map = layer_map_for_output(&output);
|
let map = layer_map_for_output(&output);
|
||||||
for layer in map.layers() {
|
for layer in map.layers() {
|
||||||
ui.collapsing(format!("{}/{:?}", layer.wl_surface().id(), layer.wl_surface().client_id()), |ui| {
|
ui.collapsing(
|
||||||
ui.label(format!("Alive: {:?} {:?} {:?}", layer.alive(), layer.layer_surface().alive(), layer.wl_surface().alive()));
|
format!(
|
||||||
ui.label(format!("Layer: {:?}", layer.layer()));
|
"{}/{:?}",
|
||||||
ui.label(format!("Namespace: {:?}", layer.namespace()));
|
layer.wl_surface().id(),
|
||||||
ui.label(format!("Geometry: {:?}", layer.bbox()));
|
layer.wl_surface().client_id()
|
||||||
ui.label(format!("Anchor: {:?}", layer.cached_state().anchor));
|
),
|
||||||
ui.label(format!("Margin: {:?}", layer.cached_state().margin));
|
|ui| {
|
||||||
ui.label(format!("Exclusive: {:?}", layer.cached_state().exclusive_zone));
|
ui.label(format!(
|
||||||
});
|
"Alive: {:?} {:?} {:?}",
|
||||||
|
layer.alive(),
|
||||||
|
layer.layer_surface().alive(),
|
||||||
|
layer.wl_surface().alive()
|
||||||
|
));
|
||||||
|
ui.label(format!("Layer: {:?}", layer.layer()));
|
||||||
|
ui.label(format!("Namespace: {:?}", layer.namespace()));
|
||||||
|
ui.label(format!("Geometry: {:?}", layer.bbox()));
|
||||||
|
ui.label(format!(
|
||||||
|
"Anchor: {:?}",
|
||||||
|
layer.cached_state().anchor
|
||||||
|
));
|
||||||
|
ui.label(format!(
|
||||||
|
"Margin: {:?}",
|
||||||
|
layer.cached_state().margin
|
||||||
|
));
|
||||||
|
ui.label(format!(
|
||||||
|
"Exclusive: {:?}",
|
||||||
|
layer.cached_state().exclusive_zone
|
||||||
|
));
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
ui.label(format!("{:?}", map));
|
ui.label(format!("{:?}", map));
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,14 @@
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::{Action, Config},
|
config::{Action, Config},
|
||||||
shell::{
|
shell::{grabs::SeatMoveGrabState, Workspace},
|
||||||
Workspace,
|
|
||||||
grabs::SeatMoveGrabState,
|
|
||||||
},
|
|
||||||
utils::prelude::*,
|
utils::prelude::*,
|
||||||
};
|
};
|
||||||
use smithay::{
|
use smithay::{
|
||||||
backend::input::{Device, DeviceCapability, InputBackend, InputEvent, KeyState},
|
backend::input::{Device, DeviceCapability, InputBackend, InputEvent, KeyState},
|
||||||
desktop::{layer_map_for_output, Kind, WindowSurfaceType},
|
desktop::{layer_map_for_output, Kind, WindowSurfaceType},
|
||||||
reexports::wayland_server::{protocol::wl_surface::WlSurface, DisplayHandle, Resource},
|
reexports::wayland_server::{protocol::wl_surface::WlSurface, DisplayHandle, Resource},
|
||||||
utils::{Logical, Point, Rectangle, Size, Buffer},
|
utils::{Buffer, Logical, Point, Rectangle, Size},
|
||||||
wayland::{
|
wayland::{
|
||||||
data_device::set_data_device_focus,
|
data_device::set_data_device_focus,
|
||||||
output::Output,
|
output::Output,
|
||||||
|
|
@ -118,21 +115,16 @@ pub fn add_seat(dh: &DisplayHandle, config: &Config, name: String) -> Seat<State
|
||||||
// devices appear), we have to surrender to reality and just always expose a keyboard and pointer.
|
// devices appear), we have to surrender to reality and just always expose a keyboard and pointer.
|
||||||
let dh_clone = dh.clone();
|
let dh_clone = dh.clone();
|
||||||
let conf = config.xkb_config();
|
let conf = config.xkb_config();
|
||||||
let _ = seat.add_keyboard(
|
let _ = seat.add_keyboard((&conf).into(), 200, 25, move |seat, focus| {
|
||||||
(&conf).into(),
|
if let Some(client) = focus.and_then(|s| dh_clone.get_client(s.id()).ok()) {
|
||||||
200,
|
set_data_device_focus(&dh_clone, seat, Some(client));
|
||||||
25,
|
let client2 = focus
|
||||||
move |seat, focus| {
|
.and_then(|s| dh_clone.get_client(s.id()).ok())
|
||||||
if let Some(client) =
|
.unwrap();
|
||||||
focus.and_then(|s| dh_clone.get_client(s.id()).ok())
|
set_primary_focus(&dh_clone, seat, Some(client2))
|
||||||
{
|
}
|
||||||
set_data_device_focus(&dh_clone, seat, Some(client));
|
});
|
||||||
let client2 = focus.and_then(|s| dh_clone.get_client(s.id()).ok()).unwrap();
|
|
||||||
set_primary_focus(&dh_clone, seat, Some(client2))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let owned_seat = seat.clone();
|
let owned_seat = seat.clone();
|
||||||
seat.add_pointer(move |status| {
|
seat.add_pointer(move |status| {
|
||||||
*owned_seat
|
*owned_seat
|
||||||
|
|
@ -178,7 +170,7 @@ impl State {
|
||||||
for cap in devices.remove_device(&device) {
|
for cap in devices.remove_device(&device) {
|
||||||
match cap {
|
match cap {
|
||||||
// TODO: Handle touch, tablet
|
// TODO: Handle touch, tablet
|
||||||
_ => {},
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -391,11 +383,16 @@ impl State {
|
||||||
let home = match std::env::var("HOME") {
|
let home = match std::env::var("HOME") {
|
||||||
Ok(home) => home,
|
Ok(home) => home,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
slog_scope::error!("$HOME is not set, can't save screenshots: {}", err);
|
slog_scope::error!(
|
||||||
|
"$HOME is not set, can't save screenshots: {}",
|
||||||
|
err
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let timestamp = match std::time::SystemTime::UNIX_EPOCH.elapsed() {
|
let timestamp = match std::time::SystemTime::UNIX_EPOCH
|
||||||
|
.elapsed()
|
||||||
|
{
|
||||||
Ok(duration) => duration.as_secs(),
|
Ok(duration) => duration.as_secs(),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
slog_scope::error!("Unable to get timestamp, can't save screenshots: {}", err);
|
slog_scope::error!("Unable to get timestamp, can't save screenshots: {}", err);
|
||||||
|
|
@ -403,17 +400,35 @@ impl State {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
for output in self.common.shell.outputs.clone().into_iter() {
|
for output in self.common.shell.outputs.clone().into_iter() {
|
||||||
match self.backend.offscreen_for_output(&output, &mut self.common) {
|
match self
|
||||||
|
.backend
|
||||||
|
.offscreen_for_output(&output, &mut self.common)
|
||||||
|
{
|
||||||
Ok((buffer, size)) => {
|
Ok((buffer, size)) => {
|
||||||
let mut path = std::path::PathBuf::new();
|
let mut path = std::path::PathBuf::new();
|
||||||
path.push(&home);
|
path.push(&home);
|
||||||
path.push(format!("{}_{}.png", output.name(), timestamp));
|
path.push(format!(
|
||||||
|
"{}_{}.png",
|
||||||
|
output.name(),
|
||||||
|
timestamp
|
||||||
|
));
|
||||||
|
|
||||||
fn write_png(path: impl AsRef<std::path::Path>, data: Vec<u8>, size: Size<i32, Buffer>) -> anyhow::Result<()> {
|
fn write_png(
|
||||||
use std::{io, fs};
|
path: impl AsRef<std::path::Path>,
|
||||||
|
data: Vec<u8>,
|
||||||
|
size: Size<i32, Buffer>,
|
||||||
|
) -> anyhow::Result<()>
|
||||||
|
{
|
||||||
|
use std::{fs, io};
|
||||||
|
|
||||||
let file = io::BufWriter::new(fs::File::create(&path)?);
|
let file = io::BufWriter::new(
|
||||||
let mut encoder = png::Encoder::new(file, size.w as u32, size.h as u32);
|
fs::File::create(&path)?,
|
||||||
|
);
|
||||||
|
let mut encoder = png::Encoder::new(
|
||||||
|
file,
|
||||||
|
size.w as u32,
|
||||||
|
size.h as u32,
|
||||||
|
);
|
||||||
encoder.set_color(png::ColorType::Rgba);
|
encoder.set_color(png::ColorType::Rgba);
|
||||||
encoder.set_depth(png::BitDepth::Eight);
|
encoder.set_depth(png::BitDepth::Eight);
|
||||||
let mut writer = encoder.write_header()?;
|
let mut writer = encoder.write_header()?;
|
||||||
|
|
@ -422,10 +437,18 @@ impl State {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(err) = write_png(&path, buffer, size) {
|
if let Err(err) = write_png(&path, buffer, size) {
|
||||||
slog_scope::error!("Unable to save screenshot at {}: {}", path.display(), err);
|
slog_scope::error!(
|
||||||
|
"Unable to save screenshot at {}: {}",
|
||||||
|
path.display(),
|
||||||
|
err
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Err(err) => slog_scope::error!("Could not save screenshot for output {}: {}", output.name(), err),
|
Err(err) => slog_scope::error!(
|
||||||
|
"Could not save screenshot for output {}: {}",
|
||||||
|
output.name(),
|
||||||
|
err
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,31 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
use crate::utils::prelude::*;
|
|
||||||
use super::Shell;
|
use super::Shell;
|
||||||
|
use crate::utils::prelude::*;
|
||||||
|
|
||||||
|
use cosmic_protocols::workspace::v1::server::zcosmic_workspace_handle_v1::State as WState;
|
||||||
use smithay::{
|
use smithay::{
|
||||||
backend::renderer::{Renderer, ImportAll},
|
backend::renderer::{ImportAll, Renderer},
|
||||||
desktop::{Kind, Window, draw_window, space::{RenderElement, SpaceOutputTuple}},
|
desktop::{
|
||||||
|
draw_window,
|
||||||
|
space::{RenderElement, SpaceOutputTuple},
|
||||||
|
Kind, Window,
|
||||||
|
},
|
||||||
reexports::{
|
reexports::{
|
||||||
wayland_protocols::xdg::shell::server::xdg_toplevel::State as XdgState,
|
wayland_protocols::xdg::shell::server::xdg_toplevel::State as XdgState,
|
||||||
wayland_server::DisplayHandle,
|
wayland_server::DisplayHandle,
|
||||||
},
|
},
|
||||||
utils::{IsAlive, Logical, Physical, Point, Rectangle, Scale},
|
utils::{IsAlive, Logical, Physical, Point, Rectangle, Scale},
|
||||||
wayland::{
|
wayland::{
|
||||||
|
output::Output,
|
||||||
seat::{
|
seat::{
|
||||||
AxisFrame, ButtonEvent, MotionEvent, PointerGrab, PointerGrabStartData,
|
AxisFrame, ButtonEvent, MotionEvent, PointerGrab, PointerGrabStartData,
|
||||||
PointerInnerHandle,
|
PointerInnerHandle,
|
||||||
},
|
},
|
||||||
seat::{Seat, Focus},
|
seat::{Focus, Seat},
|
||||||
output::Output,
|
|
||||||
Serial,
|
Serial,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use cosmic_protocols::workspace::v1::server::zcosmic_workspace_handle_v1::State as WState;
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
|
||||||
impl Shell {
|
impl Shell {
|
||||||
|
|
@ -34,31 +38,37 @@ impl Shell {
|
||||||
) {
|
) {
|
||||||
// TODO touch grab
|
// TODO touch grab
|
||||||
if let Some(pointer) = seat.get_pointer() {
|
if let Some(pointer) = seat.get_pointer() {
|
||||||
let workspace = self.space_for_window_mut(window.toplevel().wl_surface()).unwrap();
|
let workspace = self
|
||||||
|
.space_for_window_mut(window.toplevel().wl_surface())
|
||||||
|
.unwrap();
|
||||||
if workspace.fullscreen.values().any(|w| w == window) {
|
if workspace.fullscreen.values().any(|w| w == window) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let pos = pointer.current_location();
|
let pos = pointer.current_location();
|
||||||
let output = workspace.space.outputs_for_window(&window)
|
let output = workspace
|
||||||
|
.space
|
||||||
|
.outputs_for_window(&window)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.find(|o| o.geometry().contains(pos.to_i32_round()))
|
.find(|o| o.geometry().contains(pos.to_i32_round()))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut initial_window_location = workspace.space.window_location(&window).unwrap();
|
let mut initial_window_location = workspace.space.window_location(&window).unwrap();
|
||||||
|
|
||||||
let output = match &window.toplevel() {
|
let output = match &window.toplevel() {
|
||||||
Kind::Xdg(surface) => {
|
Kind::Xdg(surface) => {
|
||||||
// If surface is maximized then unmaximize it
|
// If surface is maximized then unmaximize it
|
||||||
let current_state = surface.current_state();
|
let current_state = surface.current_state();
|
||||||
if current_state.states.contains(XdgState::Maximized) {
|
if current_state.states.contains(XdgState::Maximized) {
|
||||||
workspace.floating_layer.unmaximize_request(&mut workspace.space, window);
|
workspace
|
||||||
|
.floating_layer
|
||||||
|
.unmaximize_request(&mut workspace.space, window);
|
||||||
let new_size = surface.with_pending_state(|state| state.size);
|
let new_size = surface.with_pending_state(|state| state.size);
|
||||||
let ratio = pos.x / output.geometry().size.w as f64;
|
let ratio = pos.x / output.geometry().size.w as f64;
|
||||||
|
|
||||||
initial_window_location = new_size.map(|size| (
|
initial_window_location = new_size
|
||||||
pos.x - (size.w as f64 * ratio),
|
.map(|size| (pos.x - (size.w as f64 * ratio), pos.y).into())
|
||||||
pos.y,
|
.unwrap_or_else(|| pos)
|
||||||
).into()).unwrap_or_else(|| pos).to_i32_round();
|
.to_i32_round();
|
||||||
}
|
}
|
||||||
|
|
||||||
output
|
output
|
||||||
|
|
@ -67,8 +77,8 @@ impl Shell {
|
||||||
|
|
||||||
let was_tiled = if workspace.tiling_layer.windows.contains(&window) {
|
let was_tiled = if workspace.tiling_layer.windows.contains(&window) {
|
||||||
workspace
|
workspace
|
||||||
.tiling_layer
|
.tiling_layer
|
||||||
.unmap_window(&mut workspace.space, &window);
|
.unmap_window(&mut workspace.space, &window);
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
workspace
|
workspace
|
||||||
|
|
@ -76,19 +86,20 @@ impl Shell {
|
||||||
.unmap_window(&mut workspace.space, &window);
|
.unmap_window(&mut workspace.space, &window);
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
|
|
||||||
let workspace_handle = workspace.handle;
|
let workspace_handle = workspace.handle;
|
||||||
let workspace_is_empty = workspace.space.windows().next().is_none();
|
let workspace_is_empty = workspace.space.windows().next().is_none();
|
||||||
|
|
||||||
if workspace_is_empty {
|
if workspace_is_empty {
|
||||||
self.workspace_state.update().add_workspace_state(&workspace_handle, WState::Hidden);
|
self.workspace_state
|
||||||
|
.update()
|
||||||
|
.add_workspace_state(&workspace_handle, WState::Hidden);
|
||||||
}
|
}
|
||||||
self.toplevel_info_state
|
self.toplevel_info_state
|
||||||
.toplevel_leave_workspace(&window, &workspace_handle);
|
.toplevel_leave_workspace(&window, &workspace_handle);
|
||||||
self.toplevel_info_state
|
self.toplevel_info_state
|
||||||
.toplevel_leave_output(&window, &output);
|
.toplevel_leave_output(&window, &output);
|
||||||
|
|
||||||
|
|
||||||
let state = MoveGrabState {
|
let state = MoveGrabState {
|
||||||
window: window.clone(),
|
window: window.clone(),
|
||||||
was_tiled,
|
was_tiled,
|
||||||
|
|
@ -97,25 +108,30 @@ impl Shell {
|
||||||
};
|
};
|
||||||
let grab = MoveSurfaceGrab::new(start_data, window.clone(), seat);
|
let grab = MoveSurfaceGrab::new(start_data, window.clone(), seat);
|
||||||
|
|
||||||
*seat.user_data().get::<SeatMoveGrabState>().unwrap().borrow_mut() = Some(state);
|
*seat
|
||||||
|
.user_data()
|
||||||
|
.get::<SeatMoveGrabState>()
|
||||||
|
.unwrap()
|
||||||
|
.borrow_mut() = Some(state);
|
||||||
pointer.set_grab(grab, serial, Focus::Clear);
|
pointer.set_grab(grab, serial, Focus::Clear);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn drop_move(
|
fn drop_move(&mut self, dh: &DisplayHandle, seat: &Seat<State>, output: &Output) {
|
||||||
&mut self,
|
if let Some(move_state) = seat
|
||||||
dh: &DisplayHandle,
|
.user_data()
|
||||||
seat: &Seat<State>,
|
.get::<SeatMoveGrabState>()
|
||||||
|
.unwrap()
|
||||||
output: &Output,
|
.borrow_mut()
|
||||||
) {
|
.take()
|
||||||
if let Some(move_state) = seat.user_data().get::<SeatMoveGrabState>().unwrap().borrow_mut().take() {
|
{
|
||||||
let pointer = seat.get_pointer().unwrap();
|
let pointer = seat.get_pointer().unwrap();
|
||||||
let window = move_state.window;
|
let window = move_state.window;
|
||||||
|
|
||||||
if window.alive() {
|
if window.alive() {
|
||||||
let delta = pointer.current_location() - move_state.initial_cursor_location;
|
let delta = pointer.current_location() - move_state.initial_cursor_location;
|
||||||
let window_location = (move_state.initial_window_location.to_f64() + delta).to_i32_round();
|
let window_location =
|
||||||
|
(move_state.initial_window_location.to_f64() + delta).to_i32_round();
|
||||||
let surface = window.toplevel().wl_surface().clone();
|
let surface = window.toplevel().wl_surface().clone();
|
||||||
|
|
||||||
let workspace_handle = self.active_space(output).handle;
|
let workspace_handle = self.active_space(output).handle;
|
||||||
|
|
@ -137,9 +153,12 @@ impl Shell {
|
||||||
focus_stack.iter(),
|
focus_stack.iter(),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
workspace
|
workspace.floating_layer.map_window(
|
||||||
.floating_layer
|
&mut workspace.space,
|
||||||
.map_window(&mut workspace.space, window, &seat, window_location);
|
window,
|
||||||
|
&seat,
|
||||||
|
window_location,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.set_focus(dh, Some(&surface), &seat, None);
|
self.set_focus(dh, Some(&surface), &seat, None);
|
||||||
|
|
@ -175,14 +194,15 @@ where
|
||||||
fn id(&self) -> usize {
|
fn id(&self) -> usize {
|
||||||
self.seat_id
|
self.seat_id
|
||||||
}
|
}
|
||||||
|
|
||||||
fn location(&self, scale: impl Into<Scale<f64>>) -> Point<f64, Physical> {
|
fn location(&self, scale: impl Into<Scale<f64>>) -> Point<f64, Physical> {
|
||||||
(self.window_location - self.window.geometry().loc.to_f64()).to_physical(scale)
|
(self.window_location - self.window.geometry().loc.to_f64()).to_physical(scale)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn geometry(&self, scale: impl Into<Scale<f64>>) -> Rectangle<i32, Physical> {
|
fn geometry(&self, scale: impl Into<Scale<f64>>) -> Rectangle<i32, Physical> {
|
||||||
let scale = scale.into();
|
let scale = scale.into();
|
||||||
self.window.physical_bbox_with_popups(RenderElement::<R>::location(self, scale), scale)
|
self.window
|
||||||
|
.physical_bbox_with_popups(RenderElement::<R>::location(self, scale), scale)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn accumulated_damage(
|
fn accumulated_damage(
|
||||||
|
|
@ -191,15 +211,20 @@ where
|
||||||
for_values: Option<SpaceOutputTuple<'_, '_>>,
|
for_values: Option<SpaceOutputTuple<'_, '_>>,
|
||||||
) -> Vec<Rectangle<i32, Physical>> {
|
) -> Vec<Rectangle<i32, Physical>> {
|
||||||
let scale = scale.into();
|
let scale = scale.into();
|
||||||
self.window.accumulated_damage(RenderElement::<R>::location(self, scale), scale, for_values.map(|t| (t.0, t.1)))
|
self.window.accumulated_damage(
|
||||||
|
RenderElement::<R>::location(self, scale),
|
||||||
|
scale,
|
||||||
|
for_values.map(|t| (t.0, t.1)),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn opaque_regions(
|
fn opaque_regions(
|
||||||
&self,
|
&self,
|
||||||
scale: impl Into<Scale<f64>>,
|
scale: impl Into<Scale<f64>>,
|
||||||
) -> Option<Vec<Rectangle<i32, Physical>>> {
|
) -> Option<Vec<Rectangle<i32, Physical>>> {
|
||||||
let scale = scale.into();
|
let scale = scale.into();
|
||||||
self.window.opaque_regions(RenderElement::<R>::location(self, scale), scale)
|
self.window
|
||||||
|
.opaque_regions(RenderElement::<R>::location(self, scale), scale)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(
|
fn draw(
|
||||||
|
|
@ -218,19 +243,20 @@ where
|
||||||
impl MoveGrabState {
|
impl MoveGrabState {
|
||||||
pub fn render<I>(&self, seat: &Seat<State>, output: &Output) -> Option<I>
|
pub fn render<I>(&self, seat: &Seat<State>, output: &Output) -> Option<I>
|
||||||
where
|
where
|
||||||
I: From<MoveGrabRenderElement>
|
I: From<MoveGrabRenderElement>,
|
||||||
{
|
{
|
||||||
let cursor_at = seat.get_pointer().unwrap().current_location();
|
let cursor_at = seat.get_pointer().unwrap().current_location();
|
||||||
let delta = cursor_at - self.initial_cursor_location;
|
let delta = cursor_at - self.initial_cursor_location;
|
||||||
let mut window_geo = self.window.bbox();
|
let mut window_geo = self.window.bbox();
|
||||||
window_geo.loc += (self.initial_window_location.to_f64() + delta).to_i32_round();
|
window_geo.loc += (self.initial_window_location.to_f64() + delta).to_i32_round();
|
||||||
|
|
||||||
if !output.geometry().intersection(window_geo).is_some() {
|
if !output.geometry().intersection(window_geo).is_some() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let delta = cursor_at - self.initial_cursor_location;
|
let delta = cursor_at - self.initial_cursor_location;
|
||||||
let window_location = self.initial_window_location.to_f64() + delta - output.geometry().loc.to_f64();
|
let window_location =
|
||||||
|
self.initial_window_location.to_f64() + delta - output.geometry().loc.to_f64();
|
||||||
Some(I::from(MoveGrabRenderElement {
|
Some(I::from(MoveGrabRenderElement {
|
||||||
seat_id: seat.id(),
|
seat_id: seat.id(),
|
||||||
window: self.window.clone(),
|
window: self.window.clone(),
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use smithay::{
|
||||||
reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::{
|
reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::{
|
||||||
ResizeEdge, State as XdgState,
|
ResizeEdge, State as XdgState,
|
||||||
},
|
},
|
||||||
utils::{IsAlive, Rectangle, Point, Logical},
|
utils::{IsAlive, Logical, Point, Rectangle},
|
||||||
wayland::{
|
wayland::{
|
||||||
compositor::with_states,
|
compositor::with_states,
|
||||||
output::Output,
|
output::Output,
|
||||||
|
|
@ -40,7 +40,13 @@ impl FloatingLayout {
|
||||||
Default::default()
|
Default::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn map_window(&mut self, space: &mut Space, window: Window, seat: &Seat<State>, position: impl Into<Option<Point<i32, Logical>>>) {
|
pub fn map_window(
|
||||||
|
&mut self,
|
||||||
|
space: &mut Space,
|
||||||
|
window: Window,
|
||||||
|
seat: &Seat<State>,
|
||||||
|
position: impl Into<Option<Point<i32, Logical>>>,
|
||||||
|
) {
|
||||||
if let Some(output) = super::output_from_seat(Some(seat), space) {
|
if let Some(output) = super::output_from_seat(Some(seat), space) {
|
||||||
self.map_window_internal(space, window, &output, position.into());
|
self.map_window_internal(space, window, &output, position.into());
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -58,8 +64,17 @@ impl FloatingLayout {
|
||||||
// TODO make sure all windows are still visible on any output or move them
|
// TODO make sure all windows are still visible on any output or move them
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map_window_internal(&mut self, space: &mut Space, window: Window, output: &Output, position: Option<Point<i32, Logical>>) {
|
fn map_window_internal(
|
||||||
let last_geometry = window.user_data().get::<WindowUserData>().map(|u| u.lock().unwrap().last_geometry);
|
&mut self,
|
||||||
|
space: &mut Space,
|
||||||
|
window: Window,
|
||||||
|
output: &Output,
|
||||||
|
position: Option<Point<i32, Logical>>,
|
||||||
|
) {
|
||||||
|
let last_geometry = window
|
||||||
|
.user_data()
|
||||||
|
.get::<WindowUserData>()
|
||||||
|
.map(|u| u.lock().unwrap().last_geometry);
|
||||||
let mut win_geo = window.geometry();
|
let mut win_geo = window.geometry();
|
||||||
|
|
||||||
let layers = layer_map_for_output(&output);
|
let layers = layer_map_for_output(&output);
|
||||||
|
|
@ -112,10 +127,15 @@ impl FloatingLayout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let position = position.or_else(|| last_geometry.map(|g| g.loc)).unwrap_or_else(|| (
|
let position = position
|
||||||
geometry.loc.x + (geometry.size.w / 2) - (win_geo.size.w / 2) + win_geo.loc.x,
|
.or_else(|| last_geometry.map(|g| g.loc))
|
||||||
geometry.loc.y + (geometry.size.h / 2) - (win_geo.size.h / 2) + win_geo.loc.y,
|
.unwrap_or_else(|| {
|
||||||
).into());
|
(
|
||||||
|
geometry.loc.x + (geometry.size.w / 2) - (win_geo.size.w / 2) + win_geo.loc.x,
|
||||||
|
geometry.loc.y + (geometry.size.h / 2) - (win_geo.size.h / 2) + win_geo.loc.y,
|
||||||
|
)
|
||||||
|
.into()
|
||||||
|
});
|
||||||
|
|
||||||
#[allow(irrefutable_let_patterns)]
|
#[allow(irrefutable_let_patterns)]
|
||||||
if let Kind::Xdg(xdg) = &window.toplevel() {
|
if let Kind::Xdg(xdg) = &window.toplevel() {
|
||||||
|
|
@ -138,19 +158,21 @@ impl FloatingLayout {
|
||||||
pub fn unmap_window(&mut self, space: &mut Space, window: &Window) {
|
pub fn unmap_window(&mut self, space: &mut Space, window: &Window) {
|
||||||
#[allow(irrefutable_let_patterns)]
|
#[allow(irrefutable_let_patterns)]
|
||||||
let is_maximized = match &window.toplevel() {
|
let is_maximized = match &window.toplevel() {
|
||||||
Kind::Xdg(surface) => surface.with_pending_state(|state| {
|
Kind::Xdg(surface) => {
|
||||||
state.states.contains(XdgState::Maximized)
|
surface.with_pending_state(|state| state.states.contains(XdgState::Maximized))
|
||||||
})
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if !is_maximized {
|
if !is_maximized {
|
||||||
if let Some(location) = space.window_location(window) {
|
if let Some(location) = space.window_location(window) {
|
||||||
let user_data = window.user_data();
|
let user_data = window.user_data();
|
||||||
user_data.insert_if_missing(|| WindowUserData::default());
|
user_data.insert_if_missing(|| WindowUserData::default());
|
||||||
user_data.get::<WindowUserData>().unwrap().lock().unwrap().last_geometry = Rectangle::from_loc_and_size(
|
user_data
|
||||||
location,
|
.get::<WindowUserData>()
|
||||||
window.geometry().size,
|
.unwrap()
|
||||||
);
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.last_geometry = Rectangle::from_loc_and_size(location, window.geometry().size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -162,16 +184,18 @@ impl FloatingLayout {
|
||||||
pub fn maximize_request(&mut self, space: &mut Space, window: &Window, output: &Output) {
|
pub fn maximize_request(&mut self, space: &mut Space, window: &Window, output: &Output) {
|
||||||
let layers = layer_map_for_output(&output);
|
let layers = layer_map_for_output(&output);
|
||||||
let geometry = layers.non_exclusive_zone();
|
let geometry = layers.non_exclusive_zone();
|
||||||
|
|
||||||
if let Some(location) = space.window_location(window) {
|
if let Some(location) = space.window_location(window) {
|
||||||
let user_data = window.user_data();
|
let user_data = window.user_data();
|
||||||
user_data.insert_if_missing(|| WindowUserData::default());
|
user_data.insert_if_missing(|| WindowUserData::default());
|
||||||
user_data.get::<WindowUserData>().unwrap().lock().unwrap().last_geometry = Rectangle::from_loc_and_size(
|
user_data
|
||||||
location,
|
.get::<WindowUserData>()
|
||||||
window.geometry().size,
|
.unwrap()
|
||||||
);
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.last_geometry = Rectangle::from_loc_and_size(location, window.geometry().size);
|
||||||
}
|
}
|
||||||
|
|
||||||
space.map_window(
|
space.map_window(
|
||||||
&window,
|
&window,
|
||||||
(geometry.loc.x, geometry.loc.y),
|
(geometry.loc.x, geometry.loc.y),
|
||||||
|
|
@ -189,7 +213,10 @@ impl FloatingLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unmaximize_request(&mut self, space: &mut Space, window: &Window) {
|
pub fn unmaximize_request(&mut self, space: &mut Space, window: &Window) {
|
||||||
let last_geometry = window.user_data().get::<WindowUserData>().map(|u| u.lock().unwrap().last_geometry);
|
let last_geometry = window
|
||||||
|
.user_data()
|
||||||
|
.get::<WindowUserData>()
|
||||||
|
.map(|u| u.lock().unwrap().last_geometry);
|
||||||
match window.toplevel() {
|
match window.toplevel() {
|
||||||
Kind::Xdg(toplevel) => {
|
Kind::Xdg(toplevel) => {
|
||||||
toplevel.with_pending_state(|state| {
|
toplevel.with_pending_state(|state| {
|
||||||
|
|
@ -200,12 +227,7 @@ impl FloatingLayout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(last_location) = last_geometry.map(|g| g.loc) {
|
if let Some(last_location) = last_geometry.map(|g| g.loc) {
|
||||||
space.map_window(
|
space.map_window(&window, last_location, FLOATING_INDEX, true);
|
||||||
&window,
|
|
||||||
last_location,
|
|
||||||
FLOATING_INDEX,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ use crate::{
|
||||||
utils::prelude::*,
|
utils::prelude::*,
|
||||||
wayland::protocols::{
|
wayland::protocols::{
|
||||||
toplevel_info::ToplevelInfoState,
|
toplevel_info::ToplevelInfoState,
|
||||||
toplevel_management::{ToplevelManagementState, ManagementCapabilities},
|
toplevel_management::{ManagementCapabilities, ToplevelManagementState},
|
||||||
workspace::{
|
workspace::{
|
||||||
WorkspaceCapabilities, WorkspaceGroupHandle, WorkspaceHandle, WorkspaceState,
|
WorkspaceCapabilities, WorkspaceGroupHandle, WorkspaceHandle, WorkspaceState,
|
||||||
WorkspaceUpdateGuard,
|
WorkspaceUpdateGuard,
|
||||||
|
|
@ -36,8 +36,8 @@ use crate::{
|
||||||
|
|
||||||
pub const MAX_WORKSPACES: usize = 10;
|
pub const MAX_WORKSPACES: usize = 10;
|
||||||
pub mod focus;
|
pub mod focus;
|
||||||
pub mod layout;
|
|
||||||
pub mod grabs;
|
pub mod grabs;
|
||||||
|
pub mod layout;
|
||||||
mod workspace;
|
mod workspace;
|
||||||
pub use self::workspace::*;
|
pub use self::workspace::*;
|
||||||
|
|
||||||
|
|
@ -90,10 +90,14 @@ impl Shell {
|
||||||
let toplevel_info_state = ToplevelInfoState::new(
|
let toplevel_info_state = ToplevelInfoState::new(
|
||||||
dh,
|
dh,
|
||||||
//|client| client.get_data::<ClientState>().unwrap().privileged,
|
//|client| client.get_data::<ClientState>().unwrap().privileged,
|
||||||
|_| true);
|
|_| true,
|
||||||
|
);
|
||||||
let toplevel_management_state = ToplevelManagementState::new::<State, _>(
|
let toplevel_management_state = ToplevelManagementState::new::<State, _>(
|
||||||
dh,
|
dh,
|
||||||
vec![ManagementCapabilities::Close, ManagementCapabilities::Activate],
|
vec![
|
||||||
|
ManagementCapabilities::Close,
|
||||||
|
ManagementCapabilities::Activate,
|
||||||
|
],
|
||||||
//|client| client.get_data::<ClientState>().unwrap().privileged,
|
//|client| client.get_data::<ClientState>().unwrap().privileged,
|
||||||
|_| true,
|
|_| true,
|
||||||
);
|
);
|
||||||
|
|
@ -461,20 +465,32 @@ impl Shell {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn outputs_for_surface<'a>(&'a self, surface: &'a WlSurface) -> impl Iterator<Item = Output> + 'a {
|
pub fn outputs_for_surface<'a>(
|
||||||
|
&'a self,
|
||||||
|
surface: &'a WlSurface,
|
||||||
|
) -> impl Iterator<Item = Output> + 'a {
|
||||||
match self.outputs.iter().find(|o| {
|
match self.outputs.iter().find(|o| {
|
||||||
let map = layer_map_for_output(o);
|
let map = layer_map_for_output(o);
|
||||||
map.layer_for_surface(surface, WindowSurfaceType::ALL).is_some()
|
map.layer_for_surface(surface, WindowSurfaceType::ALL)
|
||||||
|
.is_some()
|
||||||
}) {
|
}) {
|
||||||
Some(output) => Box::new(std::iter::once(output.clone())) as Box<dyn Iterator<Item = Output>>,
|
Some(output) => {
|
||||||
None => Box::new(self.spaces.iter().filter_map(|w| {
|
Box::new(std::iter::once(output.clone())) as Box<dyn Iterator<Item = Output>>
|
||||||
if let Some(window) = w.space.window_for_surface(surface, WindowSurfaceType::ALL) {
|
}
|
||||||
Some(w.space.outputs_for_window(&window).into_iter())
|
None => Box::new(
|
||||||
} else {
|
self.spaces
|
||||||
None
|
.iter()
|
||||||
}
|
.filter_map(|w| {
|
||||||
})
|
if let Some(window) =
|
||||||
.flatten()),
|
w.space.window_for_surface(surface, WindowSurfaceType::ALL)
|
||||||
|
{
|
||||||
|
Some(w.space.outputs_for_window(&window).into_iter())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.flatten(),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -541,20 +557,31 @@ impl Shell {
|
||||||
let workspace = &mut self.spaces[active];
|
let workspace = &mut self.spaces[active];
|
||||||
workspace.refresh(dh);
|
workspace.refresh(dh);
|
||||||
if workspace.space.windows().next().is_none()
|
if workspace.space.windows().next().is_none()
|
||||||
&& !self.workspace_state.workspace_states(&workspace.handle).map(|mut i| i.any(|s| s == &WState::Hidden)).unwrap_or(true)
|
&& !self
|
||||||
|
.workspace_state
|
||||||
|
.workspace_states(&workspace.handle)
|
||||||
|
.map(|mut i| i.any(|s| s == &WState::Hidden))
|
||||||
|
.unwrap_or(true)
|
||||||
{
|
{
|
||||||
self.workspace_state.update().add_workspace_state(&workspace.handle, WState::Hidden);
|
self.workspace_state
|
||||||
|
.update()
|
||||||
|
.add_workspace_state(&workspace.handle, WState::Hidden);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WorkspaceMode::Global { active, .. } => {
|
WorkspaceMode::Global { active, .. } => {
|
||||||
let workspace = &mut self.spaces[*active];
|
let workspace = &mut self.spaces[*active];
|
||||||
workspace.refresh(dh);
|
workspace.refresh(dh);
|
||||||
if workspace.space.windows().next().is_none()
|
if workspace.space.windows().next().is_none()
|
||||||
&& !self.workspace_state.workspace_states(&workspace.handle).map(|mut i| i.any(|s| s == &WState::Hidden)).unwrap_or(true)
|
&& !self
|
||||||
|
.workspace_state
|
||||||
|
.workspace_states(&workspace.handle)
|
||||||
|
.map(|mut i| i.any(|s| s == &WState::Hidden))
|
||||||
|
.unwrap_or(true)
|
||||||
{
|
{
|
||||||
self.workspace_state.update().add_workspace_state(&workspace.handle, WState::Hidden);
|
self.workspace_state
|
||||||
|
.update()
|
||||||
|
.add_workspace_state(&workspace.handle, WState::Hidden);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -682,9 +709,12 @@ impl Shell {
|
||||||
.toplevel_enter_workspace(&window, &new_workspace.handle);
|
.toplevel_enter_workspace(&window, &new_workspace.handle);
|
||||||
let focus_stack = new_workspace.focus_stack(&seat);
|
let focus_stack = new_workspace.focus_stack(&seat);
|
||||||
if layout::should_be_floating(&window) {
|
if layout::should_be_floating(&window) {
|
||||||
new_workspace
|
new_workspace.floating_layer.map_window(
|
||||||
.floating_layer
|
&mut new_workspace.space,
|
||||||
.map_window(&mut new_workspace.space, window, &seat, None);
|
window,
|
||||||
|
&seat,
|
||||||
|
None,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
new_workspace.tiling_layer.map_window(
|
new_workspace.tiling_layer.map_window(
|
||||||
&mut new_workspace.space,
|
&mut new_workspace.space,
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ impl Workspace {
|
||||||
.maximize_request(&mut self.space, window, output);
|
.maximize_request(&mut self.space, window, output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unmaximize_request(&mut self, window: &Window) {
|
pub fn unmaximize_request(&mut self, window: &Window) {
|
||||||
if self.fullscreen.values().any(|w| w == window) {
|
if self.fullscreen.values().any(|w| w == window) {
|
||||||
return self.unfullscreen_request(window);
|
return self.unfullscreen_request(window);
|
||||||
|
|
@ -173,14 +173,16 @@ impl Workspace {
|
||||||
if self.tiling_enabled {
|
if self.tiling_enabled {
|
||||||
for window in self.tiling_layer.windows.clone().into_iter() {
|
for window in self.tiling_layer.windows.clone().into_iter() {
|
||||||
self.tiling_layer.unmap_window(&mut self.space, &window);
|
self.tiling_layer.unmap_window(&mut self.space, &window);
|
||||||
self.floating_layer.map_window(&mut self.space, window, seat, None);
|
self.floating_layer
|
||||||
|
.map_window(&mut self.space, window, seat, None);
|
||||||
}
|
}
|
||||||
self.tiling_enabled = false;
|
self.tiling_enabled = false;
|
||||||
} else {
|
} else {
|
||||||
let focus_stack = self.focus_stack(seat);
|
let focus_stack = self.focus_stack(seat);
|
||||||
for window in self.floating_layer.windows.clone().into_iter() {
|
for window in self.floating_layer.windows.clone().into_iter() {
|
||||||
self.floating_layer.unmap_window(&mut self.space, &window);
|
self.floating_layer.unmap_window(&mut self.space, &window);
|
||||||
self.tiling_layer.map_window(&mut self.space, window, seat, focus_stack.iter())
|
self.tiling_layer
|
||||||
|
.map_window(&mut self.space, window, seat, focus_stack.iter())
|
||||||
}
|
}
|
||||||
self.tiling_enabled = true;
|
self.tiling_enabled = true;
|
||||||
}
|
}
|
||||||
|
|
@ -191,11 +193,13 @@ impl Workspace {
|
||||||
if let Some(window) = self.focus_stack(seat).iter().next().cloned() {
|
if let Some(window) = self.focus_stack(seat).iter().next().cloned() {
|
||||||
if self.tiling_layer.windows.contains(&window) {
|
if self.tiling_layer.windows.contains(&window) {
|
||||||
self.tiling_layer.unmap_window(&mut self.space, &window);
|
self.tiling_layer.unmap_window(&mut self.space, &window);
|
||||||
self.floating_layer.map_window(&mut self.space, window, seat, None);
|
self.floating_layer
|
||||||
|
.map_window(&mut self.space, window, seat, None);
|
||||||
} else if self.floating_layer.windows.contains(&window) {
|
} else if self.floating_layer.windows.contains(&window) {
|
||||||
let focus_stack = self.focus_stack(seat);
|
let focus_stack = self.focus_stack(seat);
|
||||||
self.floating_layer.unmap_window(&mut self.space, &window);
|
self.floating_layer.unmap_window(&mut self.space, &window);
|
||||||
self.tiling_layer.map_window(&mut self.space, window, seat, focus_stack.iter())
|
self.tiling_layer
|
||||||
|
.map_window(&mut self.space, window, seat, focus_stack.iter())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
79
src/state.rs
79
src/state.rs
|
|
@ -5,13 +5,11 @@ use crate::{
|
||||||
config::{Config, OutputConfig},
|
config::{Config, OutputConfig},
|
||||||
logger::LogState,
|
logger::LogState,
|
||||||
shell::Shell,
|
shell::Shell,
|
||||||
wayland::protocols::{
|
|
||||||
drm::WlDrmState,
|
|
||||||
export_dmabuf::ExportDmabufState,
|
|
||||||
output_configuration::OutputConfigurationState,
|
|
||||||
workspace::WorkspaceClientState,
|
|
||||||
},
|
|
||||||
utils::prelude::*,
|
utils::prelude::*,
|
||||||
|
wayland::protocols::{
|
||||||
|
drm::WlDrmState, export_dmabuf::ExportDmabufState,
|
||||||
|
output_configuration::OutputConfigurationState, workspace::WorkspaceClientState,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use smithay::{
|
use smithay::{
|
||||||
backend::drm::DrmNode,
|
backend::drm::DrmNode,
|
||||||
|
|
@ -22,6 +20,7 @@ use smithay::{
|
||||||
Display, DisplayHandle,
|
Display, DisplayHandle,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
utils::{Buffer, Size},
|
||||||
wayland::{
|
wayland::{
|
||||||
compositor::CompositorState,
|
compositor::CompositorState,
|
||||||
data_device::DataDeviceState,
|
data_device::DataDeviceState,
|
||||||
|
|
@ -32,7 +31,6 @@ use smithay::{
|
||||||
shm::ShmState,
|
shm::ShmState,
|
||||||
viewporter::ViewporterState,
|
viewporter::ViewporterState,
|
||||||
},
|
},
|
||||||
utils::{Size, Buffer},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
|
|
@ -210,20 +208,17 @@ impl BackendData {
|
||||||
output: &Output,
|
output: &Output,
|
||||||
state: &mut Common,
|
state: &mut Common,
|
||||||
) -> anyhow::Result<(Vec<u8>, Size<i32, Buffer>)> {
|
) -> anyhow::Result<(Vec<u8>, Size<i32, Buffer>)> {
|
||||||
|
use crate::backend::render::{render_output, AsGles2Renderer, CustomElem};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
|
use smithay::backend::renderer::{ImportAll, Renderer};
|
||||||
|
use smithay::desktop::space::RenderElement;
|
||||||
use smithay::{
|
use smithay::{
|
||||||
backend::{
|
backend::{
|
||||||
drm::NodeType,
|
drm::NodeType,
|
||||||
renderer::{
|
renderer::{gles2::Gles2Renderbuffer, Bind, ExportMem, Offscreen},
|
||||||
Bind, Offscreen, ExportMem,
|
|
||||||
gles2::Gles2Renderbuffer,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
utils::Rectangle,
|
utils::Rectangle,
|
||||||
};
|
};
|
||||||
use crate::backend::render::{render_output, AsGles2Renderer, CustomElem};
|
|
||||||
use smithay::backend::renderer::{Renderer, ImportAll};
|
|
||||||
use smithay::desktop::space::RenderElement;
|
|
||||||
|
|
||||||
fn capture<E, T, R>(
|
fn capture<E, T, R>(
|
||||||
gpu: Option<DrmNode>,
|
gpu: Option<DrmNode>,
|
||||||
|
|
@ -234,18 +229,23 @@ impl BackendData {
|
||||||
where
|
where
|
||||||
E: std::error::Error + Send + Sync + 'static,
|
E: std::error::Error + Send + Sync + 'static,
|
||||||
T: Clone + 'static,
|
T: Clone + 'static,
|
||||||
R: Renderer<Error=E, TextureId=T>
|
R: Renderer<Error = E, TextureId = T>
|
||||||
+ ImportAll
|
+ ImportAll
|
||||||
+ AsGles2Renderer
|
+ AsGles2Renderer
|
||||||
+ Offscreen<Gles2Renderbuffer>
|
+ Offscreen<Gles2Renderbuffer>
|
||||||
+ Bind<Gles2Renderbuffer>
|
+ Bind<Gles2Renderbuffer>
|
||||||
+ ExportMem,
|
+ ExportMem,
|
||||||
CustomElem: RenderElement<R>,
|
CustomElem: RenderElement<R>,
|
||||||
{
|
{
|
||||||
let size = output.geometry().size.to_f64().to_buffer(
|
let size = output
|
||||||
output.current_scale().fractional_scale(),
|
.geometry()
|
||||||
output.current_transform().into()
|
.size
|
||||||
).to_i32_round();
|
.to_f64()
|
||||||
|
.to_buffer(
|
||||||
|
output.current_scale().fractional_scale(),
|
||||||
|
output.current_transform().into(),
|
||||||
|
)
|
||||||
|
.to_i32_round();
|
||||||
let buffer = Offscreen::<Gles2Renderbuffer>::create_buffer(renderer, size)?;
|
let buffer = Offscreen::<Gles2Renderbuffer>::create_buffer(renderer, size)?;
|
||||||
renderer.bind(buffer)?;
|
renderer.bind(buffer)?;
|
||||||
render_output(
|
render_output(
|
||||||
|
|
@ -257,7 +257,8 @@ impl BackendData {
|
||||||
false,
|
false,
|
||||||
#[cfg(feature = "debug")]
|
#[cfg(feature = "debug")]
|
||||||
None,
|
None,
|
||||||
).map_err(|err| anyhow::anyhow!("Failed to render output: {:?}", err))?; // lifetime issue, grrr
|
)
|
||||||
|
.map_err(|err| anyhow::anyhow!("Failed to render output: {:?}", err))?; // lifetime issue, grrr
|
||||||
let mapping = renderer.copy_framebuffer(Rectangle::from_loc_and_size((0, 0), size))?;
|
let mapping = renderer.copy_framebuffer(Rectangle::from_loc_and_size((0, 0), size))?;
|
||||||
let data = Vec::from(renderer.map_texture(&mapping)?);
|
let data = Vec::from(renderer.map_texture(&mapping)?);
|
||||||
|
|
||||||
|
|
@ -268,12 +269,18 @@ impl BackendData {
|
||||||
BackendData::Winit(winit) => capture(None, winit.backend.renderer(), output, state),
|
BackendData::Winit(winit) => capture(None, winit.backend.renderer(), output, state),
|
||||||
BackendData::X11(x11) => capture(None, &mut x11.renderer, output, state),
|
BackendData::X11(x11) => capture(None, &mut x11.renderer, output, state),
|
||||||
BackendData::Kms(kms) => {
|
BackendData::Kms(kms) => {
|
||||||
let node = kms.target_node_for_output(output)
|
let node = kms
|
||||||
|
.target_node_for_output(output)
|
||||||
.unwrap_or(kms.primary)
|
.unwrap_or(kms.primary)
|
||||||
.node_with_type(NodeType::Render)
|
.node_with_type(NodeType::Render)
|
||||||
.with_context(|| "Unable to find node")??;
|
.with_context(|| "Unable to find node")??;
|
||||||
capture(Some(node), &mut kms.api.renderer::<Gles2Renderbuffer>(&node, &node)?, output, state)
|
capture(
|
||||||
},
|
Some(node),
|
||||||
|
&mut kms.api.renderer::<Gles2Renderbuffer>(&node, &node)?,
|
||||||
|
output,
|
||||||
|
state,
|
||||||
|
)
|
||||||
|
}
|
||||||
BackendData::Unset => unreachable!(),
|
BackendData::Unset => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -365,15 +372,17 @@ impl State {
|
||||||
drm_node: match &self.backend {
|
drm_node: match &self.backend {
|
||||||
BackendData::Kms(kms_state) => {
|
BackendData::Kms(kms_state) => {
|
||||||
match std::env::var("COSMIC_RENDER_AUTO_ASSIGN").map(|val| val.to_lowercase()) {
|
match std::env::var("COSMIC_RENDER_AUTO_ASSIGN").map(|val| val.to_lowercase()) {
|
||||||
Ok(val) if val == "y" || val == "yes" || val == "true" =>
|
Ok(val) if val == "y" || val == "yes" || val == "true" => Some(
|
||||||
Some(
|
kms_state
|
||||||
kms_state.target_node_for_output(
|
.target_node_for_output(&active_output(
|
||||||
&active_output(&self.common.last_active_seat, &self.common)
|
&self.common.last_active_seat,
|
||||||
).unwrap_or(kms_state.primary)
|
&self.common,
|
||||||
),
|
))
|
||||||
|
.unwrap_or(kms_state.primary),
|
||||||
|
),
|
||||||
_ => Some(kms_state.primary),
|
_ => Some(kms_state.primary),
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
},
|
},
|
||||||
privileged: false,
|
privileged: false,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
use crate::{input::{ActiveOutput, SeatId}, state::Common};
|
use crate::{
|
||||||
|
input::{ActiveOutput, SeatId},
|
||||||
|
state::Common,
|
||||||
|
};
|
||||||
use smithay::{
|
use smithay::{
|
||||||
utils::{Logical, Rectangle, Transform},
|
utils::{Logical, Rectangle, Transform},
|
||||||
wayland::{output::Output, seat::Seat},
|
wayland::{output::Output, seat::Seat},
|
||||||
|
|
|
||||||
|
|
@ -207,7 +207,8 @@ impl CompositorHandler for State {
|
||||||
|
|
||||||
// schedule a new render
|
// schedule a new render
|
||||||
for output in self.common.shell.outputs_for_surface(surface) {
|
for output in self.common.shell.outputs_for_surface(surface) {
|
||||||
self.backend.schedule_render(&self.common.event_loop_handle, &output);
|
self.backend
|
||||||
|
.schedule_render(&self.common.event_loop_handle, &output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,13 @@ use crate::state::State;
|
||||||
use smithay::{
|
use smithay::{
|
||||||
delegate_data_device,
|
delegate_data_device,
|
||||||
reexports::wayland_server::protocol::{wl_data_source::WlDataSource, wl_surface::WlSurface},
|
reexports::wayland_server::protocol::{wl_data_source::WlDataSource, wl_surface::WlSurface},
|
||||||
|
utils::IsAlive,
|
||||||
wayland::{
|
wayland::{
|
||||||
data_device::{
|
data_device::{
|
||||||
ClientDndGrabHandler, DataDeviceHandler, DataDeviceState, ServerDndGrabHandler,
|
ClientDndGrabHandler, DataDeviceHandler, DataDeviceState, ServerDndGrabHandler,
|
||||||
},
|
},
|
||||||
seat::Seat,
|
seat::Seat,
|
||||||
},
|
},
|
||||||
utils::IsAlive
|
|
||||||
};
|
};
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,66 +2,65 @@
|
||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
|
|
||||||
use std::{
|
use std::{cell::RefCell, time::Instant};
|
||||||
cell::RefCell,
|
|
||||||
time::Instant,
|
|
||||||
};
|
|
||||||
|
|
||||||
use smithay::{
|
use smithay::{
|
||||||
backend::{
|
backend::{
|
||||||
drm::{DrmNode, NodeType},
|
drm::{DrmNode, NodeType},
|
||||||
egl::EGLDevice,
|
egl::EGLDevice,
|
||||||
renderer::{
|
renderer::{
|
||||||
Bind,
|
gles2::{Gles2Error, Gles2Renderbuffer, Gles2Renderer},
|
||||||
Offscreen,
|
|
||||||
ExportDma,
|
|
||||||
ImportAll,
|
|
||||||
Renderer,
|
|
||||||
gles2::{Gles2Renderer, Gles2Renderbuffer, Gles2Error},
|
|
||||||
utils::with_renderer_surface_state,
|
utils::with_renderer_surface_state,
|
||||||
|
Bind, ExportDma, ImportAll, Offscreen, Renderer,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
desktop::{space::RenderElement, Kind, Window, draw_window, draw_window_popups},
|
desktop::{draw_window, draw_window_popups, space::RenderElement, Kind, Window},
|
||||||
|
reexports::wayland_server::{protocol::wl_output::WlOutput, DisplayHandle, Resource},
|
||||||
|
utils::{IsAlive, Size, Transform},
|
||||||
wayland::{
|
wayland::{
|
||||||
compositor::{get_children, with_states, SurfaceAttributes},
|
compositor::{get_children, with_states, SurfaceAttributes},
|
||||||
dmabuf::get_dmabuf,
|
dmabuf::get_dmabuf,
|
||||||
output::Output,
|
output::Output,
|
||||||
seat::CursorImageStatus,
|
seat::CursorImageStatus,
|
||||||
},
|
},
|
||||||
reexports::{
|
|
||||||
wayland_server::{DisplayHandle, Resource, protocol::wl_output::WlOutput},
|
|
||||||
},
|
|
||||||
utils::{IsAlive, Size, Transform},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
backend::render::{render_output, render_workspace, cursor::draw_cursor, AsGles2Renderer, CustomElem},
|
backend::render::{
|
||||||
|
cursor::draw_cursor, render_output, render_workspace, AsGles2Renderer, CustomElem,
|
||||||
|
},
|
||||||
state::{BackendData, ClientState, Common},
|
state::{BackendData, ClientState, Common},
|
||||||
utils::prelude::*,
|
utils::prelude::*,
|
||||||
wayland::protocols::{
|
wayland::protocols::{
|
||||||
export_dmabuf::{
|
export_dmabuf::{delegate_export_dmabuf, Capture, CaptureError, ExportDmabufHandler},
|
||||||
delegate_export_dmabuf, ExportDmabufHandler, Capture, CaptureError,
|
|
||||||
},
|
|
||||||
workspace::WorkspaceHandle,
|
workspace::WorkspaceHandle,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
impl ExportDmabufHandler for State {
|
impl ExportDmabufHandler for State {
|
||||||
fn capture_output(&mut self, _dh: &DisplayHandle, output: WlOutput, overlay_cursor: bool) -> Result<Capture, CaptureError> {
|
fn capture_output(
|
||||||
|
&mut self,
|
||||||
|
_dh: &DisplayHandle,
|
||||||
|
output: WlOutput,
|
||||||
|
overlay_cursor: bool,
|
||||||
|
) -> Result<Capture, CaptureError> {
|
||||||
let output = Output::from_resource(&output)
|
let output = Output::from_resource(&output)
|
||||||
.ok_or(CaptureError::Permanent(anyhow!("Output is gone").into()))?;
|
.ok_or(CaptureError::Permanent(anyhow!("Output is gone").into()))?;
|
||||||
|
|
||||||
let renderer = match self.backend {
|
let renderer = match self.backend {
|
||||||
BackendData::Kms(ref mut kms) => {
|
BackendData::Kms(ref mut kms) => {
|
||||||
// the kms backend just keeps its dmabufs easily accessible for capture.
|
// the kms backend just keeps its dmabufs easily accessible for capture.
|
||||||
return kms.capture_output(&output)
|
return kms
|
||||||
|
.capture_output(&output)
|
||||||
.map(|(device, dmabuf, presentation_time)| Capture {
|
.map(|(device, dmabuf, presentation_time)| Capture {
|
||||||
device,
|
device,
|
||||||
dmabuf,
|
dmabuf,
|
||||||
presentation_time,
|
presentation_time,
|
||||||
})
|
})
|
||||||
.ok_or(CaptureError::Temporary(anyhow!("Surface not initialized yet").into()));
|
.ok_or(CaptureError::Temporary(
|
||||||
},
|
anyhow!("Surface not initialized yet").into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
BackendData::Winit(ref mut winit) => winit.backend.renderer(),
|
BackendData::Winit(ref mut winit) => winit.backend.renderer(),
|
||||||
BackendData::X11(ref mut x11) => &mut x11.renderer,
|
BackendData::X11(ref mut x11) => &mut x11.renderer,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
|
|
@ -70,14 +69,20 @@ impl ExportDmabufHandler for State {
|
||||||
.context("Failed to find DrmNode")
|
.context("Failed to find DrmNode")
|
||||||
.map_err(|err| CaptureError::Permanent(err.into()))?;
|
.map_err(|err| CaptureError::Permanent(err.into()))?;
|
||||||
|
|
||||||
let size = output.geometry().size.to_f64().to_buffer(
|
let size = output
|
||||||
output.current_scale().fractional_scale(),
|
.geometry()
|
||||||
output.current_transform().into()
|
.size
|
||||||
).to_i32_round();
|
.to_f64()
|
||||||
|
.to_buffer(
|
||||||
|
output.current_scale().fractional_scale(),
|
||||||
|
output.current_transform().into(),
|
||||||
|
)
|
||||||
|
.to_i32_round();
|
||||||
let buffer = Offscreen::<Gles2Renderbuffer>::create_buffer(renderer, size)
|
let buffer = Offscreen::<Gles2Renderbuffer>::create_buffer(renderer, size)
|
||||||
.context("Failed to create render buffer for offscreen capture")
|
.context("Failed to create render buffer for offscreen capture")
|
||||||
.map_err(|err| CaptureError::Temporary(err.into()))?;
|
.map_err(|err| CaptureError::Temporary(err.into()))?;
|
||||||
renderer.bind(buffer)
|
renderer
|
||||||
|
.bind(buffer)
|
||||||
.context("Failed to bind render buffer for offscreen capture")
|
.context("Failed to bind render buffer for offscreen capture")
|
||||||
.map_err(|err| CaptureError::Temporary(err.into()))?;
|
.map_err(|err| CaptureError::Temporary(err.into()))?;
|
||||||
render_output(
|
render_output(
|
||||||
|
|
@ -90,9 +95,10 @@ impl ExportDmabufHandler for State {
|
||||||
#[cfg(feature = "debug")]
|
#[cfg(feature = "debug")]
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.context("Failed to render desktop for offscreen capture")
|
.context("Failed to render desktop for offscreen capture")
|
||||||
.map_err(|err| CaptureError::Temporary(err.into()))?;
|
.map_err(|err| CaptureError::Temporary(err.into()))?;
|
||||||
let dmabuf = renderer.export_framebuffer(size)
|
let dmabuf = renderer
|
||||||
|
.export_framebuffer(size)
|
||||||
.context("Failed to export buffer for offscreen capture")
|
.context("Failed to export buffer for offscreen capture")
|
||||||
.map_err(|err| CaptureError::Temporary(err.into()))?;
|
.map_err(|err| CaptureError::Temporary(err.into()))?;
|
||||||
|
|
||||||
|
|
@ -103,10 +109,21 @@ impl ExportDmabufHandler for State {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn capture_workspace(&mut self, _dh: &DisplayHandle, workspace: WorkspaceHandle, wl_output: WlOutput, overlay_cursor: bool) -> Result<Capture, CaptureError> {
|
fn capture_workspace(
|
||||||
|
&mut self,
|
||||||
|
_dh: &DisplayHandle,
|
||||||
|
workspace: WorkspaceHandle,
|
||||||
|
wl_output: WlOutput,
|
||||||
|
overlay_cursor: bool,
|
||||||
|
) -> Result<Capture, CaptureError> {
|
||||||
let output = Output::from_resource(&wl_output)
|
let output = Output::from_resource(&wl_output)
|
||||||
.ok_or(CaptureError::Permanent(anyhow!("Output is gone").into()))?;
|
.ok_or(CaptureError::Permanent(anyhow!("Output is gone").into()))?;
|
||||||
let workspace = self.common.shell.spaces.iter().find(|w| w.handle == workspace)
|
let workspace = self
|
||||||
|
.common
|
||||||
|
.shell
|
||||||
|
.spaces
|
||||||
|
.iter()
|
||||||
|
.find(|w| w.handle == workspace)
|
||||||
.ok_or(CaptureError::Permanent(anyhow!("Workspace is gone").into()))?
|
.ok_or(CaptureError::Permanent(anyhow!("Workspace is gone").into()))?
|
||||||
.idx;
|
.idx;
|
||||||
if self.common.shell.active_space(&output).idx == workspace {
|
if self.common.shell.active_space(&output).idx == workspace {
|
||||||
|
|
@ -117,50 +134,66 @@ impl ExportDmabufHandler for State {
|
||||||
let device = device_from_renderer(winit.backend.renderer())
|
let device = device_from_renderer(winit.backend.renderer())
|
||||||
.context("Failed to find DrmNode")
|
.context("Failed to find DrmNode")
|
||||||
.map_err(|err| CaptureError::Permanent(err.into()))?;
|
.map_err(|err| CaptureError::Permanent(err.into()))?;
|
||||||
capture_workspace(device, winit.backend.renderer(), &output, workspace, &mut self.common)
|
capture_workspace(
|
||||||
},
|
device,
|
||||||
|
winit.backend.renderer(),
|
||||||
|
&output,
|
||||||
|
workspace,
|
||||||
|
&mut self.common,
|
||||||
|
)
|
||||||
|
}
|
||||||
BackendData::X11(ref mut x11) => {
|
BackendData::X11(ref mut x11) => {
|
||||||
let device = device_from_renderer(&x11.renderer)
|
let device = device_from_renderer(&x11.renderer)
|
||||||
.context("Failed to find DrmNode")
|
.context("Failed to find DrmNode")
|
||||||
.map_err(|err| CaptureError::Permanent(err.into()))?;
|
.map_err(|err| CaptureError::Permanent(err.into()))?;
|
||||||
capture_workspace(device, &mut x11.renderer, &output, workspace, &mut self.common)
|
capture_workspace(
|
||||||
},
|
device,
|
||||||
|
&mut x11.renderer,
|
||||||
|
&output,
|
||||||
|
workspace,
|
||||||
|
&mut self.common,
|
||||||
|
)
|
||||||
|
}
|
||||||
BackendData::Kms(ref mut kms) => {
|
BackendData::Kms(ref mut kms) => {
|
||||||
let node = kms.target_node_for_output(&output)
|
let node = kms
|
||||||
|
.target_node_for_output(&output)
|
||||||
.unwrap_or(kms.primary)
|
.unwrap_or(kms.primary)
|
||||||
.node_with_type(NodeType::Render)
|
.node_with_type(NodeType::Render)
|
||||||
.with_context(|| "Unable to find node")
|
.with_context(|| "Unable to find node")
|
||||||
.map_err(|x| CaptureError::Permanent(x.into()))?
|
.map_err(|x| CaptureError::Permanent(x.into()))?
|
||||||
.map_err(|x| CaptureError::Permanent(x.into()))?;
|
.map_err(|x| CaptureError::Permanent(x.into()))?;
|
||||||
let mut renderer = kms.api.renderer::<Gles2Renderbuffer>(&node, &node)
|
let mut renderer = kms
|
||||||
|
.api
|
||||||
|
.renderer::<Gles2Renderbuffer>(&node, &node)
|
||||||
.with_context(|| format!("Failed to optain renderer for {:?}", node))
|
.with_context(|| format!("Failed to optain renderer for {:?}", node))
|
||||||
.map_err(|x| CaptureError::Permanent(x.into()))?;
|
.map_err(|x| CaptureError::Permanent(x.into()))?;
|
||||||
capture_workspace(
|
capture_workspace(node, &mut renderer, &output, workspace, &mut self.common)
|
||||||
node,
|
}
|
||||||
&mut renderer,
|
|
||||||
&output,
|
|
||||||
workspace,
|
|
||||||
&mut self.common,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
BackendData::Unset => unreachable!(),
|
BackendData::Unset => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn capture_toplevel(&mut self, dh: &DisplayHandle, window: Window, overlay_cursor: bool) -> Result<Capture, CaptureError> {
|
fn capture_toplevel(
|
||||||
|
&mut self,
|
||||||
|
dh: &DisplayHandle,
|
||||||
|
window: Window,
|
||||||
|
overlay_cursor: bool,
|
||||||
|
) -> Result<Capture, CaptureError> {
|
||||||
let Kind::Xdg(xdg) = window.toplevel();
|
let Kind::Xdg(xdg) = window.toplevel();
|
||||||
let surface = xdg.wl_surface();
|
let surface = xdg.wl_surface();
|
||||||
let window_transform = with_states(surface, |states| states
|
let window_transform = with_states(surface, |states| {
|
||||||
.cached_state
|
states
|
||||||
.current::<SurfaceAttributes>()
|
.cached_state
|
||||||
.buffer_transform
|
.current::<SurfaceAttributes>()
|
||||||
.into()
|
.buffer_transform
|
||||||
);
|
.into()
|
||||||
|
});
|
||||||
|
|
||||||
let workspace = self.common.shell.space_for_window(surface);
|
let workspace = self.common.shell.space_for_window(surface);
|
||||||
let pointers = if overlay_cursor && workspace.is_some() {
|
let pointers = if overlay_cursor && workspace.is_some() {
|
||||||
self.common.seats
|
self.common
|
||||||
|
.seats
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|seat| {
|
.filter_map(|seat| {
|
||||||
let cursor_status = seat
|
let cursor_status = seat
|
||||||
|
|
@ -181,14 +214,22 @@ impl ExportDmabufHandler for State {
|
||||||
let workspace = workspace.as_deref()?;
|
let workspace = workspace.as_deref()?;
|
||||||
let loc = seat.get_pointer().map(|ptr| ptr.current_location())?;
|
let loc = seat.get_pointer().map(|ptr| ptr.current_location())?;
|
||||||
let output = active_output(seat, &self.common);
|
let output = active_output(seat, &self.common);
|
||||||
|
|
||||||
if self.common.shell.active_space(&output).idx == workspace.idx {
|
if self.common.shell.active_space(&output).idx == workspace.idx {
|
||||||
let relative = self.common.shell.space_relative_output_geometry(loc, &output);
|
let relative = self
|
||||||
|
.common
|
||||||
|
.shell
|
||||||
|
.space_relative_output_geometry(loc, &output);
|
||||||
// unwrap is safe, because we got this workspace from `space_for_window`. It has to contain the window.
|
// unwrap is safe, because we got this workspace from `space_for_window`. It has to contain the window.
|
||||||
let bbox = workspace.space.window_bbox(&window).unwrap();
|
let bbox = workspace.space.window_bbox(&window).unwrap();
|
||||||
bbox.contains(relative.to_i32_round()).then_some((seat, (relative - bbox.loc.to_f64()).to_i32_round()))
|
bbox.contains(relative.to_i32_round())
|
||||||
} else { None }
|
.then_some((seat, (relative - bbox.loc.to_f64()).to_i32_round()))
|
||||||
} else { None }
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -198,7 +239,8 @@ impl ExportDmabufHandler for State {
|
||||||
let device = match self.backend {
|
let device = match self.backend {
|
||||||
BackendData::Winit(ref mut winit) => device_from_renderer(winit.backend.renderer()),
|
BackendData::Winit(ref mut winit) => device_from_renderer(winit.backend.renderer()),
|
||||||
BackendData::X11(ref x11) => device_from_renderer(&x11.renderer),
|
BackendData::X11(ref x11) => device_from_renderer(&x11.renderer),
|
||||||
BackendData::Kms(ref kms) => Ok(dh.get_client(window.toplevel().wl_surface().id())
|
BackendData::Kms(ref kms) => Ok(dh
|
||||||
|
.get_client(window.toplevel().wl_surface().id())
|
||||||
.ok()
|
.ok()
|
||||||
.with_context(|| "Unable to find matching wayland client")
|
.with_context(|| "Unable to find matching wayland client")
|
||||||
.map_err(|x| CaptureError::Permanent(x.into()))?
|
.map_err(|x| CaptureError::Permanent(x.into()))?
|
||||||
|
|
@ -209,12 +251,18 @@ impl ExportDmabufHandler for State {
|
||||||
.unwrap_or_else(|| kms.primary.clone())),
|
.unwrap_or_else(|| kms.primary.clone())),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
.context("Failed to find DrmNode")
|
.context("Failed to find DrmNode")
|
||||||
.map_err(|err| CaptureError::Permanent(err.into()))?;
|
.map_err(|err| CaptureError::Permanent(err.into()))?;
|
||||||
|
|
||||||
// first lets check, if we can just send a dmabuf from the client directly
|
// first lets check, if we can just send a dmabuf from the client directly
|
||||||
if pointers.is_empty() && window_transform == Transform::Normal && get_children(surface).is_empty() && self.common.shell.popups.find_popup(surface).is_none() {
|
if pointers.is_empty()
|
||||||
let dmabuf = with_renderer_surface_state(surface, |state| state.wl_buffer().and_then(|buf| get_dmabuf(buf).ok()));
|
&& window_transform == Transform::Normal
|
||||||
|
&& get_children(surface).is_empty()
|
||||||
|
&& self.common.shell.popups.find_popup(surface).is_none()
|
||||||
|
{
|
||||||
|
let dmabuf = with_renderer_surface_state(surface, |state| {
|
||||||
|
state.wl_buffer().and_then(|buf| get_dmabuf(buf).ok())
|
||||||
|
});
|
||||||
if let Some(dmabuf) = dmabuf {
|
if let Some(dmabuf) = dmabuf {
|
||||||
return Ok(Capture {
|
return Ok(Capture {
|
||||||
device,
|
device,
|
||||||
|
|
@ -230,47 +278,77 @@ impl ExportDmabufHandler for State {
|
||||||
BackendData::Winit(ref mut winit) => winit.backend.renderer(),
|
BackendData::Winit(ref mut winit) => winit.backend.renderer(),
|
||||||
BackendData::X11(ref mut x11) => &mut x11.renderer,
|
BackendData::X11(ref mut x11) => &mut x11.renderer,
|
||||||
BackendData::Kms(ref mut kms) => {
|
BackendData::Kms(ref mut kms) => {
|
||||||
_tmp_multirenderer = Some(kms.api.renderer::<Gles2Renderbuffer>(&device, &device)
|
_tmp_multirenderer = Some(
|
||||||
.with_context(|| format!("Failed to optain renderer for {:?}", device))
|
kms.api
|
||||||
.map_err(|x| CaptureError::Permanent(x.into()))?);
|
.renderer::<Gles2Renderbuffer>(&device, &device)
|
||||||
|
.with_context(|| format!("Failed to optain renderer for {:?}", device))
|
||||||
|
.map_err(|x| CaptureError::Permanent(x.into()))?,
|
||||||
|
);
|
||||||
_tmp_multirenderer.as_mut().unwrap().as_gles2()
|
_tmp_multirenderer.as_mut().unwrap().as_gles2()
|
||||||
},
|
}
|
||||||
BackendData::Unset => unreachable!(),
|
BackendData::Unset => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let bbox = window.bbox_with_popups();
|
let bbox = window.bbox_with_popups();
|
||||||
let size = bbox.size + Size::from((-bbox.loc.x, -bbox.loc.y));
|
let size = bbox.size + Size::from((-bbox.loc.x, -bbox.loc.y));
|
||||||
let buffer = Offscreen::<Gles2Renderbuffer>::create_buffer(renderer, size.to_buffer(1, window_transform))
|
let buffer = Offscreen::<Gles2Renderbuffer>::create_buffer(
|
||||||
.context("Failed to create render buffer for offscreen capture")
|
renderer,
|
||||||
.map_err(|err| CaptureError::Temporary(err.into()))?;
|
size.to_buffer(1, window_transform),
|
||||||
renderer.bind(buffer)
|
)
|
||||||
|
.context("Failed to create render buffer for offscreen capture")
|
||||||
|
.map_err(|err| CaptureError::Temporary(err.into()))?;
|
||||||
|
renderer
|
||||||
|
.bind(buffer)
|
||||||
.context("Failed to bind render buffer for offscreen capture")
|
.context("Failed to bind render buffer for offscreen capture")
|
||||||
.map_err(|err| CaptureError::Temporary(err.into()))?;
|
.map_err(|err| CaptureError::Temporary(err.into()))?;
|
||||||
renderer.render(size.to_physical(1), Transform::Normal, |renderer, frame| {
|
renderer
|
||||||
let log = slog_scope::logger();
|
.render(size.to_physical(1), Transform::Normal, |renderer, frame| {
|
||||||
let damage = &[window.physical_bbox_with_popups((0.0, 0.0), 1.0)];
|
let log = slog_scope::logger();
|
||||||
draw_window(renderer, frame, &window, 1.0, (0.0, 0.0), damage, &log)?;
|
let damage = &[window.physical_bbox_with_popups((0.0, 0.0), 1.0)];
|
||||||
draw_window_popups(renderer, frame, &window, 1.0, (0.0, 0.0), damage, &log)?;
|
draw_window(renderer, frame, &window, 1.0, (0.0, 0.0), damage, &log)?;
|
||||||
for (seat, loc) in pointers.into_iter() {
|
draw_window_popups(renderer, frame, &window, 1.0, (0.0, 0.0), damage, &log)?;
|
||||||
if let Some(cursor_elem) = draw_cursor::<_, CustomElem>(renderer, seat, loc, &self.common.start_time, true) {
|
for (seat, loc) in pointers.into_iter() {
|
||||||
let damage = RenderElement::<Gles2Renderer>::accumulated_damage(&cursor_elem, 1.0, None);
|
if let Some(cursor_elem) = draw_cursor::<_, CustomElem>(
|
||||||
cursor_elem.draw(renderer, frame, 1.0, loc.to_physical(1.0), &damage, &log)?;
|
renderer,
|
||||||
|
seat,
|
||||||
|
loc,
|
||||||
|
&self.common.start_time,
|
||||||
|
true,
|
||||||
|
) {
|
||||||
|
let damage = RenderElement::<Gles2Renderer>::accumulated_damage(
|
||||||
|
&cursor_elem,
|
||||||
|
1.0,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
cursor_elem.draw(
|
||||||
|
renderer,
|
||||||
|
frame,
|
||||||
|
1.0,
|
||||||
|
loc.to_physical(1.0),
|
||||||
|
&damage,
|
||||||
|
&log,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
Result::<(), Gles2Error>::Ok(())
|
||||||
Result::<(), Gles2Error>::Ok(())
|
})
|
||||||
})
|
|
||||||
.context("Failed to render window for offscreen capture")
|
.context("Failed to render window for offscreen capture")
|
||||||
.map_err(|err| CaptureError::Temporary(err.into()))?
|
.map_err(|err| CaptureError::Temporary(err.into()))?
|
||||||
.context("Failed to render window for offscreen capture")
|
.context("Failed to render window for offscreen capture")
|
||||||
.map_err(|err| CaptureError::Temporary(err.into()))?;
|
.map_err(|err| CaptureError::Temporary(err.into()))?;
|
||||||
|
|
||||||
let dmabuf = renderer.export_framebuffer(size.to_buffer(1, window_transform))
|
let dmabuf = renderer
|
||||||
|
.export_framebuffer(size.to_buffer(1, window_transform))
|
||||||
.context("Failed to export buffer for offscreen capture")
|
.context("Failed to export buffer for offscreen capture")
|
||||||
.map_err(|err| CaptureError::Temporary(err.into()))?;
|
.map_err(|err| CaptureError::Temporary(err.into()))?;
|
||||||
|
|
||||||
Ok(Capture { device, dmabuf, presentation_time: Instant::now() })
|
Ok(Capture {
|
||||||
|
device,
|
||||||
|
dmabuf,
|
||||||
|
presentation_time: Instant::now(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_time(&mut self) -> Instant {
|
fn start_time(&mut self) -> Instant {
|
||||||
self.common.start_time
|
self.common.start_time
|
||||||
}
|
}
|
||||||
|
|
@ -286,22 +364,28 @@ fn capture_workspace<E, T, R>(
|
||||||
where
|
where
|
||||||
E: std::error::Error + Send + Sync + 'static,
|
E: std::error::Error + Send + Sync + 'static,
|
||||||
T: Clone + 'static,
|
T: Clone + 'static,
|
||||||
R: Renderer<Error=E, TextureId=T>
|
R: Renderer<Error = E, TextureId = T>
|
||||||
+ ImportAll
|
+ ImportAll
|
||||||
+ AsGles2Renderer
|
+ AsGles2Renderer
|
||||||
+ Offscreen<Gles2Renderbuffer>
|
+ Offscreen<Gles2Renderbuffer>
|
||||||
+ Bind<Gles2Renderbuffer>
|
+ Bind<Gles2Renderbuffer>
|
||||||
+ ExportDma,
|
+ ExportDma,
|
||||||
CustomElem: RenderElement<R>,
|
CustomElem: RenderElement<R>,
|
||||||
{
|
{
|
||||||
let size = output.geometry().size.to_f64().to_buffer(
|
let size = output
|
||||||
output.current_scale().fractional_scale(),
|
.geometry()
|
||||||
output.current_transform().into()
|
.size
|
||||||
).to_i32_round();
|
.to_f64()
|
||||||
|
.to_buffer(
|
||||||
|
output.current_scale().fractional_scale(),
|
||||||
|
output.current_transform().into(),
|
||||||
|
)
|
||||||
|
.to_i32_round();
|
||||||
let buffer = Offscreen::<Gles2Renderbuffer>::create_buffer(renderer, size)
|
let buffer = Offscreen::<Gles2Renderbuffer>::create_buffer(renderer, size)
|
||||||
.context("Failed to create render buffer for offscreen capture")
|
.context("Failed to create render buffer for offscreen capture")
|
||||||
.map_err(|err| CaptureError::Temporary(err.into()))?;
|
.map_err(|err| CaptureError::Temporary(err.into()))?;
|
||||||
renderer.bind(buffer)
|
renderer
|
||||||
|
.bind(buffer)
|
||||||
.context("Failed to bind render buffer for offscreen capture")
|
.context("Failed to bind render buffer for offscreen capture")
|
||||||
.map_err(|err| CaptureError::Temporary(err.into()))?;
|
.map_err(|err| CaptureError::Temporary(err.into()))?;
|
||||||
render_workspace(
|
render_workspace(
|
||||||
|
|
@ -315,19 +399,26 @@ where
|
||||||
#[cfg(feature = "debug")]
|
#[cfg(feature = "debug")]
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.map_err(|err| anyhow!("Failed to render desktop for offscreen capture: {:?}", err)) // meh..
|
.map_err(|err| anyhow!("Failed to render desktop for offscreen capture: {:?}", err)) // meh..
|
||||||
.map_err(|err| CaptureError::Temporary(err.into()))?;
|
.map_err(|err| CaptureError::Temporary(err.into()))?;
|
||||||
let dmabuf = renderer.export_framebuffer(size)
|
let dmabuf = renderer
|
||||||
|
.export_framebuffer(size)
|
||||||
.context("Failed to export buffer for offscreen capture")
|
.context("Failed to export buffer for offscreen capture")
|
||||||
.map_err(|err| CaptureError::Temporary(err.into()))?;
|
.map_err(|err| CaptureError::Temporary(err.into()))?;
|
||||||
|
|
||||||
Ok(Capture { device: gpu, dmabuf, presentation_time: Instant::now() })
|
Ok(Capture {
|
||||||
|
device: gpu,
|
||||||
|
dmabuf,
|
||||||
|
presentation_time: Instant::now(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn device_from_renderer(renderer: &Gles2Renderer) -> Result<DrmNode> {
|
fn device_from_renderer(renderer: &Gles2Renderer) -> Result<DrmNode> {
|
||||||
EGLDevice::device_for_display(renderer.egl_context().display())?
|
EGLDevice::device_for_display(renderer.egl_context().display())?
|
||||||
.try_get_render_node()?
|
.try_get_render_node()?
|
||||||
.ok_or(anyhow!("No node associated with context (software context?)"))
|
.ok_or(anyhow!(
|
||||||
|
"No node associated with context (software context?)"
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
delegate_export_dmabuf!(State);
|
delegate_export_dmabuf!(State);
|
||||||
|
|
|
||||||
|
|
@ -19,18 +19,19 @@ impl ToplevelManagementHandler for State {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn activate(&mut self, dh: &DisplayHandle, window: &Window, seat: Option<Seat<Self>>) {
|
fn activate(&mut self, dh: &DisplayHandle, window: &Window, seat: Option<Seat<Self>>) {
|
||||||
if let Some(idx) = self.common.shell.space_for_window(window.toplevel().wl_surface()).map(|w| w.idx) {
|
if let Some(idx) = self
|
||||||
|
.common
|
||||||
|
.shell
|
||||||
|
.space_for_window(window.toplevel().wl_surface())
|
||||||
|
.map(|w| w.idx)
|
||||||
|
{
|
||||||
let seat = seat.unwrap_or(self.common.last_active_seat.clone());
|
let seat = seat.unwrap_or(self.common.last_active_seat.clone());
|
||||||
let output = active_output(&seat, &self.common);
|
let output = active_output(&seat, &self.common);
|
||||||
if self.common.shell.active_space(&output).idx != idx {
|
if self.common.shell.active_space(&output).idx != idx {
|
||||||
self.common.shell.activate(&seat, &output, idx as usize);
|
self.common.shell.activate(&seat, &output, idx as usize);
|
||||||
}
|
}
|
||||||
self.common.set_focus(
|
self.common
|
||||||
dh,
|
.set_focus(dh, Some(window.toplevel().wl_surface()), &seat, None);
|
||||||
Some(window.toplevel().wl_surface()),
|
|
||||||
&seat,
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -181,7 +181,9 @@ impl XdgShellHandler for State {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
self.common.shell.move_request(&window, &seat, serial, start_data);
|
self.common
|
||||||
|
.shell
|
||||||
|
.move_request(&window, &seat, serial, start_data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,12 @@ fn unconstrain_xdg_popup(
|
||||||
if let Some(output_rect) = space
|
if let Some(output_rect) = space
|
||||||
.outputs_for_window(window)
|
.outputs_for_window(window)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.find(|o| space.output_geometry(o).map(|rect| rect.contains(anchor_point)).unwrap_or(false))
|
.find(|o| {
|
||||||
|
space
|
||||||
|
.output_geometry(o)
|
||||||
|
.map(|rect| rect.contains(anchor_point))
|
||||||
|
.unwrap_or(false)
|
||||||
|
})
|
||||||
.map(|o| space.output_geometry(&o).unwrap())
|
.map(|o| space.output_geometry(&o).unwrap())
|
||||||
{
|
{
|
||||||
// the output_rect represented relative to the parents coordinate system
|
// the output_rect represented relative to the parents coordinate system
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,8 @@ use smithay::{
|
||||||
Format, Fourcc, Modifier,
|
Format, Fourcc, Modifier,
|
||||||
},
|
},
|
||||||
reexports::wayland_server::{
|
reexports::wayland_server::{
|
||||||
backend::GlobalId, protocol::wl_buffer::WlBuffer, Client, DataInit,
|
backend::GlobalId, protocol::wl_buffer::WlBuffer, Client, DataInit, Dispatch,
|
||||||
Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
|
DisplayHandle, GlobalDispatch, New, Resource,
|
||||||
},
|
},
|
||||||
wayland::{
|
wayland::{
|
||||||
buffer::BufferHandler,
|
buffer::BufferHandler,
|
||||||
|
|
|
||||||
|
|
@ -1,46 +1,33 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
|
use smithay::{
|
||||||
|
backend::{
|
||||||
|
allocator::{dmabuf::Dmabuf, Buffer},
|
||||||
|
drm::DrmNode,
|
||||||
|
},
|
||||||
|
desktop::Window,
|
||||||
|
reexports::wayland_server::{
|
||||||
|
self, backend::GlobalId, protocol::wl_output::WlOutput, Client, Dispatch, DisplayHandle,
|
||||||
|
GlobalDispatch,
|
||||||
|
},
|
||||||
|
};
|
||||||
use std::{
|
use std::{
|
||||||
fs::File,
|
fs::File,
|
||||||
io::{Seek, SeekFrom},
|
io::{Seek, SeekFrom},
|
||||||
os::unix::io::{FromRawFd, IntoRawFd},
|
os::unix::io::{FromRawFd, IntoRawFd},
|
||||||
time::Instant,
|
time::Instant,
|
||||||
};
|
};
|
||||||
use smithay::{
|
|
||||||
backend::{
|
|
||||||
allocator::{
|
|
||||||
Buffer,
|
|
||||||
dmabuf::Dmabuf,
|
|
||||||
},
|
|
||||||
drm::DrmNode,
|
|
||||||
},
|
|
||||||
desktop::Window,
|
|
||||||
reexports::{
|
|
||||||
wayland_server::{
|
|
||||||
self,
|
|
||||||
Client,
|
|
||||||
Dispatch,
|
|
||||||
GlobalDispatch,
|
|
||||||
DisplayHandle,
|
|
||||||
backend::GlobalId,
|
|
||||||
protocol::wl_output::WlOutput,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
use cosmic_protocols::{
|
use cosmic_protocols::export_dmabuf::v1::server::{
|
||||||
export_dmabuf::v1::server::{
|
zcosmic_export_dmabuf_frame_v1::{self, CancelReason, Flags, ZcosmicExportDmabufFrameV1},
|
||||||
zcosmic_export_dmabuf_manager_v1::{self, ZcosmicExportDmabufManagerV1},
|
zcosmic_export_dmabuf_manager_v1::{self, ZcosmicExportDmabufManagerV1},
|
||||||
zcosmic_export_dmabuf_frame_v1::{self, CancelReason, Flags, ZcosmicExportDmabufFrameV1},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::wayland::protocols::{
|
use crate::wayland::protocols::{
|
||||||
toplevel_info::{ToplevelInfoHandler, window_from_handle},
|
toplevel_info::{window_from_handle, ToplevelInfoHandler},
|
||||||
workspace::{WorkspaceHandle, WorkspaceHandler},
|
workspace::{WorkspaceHandle, WorkspaceHandler},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/// Export Dmabuf global state
|
/// Export Dmabuf global state
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ExportDmabufState {
|
pub struct ExportDmabufState {
|
||||||
|
|
@ -63,9 +50,12 @@ impl ExportDmabufState {
|
||||||
F: for<'a> Fn(&'a Client) -> bool + Send + Sync + 'static,
|
F: for<'a> Fn(&'a Client) -> bool + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
ExportDmabufState {
|
ExportDmabufState {
|
||||||
global: display.create_global::<D, ZcosmicExportDmabufManagerV1, _>(1, ExportDmabufGlobalData {
|
global: display.create_global::<D, ZcosmicExportDmabufManagerV1, _>(
|
||||||
filter: Box::new(client_filter),
|
1,
|
||||||
}),
|
ExportDmabufGlobalData {
|
||||||
|
filter: Box::new(client_filter),
|
||||||
|
},
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -88,18 +78,35 @@ pub struct Capture {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ExportDmabufHandler {
|
pub trait ExportDmabufHandler {
|
||||||
fn capture_output(&mut self, dh: &DisplayHandle, output: WlOutput, overlay_cursor: bool) -> Result<Capture, CaptureError>;
|
fn capture_output(
|
||||||
fn capture_workspace(&mut self, dh: &DisplayHandle, workspace: WorkspaceHandle, output: WlOutput, overlay_cursor: bool) -> Result<Capture, CaptureError>;
|
&mut self,
|
||||||
fn capture_toplevel(&mut self, dh: &DisplayHandle, toplevel: Window, overlay_cursor: bool) -> Result<Capture, CaptureError>;
|
dh: &DisplayHandle,
|
||||||
|
output: WlOutput,
|
||||||
|
overlay_cursor: bool,
|
||||||
|
) -> Result<Capture, CaptureError>;
|
||||||
|
fn capture_workspace(
|
||||||
|
&mut self,
|
||||||
|
dh: &DisplayHandle,
|
||||||
|
workspace: WorkspaceHandle,
|
||||||
|
output: WlOutput,
|
||||||
|
overlay_cursor: bool,
|
||||||
|
) -> Result<Capture, CaptureError>;
|
||||||
|
fn capture_toplevel(
|
||||||
|
&mut self,
|
||||||
|
dh: &DisplayHandle,
|
||||||
|
toplevel: Window,
|
||||||
|
overlay_cursor: bool,
|
||||||
|
) -> Result<Capture, CaptureError>;
|
||||||
fn start_time(&mut self) -> Instant;
|
fn start_time(&mut self) -> Instant;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D> GlobalDispatch<ZcosmicExportDmabufManagerV1, ExportDmabufGlobalData, D> for ExportDmabufState
|
impl<D> GlobalDispatch<ZcosmicExportDmabufManagerV1, ExportDmabufGlobalData, D>
|
||||||
|
for ExportDmabufState
|
||||||
where
|
where
|
||||||
D: GlobalDispatch<ZcosmicExportDmabufManagerV1, ExportDmabufGlobalData>
|
D: GlobalDispatch<ZcosmicExportDmabufManagerV1, ExportDmabufGlobalData>
|
||||||
+ Dispatch<ZcosmicExportDmabufManagerV1, ()>
|
+ Dispatch<ZcosmicExportDmabufManagerV1, ()>
|
||||||
+ Dispatch<ZcosmicExportDmabufFrameV1, ()>
|
+ Dispatch<ZcosmicExportDmabufFrameV1, ()>
|
||||||
+ ExportDmabufHandler,
|
+ ExportDmabufHandler,
|
||||||
{
|
{
|
||||||
fn bind(
|
fn bind(
|
||||||
_state: &mut D,
|
_state: &mut D,
|
||||||
|
|
@ -120,11 +127,11 @@ where
|
||||||
impl<D> Dispatch<ZcosmicExportDmabufManagerV1, (), D> for ExportDmabufState
|
impl<D> Dispatch<ZcosmicExportDmabufManagerV1, (), D> for ExportDmabufState
|
||||||
where
|
where
|
||||||
D: GlobalDispatch<ZcosmicExportDmabufManagerV1, ExportDmabufGlobalData>
|
D: GlobalDispatch<ZcosmicExportDmabufManagerV1, ExportDmabufGlobalData>
|
||||||
+ Dispatch<ZcosmicExportDmabufManagerV1, ()>
|
+ Dispatch<ZcosmicExportDmabufManagerV1, ()>
|
||||||
+ Dispatch<ZcosmicExportDmabufFrameV1, ()>
|
+ Dispatch<ZcosmicExportDmabufFrameV1, ()>
|
||||||
+ ExportDmabufHandler
|
+ ExportDmabufHandler
|
||||||
+ WorkspaceHandler
|
+ WorkspaceHandler
|
||||||
+ ToplevelInfoHandler
|
+ ToplevelInfoHandler,
|
||||||
{
|
{
|
||||||
fn request(
|
fn request(
|
||||||
state: &mut D,
|
state: &mut D,
|
||||||
|
|
@ -147,7 +154,7 @@ where
|
||||||
Ok(capture) => handle_capture(capture, frame, start_time),
|
Ok(capture) => handle_capture(capture, frame, start_time),
|
||||||
Err(err) => frame.cancel(err.into()),
|
Err(err) => frame.cancel(err.into()),
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
zcosmic_export_dmabuf_manager_v1::Request::CaptureWorkspace {
|
zcosmic_export_dmabuf_manager_v1::Request::CaptureWorkspace {
|
||||||
frame,
|
frame,
|
||||||
overlay_cursor,
|
overlay_cursor,
|
||||||
|
|
@ -157,14 +164,19 @@ where
|
||||||
let frame = data_init.init(frame, ());
|
let frame = data_init.init(frame, ());
|
||||||
match state.workspace_state().workspace_handle(&workspace) {
|
match state.workspace_state().workspace_handle(&workspace) {
|
||||||
Some(workspace) => {
|
Some(workspace) => {
|
||||||
match state.capture_workspace(dhandle, workspace, output, overlay_cursor != 0) {
|
match state.capture_workspace(
|
||||||
|
dhandle,
|
||||||
|
workspace,
|
||||||
|
output,
|
||||||
|
overlay_cursor != 0,
|
||||||
|
) {
|
||||||
Ok(capture) => handle_capture(capture, frame, start_time),
|
Ok(capture) => handle_capture(capture, frame, start_time),
|
||||||
Err(err) => frame.cancel(err.into()),
|
Err(err) => frame.cancel(err.into()),
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
None => frame.cancel(CancelReason::Permanent),
|
None => frame.cancel(CancelReason::Permanent),
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
zcosmic_export_dmabuf_manager_v1::Request::CaptureToplevel {
|
zcosmic_export_dmabuf_manager_v1::Request::CaptureToplevel {
|
||||||
frame,
|
frame,
|
||||||
overlay_cursor,
|
overlay_cursor,
|
||||||
|
|
@ -177,36 +189,38 @@ where
|
||||||
Ok(capture) => handle_capture(capture, frame, start_time),
|
Ok(capture) => handle_capture(capture, frame, start_time),
|
||||||
Err(err) => frame.cancel(err.into()),
|
Err(err) => frame.cancel(err.into()),
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
None => frame.cancel(CancelReason::Permanent),
|
None => frame.cancel(CancelReason::Permanent),
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
zcosmic_export_dmabuf_manager_v1::Request::Destroy => {},
|
zcosmic_export_dmabuf_manager_v1::Request::Destroy => {}
|
||||||
_ => {},
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<CaptureError> for CancelReason {
|
impl From<CaptureError> for CancelReason {
|
||||||
fn from(err: CaptureError) -> Self {
|
fn from(err: CaptureError) -> Self {
|
||||||
match err {
|
match err {
|
||||||
CaptureError::Temporary(err) => {
|
CaptureError::Temporary(err) => {
|
||||||
slog_scope::debug!("Temporary Capture Error: {}", err);
|
slog_scope::debug!("Temporary Capture Error: {}", err);
|
||||||
CancelReason::Temporary
|
CancelReason::Temporary
|
||||||
},
|
}
|
||||||
CaptureError::Permanent(err) => {
|
CaptureError::Permanent(err) => {
|
||||||
slog_scope::warn!("Permanent Capture Error: {}", err);
|
slog_scope::warn!("Permanent Capture Error: {}", err);
|
||||||
CancelReason::Permanent
|
CancelReason::Permanent
|
||||||
},
|
|
||||||
CaptureError::Resizing => {
|
|
||||||
CancelReason::Resizing
|
|
||||||
}
|
}
|
||||||
}
|
CaptureError::Resizing => CancelReason::Resizing,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_capture(capture: Capture, frame: ZcosmicExportDmabufFrameV1, start_time: Instant) {
|
fn handle_capture(capture: Capture, frame: ZcosmicExportDmabufFrameV1, start_time: Instant) {
|
||||||
let Capture { device, dmabuf, presentation_time } = capture;
|
let Capture {
|
||||||
|
device,
|
||||||
|
dmabuf,
|
||||||
|
presentation_time,
|
||||||
|
} = capture;
|
||||||
let format = dmabuf.format();
|
let format = dmabuf.format();
|
||||||
let modifier: u64 = format.modifier.into();
|
let modifier: u64 = format.modifier.into();
|
||||||
|
|
||||||
|
|
@ -224,7 +238,11 @@ fn handle_capture(capture: Capture, frame: ZcosmicExportDmabufFrameV1, start_tim
|
||||||
dmabuf.num_planes() as u32,
|
dmabuf.num_planes() as u32,
|
||||||
);
|
);
|
||||||
|
|
||||||
for (i, (handle, (offset, stride))) in dmabuf.handles().zip(dmabuf.offsets().zip(dmabuf.strides())).enumerate() {
|
for (i, (handle, (offset, stride))) in dmabuf
|
||||||
|
.handles()
|
||||||
|
.zip(dmabuf.offsets().zip(dmabuf.strides()))
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
let mut file = unsafe { File::from_raw_fd(handle) };
|
let mut file = unsafe { File::from_raw_fd(handle) };
|
||||||
let size = match file.seek(SeekFrom::End(0)) {
|
let size = match file.seek(SeekFrom::End(0)) {
|
||||||
Ok(size) => size,
|
Ok(size) => size,
|
||||||
|
|
@ -240,31 +258,20 @@ fn handle_capture(capture: Capture, frame: ZcosmicExportDmabufFrameV1, start_tim
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let handle = file.into_raw_fd();
|
let handle = file.into_raw_fd();
|
||||||
frame.object(
|
frame.object(i as u32, handle, size as u32, offset, stride, i as u32);
|
||||||
i as u32,
|
|
||||||
handle,
|
|
||||||
size as u32,
|
|
||||||
offset,
|
|
||||||
stride,
|
|
||||||
i as u32,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let duration = presentation_time.duration_since(start_time);
|
let duration = presentation_time.duration_since(start_time);
|
||||||
let (tv_sec, tv_nsec) = (duration.as_secs(), duration.subsec_nanos());
|
let (tv_sec, tv_nsec) = (duration.as_secs(), duration.subsec_nanos());
|
||||||
frame.ready(
|
frame.ready((tv_sec >> 32) as u32, (tv_sec & 0xFFFFFFFF) as u32, tv_nsec);
|
||||||
(tv_sec >> 32) as u32,
|
|
||||||
(tv_sec & 0xFFFFFFFF) as u32,
|
|
||||||
tv_nsec,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D> Dispatch<ZcosmicExportDmabufFrameV1, (), D> for ExportDmabufState
|
impl<D> Dispatch<ZcosmicExportDmabufFrameV1, (), D> for ExportDmabufState
|
||||||
where
|
where
|
||||||
D: GlobalDispatch<ZcosmicExportDmabufManagerV1, ExportDmabufGlobalData>
|
D: GlobalDispatch<ZcosmicExportDmabufManagerV1, ExportDmabufGlobalData>
|
||||||
+ Dispatch<ZcosmicExportDmabufManagerV1, ()>
|
+ Dispatch<ZcosmicExportDmabufManagerV1, ()>
|
||||||
+ Dispatch<ZcosmicExportDmabufFrameV1, ()>
|
+ Dispatch<ZcosmicExportDmabufFrameV1, ()>
|
||||||
+ ExportDmabufHandler,
|
+ ExportDmabufHandler,
|
||||||
{
|
{
|
||||||
fn request(
|
fn request(
|
||||||
_state: &mut D,
|
_state: &mut D,
|
||||||
|
|
@ -276,8 +283,8 @@ where
|
||||||
_data_init: &mut wayland_server::DataInit<'_, D>,
|
_data_init: &mut wayland_server::DataInit<'_, D>,
|
||||||
) {
|
) {
|
||||||
match request {
|
match request {
|
||||||
zcosmic_export_dmabuf_frame_v1::Request::Destroy => {},
|
zcosmic_export_dmabuf_frame_v1::Request::Destroy => {}
|
||||||
_ => {},
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,7 @@ use smithay::{
|
||||||
wayland_server::{
|
wayland_server::{
|
||||||
backend::{ClientId, GlobalId, ObjectId},
|
backend::{ClientId, GlobalId, ObjectId},
|
||||||
protocol::wl_output::WlOutput,
|
protocol::wl_output::WlOutput,
|
||||||
Client, DataInit, Dispatch, DisplayHandle,
|
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
|
||||||
GlobalDispatch, New, Resource,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
utils::{Logical, Physical, Point, Size, Transform},
|
utils::{Logical, Physical, Point, Size, Transform},
|
||||||
|
|
@ -133,8 +132,7 @@ struct OutputStateInner {
|
||||||
}
|
}
|
||||||
type OutputState = Mutex<OutputStateInner>;
|
type OutputState = Mutex<OutputStateInner>;
|
||||||
|
|
||||||
impl<D> GlobalDispatch<ZwlrOutputManagerV1, OutputMngrGlobalData, D>
|
impl<D> GlobalDispatch<ZwlrOutputManagerV1, OutputMngrGlobalData, D> for OutputConfigurationState<D>
|
||||||
for OutputConfigurationState<D>
|
|
||||||
where
|
where
|
||||||
D: GlobalDispatch<ZwlrOutputManagerV1, OutputMngrGlobalData>
|
D: GlobalDispatch<ZwlrOutputManagerV1, OutputMngrGlobalData>
|
||||||
+ Dispatch<ZwlrOutputManagerV1, OutputMngrInstanceData>
|
+ Dispatch<ZwlrOutputManagerV1, OutputMngrInstanceData>
|
||||||
|
|
@ -176,8 +174,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D> Dispatch<ZwlrOutputManagerV1, OutputMngrInstanceData, D>
|
impl<D> Dispatch<ZwlrOutputManagerV1, OutputMngrInstanceData, D> for OutputConfigurationState<D>
|
||||||
for OutputConfigurationState<D>
|
|
||||||
where
|
where
|
||||||
D: GlobalDispatch<ZwlrOutputManagerV1, OutputMngrGlobalData>
|
D: GlobalDispatch<ZwlrOutputManagerV1, OutputMngrGlobalData>
|
||||||
+ Dispatch<ZwlrOutputManagerV1, OutputMngrInstanceData>
|
+ Dispatch<ZwlrOutputManagerV1, OutputMngrInstanceData>
|
||||||
|
|
@ -279,8 +276,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D> Dispatch<ZwlrOutputConfigurationV1, PendingConfiguration, D>
|
impl<D> Dispatch<ZwlrOutputConfigurationV1, PendingConfiguration, D> for OutputConfigurationState<D>
|
||||||
for OutputConfigurationState<D>
|
|
||||||
where
|
where
|
||||||
D: GlobalDispatch<ZwlrOutputManagerV1, OutputMngrGlobalData>
|
D: GlobalDispatch<ZwlrOutputManagerV1, OutputMngrGlobalData>
|
||||||
+ Dispatch<ZwlrOutputManagerV1, OutputMngrInstanceData>
|
+ Dispatch<ZwlrOutputManagerV1, OutputMngrInstanceData>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
use std::{
|
use std::{collections::HashMap, sync::Mutex};
|
||||||
collections::HashMap,
|
|
||||||
sync::Mutex,
|
|
||||||
};
|
|
||||||
|
|
||||||
use smithay::{
|
use smithay::{
|
||||||
desktop::Window,
|
desktop::Window,
|
||||||
|
|
@ -12,11 +9,10 @@ use smithay::{
|
||||||
wayland_server::{
|
wayland_server::{
|
||||||
backend::{ClientId, GlobalId, ObjectId},
|
backend::{ClientId, GlobalId, ObjectId},
|
||||||
protocol::wl_surface::WlSurface,
|
protocol::wl_surface::WlSurface,
|
||||||
Client, DataInit, Dispatch, DisplayHandle,
|
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
|
||||||
GlobalDispatch, New, Resource,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
utils::{IsAlive, Rectangle, Logical},
|
utils::{IsAlive, Logical, Rectangle},
|
||||||
wayland::{
|
wayland::{
|
||||||
compositor::with_states, output::Output, shell::xdg::XdgToplevelSurfaceRoleAttributes,
|
compositor::with_states, output::Output, shell::xdg::XdgToplevelSurfaceRoleAttributes,
|
||||||
},
|
},
|
||||||
|
|
@ -68,21 +64,18 @@ pub type ToplevelHandleState = Mutex<ToplevelHandleStateInner>;
|
||||||
|
|
||||||
impl ToplevelHandleStateInner {
|
impl ToplevelHandleStateInner {
|
||||||
fn from_window(window: &Window) -> ToplevelHandleState {
|
fn from_window(window: &Window) -> ToplevelHandleState {
|
||||||
ToplevelHandleState::new(
|
ToplevelHandleState::new(ToplevelHandleStateInner {
|
||||||
ToplevelHandleStateInner {
|
outputs: Vec::new(),
|
||||||
outputs: Vec::new(),
|
workspaces: Vec::new(),
|
||||||
workspaces: Vec::new(),
|
title: String::new(),
|
||||||
title: String::new(),
|
app_id: String::new(),
|
||||||
app_id: String::new(),
|
states: Vec::new(),
|
||||||
states: Vec::new(),
|
window: window.clone(),
|
||||||
window: window.clone(),
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D> GlobalDispatch<ZcosmicToplevelInfoV1, ToplevelInfoGlobalData, D>
|
impl<D> GlobalDispatch<ZcosmicToplevelInfoV1, ToplevelInfoGlobalData, D> for ToplevelInfoState<D>
|
||||||
for ToplevelInfoState<D>
|
|
||||||
where
|
where
|
||||||
D: GlobalDispatch<ZcosmicToplevelInfoV1, ToplevelInfoGlobalData>
|
D: GlobalDispatch<ZcosmicToplevelInfoV1, ToplevelInfoGlobalData>
|
||||||
+ Dispatch<ZcosmicToplevelInfoV1, ()>
|
+ Dispatch<ZcosmicToplevelInfoV1, ()>
|
||||||
|
|
@ -256,8 +249,7 @@ where
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
state.rectangles
|
state.rectangles.retain(|_, (surface, _)| surface.alive());
|
||||||
.retain(|_, (surface, _)| surface.alive());
|
|
||||||
if window.alive() {
|
if window.alive() {
|
||||||
std::mem::drop(state);
|
std::mem::drop(state);
|
||||||
for instance in &self.instances {
|
for instance in &self.instances {
|
||||||
|
|
@ -476,13 +468,7 @@ fn send_toplevel_to_client<D>(
|
||||||
pub fn window_from_handle(handle: ZcosmicToplevelHandleV1) -> Option<Window> {
|
pub fn window_from_handle(handle: ZcosmicToplevelHandleV1) -> Option<Window> {
|
||||||
handle
|
handle
|
||||||
.data::<ToplevelHandleState>()
|
.data::<ToplevelHandleState>()
|
||||||
.map(|state|
|
.map(|state| state.lock().unwrap().window.clone())
|
||||||
state
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.window
|
|
||||||
.clone()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! delegate_toplevel_info {
|
macro_rules! delegate_toplevel_info {
|
||||||
|
|
|
||||||
|
|
@ -2,29 +2,21 @@
|
||||||
|
|
||||||
use smithay::{
|
use smithay::{
|
||||||
desktop::Window,
|
desktop::Window,
|
||||||
reexports::{
|
reexports::wayland_server::{
|
||||||
wayland_server::{
|
backend::{ClientId, GlobalId, ObjectId},
|
||||||
backend::{ClientId, GlobalId, ObjectId},
|
protocol::wl_surface::WlSurface,
|
||||||
protocol::wl_surface::WlSurface,
|
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
|
||||||
Client, DataInit, Dispatch, DisplayHandle,
|
|
||||||
GlobalDispatch, New, Resource,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
utils::{Logical, Rectangle},
|
utils::{Logical, Rectangle},
|
||||||
wayland::{
|
wayland::{output::Output, seat::Seat},
|
||||||
output::Output,
|
|
||||||
seat::Seat,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
use cosmic_protocols::toplevel_management::v1::server::{
|
|
||||||
zcosmic_toplevel_manager_v1::{self, ZcosmicToplevelManagerV1},
|
|
||||||
};
|
|
||||||
pub use cosmic_protocols::toplevel_management::v1::server::zcosmic_toplevel_manager_v1::ZcosmicToplelevelManagementCapabilitiesV1 as ManagementCapabilities;
|
pub use cosmic_protocols::toplevel_management::v1::server::zcosmic_toplevel_manager_v1::ZcosmicToplelevelManagementCapabilitiesV1 as ManagementCapabilities;
|
||||||
|
use cosmic_protocols::toplevel_management::v1::server::zcosmic_toplevel_manager_v1::{
|
||||||
|
self, ZcosmicToplevelManagerV1,
|
||||||
|
};
|
||||||
|
|
||||||
use super::toplevel_info::{ToplevelInfoHandler, ToplevelState, window_from_handle};
|
use super::toplevel_info::{window_from_handle, ToplevelInfoHandler, ToplevelState};
|
||||||
|
|
||||||
|
|
||||||
pub struct ToplevelManagementState {
|
pub struct ToplevelManagementState {
|
||||||
instances: Vec<ZcosmicToplevelManagerV1>,
|
instances: Vec<ZcosmicToplevelManagerV1>,
|
||||||
|
|
@ -52,13 +44,17 @@ pub struct ToplevelManagerGlobalData {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToplevelManagementState {
|
impl ToplevelManagementState {
|
||||||
pub fn new<D, F>(dh: &DisplayHandle, capabilities: Vec<ManagementCapabilities>, client_filter: F) -> ToplevelManagementState
|
pub fn new<D, F>(
|
||||||
|
dh: &DisplayHandle,
|
||||||
|
capabilities: Vec<ManagementCapabilities>,
|
||||||
|
client_filter: F,
|
||||||
|
) -> ToplevelManagementState
|
||||||
where
|
where
|
||||||
D: GlobalDispatch<ZcosmicToplevelManagerV1, ToplevelManagerGlobalData>
|
D: GlobalDispatch<ZcosmicToplevelManagerV1, ToplevelManagerGlobalData>
|
||||||
+ Dispatch<ZcosmicToplevelManagerV1, ()>
|
+ Dispatch<ZcosmicToplevelManagerV1, ()>
|
||||||
+ ToplevelManagementHandler
|
+ ToplevelManagementHandler
|
||||||
+ 'static,
|
+ 'static,
|
||||||
F: for<'a> Fn(&'a Client) -> bool + Send + Sync + 'static
|
F: for<'a> Fn(&'a Client) -> bool + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
let global = dh.create_global::<D, ZcosmicToplevelManagerV1, _>(
|
let global = dh.create_global::<D, ZcosmicToplevelManagerV1, _>(
|
||||||
1,
|
1,
|
||||||
|
|
@ -73,7 +69,11 @@ impl ToplevelManagementState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rectangle_for(&mut self, window: &Window, client: &ClientId) -> Option<(WlSurface, Rectangle<i32, Logical>)> {
|
pub fn rectangle_for(
|
||||||
|
&mut self,
|
||||||
|
window: &Window,
|
||||||
|
client: &ClientId,
|
||||||
|
) -> Option<(WlSurface, Rectangle<i32, Logical>)> {
|
||||||
if let Some(state) = window.user_data().get::<ToplevelState>() {
|
if let Some(state) = window.user_data().get::<ToplevelState>() {
|
||||||
state.lock().unwrap().rectangles.get(client).cloned()
|
state.lock().unwrap().rectangles.get(client).cloned()
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -86,12 +86,13 @@ impl ToplevelManagementState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D> GlobalDispatch<ZcosmicToplevelManagerV1, ToplevelManagerGlobalData, D> for ToplevelManagementState
|
impl<D> GlobalDispatch<ZcosmicToplevelManagerV1, ToplevelManagerGlobalData, D>
|
||||||
|
for ToplevelManagementState
|
||||||
where
|
where
|
||||||
D: GlobalDispatch<ZcosmicToplevelManagerV1, ToplevelManagerGlobalData>
|
D: GlobalDispatch<ZcosmicToplevelManagerV1, ToplevelManagerGlobalData>
|
||||||
+ Dispatch<ZcosmicToplevelManagerV1, ()>
|
+ Dispatch<ZcosmicToplevelManagerV1, ()>
|
||||||
+ ToplevelManagementHandler
|
+ ToplevelManagementHandler
|
||||||
+ 'static
|
+ 'static,
|
||||||
{
|
{
|
||||||
fn bind(
|
fn bind(
|
||||||
state: &mut D,
|
state: &mut D,
|
||||||
|
|
@ -120,13 +121,12 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl<D> Dispatch<ZcosmicToplevelManagerV1, (), D> for ToplevelManagementState
|
impl<D> Dispatch<ZcosmicToplevelManagerV1, (), D> for ToplevelManagementState
|
||||||
where
|
where
|
||||||
D: GlobalDispatch<ZcosmicToplevelManagerV1, ToplevelManagerGlobalData>
|
D: GlobalDispatch<ZcosmicToplevelManagerV1, ToplevelManagerGlobalData>
|
||||||
+ Dispatch<ZcosmicToplevelManagerV1, ()>
|
+ Dispatch<ZcosmicToplevelManagerV1, ()>
|
||||||
+ ToplevelManagementHandler
|
+ ToplevelManagementHandler
|
||||||
+ 'static
|
+ 'static,
|
||||||
{
|
{
|
||||||
fn request(
|
fn request(
|
||||||
state: &mut D,
|
state: &mut D,
|
||||||
|
|
@ -141,36 +141,43 @@ where
|
||||||
zcosmic_toplevel_manager_v1::Request::Activate { toplevel, seat } => {
|
zcosmic_toplevel_manager_v1::Request::Activate { toplevel, seat } => {
|
||||||
let window = window_from_handle(toplevel).unwrap();
|
let window = window_from_handle(toplevel).unwrap();
|
||||||
state.activate(dh, &window, Seat::from_resource(&seat));
|
state.activate(dh, &window, Seat::from_resource(&seat));
|
||||||
},
|
}
|
||||||
zcosmic_toplevel_manager_v1::Request::Close { toplevel } => {
|
zcosmic_toplevel_manager_v1::Request::Close { toplevel } => {
|
||||||
let window = window_from_handle(toplevel).unwrap();
|
let window = window_from_handle(toplevel).unwrap();
|
||||||
state.close(dh, &window);
|
state.close(dh, &window);
|
||||||
},
|
}
|
||||||
zcosmic_toplevel_manager_v1::Request::SetFullscreen { toplevel, output } => {
|
zcosmic_toplevel_manager_v1::Request::SetFullscreen { toplevel, output } => {
|
||||||
let window = window_from_handle(toplevel).unwrap();
|
let window = window_from_handle(toplevel).unwrap();
|
||||||
state.fullscreen(dh, &window, output.as_ref().and_then(Output::from_resource))
|
state.fullscreen(dh, &window, output.as_ref().and_then(Output::from_resource))
|
||||||
},
|
}
|
||||||
zcosmic_toplevel_manager_v1::Request::UnsetFullscreen { toplevel } => {
|
zcosmic_toplevel_manager_v1::Request::UnsetFullscreen { toplevel } => {
|
||||||
let window = window_from_handle(toplevel).unwrap();
|
let window = window_from_handle(toplevel).unwrap();
|
||||||
state.unfullscreen(dh, &window);
|
state.unfullscreen(dh, &window);
|
||||||
},
|
}
|
||||||
zcosmic_toplevel_manager_v1::Request::SetMaximized { toplevel } => {
|
zcosmic_toplevel_manager_v1::Request::SetMaximized { toplevel } => {
|
||||||
let window = window_from_handle(toplevel).unwrap();
|
let window = window_from_handle(toplevel).unwrap();
|
||||||
state.maximize(dh, &window);
|
state.maximize(dh, &window);
|
||||||
},
|
}
|
||||||
zcosmic_toplevel_manager_v1::Request::UnsetMaximized { toplevel } => {
|
zcosmic_toplevel_manager_v1::Request::UnsetMaximized { toplevel } => {
|
||||||
let window = window_from_handle(toplevel).unwrap();
|
let window = window_from_handle(toplevel).unwrap();
|
||||||
state.unmaximize(dh, &window);
|
state.unmaximize(dh, &window);
|
||||||
},
|
}
|
||||||
zcosmic_toplevel_manager_v1::Request::SetMinimized { toplevel } => {
|
zcosmic_toplevel_manager_v1::Request::SetMinimized { toplevel } => {
|
||||||
let window = window_from_handle(toplevel).unwrap();
|
let window = window_from_handle(toplevel).unwrap();
|
||||||
state.minimize(dh, &window);
|
state.minimize(dh, &window);
|
||||||
},
|
}
|
||||||
zcosmic_toplevel_manager_v1::Request::UnsetMinimized { toplevel } => {
|
zcosmic_toplevel_manager_v1::Request::UnsetMinimized { toplevel } => {
|
||||||
let window = window_from_handle(toplevel).unwrap();
|
let window = window_from_handle(toplevel).unwrap();
|
||||||
state.unminimize(dh, &window);
|
state.unminimize(dh, &window);
|
||||||
},
|
}
|
||||||
zcosmic_toplevel_manager_v1::Request::SetRectangle { toplevel, surface, x, y, width, height } => {
|
zcosmic_toplevel_manager_v1::Request::SetRectangle {
|
||||||
|
toplevel,
|
||||||
|
surface,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
} => {
|
||||||
let window = window_from_handle(toplevel).unwrap();
|
let window = window_from_handle(toplevel).unwrap();
|
||||||
if let Some(toplevel_state) = window.user_data().get::<ToplevelState>() {
|
if let Some(toplevel_state) = window.user_data().get::<ToplevelState>() {
|
||||||
let mut toplevel_state = toplevel_state.lock().unwrap();
|
let mut toplevel_state = toplevel_state.lock().unwrap();
|
||||||
|
|
@ -178,11 +185,17 @@ where
|
||||||
if width == 0 && height == 0 {
|
if width == 0 && height == 0 {
|
||||||
toplevel_state.rectangles.remove(&client);
|
toplevel_state.rectangles.remove(&client);
|
||||||
} else {
|
} else {
|
||||||
toplevel_state.rectangles.insert(client, (surface, Rectangle::from_loc_and_size((x, y), (width, height))));
|
toplevel_state.rectangles.insert(
|
||||||
|
client,
|
||||||
|
(
|
||||||
|
surface,
|
||||||
|
Rectangle::from_loc_and_size((x, y), (width, height)),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -190,7 +203,11 @@ where
|
||||||
fn destroyed(state: &mut D, client: ClientId, resource: ObjectId, _data: &()) {
|
fn destroyed(state: &mut D, client: ClientId, resource: ObjectId, _data: &()) {
|
||||||
let mng_state = state.toplevel_management_state();
|
let mng_state = state.toplevel_management_state();
|
||||||
mng_state.instances.retain(|i| i.id() != resource);
|
mng_state.instances.retain(|i| i.id() != resource);
|
||||||
if !mng_state.instances.iter().any(|i| i.client_id().map(|c| c == client).unwrap_or(false)) {
|
if !mng_state
|
||||||
|
.instances
|
||||||
|
.iter()
|
||||||
|
.any(|i| i.client_id().map(|c| c == client).unwrap_or(false))
|
||||||
|
{
|
||||||
for toplevel in state.toplevel_info_state_mut().toplevels.iter() {
|
for toplevel in state.toplevel_info_state_mut().toplevels.iter() {
|
||||||
if let Some(toplevel_state) = toplevel.user_data().get::<ToplevelState>() {
|
if let Some(toplevel_state) = toplevel.user_data().get::<ToplevelState>() {
|
||||||
toplevel_state.lock().unwrap().rectangles.remove(&client);
|
toplevel_state.lock().unwrap().rectangles.remove(&client);
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,11 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
use std::{
|
use std::{collections::HashSet, sync::Mutex};
|
||||||
collections::HashSet,
|
|
||||||
sync::Mutex,
|
|
||||||
};
|
|
||||||
|
|
||||||
use smithay::{
|
use smithay::{
|
||||||
reexports::wayland_server::{
|
reexports::wayland_server::{
|
||||||
backend::{ClientData, ClientId, GlobalId, ObjectId},
|
backend::{ClientData, ClientId, GlobalId, ObjectId},
|
||||||
Client, DataInit, Dispatch, DisplayHandle,
|
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
|
||||||
GlobalDispatch, New, Resource,
|
|
||||||
},
|
},
|
||||||
wayland::output::Output,
|
wayland::output::Output,
|
||||||
};
|
};
|
||||||
|
|
@ -142,8 +138,7 @@ pub trait WorkspaceClientHandler {
|
||||||
fn workspace_state(&self) -> &WorkspaceClientState;
|
fn workspace_state(&self) -> &WorkspaceClientState;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D> GlobalDispatch<ZcosmicWorkspaceManagerV1, WorkspaceGlobalData, D>
|
impl<D> GlobalDispatch<ZcosmicWorkspaceManagerV1, WorkspaceGlobalData, D> for WorkspaceState<D>
|
||||||
for WorkspaceState<D>
|
|
||||||
where
|
where
|
||||||
D: GlobalDispatch<ZcosmicWorkspaceManagerV1, WorkspaceGlobalData>
|
D: GlobalDispatch<ZcosmicWorkspaceManagerV1, WorkspaceGlobalData>
|
||||||
+ Dispatch<ZcosmicWorkspaceManagerV1, ()>
|
+ Dispatch<ZcosmicWorkspaceManagerV1, ()>
|
||||||
|
|
@ -477,7 +472,7 @@ where
|
||||||
.map(|w| w.states.iter())
|
.map(|w| w.states.iter())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn group_handle(
|
pub fn group_handle(
|
||||||
&self,
|
&self,
|
||||||
group: &ZcosmicWorkspaceGroupHandleV1,
|
group: &ZcosmicWorkspaceGroupHandleV1,
|
||||||
|
|
@ -493,7 +488,11 @@ where
|
||||||
) -> Option<WorkspaceHandle> {
|
) -> Option<WorkspaceHandle> {
|
||||||
self.groups
|
self.groups
|
||||||
.iter()
|
.iter()
|
||||||
.find_map(|g| g.workspaces.iter().find(|w| w.instances.contains(workspace)))
|
.find_map(|g| {
|
||||||
|
g.workspaces
|
||||||
|
.iter()
|
||||||
|
.find(|w| w.instances.contains(workspace))
|
||||||
|
})
|
||||||
.map(|w| WorkspaceHandle { id: w.id })
|
.map(|w| WorkspaceHandle { id: w.id })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue