diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index 59eaed73..e303d759 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -287,6 +287,32 @@ impl Client { connector } + pub fn get_seat_workspace(&self, seat: Seat) -> Workspace { + let res = self.send_with_response(&ClientMessage::GetSeatWorkspace { seat }); + get_response!(res, Workspace(0), GetSeatWorkspace { workspace }); + workspace + } + + pub fn set_default_workspace_capture(&self, capture: bool) { + self.send(&ClientMessage::SetDefaultWorkspaceCapture { capture }); + } + + pub fn set_workspace_capture(&self, workspace: Workspace, capture: bool) { + self.send(&ClientMessage::SetWorkspaceCapture { workspace, capture }); + } + + pub fn get_default_workspace_capture(&self) -> bool { + let res = self.send_with_response(&ClientMessage::GetDefaultWorkspaceCapture); + get_response!(res, true, GetDefaultWorkspaceCapture { capture }); + capture + } + + pub fn get_workspace_capture(&self, workspace: Workspace) -> bool { + let res = self.send_with_response(&ClientMessage::GetWorkspaceCapture { workspace }); + get_response!(res, true, GetWorkspaceCapture { capture }); + capture + } + pub fn show_workspace(&self, seat: Seat, workspace: Workspace) { self.send(&ClientMessage::ShowWorkspace { seat, workspace }); } diff --git a/jay-config/src/_private/ipc.rs b/jay-config/src/_private/ipc.rs index b2850882..e5f2def8 100644 --- a/jay-config/src/_private/ipc.rs +++ b/jay-config/src/_private/ipc.rs @@ -316,6 +316,20 @@ pub enum ClientMessage<'a> { MakeRenderDevice { device: DrmDevice, }, + GetSeatWorkspace { + seat: Seat, + }, + SetDefaultWorkspaceCapture { + capture: bool, + }, + GetDefaultWorkspaceCapture, + SetWorkspaceCapture { + workspace: Workspace, + capture: bool, + }, + GetWorkspaceCapture { + workspace: Workspace, + }, } #[derive(Encode, Decode, Debug)] @@ -409,6 +423,15 @@ pub enum Response { width: i32, height: i32, }, + GetSeatWorkspace { + workspace: Workspace, + }, + GetDefaultWorkspaceCapture { + capture: bool, + }, + GetWorkspaceCapture { + capture: bool, + }, } #[derive(Encode, Decode, Debug)] diff --git a/jay-config/src/input.rs b/jay-config/src/input.rs index e79c1952..103bd228 100644 --- a/jay-config/src/input.rs +++ b/jay-config/src/input.rs @@ -254,6 +254,14 @@ impl Seat { get!().toggle_floating(self); } + /// Returns the workspace that is currently active on the output that contains the seat's + /// cursor. + /// + /// If no such workspace exists, `exists` returns `false` for the returned workspace. + pub fn get_workspace(self) -> Workspace { + get!(Workspace(0)).get_seat_workspace(self) + } + /// Shows the workspace and sets the keyboard focus of the seat to that workspace. /// /// If the workspace doesn't currently exist, it is created on the output that contains the diff --git a/jay-config/src/lib.rs b/jay-config/src/lib.rs index 83fb35ee..9a9bc913 100644 --- a/jay-config/src/lib.rs +++ b/jay-config/src/lib.rs @@ -106,10 +106,53 @@ pub fn is_reload() -> bool { get!(false).is_reload() } +/// Sets whether new workspaces are captured by default. +/// +/// The default is `true`. +pub fn set_default_workspace_capture(capture: bool) { + get!().set_default_workspace_capture(capture) +} + +/// Returns whether new workspaces are captured by default. +pub fn get_default_workspace_capture() -> bool { + get!(true).get_default_workspace_capture() +} + +/// Toggles whether new workspaces are captured by default. +pub fn toggle_default_workspace_capture() { + let get = get!(); + get.set_default_workspace_capture(!get.get_default_workspace_capture()); +} + /// A workspace. #[derive(Encode, Decode, Copy, Clone, Debug, Hash, Eq, PartialEq)] pub struct Workspace(pub u64); +impl Workspace { + /// Returns whether this workspace existed at the time `Seat::get_workspace` was called. + pub fn exists(self) -> bool { + self.0 != 0 + } + + /// Sets whether the workspaces is captured. + /// + /// The default is determined by `set_default_workspace_capture`. + pub fn set_capture(self, capture: bool) { + get!().set_workspace_capture(self, capture) + } + + /// Returns whether the workspaces is captured. + pub fn get_capture(self) -> bool { + get!(true).get_workspace_capture(self) + } + + /// Toggles whether the workspaces is captured. + pub fn toggle_capture(self) { + let get = get!(); + get.set_workspace_capture(self, !get.get_workspace_capture(self)); + } +} + /// Returns the workspace with the given name. /// /// Workspaces are identified by their name. Calling this function alone does not create the diff --git a/jay-config/src/theme.rs b/jay-config/src/theme.rs index bd191bc3..306b15ea 100644 --- a/jay-config/src/theme.rs +++ b/jay-config/src/theme.rs @@ -245,6 +245,14 @@ pub mod colors { /// /// Default: `#ffffff`. const 11 => BAR_STATUS_TEXT_COLOR, + /// The title background color of an unfocused window that might be captured. + /// + /// Default: `#220303`. + const 12 => CAPTURED_UNFOCUSED_TITLE_BACKGROUND_COLOR, + /// The title background color of a focused window that might be captured. + /// + /// Default: `#772831`. + const 13 => CAPTURED_FOCUSED_TITLE_BACKGROUND_COLOR, } } diff --git a/src/compositor.rs b/src/compositor.rs index f0f7e7f5..85205aba 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -198,6 +198,7 @@ fn start_compositor2( testers: Default::default(), render_ctx_watchers: Default::default(), workspace_watchers: Default::default(), + default_workspace_capture: Cell::new(true), }); state.tracker.register(ClientId::from_raw(0)); create_dummy_output(&state); @@ -409,6 +410,7 @@ fn create_dummy_output(state: &Rc) { visible_on_desired_output: Default::default(), desired_output: CloneCell::new(dummy_output.global.output_id.clone()), jay_workspaces: Default::default(), + capture: Cell::new(false), }); dummy_workspace.output_link.set(Some( dummy_output.workspaces.add_last(dummy_workspace.clone()), diff --git a/src/config/handler.rs b/src/config/handler.rs index 55dbd2fb..9fb8be67 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -548,6 +548,56 @@ impl ConfigProxyHandler { }); } + fn handle_get_workspace_capture(&self, workspace: Workspace) -> Result<(), CphError> { + let name = self.get_workspace(workspace)?; + let capture = match self.state.workspaces.get(name.as_str()) { + Some(ws) => ws.capture.get(), + None => self.state.default_workspace_capture.get(), + }; + self.respond(Response::GetWorkspaceCapture { capture }); + Ok(()) + } + + fn handle_set_workspace_capture( + &self, + workspace: Workspace, + capture: bool, + ) -> Result<(), CphError> { + let name = self.get_workspace(workspace)?; + if let Some(ws) = self.state.workspaces.get(name.as_str()) { + ws.capture.set(capture); + ws.output.get().schedule_update_render_data(); + } + Ok(()) + } + + fn handle_get_default_workspace_capture(&self) { + self.respond(Response::GetDefaultWorkspaceCapture { + capture: self.state.default_workspace_capture.get(), + }); + } + + fn handle_set_default_workspace_capture(&self, capture: bool) { + self.state.default_workspace_capture.set(capture); + } + + fn handle_get_seat_workspace(&self, seat: Seat) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + let output = seat.get_output(); + let mut workspace = 0; + if !output.is_dummy { + if let Some(ws) = output.workspace.get() { + if let Some(ws) = self.workspaces_by_name.get(&ws.name) { + workspace = ws; + } + } + } + self.respond(Response::GetSeatWorkspace { + workspace: Workspace(workspace), + }); + Ok(()) + } + fn handle_show_workspace(&self, seat: Seat, ws: Workspace) -> Result<(), CphError> { let seat = self.get_seat(seat)?; let name = self.get_workspace(ws)?; @@ -984,6 +1034,10 @@ impl ConfigProxyHandler { let colorable = match colorable { UNFOCUSED_TITLE_BACKGROUND_COLOR => &colors.unfocused_title_background, FOCUSED_TITLE_BACKGROUND_COLOR => &colors.focused_title_background, + CAPTURED_UNFOCUSED_TITLE_BACKGROUND_COLOR => { + &colors.captured_unfocused_title_background + } + CAPTURED_FOCUSED_TITLE_BACKGROUND_COLOR => &colors.captured_focused_title_background, FOCUSED_INACTIVE_TITLE_BACKGROUND_COLOR => &colors.focused_inactive_title_background, BACKGROUND_COLOR => &colors.background, BAR_BACKGROUND_COLOR => &colors.bar_background, @@ -1226,6 +1280,21 @@ impl ConfigProxyHandler { ClientMessage::MakeRenderDevice { device } => self .handle_make_render_device(device) .wrn("make_render_device")?, + ClientMessage::GetSeatWorkspace { seat } => self + .handle_get_seat_workspace(seat) + .wrn("get_seat_workspace")?, + ClientMessage::SetDefaultWorkspaceCapture { capture } => { + self.handle_set_default_workspace_capture(capture) + } + ClientMessage::GetDefaultWorkspaceCapture => { + self.handle_get_default_workspace_capture() + } + ClientMessage::SetWorkspaceCapture { workspace, capture } => self + .handle_set_workspace_capture(workspace, capture) + .wrn("set_workspace_capture")?, + ClientMessage::GetWorkspaceCapture { workspace } => self + .handle_get_workspace_capture(workspace) + .wrn("get_workspace_capture")?, } Ok(()) } diff --git a/src/ifs/jay_screencast.rs b/src/ifs/jay_screencast.rs index 7befdc36..dc368237 100644 --- a/src/ifs/jay_screencast.rs +++ b/src/ifs/jay_screencast.rs @@ -181,6 +181,9 @@ impl JayScreencast { fn detach(&self) { if let Some(output) = self.output.take() { output.screencasts.remove(&(self.client.id, self.id)); + if output.screencasts.is_empty() { + output.state.damage(); + } } } @@ -323,6 +326,9 @@ impl JayScreencast { } self.detach(); if let Some(new) = &output { + if new.screencasts.is_empty() { + new.state.damage(); + } new.screencasts.set((self.client.id, self.id), self.clone()); } self.output.set(output); diff --git a/src/render/renderer/renderer.rs b/src/render/renderer/renderer.rs index f7d8557d..2a29e6e6 100644 --- a/src/render/renderer/renderer.rs +++ b/src/render/renderer/renderer.rs @@ -112,16 +112,27 @@ impl Renderer<'_> { x, y, ); + let has_captures = + !output.screencasts.is_empty() || !output.global.pending_captures.is_empty(); let rd = output.render_data.borrow_mut(); if let Some(aw) = &rd.active_workspace { - let c = theme.colors.focused_title_background.get(); - self.base.fill_boxes2(slice::from_ref(aw), &c, x, y); + let c = match has_captures && aw.captured { + true => theme.colors.captured_focused_title_background.get(), + false => theme.colors.focused_title_background.get(), + }; + self.base.fill_boxes2(slice::from_ref(&aw.rect), &c, x, y); } let c = theme.colors.separator.get(); self.base .fill_boxes2(slice::from_ref(&rd.underline), &c, x, y); let c = theme.colors.unfocused_title_background.get(); self.base.fill_boxes2(&rd.inactive_workspaces, &c, x, y); + let c = match has_captures { + true => theme.colors.captured_unfocused_title_background.get(), + false => theme.colors.unfocused_title_background.get(), + }; + self.base + .fill_boxes2(&rd.captured_inactive_workspaces, &c, x, y); let scale = output.preferred_scale.get(); for title in &rd.titles { let (x, y) = self.base.scale_point(x + title.tex_x, y + title.tex_y); diff --git a/src/state.rs b/src/state.rs index 37ec26c0..2a454ea3 100644 --- a/src/state.rs +++ b/src/state.rs @@ -123,6 +123,7 @@ pub struct State { pub testers: RefCell>>, pub render_ctx_watchers: CopyHashMap<(ClientId, JayRenderCtxId), Rc>, pub workspace_watchers: CopyHashMap<(ClientId, JayWorkspaceWatcherId), Rc>, + pub default_workspace_capture: Cell, } // impl Drop for State { diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index f52078db..2d7ac31f 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -113,6 +113,7 @@ impl ConnectorHandler { active_workspace: None, underline: Default::default(), inactive_workspaces: Default::default(), + captured_inactive_workspaces: Default::default(), titles: Default::default(), status: None, }), diff --git a/src/theme.rs b/src/theme.rs index 1133baef..8df6861f 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -86,6 +86,8 @@ colors! { background = (0x00, 0x10, 0x19), unfocused_title_background = (0x22, 0x22, 0x22), focused_title_background = (0x28, 0x55, 0x77), + captured_unfocused_title_background = (0x22, 0x03, 0x03), + captured_focused_title_background = (0x77, 0x28, 0x31), focused_inactive_title_background = (0x5f, 0x67, 0x6a), unfocused_title_text = (0x88, 0x88, 0x88), focused_title_text = (0xff, 0xff, 0xff), diff --git a/src/tree/output.rs b/src/tree/output.rs index 7a3a5bca..3c86881f 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -78,6 +78,11 @@ pub async fn output_render_data(state: Rc) { impl OutputNode { pub fn perform_screencopies(&self, fb: &Framebuffer, tex: &Texture) { + if let Some(workspace) = self.workspace.get() { + if !workspace.capture.get() { + return; + } + } self.global.perform_screencopies(fb, tex); for sc in self.screencasts.lock().values() { sc.copy_texture(self, tex); @@ -137,6 +142,7 @@ impl OutputNode { let mut rd = self.render_data.borrow_mut(); rd.titles.clear(); rd.inactive_workspaces.clear(); + rd.captured_inactive_workspaces.clear(); rd.active_workspace = None; rd.status = None; let mut pos = 0; @@ -204,7 +210,12 @@ impl OutputNode { } let rect = Rect::new_sized(pos, 0, title_width, th).unwrap(); if Some(ws.id) == active_id { - rd.active_workspace = Some(rect); + rd.active_workspace = Some(OutputWorkspaceRenderData { + rect, + captured: ws.capture.get(), + }); + } else if ws.capture.get() { + rd.captured_inactive_workspaces.push(rect); } else { rd.inactive_workspaces.push(rect); } @@ -310,6 +321,7 @@ impl OutputNode { visible_on_desired_output: Cell::new(false), desired_output: CloneCell::new(self.global.output_id.clone()), jay_workspaces: Default::default(), + capture: self.state.default_workspace_capture.clone(), }); ws.output_link .set(Some(self.workspaces.add_last(ws.clone()))); @@ -462,11 +474,18 @@ pub struct OutputStatus { pub tex: Rc, } +#[derive(Copy, Clone)] +pub struct OutputWorkspaceRenderData { + pub rect: Rect, + pub captured: bool, +} + #[derive(Default)] pub struct OutputRenderData { - pub active_workspace: Option, + pub active_workspace: Option, pub underline: Rect, pub inactive_workspaces: Vec, + pub captured_inactive_workspaces: Vec, pub titles: Vec, pub status: Option, } diff --git a/src/tree/workspace.rs b/src/tree/workspace.rs index 904981dd..b3f8cf56 100644 --- a/src/tree/workspace.rs +++ b/src/tree/workspace.rs @@ -42,6 +42,7 @@ pub struct WorkspaceNode { pub visible_on_desired_output: Cell, pub desired_output: CloneCell>, pub jay_workspaces: CopyHashMap<(ClientId, JayWorkspaceId), Rc>, + pub capture: Cell, } impl WorkspaceNode {