use crate::{ state::State, utils::iced::{IcedElement, Program}, utils::prelude::SeatExt, wayland::handlers::screencopy::ScreencopySessions, }; use calloop::LoopHandle; use cosmic::Element; use cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_session_v1::InputType; use smithay::{ backend::{ input::KeyState, renderer::{ element::{ memory::MemoryRenderBufferRenderElement, surface::WaylandSurfaceRenderElement, AsRenderElements, }, ImportAll, ImportMem, Renderer, }, }, desktop::space::SpaceElement, input::{ keyboard::{KeyboardTarget, KeysymHandle, ModifiersState}, pointer::{AxisFrame, ButtonEvent, MotionEvent, PointerTarget}, Seat, }, output::Output, render_elements, utils::{IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial, Size}, }; use std::{ fmt, hash::Hash, sync::{ atomic::{AtomicU8, AtomicUsize, Ordering}, Arc, Mutex, }, }; use super::CosmicSurface; #[derive(Clone, PartialEq, Eq, Hash)] pub struct CosmicStack(IcedElement); impl fmt::Debug for CosmicStack { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.with_program(|stack| { f.debug_struct("CosmicStack") .field("internal", stack) .finish_non_exhaustive() }) } } #[derive(Debug, Clone)] pub struct CosmicStackInternal { windows: Arc>>, active: Arc, previous_keyboard: Arc, pointer_entered: Option>, previous_pointer: Arc, last_location: Arc, Serial, u32)>>>, } impl CosmicStackInternal { pub fn swap_focus(&self, focus: Focus) -> Focus { if let Some(pointer_entered) = self.pointer_entered.as_ref() { unsafe { std::mem::transmute::( pointer_entered.swap(focus as u8, Ordering::SeqCst), ) } } else { Focus::Window } } pub fn current_focus(&self) -> Focus { if let Some(pointer_entered) = self.pointer_entered.as_ref() { unsafe { std::mem::transmute::(pointer_entered.load(Ordering::SeqCst)) } } else { Focus::Window } } } const TAB_HEIGHT: i32 = 24; #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Focus { None, Header, Window, } impl CosmicStack { pub fn new( window: impl Into, handle: LoopHandle<'static, crate::state::Data>, ) -> CosmicStack { let window = window.into(); let width = window.geometry().size.w; CosmicStack(IcedElement::new( CosmicStackInternal { windows: Arc::new(Mutex::new(vec![window])), active: Arc::new(AtomicUsize::new(0)), previous_keyboard: Arc::new(AtomicUsize::new(0)), pointer_entered: None, previous_pointer: Arc::new(AtomicUsize::new(0)), last_location: Arc::new(Mutex::new(None)), }, (width, TAB_HEIGHT), handle, )) } //pub fn add_window() //pub fn remove_window() //pub fn len pub fn active(&self) -> CosmicSurface { self.0 .with_program(|p| p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)].clone()) } pub fn set_active(&self, window: &CosmicSurface) { self.0.with_program(|p| { if let Some(val) = p.windows.lock().unwrap().iter().position(|w| w == window) { let old = p.active.swap(val, Ordering::SeqCst); p.previous_keyboard.store(old, Ordering::SeqCst); p.previous_pointer.store(old, Ordering::SeqCst); } }) } pub fn surfaces(&self) -> impl Iterator { self.0.with_program(|p| { p.windows .lock() .unwrap() .iter() .cloned() .collect::>() .into_iter() }) } pub fn offset(&self) -> Point { Point::from((0, TAB_HEIGHT)) } pub fn set_geometry(&self, geo: Rectangle) { self.0.with_program(|p| { let loc = (geo.loc.x, geo.loc.y + TAB_HEIGHT); let size = (geo.size.w, geo.size.h - TAB_HEIGHT); for window in p.windows.lock().unwrap().iter() { window.set_geometry(Rectangle::from_loc_and_size(loc, size)); } }); self.0.resize(Size::from((geo.size.w, TAB_HEIGHT))); } fn keyboard_leave_if_previous( &self, seat: &Seat, data: &mut State, serial: Serial, ) -> usize { self.0.with_program(|p| { let active = p.active.load(Ordering::SeqCst); let previous = p.previous_keyboard.swap(active, Ordering::SeqCst); if previous != active { KeyboardTarget::leave(&p.windows.lock().unwrap()[previous], seat, data, serial); KeyboardTarget::enter( &p.windows.lock().unwrap()[active], seat, data, Vec::new(), //seat.keys(), serial, ) } active }) } fn pointer_leave_if_previous( &self, seat: &Seat, data: &mut State, serial: Serial, time: u32, location: Point, ) -> usize { self.0.with_program(|p| { let active = p.active.load(Ordering::SeqCst); let previous = p.previous_pointer.swap(active, Ordering::SeqCst); if previous != active { if let Some(sessions) = p.windows.lock().unwrap()[previous] .user_data() .get::() { for session in &*sessions.0.borrow() { session.cursor_leave(seat, InputType::Pointer) } } PointerTarget::leave( &p.windows.lock().unwrap()[previous], seat, data, serial, time, ); if let Some(sessions) = p.windows.lock().unwrap()[active] .user_data() .get::() { for session in &*sessions.0.borrow() { session.cursor_enter(seat, InputType::Pointer) } } PointerTarget::enter( &p.windows.lock().unwrap()[active], seat, data, &MotionEvent { location, serial, time, }, ); } active }) } } impl Program for CosmicStackInternal { type Message = (); fn view(&self) -> Element<'_, Self::Message> { cosmic::iced::widget::text("TODO").into() } } impl IsAlive for CosmicStack { fn alive(&self) -> bool { self.0 .with_program(|p| p.windows.lock().unwrap().iter().any(IsAlive::alive)) } } impl SpaceElement for CosmicStack { fn bbox(&self) -> Rectangle { self.0.with_program(|p| { let mut bbox = SpaceElement::bbox(&p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)]); bbox.size.h += TAB_HEIGHT; bbox }) } fn is_in_input_region(&self, point: &Point) -> bool { let mut point = *point; if point.y < TAB_HEIGHT as f64 { return true; } point.y -= TAB_HEIGHT as f64; self.0.with_program(|p| { SpaceElement::is_in_input_region( &p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)], &point, ) }) } fn set_activate(&self, activated: bool) { SpaceElement::set_activate(&self.0, activated); self.0.with_program(|p| { p.windows .lock() .unwrap() .iter() .for_each(|w| SpaceElement::set_activate(w, activated)) }) } fn output_enter(&self, output: &Output, overlap: Rectangle) { SpaceElement::output_enter(&self.0, output, overlap); self.0.with_program(|p| { p.windows .lock() .unwrap() .iter() .for_each(|w| SpaceElement::output_enter(w, output, overlap)) }) } fn output_leave(&self, output: &Output) { SpaceElement::output_leave(&self.0, output); self.0.with_program(|p| { p.windows .lock() .unwrap() .iter() .for_each(|w| SpaceElement::output_leave(w, output)) }) } fn geometry(&self) -> Rectangle { self.0.with_program(|p| { let mut geo = SpaceElement::geometry(&p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)]); geo.size.h += TAB_HEIGHT; geo }) } fn z_index(&self) -> u8 { self.0.with_program(|p| { SpaceElement::z_index(&p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)]) }) } fn refresh(&self) { self.0.with_program(|p| { let mut windows = p.windows.lock().unwrap(); windows.retain(IsAlive::alive); // TODO: We don't handle empty stacks properly let len = windows.len(); let _ = p .active .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |active| { (active > len).then_some(len - 1) }); windows.iter().for_each(|w| SpaceElement::refresh(w)) }) } } impl KeyboardTarget for CosmicStack { fn enter( &self, seat: &Seat, data: &mut State, keys: Vec>, serial: Serial, ) { self.0.with_program(|p| { let active = p.active.load(Ordering::SeqCst); p.previous_keyboard.store(active, Ordering::SeqCst); KeyboardTarget::enter( &p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)], seat, data, keys, serial, ) }) } fn leave(&self, seat: &Seat, data: &mut State, serial: Serial) { let active = self.keyboard_leave_if_previous(seat, data, serial); self.0.with_program(|p| { KeyboardTarget::leave(&p.windows.lock().unwrap()[active], seat, data, serial) }) } fn key( &self, seat: &Seat, data: &mut State, key: KeysymHandle<'_>, state: KeyState, serial: Serial, time: u32, ) { let active = self.keyboard_leave_if_previous(seat, data, serial); self.0.with_program(|p| { KeyboardTarget::key( &p.windows.lock().unwrap()[active], seat, data, key, state, serial, time, ) }) } fn modifiers( &self, seat: &Seat, data: &mut State, modifiers: ModifiersState, serial: Serial, ) { let active = self.keyboard_leave_if_previous(seat, data, serial); self.0.with_program(|p| { KeyboardTarget::modifiers( &p.windows.lock().unwrap()[active], seat, data, modifiers, serial, ) }) } } impl PointerTarget for CosmicStack { fn enter(&self, seat: &Seat, data: &mut State, event: &MotionEvent) { if self.0.with_program(|p| { if let Some(sessions) = p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)] .user_data() .get::() { for session in &*sessions.0.borrow() { session.cursor_enter(seat, InputType::Pointer) } } if event.location.y < TAB_HEIGHT as f64 { let focus = p.swap_focus(Focus::Header); assert_eq!(focus, Focus::None); true } else { let focus = p.swap_focus(Focus::Window); assert_eq!(focus, Focus::None); *p.last_location.lock().unwrap() = Some((event.location, event.serial, event.time)); let active = p.active.load(Ordering::SeqCst); p.previous_pointer.store(active, Ordering::SeqCst); PointerTarget::enter( &p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)], seat, data, event, ); false } }) { PointerTarget::enter(&self.0, seat, data, event) } } fn motion(&self, seat: &Seat, data: &mut State, event: &MotionEvent) { let active = self.pointer_leave_if_previous(seat, data, event.serial, event.time, event.location); if let Some((previous, next)) = self.0.with_program(|p| { if let Some(sessions) = p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)] .user_data() .get::() { for session in &*sessions.0.borrow() { let buffer_loc = (event.location.x, event.location.y); // we always screencast windows at 1x1 scale if let Some((geo, hotspot)) = seat.cursor_geometry(buffer_loc, data.common.clock.now()) { session.cursor_info(seat, InputType::Pointer, geo, hotspot); } } } if event.location.y < TAB_HEIGHT as f64 { let previous = p.swap_focus(Focus::Header); if previous == Focus::Window { PointerTarget::leave( &p.windows.lock().unwrap()[active], seat, data, event.serial, event.time, ); } Some((previous, Focus::Header)) } else { let mut event = event.clone(); event.location.y -= TAB_HEIGHT as f64; let previous = p.swap_focus(Focus::Window); if previous != Focus::Window { PointerTarget::enter(&p.windows.lock().unwrap()[active], seat, data, &event); } else { PointerTarget::motion(&p.windows.lock().unwrap()[active], seat, data, &event); } Some((previous, Focus::Window)) } }) { match (previous, next) { (Focus::Header, Focus::Header) => PointerTarget::motion(&self.0, seat, data, event), (_, Focus::Header) => PointerTarget::enter(&self.0, seat, data, event), (Focus::Header, _) => { PointerTarget::leave(&self.0, seat, data, event.serial, event.time) } _ => {} } } } fn button(&self, seat: &Seat, data: &mut State, event: &ButtonEvent) { if let Some((location, _serial, _time)) = self .0 .with_program(|p| p.last_location.lock().unwrap().clone()) { self.pointer_leave_if_previous(seat, data, event.serial, event.time, location); } match self.0.with_program(|p| p.current_focus()) { Focus::Header => PointerTarget::button(&self.0, seat, data, event), Focus::Window => self.0.with_program(|p| { PointerTarget::button( &p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)], seat, data, event, ) }), _ => {} } } fn axis(&self, seat: &Seat, data: &mut State, frame: AxisFrame) { if let Some((location, serial, time)) = self .0 .with_program(|p| p.last_location.lock().unwrap().clone()) { self.pointer_leave_if_previous(seat, data, serial, time, location); } match self.0.with_program(|p| p.current_focus()) { Focus::Header => PointerTarget::axis(&self.0, seat, data, frame), Focus::Window => self.0.with_program(|p| { PointerTarget::axis( &p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)], seat, data, frame, ) }), _ => {} } } fn leave(&self, seat: &Seat, data: &mut State, serial: Serial, time: u32) { if let Some((location, serial, time)) = self .0 .with_program(|p| p.last_location.lock().unwrap().clone()) { self.pointer_leave_if_previous(seat, data, serial, time, location); } let previous = self.0.with_program(|p| { if let Some(sessions) = p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)] .user_data() .get::() { for session in &*sessions.0.borrow() { session.cursor_leave(seat, InputType::Pointer) } } p.swap_focus(Focus::None) }); assert!(previous != Focus::None); match previous { Focus::Header => PointerTarget::leave(&self.0, seat, data, serial, time), Focus::Window => self.0.with_program(|p| { PointerTarget::leave( &p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)], seat, data, serial, time, ) }), _ => {} } } } render_elements! { pub CosmicStackRenderElement where R: ImportAll + ImportMem; Header=MemoryRenderBufferRenderElement, Window=WaylandSurfaceRenderElement, } impl AsRenderElements for CosmicStack where R: Renderer + ImportAll + ImportMem, ::TextureId: 'static, { type RenderElement = CosmicStackRenderElement; fn render_elements>( &self, renderer: &mut R, mut location: Point, scale: Scale, ) -> Vec { let mut elements = AsRenderElements::::render_elements::>( &self.0, renderer, location, scale, ); location.y += TAB_HEIGHT; elements.extend(self.0.with_program(|p| { let elements = AsRenderElements::::render_elements::>( &p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)], renderer, location, scale, ); elements })); elements.into_iter().map(C::from).collect() } }