diff options
Diffstat (limited to 'src/bar_state.rs')
| -rw-r--r-- | src/bar_state.rs | 651 |
1 files changed, 0 insertions, 651 deletions
diff --git a/src/bar_state.rs b/src/bar_state.rs deleted file mode 100644 index 55705da..0000000 --- a/src/bar_state.rs +++ /dev/null @@ -1,651 +0,0 @@ -use std::cell::RefCell; -use std::collections::BinaryHeap; -use std::io; -use std::rc::Rc; - -use smithay_client_toolkit::{ - environment::Environment, - reexports::{ - client::protocol::{wl_output::WlOutput, wl_seat::WlSeat, wl_shm, wl_surface::WlSurface}, - client::{Attached, Main}, - protocols::wlr::unstable::layer_shell::v1::client::{ - zwlr_layer_shell_v1, zwlr_layer_surface_v1, - }, - }, - shm::AutoMemPool, -}; - -use pangocairo::cairo; - -use crate::button_manager::ButtonManager; -use crate::config::Config; -use crate::i3bar_protocol::{self, Block, MinWidth}; -use crate::ord_adaptor::DefaultLess; -use crate::pointer_btn::PointerBtn; -use crate::river_protocols::zriver_command_callback_v1; -use crate::river_protocols::zriver_control_v1; -use crate::river_protocols::zriver_output_status_v1; -use crate::river_protocols::zriver_status_manager_v1; -use crate::status_cmd::StatusCmd; -use crate::tags::{compute_tag_label, TagState, TagsInfo}; -use crate::text; -use crate::text::{ComputedText, RenderOptions}; -use crate::Env; - -pub struct BarState { - pub status_cmd: Option<StatusCmd>, - config: Rc<RefCell<Config>>, - blocks: Vec<Block>, - blocks_cache: Vec<ComputedBlock>, - surfaces: Vec<Surface>, - layer_shell: Attached<zwlr_layer_shell_v1::ZwlrLayerShellV1>, - river_status: Option<Attached<zriver_status_manager_v1::ZriverStatusManagerV1>>, - river_control: Option<Attached<zriver_control_v1::ZriverControlV1>>, -} - -impl BarState { - pub fn new(env: &Environment<Env>) -> Self { - let mut error = Ok(()); - - let config = Config::new() - .map_err(|e| error = Err(e)) - .unwrap_or_default(); - - let status_cmd = match &error { - Err(_) => None, - Ok(()) => config.command.as_deref().and_then(|cmd| { - StatusCmd::new(cmd) - .map_err(|e| error = Err(anyhow!(e))) - .ok() - }), - }; - - let mut s = Self { - status_cmd, - config: Rc::new(RefCell::new(config)), - blocks: Vec::new(), - blocks_cache: Vec::new(), - surfaces: Vec::default(), - layer_shell: env.require_global::<zwlr_layer_shell_v1::ZwlrLayerShellV1>(), - river_status: env.get_global::<zriver_status_manager_v1::ZriverStatusManagerV1>(), - river_control: env.get_global::<zriver_control_v1::ZriverControlV1>(), - }; - - if let Err(e) = error { - s.set_error(e.to_string()); - } - - s - } - - pub fn set_blocks(&mut self, blocks: Vec<Block>) { - self.blocks = blocks; - for s in &mut self.surfaces { - s.dirty = true; - } - } - - pub fn set_error(&mut self, error: impl Into<String>) { - self.set_blocks(vec![Block { - full_text: error.into(), - ..Default::default() - }]); - } - - pub fn remove_surface(&mut self, output_id: u32) { - self.surfaces.retain(|s| s.output_id != output_id); - } - - pub fn notify_available(&mut self) -> io::Result<()> { - if let Some(cmd) = &mut self.status_cmd { - if let Some(blocks) = cmd.notify_available()? { - self.set_blocks(blocks); - } - } - Ok(()) - } - - pub fn handle_click( - &mut self, - surface: &WlSurface, - seat: &WlSeat, - x: f64, - y: f64, - btn: PointerBtn, - ) { - if let Some(s) = self - .surfaces - .iter() - .find(|s| &s.surface.detach() == surface) - { - if let Some(event) = s.click(btn, seat, x, y) { - if let Some(cmd) = &mut self.status_cmd { - if let Err(e) = cmd.send_click_event(&event) { - self.set_error(e.to_string()); - } - } - } - } - } - - pub fn add_surface(&mut self, output: &WlOutput, output_id: u32, env: &Environment<Env>) { - let surface = env.create_surface(); - let pool = env - .create_auto_pool() - .expect("Failed to create a memory pool!"); - - let layer_surface = self.layer_shell.get_layer_surface( - &surface, - Some(output), - zwlr_layer_shell_v1::Layer::Top, - "i3bar-river".to_owned(), - ); - - // Set the height - layer_surface.set_size(0, self.config.borrow().height); - // Anchor to the top - layer_surface.set_anchor( - zwlr_layer_surface_v1::Anchor::Top - | zwlr_layer_surface_v1::Anchor::Left - | zwlr_layer_surface_v1::Anchor::Right, - ); - - layer_surface.quick_assign(move |layer_surface, event, mut data| { - let bar_state: &mut BarState = data.get().unwrap(); - match event { - zwlr_layer_surface_v1::Event::Closed => { - bar_state.remove_surface(output_id); - } - zwlr_layer_surface_v1::Event::Configure { - serial, - width, - height, - } => { - layer_surface.ack_configure(serial); - if let Some(s) = bar_state - .surfaces - .iter_mut() - .find(|s| s.output_id == output_id) - { - if s.configure(width, height) { - s.draw(&bar_state.blocks, &mut bar_state.blocks_cache); - } - } - } - _ => (), - } - }); - - // Commit so that the server will send a configure event - surface.commit(); - - let (river_output_status, tags_info) = if let Some(river_status) = &self.river_status { - let river_output_status = river_status.get_river_output_status(output); - river_output_status.quick_assign(move |_, event, mut data| { - let bar_state: &mut BarState = data.get().unwrap(); - if let Some(s) = bar_state - .surfaces - .iter_mut() - .find(|s| s.output_id == output_id) - { - match event { - zriver_output_status_v1::Event::FocusedTags { tags } => { - s.tags_info.as_mut().unwrap().focused = tags; - } - zriver_output_status_v1::Event::UrgentTags { tags } => { - s.tags_info.as_mut().unwrap().urgent = tags; - } - _ => (), - } - s.dirty = true; - } - }); - (Some(river_output_status), Some(TagsInfo::default())) - } else { - (None, None) - }; - - self.surfaces.push(Surface { - output_id, - surface, - layer_surface, - pool, - dimensions: (0, 0), - config: self.config.clone(), - dirty: true, - frame_scheduled: false, - river_output_status, - river_control: self.river_control.clone(), - tags_info, - tags_computed: Vec::with_capacity(9), - tags_btns: Default::default(), - blocks_btns: Default::default(), - }); - } -} - -pub struct Surface { - output_id: u32, - surface: Attached<WlSurface>, - layer_surface: Main<zwlr_layer_surface_v1::ZwlrLayerSurfaceV1>, - pool: AutoMemPool, - dimensions: (u32, u32), - config: Rc<RefCell<Config>>, - dirty: bool, - frame_scheduled: bool, - // river stuff - river_output_status: Option<Main<zriver_output_status_v1::ZriverOutputStatusV1>>, - river_control: Option<Attached<zriver_control_v1::ZriverControlV1>>, - // tags - tags_info: Option<TagsInfo>, - tags_computed: Vec<ComputedText>, - // buttons - tags_btns: ButtonManager, - blocks_btns: ButtonManager<(Option<String>, Option<String>)>, -} - -impl Surface { - /// Handle `configure` event. Returns `true` if configuration changed. - fn configure(&mut self, width: u32, height: u32) -> bool { - if self.dimensions != (width, height) { - self.dimensions = (width, height); - self.layer_surface.set_exclusive_zone(height as i32); - self.dirty = true; - true - } else { - false - } - } - - fn click( - &self, - button: PointerBtn, - seat: &WlSeat, - x: f64, - _y: f64, - ) -> Option<i3bar_protocol::Event> { - if let Some((id, river_control)) = self.tags_btns.click(x).zip(self.river_control.as_ref()) - { - let cmd = match button { - PointerBtn::Left => "set-focused-tags", - PointerBtn::Right => "toggle-focused-tags", - _ => return None, - }; - river_control.add_argument(cmd.into()); - river_control.add_argument((1u32 << id).to_string()); - let result = river_control.run_command(seat); - result.quick_assign(|_, event, _| match event { - zriver_command_callback_v1::Event::Success { output } => { - info!("River cmd output: '{output}'"); - } - zriver_command_callback_v1::Event::Failure { failure_message: f } => { - error!("River error: '{f}'"); - } - }); - } else if let Some((name, instance)) = self.blocks_btns.click(x) { - return Some(i3bar_protocol::Event { - name: name.as_deref(), - instance: instance.as_deref(), - button, - ..Default::default() - }); - } - None - } - - fn draw(&mut self, blocks: &[Block], blocks_cache: &mut Vec<ComputedBlock>) { - if self.dirty { - let config = self.config.borrow(); - - let stride = 4 * self.dimensions.0 as i32; - let width = self.dimensions.0 as i32; - let height = self.dimensions.1 as i32; - let width_f = width as f64; - let height_f = height as f64; - - let (canvas, buffer) = self - .pool - .buffer(width, height, stride, wl_shm::Format::Argb8888) - .unwrap(); - - let cairo_surf = unsafe { - cairo::ImageSurface::create_for_data_unsafe( - canvas.as_mut_ptr(), - cairo::Format::ARgb32, - width, - height, - stride, - ) - .expect("cairo surface") - }; - - let cairo_ctx = cairo::Context::new(&cairo_surf).expect("cairo context"); - let pango_layout = pangocairo::create_layout(&cairo_ctx).expect("pango layout"); - pango_layout.set_font_description(Some(&config.font)); - pango_layout.set_height(height); - - // Background - config.background.apply(&cairo_ctx); - cairo_ctx.paint().expect("cairo paint"); - - // Display tags - let mut offset_left = 0.0; - if let Some(tags_info) = &self.tags_info { - if self.tags_computed.is_empty() { - let mut x_offset = 0.0; - // TODO make configurable - for (id, text) in ["1", "2", "3", "4", "5", "6", "7", "8", "9"] - .iter() - .enumerate() - { - let tag = - compute_tag_label(text.to_string(), config.font.clone(), &cairo_ctx); - self.tags_btns.push(x_offset, tag.width, id); - x_offset += tag.width; - self.tags_computed.push(tag); - } - } - for (i, label) in self.tags_computed.iter().enumerate() { - let state = tags_info.get_state(i); - let (bg, fg) = match state { - TagState::Focused => (config.tag_focused_bg, config.tag_focused_fg), - TagState::Inactive => (config.tag_bg, config.tag_fg), - TagState::Urgent => (config.tag_urgent_bg, config.tag_urgent_fg), - }; - label.render( - &cairo_ctx, - RenderOptions { - x_offset: offset_left, - bar_height: height_f, - fg_color: fg, - bg_color: Some(bg), - r_left: if i == 0 || tags_info.get_state(i.saturating_sub(1)) != state { - config.tags_r - } else { - 0.0 - }, - r_right: if i == 8 || tags_info.get_state(i + 1) != state { - config.tags_r - } else { - 0.0 - }, - overlap: 0.0, - }, - ); - offset_left += label.width; - } - } - - // Display the blocks - render_blocks( - &cairo_ctx, - &*config, - blocks, - blocks_cache, - &mut self.blocks_btns, - offset_left, - width_f, - height_f, - ); - - // Attach the buffer to the surface and mark the entire surface as damaged - self.surface.attach(Some(&buffer), 0, 0); - self.surface - .damage_buffer(0, 0, width as i32, height as i32); - } - - // Schedule next frame - if !self.frame_scheduled { - self.frame_scheduled = true; - let output_id = self.output_id; - let frame = self.surface.frame(); - frame.quick_assign(move |_callback, _event, mut data| { - let bar_state: &mut BarState = data.get().unwrap(); - if let Some(s) = bar_state - .surfaces - .iter_mut() - .find(|s| s.output_id == output_id) - { - s.frame_scheduled = false; - s.draw(&bar_state.blocks, &mut bar_state.blocks_cache); - } - }); - } - - // Finally, commit the surface - self.surface.commit(); - self.dirty = false; - } -} - -impl Drop for Surface { - fn drop(&mut self) { - self.layer_surface.destroy(); - self.surface.destroy(); - if let Some(ros) = &self.river_output_status { - ros.destroy(); - } - } -} - -#[allow(clippy::too_many_arguments)] -fn render_blocks( - context: &cairo::Context, - config: &Config, - blocks: &[Block], - blocks_cache: &mut Vec<ComputedBlock>, - buttons: &mut ButtonManager<(Option<String>, Option<String>)>, - offset_left: f64, - full_width: f64, - full_height: f64, -) { - let comp_min_width = |block: &Block| { - let markup = block.markup.as_deref() == Some("pango"); - match &block.min_width { - Some(MinWidth::Pixels(p)) => Some(*p as f64), - Some(MinWidth::Text(t)) => Some(text::width_of(t, context, markup, &config.font.0)), - None => None, - } - }; - let comp_full = |block: &Block, min_width: Option<f64>| { - let markup = block.markup.as_deref() == Some("pango"); - text::Text { - text: block.full_text.clone(), - attr: text::Attributes { - font: config.font.clone(), - padding_left: 0.0, - padding_right: 0.0, - min_width, - align: block.align.unwrap_or_default(), - markup, - }, - } - .compute(context) - }; - let comp_short = |block: &Block, min_width: Option<f64>| { - let markup = block.markup.as_deref() == Some("pango"); - block.short_text.as_ref().map(|short_text| { - text::Text { - text: short_text.clone(), - attr: text::Attributes { - font: config.font.clone(), - padding_left: 0.0, - padding_right: 0.0, - min_width, - align: block.align.unwrap_or_default(), - markup, - }, - } - .compute(context) - }) - }; - - // update cashe - if blocks.len() != blocks_cache.len() { - blocks_cache.clear(); - for block in blocks { - let mw = comp_min_width(block); - blocks_cache.push(ComputedBlock { - block: block.clone(), - full: comp_full(block, mw), - short: comp_short(block, mw), - min_width: mw, - }); - } - } else { - for (block, computed) in blocks.iter().zip(blocks_cache.iter_mut()) { - if block.min_width != computed.block.min_width || block.markup != computed.block.markup - { - let mw = comp_min_width(block); - *computed = ComputedBlock { - block: block.clone(), - full: comp_full(block, mw), - short: comp_short(block, mw), - min_width: mw, - }; - } else { - if block.full_text != computed.block.full_text { - computed.block.full_text = block.full_text.clone(); - computed.full = comp_full(block, computed.min_width); - } - if block.short_text != computed.block.short_text { - computed.block.full_text = block.full_text.clone(); - computed.short = comp_short(block, computed.min_width); - } - computed.block.color = block.color; - computed.block.background = block.background; - computed.block.align = block.align; - computed.block.name = block.name.clone(); - computed.block.instance = block.instance.clone(); - computed.block.separator = block.separator; - computed.block.separator_block_width = block.separator_block_width; - } - } - } - - #[derive(Debug)] - struct LogialBlock<'a> { - blocks: Vec<(&'a ComputedBlock, bool)>, - delta: f64, - separator: bool, - separator_block_width: u8, - } - - let mut blocks_computed = Vec::new(); - let mut blocks_width = 0.0; - let mut s_start = 0; - while s_start < blocks.len() { - let mut s_end = s_start + 1; - let series_name = &blocks[s_start].name; - while s_end < blocks.len() - && blocks[s_end - 1].separator_block_width == 0 - && &blocks[s_end].name == series_name - { - s_end += 1; - } - - let mut series = LogialBlock { - blocks: Vec::with_capacity(s_end - s_start), - delta: 0.0, - separator: blocks[s_end - 1].separator, - separator_block_width: blocks[s_end - 1].separator_block_width, - }; - - for comp in &blocks_cache[s_start..s_end] { - blocks_width += comp.full.width; - if let Some(short) = &comp.short { - series.delta += comp.full.width - short.width; - } - series.blocks.push((comp, false)); - } - if s_end != blocks.len() { - blocks_width += series.separator_block_width as f64; - } - blocks_computed.push(series); - s_start = s_end; - } - - // Progressively switch to short mode - if offset_left + blocks_width > full_width { - let mut heap = BinaryHeap::new(); - for (i, b) in blocks_computed.iter().enumerate() { - if b.delta > 0.0 { - heap.push((DefaultLess(b.delta), i)); - } - } - while let Some((DefaultLess(delta), to_switch)) = heap.pop() { - for comp in &mut blocks_computed[to_switch].blocks { - comp.1 = true; - } - blocks_width -= delta; - if offset_left + blocks_width <= full_width { - break; - } - } - } - - // Remove all the empy blocks - for s in &mut blocks_computed { - s.blocks.retain(|(text, is_short)| { - (*is_short - && text - .short - .as_ref() - .map_or(text.full.width > 0.0, |s| s.width > 0.0)) - || (!is_short && text.full.width > 0.0) - }); - } - - // Render blocks - buttons.clear(); - let mut j = 0; - for series in blocks_computed { - let s_len = series.blocks.len(); - for (i, (computed, is_short)) in series.blocks.into_iter().enumerate() { - let block = &computed.block; - let to_render = if is_short { - computed.short.as_ref().unwrap_or(&computed.full) - } else { - &computed.full - }; - j += 1; - to_render.render( - context, - RenderOptions { - x_offset: full_width - blocks_width, - bar_height: full_height, - fg_color: block.color.unwrap_or(config.color), - bg_color: block.background, - r_left: if i == 0 { config.blocks_r } else { 0.0 }, - r_right: if i + 1 == s_len { config.blocks_r } else { 0.0 }, - overlap: config.blocks_overlap, - }, - ); - buttons.push( - full_width - blocks_width, - to_render.width, - (block.name.clone(), block.instance.clone()), - ); - blocks_width -= to_render.width; - } - if j != blocks.len() && series.separator_block_width > 0 { - let w = series.separator_block_width as f64; - if series.separator && config.separator_width > 0.0 { - config.separator.apply(context); - context.set_line_width(config.separator_width); - context.move_to(full_width - blocks_width + w * 0.5, full_height * 0.1); - context.line_to(full_width - blocks_width + w * 0.5, full_height * 0.9); - context.stroke().unwrap(); - } - blocks_width -= w; - } - } -} - -#[derive(Debug)] -struct ComputedBlock { - block: Block, - full: ComputedText, - short: Option<ComputedText>, - min_width: Option<f64>, -} |
