mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-04-09 18:53:42 +02:00
This commit adds support for loading custom layouts from yaml files, and also moves the custom layout loading and validating logic into the komorebi-core crate. re #50
1410 lines
49 KiB
Rust
1410 lines
49 KiB
Rust
use std::collections::VecDeque;
|
|
use std::io::ErrorKind;
|
|
use std::num::NonZeroUsize;
|
|
use std::path::PathBuf;
|
|
use std::sync::Arc;
|
|
use std::thread;
|
|
|
|
use color_eyre::eyre::anyhow;
|
|
use color_eyre::Result;
|
|
use crossbeam_channel::Receiver;
|
|
use hotwatch::notify::DebouncedEvent;
|
|
use hotwatch::Hotwatch;
|
|
use parking_lot::Mutex;
|
|
use serde::Serialize;
|
|
use uds_windows::UnixListener;
|
|
|
|
use komorebi_core::custom_layout::CustomLayout;
|
|
use komorebi_core::Arrangement;
|
|
use komorebi_core::CycleDirection;
|
|
use komorebi_core::DefaultLayout;
|
|
use komorebi_core::Flip;
|
|
use komorebi_core::FocusFollowsMouseImplementation;
|
|
use komorebi_core::Layout;
|
|
use komorebi_core::OperationDirection;
|
|
use komorebi_core::Rect;
|
|
use komorebi_core::Sizing;
|
|
|
|
use crate::container::Container;
|
|
use crate::load_configuration;
|
|
use crate::monitor::Monitor;
|
|
use crate::ring::Ring;
|
|
use crate::window::Window;
|
|
use crate::window_manager_event::WindowManagerEvent;
|
|
use crate::windows_api::WindowsApi;
|
|
use crate::winevent_listener::WINEVENT_CALLBACK_CHANNEL;
|
|
use crate::workspace::Workspace;
|
|
use crate::BORDER_OVERFLOW_IDENTIFIERS;
|
|
use crate::FLOAT_IDENTIFIERS;
|
|
use crate::LAYERED_EXE_WHITELIST;
|
|
use crate::MANAGE_IDENTIFIERS;
|
|
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
|
|
use crate::WORKSPACE_RULES;
|
|
|
|
#[derive(Debug)]
|
|
pub struct WindowManager {
|
|
pub monitors: Ring<Monitor>,
|
|
pub incoming_events: Arc<Mutex<Receiver<WindowManagerEvent>>>,
|
|
pub command_listener: UnixListener,
|
|
pub is_paused: bool,
|
|
pub invisible_borders: Rect,
|
|
pub work_area_offset: Option<Rect>,
|
|
pub focus_follows_mouse: Option<FocusFollowsMouseImplementation>,
|
|
pub hotwatch: Hotwatch,
|
|
pub virtual_desktop_id: Option<usize>,
|
|
pub has_pending_raise_op: bool,
|
|
}
|
|
|
|
#[derive(Debug, Serialize)]
|
|
pub struct State {
|
|
pub monitors: Ring<Monitor>,
|
|
pub is_paused: bool,
|
|
pub invisible_borders: Rect,
|
|
pub work_area_offset: Option<Rect>,
|
|
pub focus_follows_mouse: Option<FocusFollowsMouseImplementation>,
|
|
pub has_pending_raise_op: bool,
|
|
pub float_identifiers: Vec<String>,
|
|
pub manage_identifiers: Vec<String>,
|
|
pub layered_exe_whitelist: Vec<String>,
|
|
pub tray_and_multi_window_identifiers: Vec<String>,
|
|
pub border_overflow_identifiers: Vec<String>,
|
|
}
|
|
|
|
#[allow(clippy::fallible_impl_from)]
|
|
impl From<&mut WindowManager> for State {
|
|
fn from(wm: &mut WindowManager) -> Self {
|
|
Self {
|
|
monitors: wm.monitors.clone(),
|
|
is_paused: wm.is_paused,
|
|
invisible_borders: wm.invisible_borders,
|
|
work_area_offset: wm.work_area_offset,
|
|
focus_follows_mouse: wm.focus_follows_mouse.clone(),
|
|
has_pending_raise_op: wm.has_pending_raise_op,
|
|
float_identifiers: FLOAT_IDENTIFIERS.lock().clone(),
|
|
manage_identifiers: MANAGE_IDENTIFIERS.lock().clone(),
|
|
layered_exe_whitelist: LAYERED_EXE_WHITELIST.lock().clone(),
|
|
tray_and_multi_window_identifiers: TRAY_AND_MULTI_WINDOW_IDENTIFIERS.lock().clone(),
|
|
border_overflow_identifiers: BORDER_OVERFLOW_IDENTIFIERS.lock().clone(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl_ring_elements!(WindowManager, Monitor);
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
struct EnforceWorkspaceRuleOp {
|
|
hwnd: isize,
|
|
origin_monitor_idx: usize,
|
|
origin_workspace_idx: usize,
|
|
target_monitor_idx: usize,
|
|
target_workspace_idx: usize,
|
|
}
|
|
|
|
impl EnforceWorkspaceRuleOp {
|
|
const fn is_origin(&self, monitor_idx: usize, workspace_idx: usize) -> bool {
|
|
self.origin_monitor_idx == monitor_idx && self.origin_workspace_idx == workspace_idx
|
|
}
|
|
|
|
const fn is_target(&self, monitor_idx: usize, workspace_idx: usize) -> bool {
|
|
self.target_monitor_idx == monitor_idx && self.target_workspace_idx == workspace_idx
|
|
}
|
|
|
|
const fn is_enforced(&self) -> bool {
|
|
(self.origin_monitor_idx == self.target_monitor_idx)
|
|
&& (self.origin_workspace_idx == self.target_workspace_idx)
|
|
}
|
|
}
|
|
|
|
impl WindowManager {
|
|
#[tracing::instrument]
|
|
pub fn new(incoming: Arc<Mutex<Receiver<WindowManagerEvent>>>) -> Result<Self> {
|
|
let home = dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?;
|
|
let mut socket = home;
|
|
socket.push("komorebi.sock");
|
|
let socket = socket.as_path();
|
|
|
|
match std::fs::remove_file(&socket) {
|
|
Ok(_) => {}
|
|
Err(error) => match error.kind() {
|
|
// Doing this because ::exists() doesn't work reliably on Windows via IntelliJ
|
|
ErrorKind::NotFound => {}
|
|
_ => {
|
|
return Err(error.into());
|
|
}
|
|
},
|
|
};
|
|
|
|
let listener = UnixListener::bind(&socket)?;
|
|
|
|
let virtual_desktop_id = winvd::helpers::get_current_desktop_number().ok();
|
|
|
|
Ok(Self {
|
|
monitors: Ring::default(),
|
|
incoming_events: incoming,
|
|
command_listener: listener,
|
|
is_paused: false,
|
|
invisible_borders: Rect {
|
|
left: 7,
|
|
top: 0,
|
|
right: 14,
|
|
bottom: 7,
|
|
},
|
|
work_area_offset: None,
|
|
focus_follows_mouse: None,
|
|
hotwatch: Hotwatch::new()?,
|
|
virtual_desktop_id,
|
|
has_pending_raise_op: false,
|
|
})
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn init(&mut self) -> Result<()> {
|
|
tracing::info!("initialising");
|
|
WindowsApi::load_monitor_information(&mut self.monitors)?;
|
|
WindowsApi::load_workspace_information(&mut self.monitors)?;
|
|
self.update_focused_workspace(false)
|
|
}
|
|
|
|
#[tracing::instrument]
|
|
pub fn reload_configuration() {
|
|
tracing::info!("reloading configuration");
|
|
thread::spawn(|| load_configuration().expect("could not load configuration"));
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn watch_configuration(&mut self, enable: bool) -> Result<()> {
|
|
let home = dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?;
|
|
|
|
let mut config_v1 = home.clone();
|
|
config_v1.push("komorebi.ahk");
|
|
|
|
let mut config_v2 = home;
|
|
config_v2.push("komorebi.ahk2");
|
|
|
|
if config_v1.exists() {
|
|
self.configure_watcher(enable, config_v1)?;
|
|
} else if config_v2.exists() {
|
|
self.configure_watcher(enable, config_v2)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn configure_watcher(&mut self, enable: bool, config: PathBuf) -> Result<()> {
|
|
if config.exists() {
|
|
if enable {
|
|
tracing::info!(
|
|
"watching configuration for changes: {}",
|
|
config
|
|
.as_os_str()
|
|
.to_str()
|
|
.ok_or_else(|| anyhow!("cannot convert path to string"))?
|
|
);
|
|
// Always make absolutely sure that there isn't an already existing watch, because
|
|
// hotwatch allows multiple watches to be registered for the same path
|
|
match self.hotwatch.unwatch(config.clone()) {
|
|
Ok(_) => {}
|
|
Err(error) => match error {
|
|
hotwatch::Error::Notify(error) => match error {
|
|
hotwatch::notify::Error::WatchNotFound => {}
|
|
error => return Err(error.into()),
|
|
},
|
|
error @ hotwatch::Error::Io(_) => return Err(error.into()),
|
|
},
|
|
}
|
|
|
|
self.hotwatch.watch(config, |event| match event {
|
|
// Editing in Notepad sends a NoticeWrite while editing in (Neo)Vim sends
|
|
// a NoticeRemove, presumably because of the use of swap files?
|
|
DebouncedEvent::NoticeWrite(_) | DebouncedEvent::NoticeRemove(_) => {
|
|
thread::spawn(|| {
|
|
load_configuration().expect("could not load configuration");
|
|
});
|
|
}
|
|
_ => {}
|
|
})?;
|
|
} else {
|
|
tracing::info!(
|
|
"no longer watching configuration for changes: {}",
|
|
config
|
|
.as_os_str()
|
|
.to_str()
|
|
.ok_or_else(|| anyhow!("cannot convert path to string"))?
|
|
);
|
|
|
|
self.hotwatch.unwatch(config)?;
|
|
};
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn reconcile_monitors(&mut self) -> Result<()> {
|
|
let valid_hmonitors = WindowsApi::valid_hmonitors()?;
|
|
let mut invalid = vec![];
|
|
|
|
for monitor in self.monitors_mut() {
|
|
if !valid_hmonitors.contains(&monitor.id()) {
|
|
let mut mark_as_invalid = true;
|
|
|
|
// If an invalid hmonitor has at least one window in the window manager state,
|
|
// we can attempt to update its hmonitor id in-place so that it doesn't get reaped
|
|
if let Some(workspace) = monitor.focused_workspace() {
|
|
if let Some(container) = workspace.focused_container() {
|
|
if let Some(window) = container.focused_window() {
|
|
let actual_hmonitor = WindowsApi::monitor_from_window(window.hwnd());
|
|
if actual_hmonitor != monitor.id() {
|
|
monitor.set_id(actual_hmonitor);
|
|
mark_as_invalid = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if mark_as_invalid {
|
|
invalid.push(monitor.id());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove any invalid monitors from our state
|
|
self.monitors_mut().retain(|m| !invalid.contains(&m.id()));
|
|
|
|
let invisible_borders = self.invisible_borders;
|
|
let offset = self.work_area_offset;
|
|
|
|
for monitor in self.monitors_mut() {
|
|
let mut should_update = false;
|
|
let reference = WindowsApi::monitor(monitor.id())?;
|
|
// TODO: If this is different, force a redraw
|
|
|
|
if reference.work_area_size() != monitor.work_area_size() {
|
|
monitor.set_work_area_size(Rect {
|
|
left: reference.work_area_size().left,
|
|
top: reference.work_area_size().top,
|
|
right: reference.work_area_size().right,
|
|
bottom: reference.work_area_size().bottom,
|
|
});
|
|
|
|
should_update = true;
|
|
}
|
|
if reference.size() != monitor.size() {
|
|
monitor.set_size(Rect {
|
|
left: reference.size().left,
|
|
top: reference.size().top,
|
|
right: reference.size().right,
|
|
bottom: reference.size().bottom,
|
|
});
|
|
|
|
should_update = true;
|
|
}
|
|
|
|
if should_update {
|
|
monitor.update_focused_workspace(offset, &invisible_borders)?;
|
|
}
|
|
}
|
|
|
|
// Check for and add any new monitors that may have been plugged in
|
|
WindowsApi::load_monitor_information(&mut self.monitors)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn enforce_workspace_rules(&mut self) -> Result<()> {
|
|
let mut to_move = vec![];
|
|
|
|
let focused_monitor_idx = self.focused_monitor_idx();
|
|
let focused_workspace_idx = self
|
|
.monitors()
|
|
.get(focused_monitor_idx)
|
|
.ok_or_else(|| anyhow!("there is no monitor with that index"))?
|
|
.focused_workspace_idx();
|
|
|
|
let workspace_rules = WORKSPACE_RULES.lock();
|
|
// Go through all the monitors and workspaces
|
|
for (i, monitor) in self.monitors().iter().enumerate() {
|
|
for (j, workspace) in monitor.workspaces().iter().enumerate() {
|
|
// And all the visible windows (at the top of a container)
|
|
for window in workspace.visible_windows().into_iter().flatten() {
|
|
// If the executable names or titles of any of those windows are in our rules map
|
|
if let Some((monitor_idx, workspace_idx)) = workspace_rules.get(&window.exe()?)
|
|
{
|
|
tracing::info!(
|
|
"{} should be on monitor {}, workspace {}",
|
|
window.title()?,
|
|
*monitor_idx,
|
|
*workspace_idx
|
|
);
|
|
|
|
// Create an operation outline and save it for later in the fn
|
|
to_move.push(EnforceWorkspaceRuleOp {
|
|
hwnd: window.hwnd,
|
|
origin_monitor_idx: i,
|
|
origin_workspace_idx: j,
|
|
target_monitor_idx: *monitor_idx,
|
|
target_workspace_idx: *workspace_idx,
|
|
});
|
|
} else if let Some((monitor_idx, workspace_idx)) =
|
|
workspace_rules.get(&window.title()?)
|
|
{
|
|
tracing::info!(
|
|
"{} should be on monitor {}, workspace {}",
|
|
window.title()?,
|
|
*monitor_idx,
|
|
*workspace_idx
|
|
);
|
|
|
|
to_move.push(EnforceWorkspaceRuleOp {
|
|
hwnd: window.hwnd,
|
|
origin_monitor_idx: i,
|
|
origin_workspace_idx: j,
|
|
target_monitor_idx: *monitor_idx,
|
|
target_workspace_idx: *workspace_idx,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Only retain operations where the target is not the current workspace
|
|
to_move.retain(|op| !op.is_target(focused_monitor_idx, focused_workspace_idx));
|
|
// Only retain operations where the rule has not already been enforced
|
|
to_move.retain(|op| !op.is_enforced());
|
|
|
|
let mut should_update_focused_workspace = false;
|
|
|
|
// Parse the operation and remove any windows that are not placed according to their rules
|
|
for op in &to_move {
|
|
let origin_workspace = self
|
|
.monitors_mut()
|
|
.get_mut(op.origin_monitor_idx)
|
|
.ok_or_else(|| anyhow!("there is no monitor with that index"))?
|
|
.workspaces_mut()
|
|
.get_mut(op.origin_workspace_idx)
|
|
.ok_or_else(|| anyhow!("there is no workspace with that index"))?;
|
|
|
|
// Hide the window we are about to remove if it is on the currently focused workspace
|
|
if op.is_origin(focused_monitor_idx, focused_workspace_idx) {
|
|
Window { hwnd: op.hwnd }.hide();
|
|
should_update_focused_workspace = true;
|
|
}
|
|
|
|
origin_workspace.remove_window(op.hwnd)?;
|
|
}
|
|
|
|
// Parse the operation again and associate those removed windows with the workspace that
|
|
// their rules have defined for them
|
|
for op in &to_move {
|
|
let target_monitor = self
|
|
.monitors_mut()
|
|
.get_mut(op.target_monitor_idx)
|
|
.ok_or_else(|| anyhow!("there is no monitor with that index"))?;
|
|
|
|
// The very first time this fn is called, the workspace might not even exist yet
|
|
if target_monitor
|
|
.workspaces()
|
|
.get(op.target_workspace_idx)
|
|
.is_none()
|
|
{
|
|
// If it doesn't, let's make sure it does for the next step
|
|
target_monitor.ensure_workspace_count(op.target_workspace_idx + 1);
|
|
}
|
|
|
|
let target_workspace = target_monitor
|
|
.workspaces_mut()
|
|
.get_mut(op.target_workspace_idx)
|
|
.ok_or_else(|| anyhow!("there is no workspace with that index"))?;
|
|
|
|
target_workspace.new_container_for_window(Window { hwnd: op.hwnd });
|
|
}
|
|
|
|
// Only re-tile the focused workspace if we need to
|
|
if should_update_focused_workspace {
|
|
self.update_focused_workspace(false)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn retile_all(&mut self) -> Result<()> {
|
|
let invisible_borders = self.invisible_borders;
|
|
let offset = self.work_area_offset;
|
|
|
|
for monitor in self.monitors_mut() {
|
|
let work_area = *monitor.work_area_size();
|
|
let workspace = monitor
|
|
.focused_workspace_mut()
|
|
.ok_or_else(|| anyhow!("there is no workspace"))?;
|
|
|
|
// Reset any resize adjustments if we want to force a retile
|
|
for resize in workspace.resize_dimensions_mut() {
|
|
*resize = None;
|
|
}
|
|
|
|
workspace.update(&work_area, offset, &invisible_borders)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn validate_virtual_desktop_id(&self) {
|
|
let virtual_desktop_id = winvd::helpers::get_current_desktop_number().ok();
|
|
if let (Some(id), Some(virtual_desktop_id)) = (virtual_desktop_id, self.virtual_desktop_id)
|
|
{
|
|
if id != virtual_desktop_id {
|
|
tracing::warn!(
|
|
"ignoring events while not on virtual desktop {}",
|
|
virtual_desktop_id
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn manage_focused_window(&mut self) -> Result<()> {
|
|
let hwnd = WindowsApi::foreground_window()?;
|
|
let event = WindowManagerEvent::Manage(Window { hwnd });
|
|
Ok(WINEVENT_CALLBACK_CHANNEL.lock().0.send(event)?)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn unmanage_focused_window(&mut self) -> Result<()> {
|
|
let hwnd = WindowsApi::foreground_window()?;
|
|
let event = WindowManagerEvent::Unmanage(Window { hwnd });
|
|
Ok(WINEVENT_CALLBACK_CHANNEL.lock().0.send(event)?)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn raise_window_at_cursor_pos(&mut self) -> Result<()> {
|
|
let mut hwnd = WindowsApi::window_at_cursor_pos()?;
|
|
|
|
if self.has_pending_raise_op
|
|
|| self.focused_window()?.hwnd == hwnd
|
|
// Sometimes we need this check, because the focus may have been given by a click
|
|
// to a non-window such as the taskbar or system tray, and komorebi doesn't know that
|
|
// the focused window of the workspace is not actually focused by the OS at that point
|
|
|| WindowsApi::foreground_window()? == hwnd
|
|
{
|
|
Ok(())
|
|
} else {
|
|
let mut known_hwnd = false;
|
|
for monitor in self.monitors() {
|
|
for workspace in monitor.workspaces() {
|
|
if workspace.contains_window(hwnd) {
|
|
known_hwnd = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO: Not sure if this needs to be made configurable just yet...
|
|
let overlay_classes = [
|
|
// Chromium/Electron
|
|
"Chrome_RenderWidgetHostHWND".to_string(),
|
|
// Explorer
|
|
"DirectUIHWND".to_string(),
|
|
"SysTreeView32".to_string(),
|
|
"ToolbarWindow32".to_string(),
|
|
"NetUIHWND".to_string(),
|
|
];
|
|
|
|
if !known_hwnd {
|
|
let class = Window { hwnd }.class()?;
|
|
// Some applications (Electron/Chromium-based, explorer) have (invisible?) overlays
|
|
// windows that we need to look beyond to find the actual window to raise
|
|
if overlay_classes.contains(&class) {
|
|
for monitor in self.monitors() {
|
|
for workspace in monitor.workspaces() {
|
|
if let Some(exe_hwnd) = workspace.hwnd_from_exe(&Window { hwnd }.exe()?)
|
|
{
|
|
hwnd = exe_hwnd;
|
|
known_hwnd = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if known_hwnd {
|
|
let event = WindowManagerEvent::Raise(Window { hwnd });
|
|
self.has_pending_raise_op = true;
|
|
Ok(WINEVENT_CALLBACK_CHANNEL.lock().0.send(event)?)
|
|
} else {
|
|
tracing::debug!("not raising unknown window: {}", Window { hwnd });
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn update_focused_workspace(&mut self, mouse_follows_focus: bool) -> Result<()> {
|
|
tracing::info!("updating");
|
|
|
|
let invisible_borders = self.invisible_borders;
|
|
let offset = self.work_area_offset;
|
|
|
|
self.focused_monitor_mut()
|
|
.ok_or_else(|| anyhow!("there is no monitor"))?
|
|
.update_focused_workspace(offset, &invisible_borders)?;
|
|
|
|
if mouse_follows_focus {
|
|
if let Some(window) = self.focused_workspace()?.maximized_window() {
|
|
window.focus()?;
|
|
} else if let Some(container) = self.focused_workspace()?.monocle_container() {
|
|
if let Some(window) = container.focused_window() {
|
|
window.focus()?;
|
|
}
|
|
} else if let Ok(window) = self.focused_window_mut() {
|
|
window.focus()?;
|
|
} else {
|
|
let desktop_window = Window {
|
|
hwnd: WindowsApi::desktop_window()?,
|
|
};
|
|
|
|
// Calling this directly instead of the window.focus() wrapper because trying to
|
|
// attach to the thread of the desktop window always seems to result in "Access is
|
|
// denied (os error 5)"
|
|
WindowsApi::set_foreground_window(desktop_window.hwnd())
|
|
.map_err(|error| anyhow!("{} {}:{}", error, file!(), line!()))?;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn resize_window(
|
|
&mut self,
|
|
direction: OperationDirection,
|
|
sizing: Sizing,
|
|
step: Option<i32>,
|
|
) -> Result<()> {
|
|
let work_area = self.focused_monitor_work_area()?;
|
|
let workspace = self.focused_workspace_mut()?;
|
|
|
|
match workspace.layout() {
|
|
Layout::Default(layout) => {
|
|
tracing::info!("resizing window");
|
|
let len = NonZeroUsize::new(workspace.containers().len())
|
|
.ok_or_else(|| anyhow!("there must be at least one container"))?;
|
|
let focused_idx = workspace.focused_container_idx();
|
|
let focused_idx_resize = workspace
|
|
.resize_dimensions()
|
|
.get(focused_idx)
|
|
.ok_or_else(|| anyhow!("there is no resize adjustment for this container"))?;
|
|
|
|
if direction
|
|
.destination(
|
|
workspace.layout().as_boxed_direction().as_ref(),
|
|
workspace.layout_flip(),
|
|
focused_idx,
|
|
len,
|
|
)
|
|
.is_some()
|
|
{
|
|
let unaltered = layout.calculate(
|
|
&work_area,
|
|
len,
|
|
workspace.container_padding(),
|
|
workspace.layout_flip(),
|
|
&[],
|
|
);
|
|
|
|
let mut direction = direction;
|
|
|
|
// We only ever want to operate on the unflipped Rect positions when resizing, then we
|
|
// can flip them however they need to be flipped once the resizing has been done
|
|
if let Some(flip) = workspace.layout_flip() {
|
|
match flip {
|
|
Flip::Horizontal => {
|
|
if matches!(direction, OperationDirection::Left)
|
|
|| matches!(direction, OperationDirection::Right)
|
|
{
|
|
direction = direction.opposite();
|
|
}
|
|
}
|
|
Flip::Vertical => {
|
|
if matches!(direction, OperationDirection::Up)
|
|
|| matches!(direction, OperationDirection::Down)
|
|
{
|
|
direction = direction.opposite();
|
|
}
|
|
}
|
|
Flip::HorizontalAndVertical => direction = direction.opposite(),
|
|
}
|
|
}
|
|
|
|
let resize = layout.resize(
|
|
unaltered
|
|
.get(focused_idx)
|
|
.ok_or_else(|| anyhow!("there is no last layout"))?,
|
|
focused_idx_resize,
|
|
direction,
|
|
sizing,
|
|
step,
|
|
);
|
|
|
|
workspace.resize_dimensions_mut()[focused_idx] = resize;
|
|
return self.update_focused_workspace(false);
|
|
}
|
|
|
|
tracing::warn!("cannot resize container in this direction");
|
|
}
|
|
Layout::Custom(_) => {
|
|
tracing::warn!("containers cannot be resized when using custom layouts");
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn restore_all_windows(&mut self) {
|
|
tracing::info!("restoring all hidden windows");
|
|
|
|
for monitor in self.monitors_mut() {
|
|
for workspace in monitor.workspaces_mut() {
|
|
for containers in workspace.containers_mut() {
|
|
for window in containers.windows_mut() {
|
|
window.restore();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn move_container_to_monitor(&mut self, idx: usize, follow: bool) -> Result<()> {
|
|
tracing::info!("moving container");
|
|
|
|
let invisible_borders = self.invisible_borders;
|
|
let offset = self.work_area_offset;
|
|
|
|
let monitor = self
|
|
.focused_monitor_mut()
|
|
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
|
let workspace = monitor
|
|
.focused_workspace_mut()
|
|
.ok_or_else(|| anyhow!("there is no workspace"))?;
|
|
|
|
if workspace.maximized_window().is_some() {
|
|
return Err(anyhow!(
|
|
"cannot move native maximized window to another monitor or workspace"
|
|
));
|
|
}
|
|
|
|
let container = workspace
|
|
.remove_focused_container()
|
|
.ok_or_else(|| anyhow!("there is no container"))?;
|
|
|
|
let target_monitor = self
|
|
.monitors_mut()
|
|
.get_mut(idx)
|
|
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
|
|
|
target_monitor.add_container(container)?;
|
|
target_monitor.load_focused_workspace()?;
|
|
target_monitor.update_focused_workspace(offset, &invisible_borders)?;
|
|
|
|
if follow {
|
|
self.focus_monitor(idx)?;
|
|
}
|
|
|
|
self.update_focused_workspace(true)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn move_container_to_workspace(&mut self, idx: usize, follow: bool) -> Result<()> {
|
|
tracing::info!("moving container");
|
|
|
|
let monitor = self
|
|
.focused_monitor_mut()
|
|
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
|
|
|
monitor.move_container_to_workspace(idx, follow)?;
|
|
monitor.load_focused_workspace()?;
|
|
|
|
self.update_focused_workspace(true)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn focus_container_in_direction(&mut self, direction: OperationDirection) -> Result<()> {
|
|
tracing::info!("focusing container");
|
|
let workspace = self.focused_workspace_mut()?;
|
|
|
|
let new_idx = workspace
|
|
.new_idx_for_direction(direction)
|
|
.ok_or_else(|| anyhow!("this is not a valid direction from the current position"))?;
|
|
|
|
workspace.focus_container(new_idx);
|
|
self.focused_window_mut()?.focus()?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn move_container_in_direction(&mut self, direction: OperationDirection) -> Result<()> {
|
|
tracing::info!("moving container");
|
|
|
|
let workspace = self.focused_workspace_mut()?;
|
|
|
|
let current_idx = workspace.focused_container_idx();
|
|
let new_idx = workspace
|
|
.new_idx_for_direction(direction)
|
|
.ok_or_else(|| anyhow!("this is not a valid direction from the current position"))?;
|
|
|
|
workspace.swap_containers(current_idx, new_idx);
|
|
workspace.focus_container(new_idx);
|
|
self.update_focused_workspace(true)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn focus_container_in_cycle_direction(&mut self, direction: CycleDirection) -> Result<()> {
|
|
tracing::info!("focusing container");
|
|
let workspace = self.focused_workspace_mut()?;
|
|
|
|
let new_idx = workspace
|
|
.new_idx_for_cycle_direction(direction)
|
|
.ok_or_else(|| anyhow!("this is not a valid direction from the current position"))?;
|
|
|
|
workspace.focus_container(new_idx);
|
|
self.focused_window_mut()?.focus()?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn move_container_in_cycle_direction(&mut self, direction: CycleDirection) -> Result<()> {
|
|
tracing::info!("moving container");
|
|
|
|
let workspace = self.focused_workspace_mut()?;
|
|
|
|
let current_idx = workspace.focused_container_idx();
|
|
let new_idx = workspace
|
|
.new_idx_for_cycle_direction(direction)
|
|
.ok_or_else(|| anyhow!("this is not a valid direction from the current position"))?;
|
|
|
|
workspace.swap_containers(current_idx, new_idx);
|
|
workspace.focus_container(new_idx);
|
|
self.update_focused_workspace(true)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn cycle_container_window_in_direction(&mut self, direction: CycleDirection) -> Result<()> {
|
|
tracing::info!("cycling container windows");
|
|
|
|
let container = self.focused_container_mut()?;
|
|
|
|
let len = NonZeroUsize::new(container.windows().len())
|
|
.ok_or_else(|| anyhow!("there must be at least one window in a container"))?;
|
|
|
|
if len.get() == 1 {
|
|
return Err(anyhow!("there is only one window in this container"));
|
|
}
|
|
|
|
let current_idx = container.focused_window_idx();
|
|
let next_idx = direction.next_idx(current_idx, len);
|
|
|
|
container.focus_window(next_idx);
|
|
container.load_focused_window();
|
|
|
|
self.update_focused_workspace(true)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn add_window_to_container(&mut self, direction: OperationDirection) -> Result<()> {
|
|
tracing::info!("adding window to container");
|
|
|
|
let workspace = self.focused_workspace_mut()?;
|
|
let len = NonZeroUsize::new(workspace.containers_mut().len())
|
|
.ok_or_else(|| anyhow!("there must be at least one container"))?;
|
|
let current_container_idx = workspace.focused_container_idx();
|
|
|
|
let is_valid = direction
|
|
.destination(
|
|
workspace.layout().as_boxed_direction().as_ref(),
|
|
workspace.layout_flip(),
|
|
workspace.focused_container_idx(),
|
|
len,
|
|
)
|
|
.is_some();
|
|
|
|
if is_valid {
|
|
let new_idx = workspace.new_idx_for_direction(direction).ok_or_else(|| {
|
|
anyhow!("this is not a valid direction from the current position")
|
|
})?;
|
|
|
|
let adjusted_new_index = if new_idx > current_container_idx {
|
|
new_idx - 1
|
|
} else {
|
|
new_idx
|
|
};
|
|
|
|
workspace.move_window_to_container(adjusted_new_index)?;
|
|
self.update_focused_workspace(true)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn promote_container_to_front(&mut self) -> Result<()> {
|
|
tracing::info!("promoting container");
|
|
|
|
let workspace = self.focused_workspace_mut()?;
|
|
workspace.promote_container()?;
|
|
self.update_focused_workspace(true)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn remove_window_from_container(&mut self) -> Result<()> {
|
|
tracing::info!("removing window");
|
|
|
|
if self.focused_container()?.windows().len() == 1 {
|
|
return Err(anyhow!("a container must have at least one window"));
|
|
}
|
|
|
|
let workspace = self.focused_workspace_mut()?;
|
|
|
|
workspace.new_container_for_focused_window()?;
|
|
self.update_focused_workspace(true)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn toggle_tiling(&mut self) -> Result<()> {
|
|
let workspace = self.focused_workspace_mut()?;
|
|
workspace.set_tile(!*workspace.tile());
|
|
self.update_focused_workspace(false)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn toggle_float(&mut self) -> Result<()> {
|
|
let hwnd = WindowsApi::foreground_window()?;
|
|
let workspace = self.focused_workspace_mut()?;
|
|
|
|
let mut is_floating_window = false;
|
|
|
|
for window in workspace.floating_windows() {
|
|
if window.hwnd == hwnd {
|
|
is_floating_window = true;
|
|
}
|
|
}
|
|
|
|
if is_floating_window {
|
|
self.unfloat_window()?;
|
|
} else {
|
|
self.float_window()?;
|
|
}
|
|
|
|
self.update_focused_workspace(is_floating_window)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn float_window(&mut self) -> Result<()> {
|
|
tracing::info!("floating window");
|
|
|
|
let work_area = self.focused_monitor_work_area()?;
|
|
let invisible_borders = self.invisible_borders;
|
|
|
|
let workspace = self.focused_workspace_mut()?;
|
|
workspace.new_floating_window()?;
|
|
|
|
let window = workspace
|
|
.floating_windows_mut()
|
|
.last_mut()
|
|
.ok_or_else(|| anyhow!("there is no floating window"))?;
|
|
|
|
window.center(&work_area, &invisible_borders)?;
|
|
window.focus()?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn unfloat_window(&mut self) -> Result<()> {
|
|
tracing::info!("unfloating window");
|
|
|
|
let workspace = self.focused_workspace_mut()?;
|
|
workspace.new_container_for_floating_window()
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn toggle_monocle(&mut self) -> Result<()> {
|
|
let workspace = self.focused_workspace_mut()?;
|
|
|
|
match workspace.monocle_container() {
|
|
None => self.monocle_on()?,
|
|
Some(_) => self.monocle_off()?,
|
|
}
|
|
|
|
self.update_focused_workspace(false)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn monocle_on(&mut self) -> Result<()> {
|
|
tracing::info!("enabling monocle");
|
|
|
|
let workspace = self.focused_workspace_mut()?;
|
|
workspace.new_monocle_container()
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn monocle_off(&mut self) -> Result<()> {
|
|
tracing::info!("disabling monocle");
|
|
|
|
let workspace = self.focused_workspace_mut()?;
|
|
workspace.reintegrate_monocle_container()
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn toggle_maximize(&mut self) -> Result<()> {
|
|
let workspace = self.focused_workspace_mut()?;
|
|
|
|
match workspace.maximized_window() {
|
|
None => self.maximize_window()?,
|
|
Some(_) => self.unmaximize_window()?,
|
|
}
|
|
|
|
self.update_focused_workspace(false)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn maximize_window(&mut self) -> Result<()> {
|
|
tracing::info!("maximizing windowj");
|
|
|
|
let workspace = self.focused_workspace_mut()?;
|
|
workspace.new_maximized_window()
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn unmaximize_window(&mut self) -> Result<()> {
|
|
tracing::info!("unmaximizing window");
|
|
|
|
let workspace = self.focused_workspace_mut()?;
|
|
workspace.reintegrate_maximized_window()
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn flip_layout(&mut self, layout_flip: Flip) -> Result<()> {
|
|
tracing::info!("flipping layout");
|
|
|
|
let workspace = self.focused_workspace_mut()?;
|
|
|
|
#[allow(clippy::match_same_arms)]
|
|
match workspace.layout_flip() {
|
|
None => {
|
|
workspace.set_layout_flip(Option::from(layout_flip));
|
|
}
|
|
Some(current_layout_flip) => {
|
|
match current_layout_flip {
|
|
Flip::Horizontal => match layout_flip {
|
|
Flip::Horizontal => workspace.set_layout_flip(None),
|
|
Flip::Vertical => {
|
|
workspace.set_layout_flip(Option::from(Flip::HorizontalAndVertical))
|
|
}
|
|
Flip::HorizontalAndVertical => {
|
|
workspace.set_layout_flip(Option::from(Flip::HorizontalAndVertical))
|
|
}
|
|
},
|
|
Flip::Vertical => match layout_flip {
|
|
Flip::Horizontal => {
|
|
workspace.set_layout_flip(Option::from(Flip::HorizontalAndVertical))
|
|
}
|
|
Flip::Vertical => workspace.set_layout_flip(None),
|
|
Flip::HorizontalAndVertical => {
|
|
workspace.set_layout_flip(Option::from(Flip::HorizontalAndVertical))
|
|
}
|
|
},
|
|
Flip::HorizontalAndVertical => match layout_flip {
|
|
Flip::Horizontal => workspace.set_layout_flip(Option::from(Flip::Vertical)),
|
|
Flip::Vertical => workspace.set_layout_flip(Option::from(Flip::Horizontal)),
|
|
Flip::HorizontalAndVertical => workspace.set_layout_flip(None),
|
|
},
|
|
};
|
|
}
|
|
}
|
|
|
|
self.update_focused_workspace(false)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn change_workspace_layout_default(&mut self, layout: DefaultLayout) -> Result<()> {
|
|
tracing::info!("changing layout");
|
|
|
|
let workspace = self.focused_workspace_mut()?;
|
|
|
|
match workspace.layout() {
|
|
Layout::Default(_) => {}
|
|
Layout::Custom(layout) => {
|
|
let primary_idx =
|
|
layout.first_container_idx(layout.primary_idx().ok_or_else(|| {
|
|
anyhow!("this custom layout does not have a primary column")
|
|
})?);
|
|
|
|
if !workspace.containers().is_empty() && primary_idx < workspace.containers().len()
|
|
{
|
|
workspace.swap_containers(0, primary_idx);
|
|
}
|
|
}
|
|
}
|
|
|
|
workspace.set_layout(Layout::Default(layout));
|
|
self.update_focused_workspace(true)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn change_workspace_custom_layout(&mut self, path: PathBuf) -> Result<()> {
|
|
tracing::info!("changing layout");
|
|
|
|
let layout = CustomLayout::from_path_buf(path)?;
|
|
let workspace = self.focused_workspace_mut()?;
|
|
|
|
match workspace.layout() {
|
|
Layout::Default(_) => {
|
|
let primary_idx =
|
|
layout.first_container_idx(layout.primary_idx().ok_or_else(|| {
|
|
anyhow!("this custom layout does not have a primary column")
|
|
})?);
|
|
|
|
if !workspace.containers().is_empty() && primary_idx < workspace.containers().len()
|
|
{
|
|
workspace.swap_containers(0, primary_idx);
|
|
}
|
|
}
|
|
Layout::Custom(_) => {}
|
|
}
|
|
|
|
workspace.set_layout(Layout::Custom(layout));
|
|
self.update_focused_workspace(true)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn adjust_workspace_padding(&mut self, sizing: Sizing, adjustment: i32) -> Result<()> {
|
|
tracing::info!("adjusting workspace padding");
|
|
|
|
let workspace = self.focused_workspace_mut()?;
|
|
|
|
let padding = workspace
|
|
.workspace_padding()
|
|
.ok_or_else(|| anyhow!("there is no workspace padding"))?;
|
|
|
|
workspace.set_workspace_padding(Option::from(sizing.adjust_by(padding, adjustment)));
|
|
|
|
self.update_focused_workspace(false)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn adjust_container_padding(&mut self, sizing: Sizing, adjustment: i32) -> Result<()> {
|
|
tracing::info!("adjusting container padding");
|
|
|
|
let workspace = self.focused_workspace_mut()?;
|
|
|
|
let padding = workspace
|
|
.container_padding()
|
|
.ok_or_else(|| anyhow!("there is no container padding"))?;
|
|
|
|
workspace.set_container_padding(Option::from(sizing.adjust_by(padding, adjustment)));
|
|
|
|
self.update_focused_workspace(false)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn set_workspace_tiling(
|
|
&mut self,
|
|
monitor_idx: usize,
|
|
workspace_idx: usize,
|
|
tile: bool,
|
|
) -> Result<()> {
|
|
let monitor = self
|
|
.monitors_mut()
|
|
.get_mut(monitor_idx)
|
|
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
|
|
|
let workspace = monitor
|
|
.workspaces_mut()
|
|
.get_mut(workspace_idx)
|
|
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
|
|
|
workspace.set_tile(tile);
|
|
|
|
self.update_focused_workspace(false)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn set_workspace_layout_default(
|
|
&mut self,
|
|
monitor_idx: usize,
|
|
workspace_idx: usize,
|
|
layout: DefaultLayout,
|
|
) -> Result<()> {
|
|
tracing::info!("setting workspace layout");
|
|
|
|
let invisible_borders = self.invisible_borders;
|
|
let offset = self.work_area_offset;
|
|
let focused_monitor_idx = self.focused_monitor_idx();
|
|
|
|
let monitor = self
|
|
.monitors_mut()
|
|
.get_mut(monitor_idx)
|
|
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
|
|
|
let work_area = *monitor.work_area_size();
|
|
let focused_workspace_idx = monitor.focused_workspace_idx();
|
|
|
|
let workspace = monitor
|
|
.workspaces_mut()
|
|
.get_mut(workspace_idx)
|
|
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
|
|
|
workspace.set_layout(Layout::Default(layout));
|
|
|
|
// If this is the focused workspace on a non-focused screen, let's update it
|
|
if focused_monitor_idx != monitor_idx && focused_workspace_idx == workspace_idx {
|
|
workspace.update(&work_area, offset, &invisible_borders)?;
|
|
Ok(())
|
|
} else {
|
|
Ok(self.update_focused_workspace(false)?)
|
|
}
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn set_workspace_layout_custom(
|
|
&mut self,
|
|
monitor_idx: usize,
|
|
workspace_idx: usize,
|
|
path: PathBuf,
|
|
) -> Result<()> {
|
|
tracing::info!("setting workspace layout");
|
|
let layout = CustomLayout::from_path_buf(path)?;
|
|
let invisible_borders = self.invisible_borders;
|
|
let offset = self.work_area_offset;
|
|
let focused_monitor_idx = self.focused_monitor_idx();
|
|
|
|
let monitor = self
|
|
.monitors_mut()
|
|
.get_mut(monitor_idx)
|
|
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
|
|
|
let work_area = *monitor.work_area_size();
|
|
let focused_workspace_idx = monitor.focused_workspace_idx();
|
|
|
|
let workspace = monitor
|
|
.workspaces_mut()
|
|
.get_mut(workspace_idx)
|
|
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
|
|
|
workspace.set_layout(Layout::Custom(layout));
|
|
|
|
// If this is the focused workspace on a non-focused screen, let's update it
|
|
if focused_monitor_idx != monitor_idx && focused_workspace_idx == workspace_idx {
|
|
workspace.update(&work_area, offset, &invisible_borders)?;
|
|
Ok(())
|
|
} else {
|
|
Ok(self.update_focused_workspace(false)?)
|
|
}
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn ensure_workspaces_for_monitor(
|
|
&mut self,
|
|
monitor_idx: usize,
|
|
workspace_count: usize,
|
|
) -> Result<()> {
|
|
tracing::info!("ensuring workspace count");
|
|
|
|
let monitor = self
|
|
.monitors_mut()
|
|
.get_mut(monitor_idx)
|
|
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
|
|
|
monitor.ensure_workspace_count(workspace_count);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn set_workspace_padding(
|
|
&mut self,
|
|
monitor_idx: usize,
|
|
workspace_idx: usize,
|
|
size: i32,
|
|
) -> Result<()> {
|
|
tracing::info!("setting workspace padding");
|
|
|
|
let monitor = self
|
|
.monitors_mut()
|
|
.get_mut(monitor_idx)
|
|
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
|
|
|
let workspace = monitor
|
|
.workspaces_mut()
|
|
.get_mut(workspace_idx)
|
|
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
|
|
|
workspace.set_workspace_padding(Option::from(size));
|
|
|
|
self.update_focused_workspace(false)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn set_workspace_name(
|
|
&mut self,
|
|
monitor_idx: usize,
|
|
workspace_idx: usize,
|
|
name: String,
|
|
) -> Result<()> {
|
|
tracing::info!("setting workspace name");
|
|
|
|
let monitor = self
|
|
.monitors_mut()
|
|
.get_mut(monitor_idx)
|
|
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
|
|
|
let workspace = monitor
|
|
.workspaces_mut()
|
|
.get_mut(workspace_idx)
|
|
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
|
|
|
workspace.set_name(Option::from(name.clone()));
|
|
monitor.workspace_names_mut().insert(workspace_idx, name);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn set_container_padding(
|
|
&mut self,
|
|
monitor_idx: usize,
|
|
workspace_idx: usize,
|
|
size: i32,
|
|
) -> Result<()> {
|
|
tracing::info!("setting container padding");
|
|
|
|
let monitor = self
|
|
.monitors_mut()
|
|
.get_mut(monitor_idx)
|
|
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
|
|
|
let workspace = monitor
|
|
.workspaces_mut()
|
|
.get_mut(workspace_idx)
|
|
.ok_or_else(|| anyhow!("there is no monitor"))?;
|
|
|
|
workspace.set_container_padding(Option::from(size));
|
|
|
|
self.update_focused_workspace(false)
|
|
}
|
|
|
|
pub fn focused_monitor_work_area(&self) -> Result<Rect> {
|
|
Ok(*self
|
|
.focused_monitor()
|
|
.ok_or_else(|| anyhow!("there is no monitor"))?
|
|
.work_area_size())
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn focus_monitor(&mut self, idx: usize) -> Result<()> {
|
|
tracing::info!("focusing monitor");
|
|
|
|
if self.monitors().get(idx).is_some() {
|
|
self.monitors.focus(idx);
|
|
} else {
|
|
return Err(anyhow!("this is not a valid monitor index"));
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn monitor_idx_from_window(&mut self, window: Window) -> Option<usize> {
|
|
let hmonitor = WindowsApi::monitor_from_window(window.hwnd());
|
|
|
|
for (i, monitor) in self.monitors().iter().enumerate() {
|
|
if monitor.id() == hmonitor {
|
|
return Option::from(i);
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
pub fn monitor_idx_from_current_pos(&mut self) -> Option<usize> {
|
|
let hmonitor = WindowsApi::monitor_from_point(WindowsApi::cursor_pos().ok()?);
|
|
|
|
for (i, monitor) in self.monitors().iter().enumerate() {
|
|
if monitor.id() == hmonitor {
|
|
return Option::from(i);
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
pub fn focused_workspace(&self) -> Result<&Workspace> {
|
|
self.focused_monitor()
|
|
.ok_or_else(|| anyhow!("there is no monitor"))?
|
|
.focused_workspace()
|
|
.ok_or_else(|| anyhow!("there is no workspace"))
|
|
}
|
|
|
|
pub fn focused_workspace_mut(&mut self) -> Result<&mut Workspace> {
|
|
self.focused_monitor_mut()
|
|
.ok_or_else(|| anyhow!("there is no monitor"))?
|
|
.focused_workspace_mut()
|
|
.ok_or_else(|| anyhow!("there is no workspace"))
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn focus_workspace(&mut self, idx: usize) -> Result<()> {
|
|
tracing::info!("focusing workspace");
|
|
|
|
let monitor = self
|
|
.focused_monitor_mut()
|
|
.ok_or_else(|| anyhow!("there is no workspace"))?;
|
|
|
|
monitor.focus_workspace(idx)?;
|
|
monitor.load_focused_workspace()?;
|
|
|
|
self.update_focused_workspace(true)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn new_workspace(&mut self) -> Result<()> {
|
|
tracing::info!("adding new workspace");
|
|
|
|
let monitor = self
|
|
.focused_monitor_mut()
|
|
.ok_or_else(|| anyhow!("there is no workspace"))?;
|
|
|
|
monitor.focus_workspace(monitor.new_workspace_idx())?;
|
|
monitor.load_focused_workspace()?;
|
|
|
|
self.update_focused_workspace(true)
|
|
}
|
|
|
|
pub fn focused_container(&self) -> Result<&Container> {
|
|
self.focused_workspace()?
|
|
.focused_container()
|
|
.ok_or_else(|| anyhow!("there is no container"))
|
|
}
|
|
|
|
pub fn focused_container_mut(&mut self) -> Result<&mut Container> {
|
|
self.focused_workspace_mut()?
|
|
.focused_container_mut()
|
|
.ok_or_else(|| anyhow!("there is no container"))
|
|
}
|
|
|
|
pub fn focused_window(&self) -> Result<&Window> {
|
|
self.focused_container()?
|
|
.focused_window()
|
|
.ok_or_else(|| anyhow!("there is no window"))
|
|
}
|
|
|
|
fn focused_window_mut(&mut self) -> Result<&mut Window> {
|
|
self.focused_container_mut()?
|
|
.focused_window_mut()
|
|
.ok_or_else(|| anyhow!("there is no window"))
|
|
}
|
|
}
|