mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-01-11 22:12:53 +01:00
I think this got broken as part of the automatic Rust version syntax upgrades which joined two if clauses together with && which should have been kept separate. Now, if the user gives the --bar flag to the start command, and a static config file is not resolved, or if the static config file does not have a bar_configurations stanza, it will fallthrough to the default PowerShell snippet to try and start komorebi-bar without an explicit --config flag.
3401 lines
122 KiB
Rust
3401 lines
122 KiB
Rust
#![warn(clippy::all)]
|
|
#![allow(clippy::missing_errors_doc, clippy::doc_markdown)]
|
|
#![allow(unused_assignments)] // false positives for the error reporter
|
|
|
|
use chrono::Utc;
|
|
use komorebi_client::PathExt;
|
|
use komorebi_client::replace_env_in_path;
|
|
use komorebi_client::splash;
|
|
use std::fs::File;
|
|
use std::fs::OpenOptions;
|
|
use std::io;
|
|
use std::io::BufRead;
|
|
use std::io::BufReader;
|
|
use std::io::Write;
|
|
use std::num::NonZeroUsize;
|
|
use std::path::PathBuf;
|
|
use std::process::Command;
|
|
use std::sync::atomic::AtomicBool;
|
|
use std::sync::atomic::Ordering;
|
|
use std::time::Duration;
|
|
|
|
use clap::CommandFactory;
|
|
use clap::Parser;
|
|
use clap::ValueEnum;
|
|
use color_eyre::eyre;
|
|
use color_eyre::eyre::OptionExt;
|
|
use color_eyre::eyre::bail;
|
|
use fs_tail::TailedFile;
|
|
use komorebi_client::AppSpecificConfigurationPath;
|
|
use komorebi_client::ApplicationSpecificConfiguration;
|
|
use komorebi_client::send_message;
|
|
use komorebi_client::send_query;
|
|
use lazy_static::lazy_static;
|
|
use miette::NamedSource;
|
|
use miette::Report;
|
|
use miette::SourceOffset;
|
|
use miette::SourceSpan;
|
|
use paste::paste;
|
|
use serde::Deserialize;
|
|
use sysinfo::ProcessesToUpdate;
|
|
use which::which;
|
|
use win_msgbox::OkayCancel;
|
|
use windows::Win32::Foundation::HWND;
|
|
use windows::Win32::UI::WindowsAndMessaging::SHOW_WINDOW_CMD;
|
|
use windows::Win32::UI::WindowsAndMessaging::SW_RESTORE;
|
|
use windows::Win32::UI::WindowsAndMessaging::ShowWindow;
|
|
|
|
use komorebi_client::ApplicationConfigurationGenerator;
|
|
use komorebi_client::ApplicationIdentifier;
|
|
use komorebi_client::Axis;
|
|
use komorebi_client::CycleDirection;
|
|
use komorebi_client::DefaultLayout;
|
|
use komorebi_client::FocusFollowsMouseImplementation;
|
|
use komorebi_client::HidingBehaviour;
|
|
use komorebi_client::MoveBehaviour;
|
|
use komorebi_client::OperationBehaviour;
|
|
use komorebi_client::OperationDirection;
|
|
use komorebi_client::Rect;
|
|
use komorebi_client::Sizing;
|
|
use komorebi_client::SocketMessage;
|
|
use komorebi_client::StateQuery;
|
|
use komorebi_client::StaticConfig;
|
|
use komorebi_client::WindowKind;
|
|
use komorebi_client::splash::ValidationFeedback;
|
|
|
|
lazy_static! {
|
|
static ref HAS_CUSTOM_CONFIG_HOME: AtomicBool = AtomicBool::new(false);
|
|
static ref HOME_DIR: PathBuf = {
|
|
std::env::var("KOMOREBI_CONFIG_HOME").map_or_else(
|
|
|_| dirs::home_dir().expect("there is no home directory"),
|
|
|home_path| {
|
|
let home = home_path.replace_env();
|
|
if home.as_path().is_dir() {
|
|
HAS_CUSTOM_CONFIG_HOME.store(true, Ordering::SeqCst);
|
|
home
|
|
} else {
|
|
panic!(
|
|
"$Env:KOMOREBI_CONFIG_HOME is set to '{home_path}', which is not a valid directory",
|
|
);
|
|
}
|
|
},
|
|
)
|
|
};
|
|
static ref DATA_DIR: PathBuf = dirs::data_local_dir()
|
|
.expect("there is no local data directory")
|
|
.join("komorebi");
|
|
static ref WHKD_CONFIG_DIR: PathBuf = {
|
|
std::env::var("WHKD_CONFIG_HOME").map_or_else(
|
|
|_| {
|
|
dirs::home_dir()
|
|
.expect("there is no home directory")
|
|
.join(".config")
|
|
},
|
|
|home_path| {
|
|
let whkd_config_home = home_path.replace_env();
|
|
|
|
assert!(
|
|
whkd_config_home.is_dir(),
|
|
"$Env:WHKD_CONFIG_HOME is set to '{home_path}', which is not a valid directory"
|
|
);
|
|
|
|
whkd_config_home
|
|
},
|
|
)
|
|
};
|
|
}
|
|
|
|
shadow_rs::shadow!(build);
|
|
|
|
#[derive(thiserror::Error, Debug, miette::Diagnostic)]
|
|
#[error("{message}")]
|
|
#[diagnostic(code(komorebi::configuration), help("try fixing this syntax error"))]
|
|
struct ConfigurationError {
|
|
message: String,
|
|
#[source_code]
|
|
src: NamedSource<String>,
|
|
#[label("This bit here")]
|
|
bad_bit: SourceSpan,
|
|
}
|
|
|
|
#[derive(Copy, Clone, ValueEnum)]
|
|
enum BooleanState {
|
|
Enable,
|
|
Disable,
|
|
}
|
|
|
|
impl From<BooleanState> for bool {
|
|
fn from(b: BooleanState) -> Self {
|
|
match b {
|
|
BooleanState::Enable => true,
|
|
BooleanState::Disable => false,
|
|
}
|
|
}
|
|
}
|
|
|
|
macro_rules! gen_enum_subcommand_args {
|
|
// SubCommand Pattern: Enum Type
|
|
( $( $name:ident: $element:ty ),+ $(,)? ) => {
|
|
$(
|
|
paste! {
|
|
#[derive(clap::Parser)]
|
|
pub struct $name {
|
|
#[clap(value_enum)]
|
|
[<$element:snake>]: $element
|
|
}
|
|
}
|
|
)+
|
|
};
|
|
}
|
|
|
|
gen_enum_subcommand_args! {
|
|
Focus: OperationDirection,
|
|
Move: OperationDirection,
|
|
PreselectDirection: OperationDirection,
|
|
CycleFocus: CycleDirection,
|
|
CycleMove: CycleDirection,
|
|
CycleMoveToWorkspace: CycleDirection,
|
|
CycleSendToWorkspace: CycleDirection,
|
|
CycleSendToMonitor: CycleDirection,
|
|
CycleMoveToMonitor: CycleDirection,
|
|
CycleMonitor: CycleDirection,
|
|
CycleWorkspace: CycleDirection,
|
|
CycleEmptyWorkspace: CycleDirection,
|
|
CycleMoveWorkspaceToMonitor: CycleDirection,
|
|
Stack: OperationDirection,
|
|
CycleStack: CycleDirection,
|
|
CycleStackIndex: CycleDirection,
|
|
FlipLayout: Axis,
|
|
ChangeLayout: DefaultLayout,
|
|
CycleLayout: CycleDirection,
|
|
WatchConfiguration: BooleanState,
|
|
MouseFollowsFocus: BooleanState,
|
|
Query: StateQuery,
|
|
WindowHidingBehaviour: HidingBehaviour,
|
|
CrossMonitorMoveBehaviour: MoveBehaviour,
|
|
UnmanagedWindowOperationBehaviour: OperationBehaviour,
|
|
PromoteWindow: OperationDirection,
|
|
}
|
|
|
|
macro_rules! gen_target_subcommand_args {
|
|
// SubCommand Pattern
|
|
( $( $name:ident ),+ $(,)? ) => {
|
|
$(
|
|
#[derive(clap::Parser)]
|
|
pub struct $name {
|
|
/// Target index (zero-indexed)
|
|
target: usize,
|
|
}
|
|
)+
|
|
};
|
|
}
|
|
|
|
gen_target_subcommand_args! {
|
|
MoveToMonitor,
|
|
MoveToWorkspace,
|
|
SendToMonitor,
|
|
SendToWorkspace,
|
|
FocusMonitor,
|
|
FocusWorkspace,
|
|
FocusWorkspaces,
|
|
MoveWorkspaceToMonitor,
|
|
SwapWorkspacesWithMonitor,
|
|
FocusStackWindow,
|
|
}
|
|
|
|
macro_rules! gen_named_target_subcommand_args {
|
|
// SubCommand Pattern
|
|
( $( $name:ident ),+ $(,)? ) => {
|
|
$(
|
|
#[derive(clap::Parser)]
|
|
pub struct $name {
|
|
/// Target workspace name
|
|
workspace: String,
|
|
}
|
|
)+
|
|
};
|
|
}
|
|
|
|
gen_named_target_subcommand_args! {
|
|
MoveToNamedWorkspace,
|
|
SendToNamedWorkspace,
|
|
FocusNamedWorkspace,
|
|
ClearNamedWorkspaceLayoutRules
|
|
}
|
|
|
|
// Thanks to @danielhenrymantilla for showing me how to use cfg_attr with an optional argument like
|
|
// this on the Rust Programming Language Community Discord Server
|
|
macro_rules! gen_workspace_subcommand_args {
|
|
// Workspace Property: #[enum] Value Enum (if the value is an Enum)
|
|
// Workspace Property: Value Type (if the value is anything else)
|
|
( $( $name:ident: $(#[enum] $(@$value_enum:tt)?)? $value:ty ),+ $(,)? ) => (
|
|
paste! {
|
|
$(
|
|
#[derive(clap::Parser)]
|
|
pub struct [<Workspace $name>] {
|
|
/// Monitor index (zero-indexed)
|
|
monitor: usize,
|
|
|
|
/// Workspace index on the specified monitor (zero-indexed)
|
|
workspace: usize,
|
|
|
|
$(#[clap(value_enum)] $($value_enum)?)?
|
|
#[cfg_attr(
|
|
all($(FALSE $($value_enum)?)?),
|
|
doc = ""$name " of the workspace as a "$value ""
|
|
)]
|
|
value: $value,
|
|
}
|
|
)+
|
|
}
|
|
)
|
|
}
|
|
|
|
gen_workspace_subcommand_args! {
|
|
Name: String,
|
|
Layout: #[enum] DefaultLayout,
|
|
Tiling: #[enum] BooleanState,
|
|
}
|
|
|
|
macro_rules! gen_named_workspace_subcommand_args {
|
|
// Workspace Property: #[enum] Value Enum (if the value is an Enum)
|
|
// Workspace Property: Value Type (if the value is anything else)
|
|
( $( $name:ident: $(#[enum] $(@$value_enum:tt)?)? $value:ty ),+ $(,)? ) => (
|
|
paste! {
|
|
$(
|
|
#[derive(clap::Parser)]
|
|
pub struct [<NamedWorkspace $name>] {
|
|
/// Target workspace name
|
|
workspace: String,
|
|
|
|
$(#[clap(value_enum)] $($value_enum)?)?
|
|
#[cfg_attr(
|
|
all($(FALSE $($value_enum)?)?),
|
|
doc = ""$name " of the workspace as a "$value ""
|
|
)]
|
|
value: $value,
|
|
}
|
|
)+
|
|
}
|
|
)
|
|
}
|
|
|
|
gen_named_workspace_subcommand_args! {
|
|
Layout: #[enum] DefaultLayout,
|
|
Tiling: #[enum] BooleanState,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
pub struct ClearWorkspaceLayoutRules {
|
|
/// Monitor index (zero-indexed)
|
|
monitor: usize,
|
|
|
|
/// Workspace index on the specified monitor (zero-indexed)
|
|
workspace: usize,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
pub struct WorkspaceCustomLayout {
|
|
/// Monitor index (zero-indexed)
|
|
monitor: usize,
|
|
|
|
/// Workspace index on the specified monitor (zero-indexed)
|
|
workspace: usize,
|
|
|
|
/// JSON or YAML file from which the custom layout definition should be loaded
|
|
#[clap(value_parser = replace_env_in_path)]
|
|
path: PathBuf,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
pub struct NamedWorkspaceCustomLayout {
|
|
/// Target workspace name
|
|
workspace: String,
|
|
|
|
/// JSON or YAML file from which the custom layout definition should be loaded
|
|
#[clap(value_parser = replace_env_in_path)]
|
|
path: PathBuf,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
pub struct WorkspaceLayoutRule {
|
|
/// Monitor index (zero-indexed)
|
|
monitor: usize,
|
|
|
|
/// Workspace index on the specified monitor (zero-indexed)
|
|
workspace: usize,
|
|
|
|
/// The number of window containers on-screen required to trigger this layout rule
|
|
at_container_count: usize,
|
|
|
|
#[clap(value_enum)]
|
|
layout: DefaultLayout,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
pub struct NamedWorkspaceLayoutRule {
|
|
/// Target workspace name
|
|
workspace: String,
|
|
|
|
/// The number of window containers on-screen required to trigger this layout rule
|
|
at_container_count: usize,
|
|
|
|
#[clap(value_enum)]
|
|
layout: DefaultLayout,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
pub struct WorkspaceCustomLayoutRule {
|
|
/// Monitor index (zero-indexed)
|
|
monitor: usize,
|
|
|
|
/// Workspace index on the specified monitor (zero-indexed)
|
|
workspace: usize,
|
|
|
|
/// The number of window containers on-screen required to trigger this layout rule
|
|
at_container_count: usize,
|
|
|
|
/// JSON or YAML file from which the custom layout definition should be loaded
|
|
#[clap(value_parser = replace_env_in_path)]
|
|
path: PathBuf,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
pub struct NamedWorkspaceCustomLayoutRule {
|
|
/// Target workspace name
|
|
workspace: String,
|
|
|
|
/// The number of window containers on-screen required to trigger this layout rule
|
|
at_container_count: usize,
|
|
|
|
/// JSON or YAML file from which the custom layout definition should be loaded
|
|
#[clap(value_parser = replace_env_in_path)]
|
|
path: PathBuf,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct Resize {
|
|
#[clap(value_enum)]
|
|
edge: OperationDirection,
|
|
#[clap(value_enum)]
|
|
sizing: Sizing,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct ResizeAxis {
|
|
#[clap(value_enum)]
|
|
axis: Axis,
|
|
#[clap(value_enum)]
|
|
sizing: Sizing,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct ResizeDelta {
|
|
/// The delta of pixels by which to increase or decrease window dimensions when resizing
|
|
pixels: i32,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct InvisibleBorders {
|
|
/// Size of the left invisible border
|
|
left: i32,
|
|
/// Size of the top invisible border (usually 0)
|
|
top: i32,
|
|
/// Size of the right invisible border (usually left * 2)
|
|
right: i32,
|
|
/// Size of the bottom invisible border (usually the same as left)
|
|
bottom: i32,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct GlobalWorkAreaOffset {
|
|
/// Size of the left work area offset (set right to left * 2 to maintain right padding)
|
|
left: i32,
|
|
/// Size of the top work area offset (set bottom to the same value to maintain bottom padding)
|
|
top: i32,
|
|
/// Size of the right work area offset
|
|
right: i32,
|
|
/// Size of the bottom work area offset
|
|
bottom: i32,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct MonitorWorkAreaOffset {
|
|
/// Monitor index (zero-indexed)
|
|
monitor: usize,
|
|
/// Size of the left work area offset (set right to left * 2 to maintain right padding)
|
|
left: i32,
|
|
/// Size of the top work area offset (set bottom to the same value to maintain bottom padding)
|
|
top: i32,
|
|
/// Size of the right work area offset
|
|
right: i32,
|
|
/// Size of the bottom work area offset
|
|
bottom: i32,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct WorkspaceWorkAreaOffset {
|
|
/// Monitor index (zero-indexed)
|
|
monitor: usize,
|
|
/// Workspace index (zero-indexed)
|
|
workspace: usize,
|
|
/// Size of the left work area offset (set right to left * 2 to maintain right padding)
|
|
left: i32,
|
|
/// Size of the top work area offset (set bottom to the same value to maintain bottom padding)
|
|
top: i32,
|
|
/// Size of the right work area offset
|
|
right: i32,
|
|
/// Size of the bottom work area offset
|
|
bottom: i32,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct MonitorIndexPreference {
|
|
/// Preferred monitor index (zero-indexed)
|
|
index_preference: usize,
|
|
/// Left value of the monitor's size Rect
|
|
left: i32,
|
|
/// Top value of the monitor's size Rect
|
|
top: i32,
|
|
/// Right value of the monitor's size Rect
|
|
right: i32,
|
|
/// Bottom value of the monitor's size Rect
|
|
bottom: i32,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct DisplayIndexPreference {
|
|
/// Preferred monitor index (zero-indexed)
|
|
index_preference: usize,
|
|
/// Display name as identified in komorebic state
|
|
display: String,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct EnsureWorkspaces {
|
|
/// Monitor index (zero-indexed)
|
|
monitor: usize,
|
|
/// Number of desired workspaces
|
|
workspace_count: usize,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct EnsureNamedWorkspaces {
|
|
/// Monitor index (zero-indexed)
|
|
monitor: usize,
|
|
/// Names of desired workspaces
|
|
names: Vec<String>,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct FocusMonitorWorkspace {
|
|
/// Target monitor index (zero-indexed)
|
|
target_monitor: usize,
|
|
/// Workspace index on the target monitor (zero-indexed)
|
|
target_workspace: usize,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
pub struct SendToMonitorWorkspace {
|
|
/// Target monitor index (zero-indexed)
|
|
target_monitor: usize,
|
|
/// Workspace index on the target monitor (zero-indexed)
|
|
target_workspace: usize,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
pub struct MoveToMonitorWorkspace {
|
|
/// Target monitor index (zero-indexed)
|
|
target_monitor: usize,
|
|
/// Workspace index on the target monitor (zero-indexed)
|
|
target_workspace: usize,
|
|
}
|
|
|
|
macro_rules! gen_focused_workspace_padding_subcommand_args {
|
|
// SubCommand Pattern
|
|
( $( $name:ident ),+ $(,)? ) => {
|
|
$(
|
|
#[derive(clap::Parser)]
|
|
pub struct $name {
|
|
/// Pixels size to set as an integer
|
|
size: i32,
|
|
}
|
|
)+
|
|
};
|
|
}
|
|
|
|
gen_focused_workspace_padding_subcommand_args! {
|
|
FocusedWorkspaceContainerPadding,
|
|
FocusedWorkspacePadding,
|
|
}
|
|
|
|
macro_rules! gen_padding_subcommand_args {
|
|
// SubCommand Pattern
|
|
( $( $name:ident ),+ $(,)? ) => {
|
|
$(
|
|
#[derive(clap::Parser)]
|
|
pub struct $name {
|
|
/// Monitor index (zero-indexed)
|
|
monitor: usize,
|
|
/// Workspace index on the specified monitor (zero-indexed)
|
|
workspace: usize,
|
|
/// Pixels to pad with as an integer
|
|
size: i32,
|
|
}
|
|
)+
|
|
};
|
|
}
|
|
|
|
gen_padding_subcommand_args! {
|
|
ContainerPadding,
|
|
WorkspacePadding,
|
|
}
|
|
|
|
macro_rules! gen_named_padding_subcommand_args {
|
|
// SubCommand Pattern
|
|
( $( $name:ident ),+ $(,)? ) => {
|
|
$(
|
|
#[derive(clap::Parser)]
|
|
pub struct $name {
|
|
/// Target workspace name
|
|
workspace: String,
|
|
|
|
/// Pixels to pad with as an integer
|
|
size: i32,
|
|
}
|
|
)+
|
|
};
|
|
}
|
|
|
|
gen_named_padding_subcommand_args! {
|
|
NamedWorkspaceContainerPadding,
|
|
NamedWorkspacePadding,
|
|
}
|
|
|
|
macro_rules! gen_padding_adjustment_subcommand_args {
|
|
// SubCommand Pattern
|
|
( $( $name:ident ),+ $(,)? ) => {
|
|
$(
|
|
#[derive(clap::Parser)]
|
|
pub struct $name {
|
|
#[clap(value_enum)]
|
|
sizing: Sizing,
|
|
/// Pixels to adjust by as an integer
|
|
adjustment: i32,
|
|
}
|
|
)+
|
|
};
|
|
}
|
|
|
|
gen_padding_adjustment_subcommand_args! {
|
|
AdjustContainerPadding,
|
|
AdjustWorkspacePadding,
|
|
}
|
|
|
|
macro_rules! gen_application_target_subcommand_args {
|
|
// SubCommand Pattern
|
|
( $( $name:ident ),+ $(,)? ) => {
|
|
$(
|
|
#[derive(clap::Parser)]
|
|
pub struct $name {
|
|
#[clap(value_enum)]
|
|
identifier: ApplicationIdentifier,
|
|
/// Identifier as a string
|
|
id: String,
|
|
}
|
|
)+
|
|
};
|
|
}
|
|
|
|
gen_application_target_subcommand_args! {
|
|
IgnoreRule,
|
|
ManageRule,
|
|
IdentifyTrayApplication,
|
|
IdentifyLayeredApplication,
|
|
IdentifyObjectNameChangeApplication,
|
|
IdentifyBorderOverflowApplication,
|
|
RemoveTitleBar,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct InitialWorkspaceRule {
|
|
#[clap(value_enum)]
|
|
identifier: ApplicationIdentifier,
|
|
/// Identifier as a string
|
|
id: String,
|
|
/// Monitor index (zero-indexed)
|
|
monitor: usize,
|
|
/// Workspace index on the specified monitor (zero-indexed)
|
|
workspace: usize,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct InitialNamedWorkspaceRule {
|
|
#[clap(value_enum)]
|
|
identifier: ApplicationIdentifier,
|
|
/// Identifier as a string
|
|
id: String,
|
|
/// Name of a workspace
|
|
workspace: String,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct WorkspaceRule {
|
|
#[clap(value_enum)]
|
|
identifier: ApplicationIdentifier,
|
|
/// Identifier as a string
|
|
id: String,
|
|
/// Monitor index (zero-indexed)
|
|
monitor: usize,
|
|
/// Workspace index on the specified monitor (zero-indexed)
|
|
workspace: usize,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct NamedWorkspaceRule {
|
|
#[clap(value_enum)]
|
|
identifier: ApplicationIdentifier,
|
|
/// Identifier as a string
|
|
id: String,
|
|
/// Name of a workspace
|
|
workspace: String,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct ClearWorkspaceRules {
|
|
/// Monitor index (zero-indexed)
|
|
monitor: usize,
|
|
/// Workspace index on the specified monitor (zero-indexed)
|
|
workspace: usize,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct ClearNamedWorkspaceRules {
|
|
/// Name of a workspace
|
|
workspace: String,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct ToggleFocusFollowsMouse {
|
|
#[clap(value_enum, short, long, default_value = "windows")]
|
|
implementation: FocusFollowsMouseImplementation,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct FocusFollowsMouse {
|
|
#[clap(value_enum, short, long, default_value = "windows")]
|
|
implementation: FocusFollowsMouseImplementation,
|
|
#[clap(value_enum)]
|
|
boolean_state: BooleanState,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct Border {
|
|
#[clap(value_enum)]
|
|
boolean_state: BooleanState,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct Transparency {
|
|
#[clap(value_enum)]
|
|
boolean_state: BooleanState,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct TransparencyAlpha {
|
|
/// Alpha
|
|
alpha: u8,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct BorderColour {
|
|
#[clap(value_enum, short, long, default_value = "single")]
|
|
window_kind: WindowKind,
|
|
/// Red
|
|
r: u32,
|
|
/// Green
|
|
g: u32,
|
|
/// Blue
|
|
b: u32,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct BorderWidth {
|
|
/// Desired width of the window border
|
|
width: i32,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct BorderOffset {
|
|
/// Desired offset of the window border
|
|
offset: i32,
|
|
}
|
|
#[derive(Parser)]
|
|
struct BorderStyle {
|
|
/// Desired border style
|
|
#[clap(value_enum)]
|
|
style: komorebi_client::BorderStyle,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct BorderImplementation {
|
|
/// Desired border implementation
|
|
#[clap(value_enum)]
|
|
style: komorebi_client::BorderImplementation,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct StackbarMode {
|
|
/// Desired stackbar mode
|
|
#[clap(value_enum)]
|
|
mode: komorebi_client::StackbarMode,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct Animation {
|
|
#[clap(value_enum)]
|
|
boolean_state: BooleanState,
|
|
/// Animation type to apply the state to. If not specified, sets global state
|
|
#[clap(value_enum, short, long)]
|
|
animation_type: Option<komorebi_client::AnimationPrefix>,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct AnimationDuration {
|
|
/// Desired animation durations in ms
|
|
duration: u64,
|
|
/// Animation type to apply the duration to. If not specified, sets global duration
|
|
#[clap(value_enum, short, long)]
|
|
animation_type: Option<komorebi_client::AnimationPrefix>,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct AnimationFps {
|
|
/// Desired animation frames per second
|
|
fps: u64,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct AnimationStyle {
|
|
/// Desired ease function for animation
|
|
#[clap(value_enum, short, long, default_value = "linear")]
|
|
style: komorebi_client::AnimationStyle,
|
|
/// Animation type to apply the style to. If not specified, sets global style
|
|
#[clap(value_enum, short, long)]
|
|
animation_type: Option<komorebi_client::AnimationPrefix>,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct Docgen {
|
|
/// Output directory for generated documentation files
|
|
#[clap(short, long)]
|
|
output: Option<PathBuf>,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
#[allow(clippy::struct_excessive_bools)]
|
|
struct Start {
|
|
/// Allow the use of komorebi's custom focus-follows-mouse implementation
|
|
#[clap(hide = true)]
|
|
#[clap(short, long = "ffm")]
|
|
ffm: bool,
|
|
/// Path to a static configuration JSON file
|
|
#[clap(short, long)]
|
|
#[clap(value_parser = replace_env_in_path)]
|
|
config: Option<PathBuf>,
|
|
/// Wait for `komorebic complete-configuration` to be sent before processing events
|
|
#[clap(short, long)]
|
|
await_configuration: bool,
|
|
/// Start a TCP server on the given port to allow the direct sending of SocketMessages
|
|
#[clap(short, long)]
|
|
tcp_port: Option<usize>,
|
|
/// Start whkd in a background process
|
|
#[clap(long)]
|
|
whkd: bool,
|
|
/// Start autohotkey configuration file
|
|
#[clap(hide = true)]
|
|
#[clap(long)]
|
|
ahk: bool,
|
|
/// Start komorebi-bar in a background process
|
|
#[clap(long)]
|
|
bar: bool,
|
|
/// Start masir in a background process for focus-follows-mouse
|
|
#[clap(long)]
|
|
masir: bool,
|
|
/// Do not attempt to auto-apply a dumped state temp file from a previously running instance of komorebi
|
|
#[clap(long)]
|
|
clean_state: bool,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct Stop {
|
|
/// Stop whkd if it is running as a background process
|
|
#[clap(long)]
|
|
whkd: bool,
|
|
/// Stop ahk if it is running as a background process
|
|
#[clap(hide = true)]
|
|
#[clap(long)]
|
|
ahk: bool,
|
|
/// Stop komorebi-bar if it is running as a background process
|
|
#[clap(long)]
|
|
bar: bool,
|
|
/// Stop masir if it is running as a background process
|
|
#[clap(long)]
|
|
masir: bool,
|
|
/// Do not restore windows after stopping komorebi
|
|
#[clap(long, hide = true)]
|
|
ignore_restore: bool,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct Kill {
|
|
/// Kill whkd if it is running as a background process
|
|
#[clap(long)]
|
|
whkd: bool,
|
|
/// Kill ahk if it is running as a background process
|
|
#[clap(hide = true)]
|
|
#[clap(long)]
|
|
ahk: bool,
|
|
/// Kill komorebi-bar if it is running as a background process
|
|
#[clap(long)]
|
|
bar: bool,
|
|
/// Kill masir if it is running as a background process
|
|
#[clap(long)]
|
|
masir: bool,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct SaveResize {
|
|
/// File to which the resize layout dimensions should be saved
|
|
#[clap(value_parser = replace_env_in_path)]
|
|
path: PathBuf,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct LoadResize {
|
|
/// File from which the resize layout dimensions should be loaded
|
|
#[clap(value_parser = replace_env_in_path)]
|
|
path: PathBuf,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct LoadCustomLayout {
|
|
/// JSON or YAML file from which the custom layout definition should be loaded
|
|
#[clap(value_parser = replace_env_in_path)]
|
|
path: PathBuf,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct SubscribeSocket {
|
|
/// Name of the socket to send event notifications to
|
|
socket: String,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct UnsubscribeSocket {
|
|
/// Name of the socket to stop sending event notifications to
|
|
socket: String,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct SubscribePipe {
|
|
/// Name of the pipe to send event notifications to (without "\\.\pipe\" prepended)
|
|
named_pipe: String,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct UnsubscribePipe {
|
|
/// Name of the pipe to stop sending event notifications to (without "\\.\pipe\" prepended)
|
|
named_pipe: String,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct AhkAppSpecificConfiguration {
|
|
/// YAML file from which the application-specific configurations should be loaded
|
|
#[clap(value_parser = replace_env_in_path)]
|
|
path: PathBuf,
|
|
/// Optional YAML file of overrides to apply over the first file
|
|
#[clap(value_parser = replace_env_in_path)]
|
|
override_path: Option<PathBuf>,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct PwshAppSpecificConfiguration {
|
|
/// YAML file from which the application-specific configurations should be loaded
|
|
#[clap(value_parser = replace_env_in_path)]
|
|
path: PathBuf,
|
|
/// Optional YAML file of overrides to apply over the first file
|
|
#[clap(value_parser = replace_env_in_path)]
|
|
override_path: Option<PathBuf>,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct FormatAppSpecificConfiguration {
|
|
/// YAML file from which the application-specific configurations should be loaded
|
|
#[clap(value_parser = replace_env_in_path)]
|
|
path: PathBuf,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct ConvertAppSpecificConfiguration {
|
|
/// YAML file from which the application-specific configurations should be loaded
|
|
#[clap(value_parser = replace_env_in_path)]
|
|
path: PathBuf,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct AltFocusHack {
|
|
#[clap(value_enum)]
|
|
boolean_state: BooleanState,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct EnableAutostart {
|
|
/// Path to a static configuration JSON file
|
|
#[clap(action, short, long)]
|
|
#[clap(value_parser = replace_env_in_path)]
|
|
config: Option<PathBuf>,
|
|
/// Enable komorebi's custom focus-follows-mouse implementation
|
|
#[clap(hide = true)]
|
|
#[clap(short, long = "ffm")]
|
|
ffm: bool,
|
|
/// Enable autostart of whkd
|
|
#[clap(long)]
|
|
whkd: bool,
|
|
/// Enable autostart of ahk
|
|
#[clap(hide = true)]
|
|
#[clap(long)]
|
|
ahk: bool,
|
|
/// Enable autostart of komorebi-bar
|
|
#[clap(long)]
|
|
bar: bool,
|
|
/// Enable autostart of masir
|
|
#[clap(long)]
|
|
masir: bool,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct Check {
|
|
/// Path to a static configuration JSON file
|
|
#[clap(action, short, long)]
|
|
#[clap(value_parser = replace_env_in_path)]
|
|
komorebi_config: Option<PathBuf>,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct ReplaceConfiguration {
|
|
/// Static configuration JSON file from which the configuration should be loaded
|
|
#[clap(value_parser = replace_env_in_path)]
|
|
path: PathBuf,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct EagerFocus {
|
|
/// Case-sensitive exe identifier
|
|
exe: String,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct ScrollingLayoutColumns {
|
|
/// Desired number of visible columns
|
|
count: NonZeroUsize,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct License {
|
|
/// Email address associated with an Individual Commercial Use License
|
|
email: String,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct Splash {
|
|
mdm_server: Option<String>,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
#[clap(author, about, version = build::CLAP_LONG_VERSION)]
|
|
struct Opts {
|
|
#[clap(subcommand)]
|
|
subcmd: SubCommand,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
enum SubCommand {
|
|
#[clap(hide = true)]
|
|
Docgen(Docgen),
|
|
#[clap(hide = true)]
|
|
Splash(Splash),
|
|
/// Gather example configurations for a new-user quickstart
|
|
Quickstart,
|
|
/// Specify an email associated with an Individual Commercial Use License
|
|
#[clap(arg_required_else_help = true)]
|
|
License(License),
|
|
/// Start komorebi.exe as a background process
|
|
Start(Start),
|
|
/// Stop the komorebi.exe process and restore all hidden windows
|
|
Stop(Stop),
|
|
/// Kill background processes started by komorebic
|
|
Kill(Kill),
|
|
/// Check komorebi configuration and related files for common errors
|
|
Check(Check),
|
|
/// Show the path to komorebi.json
|
|
#[clap(alias = "config")]
|
|
Configuration,
|
|
/// Show the path to komorebi.bar.json
|
|
#[clap(alias = "bar-config")]
|
|
#[clap(alias = "bconfig")]
|
|
BarConfiguration,
|
|
/// Show the path to whkdrc
|
|
#[clap(alias = "whkd")]
|
|
Whkdrc,
|
|
/// Show the path to komorebi's data directory in %LOCALAPPDATA%
|
|
#[clap(alias = "datadir")]
|
|
DataDirectory,
|
|
/// Show a JSON representation of the current window manager state
|
|
State,
|
|
/// Show a JSON representation of the current global state
|
|
GlobalState,
|
|
/// Launch the komorebi-gui debugging tool
|
|
Gui,
|
|
/// Toggle the komorebi-shortcuts helper
|
|
ToggleShortcuts,
|
|
/// Show a JSON representation of visible windows
|
|
VisibleWindows,
|
|
/// Show information about connected monitors
|
|
#[clap(alias = "monitor-info")]
|
|
MonitorInformation,
|
|
/// Query the current window manager state
|
|
#[clap(arg_required_else_help = true)]
|
|
Query(Query),
|
|
/// Subscribe to komorebi events using a Unix Domain Socket
|
|
#[clap(arg_required_else_help = true)]
|
|
SubscribeSocket(SubscribeSocket),
|
|
/// Unsubscribe from komorebi events
|
|
#[clap(arg_required_else_help = true)]
|
|
UnsubscribeSocket(UnsubscribeSocket),
|
|
/// Subscribe to komorebi events using a Named Pipe
|
|
#[clap(arg_required_else_help = true)]
|
|
#[clap(alias = "subscribe")]
|
|
SubscribePipe(SubscribePipe),
|
|
/// Unsubscribe from komorebi events
|
|
#[clap(arg_required_else_help = true)]
|
|
#[clap(alias = "unsubscribe")]
|
|
UnsubscribePipe(UnsubscribePipe),
|
|
/// Tail komorebi.exe's process logs (cancel with Ctrl-C)
|
|
Log,
|
|
/// Quicksave the current resize layout dimensions
|
|
#[clap(alias = "quick-save")]
|
|
QuickSaveResize,
|
|
/// Load the last quicksaved resize layout dimensions
|
|
#[clap(alias = "quick-load")]
|
|
QuickLoadResize,
|
|
/// Save the current resize layout dimensions to a file
|
|
#[clap(arg_required_else_help = true)]
|
|
#[clap(alias = "save")]
|
|
SaveResize(SaveResize),
|
|
/// Load the resize layout dimensions from a file
|
|
#[clap(arg_required_else_help = true)]
|
|
#[clap(alias = "load")]
|
|
LoadResize(LoadResize),
|
|
/// Change focus to the window in the specified direction
|
|
#[clap(arg_required_else_help = true)]
|
|
Focus(Focus),
|
|
/// Move the focused window in the specified direction
|
|
#[clap(arg_required_else_help = true)]
|
|
Move(Move),
|
|
/// Preselect the specified direction for the next window to be spawned on supported layouts
|
|
#[clap(arg_required_else_help = true)]
|
|
PreselectDirection(PreselectDirection),
|
|
/// Cancel a workspace preselect set by the preselect-direction command, if one exists
|
|
CancelPreselect,
|
|
/// Minimize the focused window
|
|
Minimize,
|
|
/// Close the focused window
|
|
Close,
|
|
/// Forcibly focus the window at the cursor with a left mouse click
|
|
ForceFocus,
|
|
/// Change focus to the window in the specified cycle direction
|
|
#[clap(arg_required_else_help = true)]
|
|
CycleFocus(CycleFocus),
|
|
/// Move the focused window in the specified cycle direction
|
|
#[clap(arg_required_else_help = true)]
|
|
CycleMove(CycleMove),
|
|
/// Focus the first managed window matching the given exe
|
|
#[clap(arg_required_else_help = true)]
|
|
EagerFocus(EagerFocus),
|
|
/// Stack the focused window in the specified direction
|
|
#[clap(arg_required_else_help = true)]
|
|
Stack(Stack),
|
|
/// Unstack the focused window
|
|
Unstack,
|
|
/// Cycle the focused stack in the specified cycle direction
|
|
#[clap(arg_required_else_help = true)]
|
|
CycleStack(CycleStack),
|
|
/// Cycle the index of the focused window in the focused stack in the specified cycle direction
|
|
#[clap(arg_required_else_help = true)]
|
|
CycleStackIndex(CycleStackIndex),
|
|
/// Focus the specified window index in the focused stack
|
|
#[clap(arg_required_else_help = true)]
|
|
FocusStackWindow(FocusStackWindow),
|
|
/// Stack all windows on the focused workspace
|
|
StackAll,
|
|
/// Unstack all windows in the focused container
|
|
UnstackAll,
|
|
/// Resize the focused window in the specified direction
|
|
#[clap(arg_required_else_help = true)]
|
|
#[clap(alias = "resize")]
|
|
ResizeEdge(Resize),
|
|
/// Resize the focused window or primary column along the specified axis
|
|
#[clap(arg_required_else_help = true)]
|
|
ResizeAxis(ResizeAxis),
|
|
/// Move the focused window to the specified monitor
|
|
#[clap(arg_required_else_help = true)]
|
|
MoveToMonitor(MoveToMonitor),
|
|
/// Move the focused window to the monitor in the given cycle direction
|
|
#[clap(arg_required_else_help = true)]
|
|
CycleMoveToMonitor(CycleMoveToMonitor),
|
|
/// Move the focused window to the specified workspace
|
|
#[clap(arg_required_else_help = true)]
|
|
MoveToWorkspace(MoveToWorkspace),
|
|
/// Move the focused window to the specified workspace
|
|
#[clap(arg_required_else_help = true)]
|
|
MoveToNamedWorkspace(MoveToNamedWorkspace),
|
|
/// Move the focused window to the workspace in the given cycle direction
|
|
#[clap(arg_required_else_help = true)]
|
|
CycleMoveToWorkspace(CycleMoveToWorkspace),
|
|
/// Send the focused window to the specified monitor
|
|
#[clap(arg_required_else_help = true)]
|
|
SendToMonitor(SendToMonitor),
|
|
/// Send the focused window to the monitor in the given cycle direction
|
|
#[clap(arg_required_else_help = true)]
|
|
CycleSendToMonitor(CycleSendToMonitor),
|
|
/// Send the focused window to the specified workspace
|
|
#[clap(arg_required_else_help = true)]
|
|
SendToWorkspace(SendToWorkspace),
|
|
/// Send the focused window to the specified workspace
|
|
#[clap(arg_required_else_help = true)]
|
|
SendToNamedWorkspace(SendToNamedWorkspace),
|
|
/// Send the focused window to the workspace in the given cycle direction
|
|
#[clap(arg_required_else_help = true)]
|
|
CycleSendToWorkspace(CycleSendToWorkspace),
|
|
/// Send the focused window to the specified monitor workspace
|
|
#[clap(arg_required_else_help = true)]
|
|
SendToMonitorWorkspace(SendToMonitorWorkspace),
|
|
/// Move the focused window to the specified monitor workspace
|
|
#[clap(arg_required_else_help = true)]
|
|
MoveToMonitorWorkspace(MoveToMonitorWorkspace),
|
|
/// Send the focused window to the last focused monitor workspace
|
|
SendToLastWorkspace,
|
|
/// Move the focused window to the last focused monitor workspace
|
|
MoveToLastWorkspace,
|
|
/// Focus the specified monitor
|
|
#[clap(arg_required_else_help = true)]
|
|
FocusMonitor(FocusMonitor),
|
|
/// Focus the monitor at the current cursor location
|
|
FocusMonitorAtCursor,
|
|
/// Focus the last focused workspace on the focused monitor
|
|
FocusLastWorkspace,
|
|
/// Focus the specified workspace on the focused monitor
|
|
#[clap(arg_required_else_help = true)]
|
|
FocusWorkspace(FocusWorkspace),
|
|
/// Focus the specified workspace on all monitors
|
|
#[clap(arg_required_else_help = true)]
|
|
FocusWorkspaces(FocusWorkspaces),
|
|
/// Focus the specified workspace on the target monitor
|
|
#[clap(arg_required_else_help = true)]
|
|
FocusMonitorWorkspace(FocusMonitorWorkspace),
|
|
/// Focus the specified workspace
|
|
#[clap(arg_required_else_help = true)]
|
|
FocusNamedWorkspace(FocusNamedWorkspace),
|
|
/// Close the focused workspace (must be empty and unnamed)
|
|
CloseWorkspace,
|
|
/// Focus the monitor in the given cycle direction
|
|
#[clap(arg_required_else_help = true)]
|
|
CycleMonitor(CycleMonitor),
|
|
/// Focus the workspace in the given cycle direction
|
|
#[clap(arg_required_else_help = true)]
|
|
CycleWorkspace(CycleWorkspace),
|
|
/// Focus the next empty workspace in the given cycle direction (if one exists)
|
|
#[clap(arg_required_else_help = true)]
|
|
CycleEmptyWorkspace(CycleWorkspace),
|
|
/// Move the focused workspace to the specified monitor
|
|
#[clap(arg_required_else_help = true)]
|
|
MoveWorkspaceToMonitor(MoveWorkspaceToMonitor),
|
|
/// Move the focused workspace monitor in the given cycle direction
|
|
#[clap(arg_required_else_help = true)]
|
|
CycleMoveWorkspaceToMonitor(CycleMoveWorkspaceToMonitor),
|
|
/// Swap focused monitor workspaces with specified monitor
|
|
#[clap(arg_required_else_help = true)]
|
|
SwapWorkspacesWithMonitor(SwapWorkspacesWithMonitor),
|
|
/// Create and append a new workspace on the focused monitor
|
|
NewWorkspace,
|
|
/// Set the resize delta (used by resize-edge and resize-axis)
|
|
#[clap(arg_required_else_help = true)]
|
|
ResizeDelta(ResizeDelta),
|
|
/// Set the invisible border dimensions around each window
|
|
#[clap(arg_required_else_help = true)]
|
|
InvisibleBorders(InvisibleBorders),
|
|
/// Set offsets to exclude parts of the work area from tiling
|
|
#[clap(arg_required_else_help = true)]
|
|
GlobalWorkAreaOffset(GlobalWorkAreaOffset),
|
|
/// Set offsets for a monitor to exclude parts of the work area from tiling
|
|
#[clap(arg_required_else_help = true)]
|
|
MonitorWorkAreaOffset(MonitorWorkAreaOffset),
|
|
/// Set offsets for a workspace to exclude parts of the work area from tiling
|
|
#[clap(arg_required_else_help = true)]
|
|
WorkspaceWorkAreaOffset(WorkspaceWorkAreaOffset),
|
|
/// Toggle application of the window-based work area offset for the focused workspace
|
|
ToggleWindowBasedWorkAreaOffset,
|
|
/// Set container padding on the focused workspace
|
|
#[clap(arg_required_else_help = true)]
|
|
FocusedWorkspaceContainerPadding(FocusedWorkspaceContainerPadding),
|
|
/// Set workspace padding on the focused workspace
|
|
#[clap(arg_required_else_help = true)]
|
|
FocusedWorkspacePadding(FocusedWorkspacePadding),
|
|
/// Adjust container padding on the focused workspace
|
|
#[clap(arg_required_else_help = true)]
|
|
AdjustContainerPadding(AdjustContainerPadding),
|
|
/// Adjust workspace padding on the focused workspace
|
|
#[clap(arg_required_else_help = true)]
|
|
AdjustWorkspacePadding(AdjustWorkspacePadding),
|
|
/// Set the layout on the focused workspace
|
|
#[clap(arg_required_else_help = true)]
|
|
ChangeLayout(ChangeLayout),
|
|
/// Cycle between available layouts
|
|
#[clap(arg_required_else_help = true)]
|
|
CycleLayout(CycleLayout),
|
|
/// Set the number of visible columns for the Scrolling layout on the focused workspace
|
|
#[clap(arg_required_else_help = true)]
|
|
ScrollingLayoutColumns(ScrollingLayoutColumns),
|
|
/// Load a custom layout from file for the focused workspace
|
|
#[clap(hide = true)]
|
|
#[clap(arg_required_else_help = true)]
|
|
LoadCustomLayout(LoadCustomLayout),
|
|
/// Flip the layout on the focused workspace
|
|
#[clap(arg_required_else_help = true)]
|
|
FlipLayout(FlipLayout),
|
|
/// Promote the focused window to the largest tile via container removal and re-insertion
|
|
Promote,
|
|
/// Promote the focused window to the largest tile by swapping container indices with the largest tile
|
|
PromoteSwap,
|
|
/// Promote the user focus to the top of the tree
|
|
PromoteFocus,
|
|
/// Promote the window in the specified direction
|
|
PromoteWindow(PromoteWindow),
|
|
/// Force the retiling of all managed windows
|
|
Retile,
|
|
/// Set the monitor index preference for a monitor identified using its size
|
|
#[clap(arg_required_else_help = true)]
|
|
MonitorIndexPreference(MonitorIndexPreference),
|
|
/// Set the display index preference for a monitor identified using its display name
|
|
#[clap(arg_required_else_help = true)]
|
|
DisplayIndexPreference(DisplayIndexPreference),
|
|
/// Create at least this many workspaces for the specified monitor
|
|
#[clap(arg_required_else_help = true)]
|
|
EnsureWorkspaces(EnsureWorkspaces),
|
|
/// Create these many named workspaces for the specified monitor
|
|
#[clap(arg_required_else_help = true)]
|
|
EnsureNamedWorkspaces(EnsureNamedWorkspaces),
|
|
/// Set the container padding for the specified workspace
|
|
#[clap(arg_required_else_help = true)]
|
|
ContainerPadding(ContainerPadding),
|
|
/// Set the container padding for the specified workspace
|
|
#[clap(arg_required_else_help = true)]
|
|
NamedWorkspaceContainerPadding(NamedWorkspaceContainerPadding),
|
|
/// Set the workspace padding for the specified workspace
|
|
#[clap(arg_required_else_help = true)]
|
|
WorkspacePadding(WorkspacePadding),
|
|
/// Set the workspace padding for the specified workspace
|
|
#[clap(arg_required_else_help = true)]
|
|
NamedWorkspacePadding(NamedWorkspacePadding),
|
|
/// Set the layout for the specified workspace
|
|
#[clap(arg_required_else_help = true)]
|
|
WorkspaceLayout(WorkspaceLayout),
|
|
/// Set the layout for the specified workspace
|
|
#[clap(arg_required_else_help = true)]
|
|
NamedWorkspaceLayout(NamedWorkspaceLayout),
|
|
/// Set a custom layout for the specified workspace
|
|
#[clap(hide = true)]
|
|
#[clap(arg_required_else_help = true)]
|
|
WorkspaceCustomLayout(WorkspaceCustomLayout),
|
|
/// Set a custom layout for the specified workspace
|
|
#[clap(hide = true)]
|
|
#[clap(arg_required_else_help = true)]
|
|
NamedWorkspaceCustomLayout(NamedWorkspaceCustomLayout),
|
|
/// Add a dynamic layout rule for the specified workspace
|
|
#[clap(arg_required_else_help = true)]
|
|
WorkspaceLayoutRule(WorkspaceLayoutRule),
|
|
/// Add a dynamic layout rule for the specified workspace
|
|
#[clap(arg_required_else_help = true)]
|
|
NamedWorkspaceLayoutRule(NamedWorkspaceLayoutRule),
|
|
/// Add a dynamic custom layout for the specified workspace
|
|
#[clap(hide = true)]
|
|
#[clap(arg_required_else_help = true)]
|
|
WorkspaceCustomLayoutRule(WorkspaceCustomLayoutRule),
|
|
/// Add a dynamic custom layout for the specified workspace
|
|
#[clap(hide = true)]
|
|
#[clap(arg_required_else_help = true)]
|
|
NamedWorkspaceCustomLayoutRule(NamedWorkspaceCustomLayoutRule),
|
|
/// Clear all dynamic layout rules for the specified workspace
|
|
#[clap(arg_required_else_help = true)]
|
|
ClearWorkspaceLayoutRules(ClearWorkspaceLayoutRules),
|
|
/// Clear all dynamic layout rules for the specified workspace
|
|
#[clap(arg_required_else_help = true)]
|
|
ClearNamedWorkspaceLayoutRules(ClearNamedWorkspaceLayoutRules),
|
|
/// Enable or disable window tiling for the specified workspace
|
|
#[clap(arg_required_else_help = true)]
|
|
WorkspaceTiling(WorkspaceTiling),
|
|
/// Enable or disable window tiling for the specified workspace
|
|
#[clap(arg_required_else_help = true)]
|
|
NamedWorkspaceTiling(NamedWorkspaceTiling),
|
|
/// Set the workspace name for the specified workspace
|
|
#[clap(arg_required_else_help = true)]
|
|
WorkspaceName(WorkspaceName),
|
|
/// Toggle the behaviour for new windows (stacking or dynamic tiling)
|
|
ToggleWindowContainerBehaviour,
|
|
/// Enable or disable float override, which makes it so every new window opens in floating mode
|
|
ToggleFloatOverride,
|
|
/// Toggle the behaviour for new windows (stacking or dynamic tiling) for currently focused
|
|
/// workspace. If there was no behaviour set for the workspace previously it takes the opposite
|
|
/// of the global value.
|
|
ToggleWorkspaceWindowContainerBehaviour,
|
|
/// Enable or disable float override, which makes it so every new window opens in floating
|
|
/// mode, for the currently focused workspace. If there was no override value set for the
|
|
/// workspace previously it takes the opposite of the global value.
|
|
ToggleWorkspaceFloatOverride,
|
|
/// Toggle between the Tiling and Floating layers on the focused workspace
|
|
ToggleWorkspaceLayer,
|
|
/// Toggle the paused state for all window tiling
|
|
TogglePause,
|
|
/// Toggle window tiling on the focused workspace
|
|
ToggleTiling,
|
|
/// Toggle floating mode for the focused window
|
|
ToggleFloat,
|
|
/// Toggle monocle mode for the focused container
|
|
ToggleMonocle,
|
|
/// Toggle native maximization for the focused window
|
|
ToggleMaximize,
|
|
/// Toggle a lock for the focused container, ensuring it will not be displaced by any new windows
|
|
ToggleLock,
|
|
/// Restore all hidden windows (debugging command)
|
|
RestoreWindows,
|
|
/// Force komorebi to manage the focused window
|
|
Manage,
|
|
/// Unmanage a window that was forcibly managed
|
|
Unmanage,
|
|
/// Replace the configuration of a running instance of komorebi from a static configuration file
|
|
#[clap(arg_required_else_help = true)]
|
|
ReplaceConfiguration(ReplaceConfiguration),
|
|
/// Reload legacy komorebi.ahk or komorebi.ps1 configurations (if they exist)
|
|
ReloadConfiguration,
|
|
/// Enable or disable watching of legacy komorebi.ahk or komorebi.ps1 configurations (if they exist)
|
|
#[clap(arg_required_else_help = true)]
|
|
WatchConfiguration(WatchConfiguration),
|
|
/// For legacy komorebi.ahk or komorebi.ps1 configurations, signal that the final configuration option has been sent
|
|
CompleteConfiguration,
|
|
/// DEPRECATED since v0.1.22
|
|
#[deprecated(note = "No longer required")]
|
|
#[clap(arg_required_else_help = true)]
|
|
#[clap(hide = true)]
|
|
AltFocusHack(AltFocusHack),
|
|
/// Set the window behaviour when switching workspaces / cycling stacks
|
|
#[clap(arg_required_else_help = true)]
|
|
WindowHidingBehaviour(WindowHidingBehaviour),
|
|
/// Set the behaviour when moving windows across monitor boundaries
|
|
#[clap(arg_required_else_help = true)]
|
|
CrossMonitorMoveBehaviour(CrossMonitorMoveBehaviour),
|
|
/// Toggle the behaviour when moving windows across monitor boundaries
|
|
ToggleCrossMonitorMoveBehaviour,
|
|
/// Set the operation behaviour when the focused window is not managed
|
|
#[clap(arg_required_else_help = true)]
|
|
UnmanagedWindowOperationBehaviour(UnmanagedWindowOperationBehaviour),
|
|
/// Add a rule to float the foreground window for the rest of this session
|
|
SessionFloatRule,
|
|
/// Show all session float rules
|
|
SessionFloatRules,
|
|
/// Clear all session float rules
|
|
ClearSessionFloatRules,
|
|
/// Add a rule to ignore the specified application
|
|
#[clap(arg_required_else_help = true)]
|
|
#[clap(alias = "float-rule")]
|
|
IgnoreRule(IgnoreRule),
|
|
/// Add a rule to always manage the specified application
|
|
#[clap(arg_required_else_help = true)]
|
|
ManageRule(ManageRule),
|
|
/// Add a rule to associate an application with a workspace on first show
|
|
#[clap(arg_required_else_help = true)]
|
|
InitialWorkspaceRule(InitialWorkspaceRule),
|
|
/// Add a rule to associate an application with a named workspace on first show
|
|
#[clap(arg_required_else_help = true)]
|
|
InitialNamedWorkspaceRule(InitialNamedWorkspaceRule),
|
|
/// Add a rule to associate an application with a workspace
|
|
#[clap(arg_required_else_help = true)]
|
|
WorkspaceRule(WorkspaceRule),
|
|
/// Add a rule to associate an application with a named workspace
|
|
#[clap(arg_required_else_help = true)]
|
|
NamedWorkspaceRule(NamedWorkspaceRule),
|
|
/// Remove all application association rules for a workspace by monitor and workspace index
|
|
#[clap(arg_required_else_help = true)]
|
|
ClearWorkspaceRules(ClearWorkspaceRules),
|
|
/// Remove all application association rules for a named workspace
|
|
#[clap(arg_required_else_help = true)]
|
|
ClearNamedWorkspaceRules(ClearNamedWorkspaceRules),
|
|
/// Remove all application association rules for all workspaces
|
|
ClearAllWorkspaceRules,
|
|
/// Enforce all workspace rules, including initial workspace rules that have already been applied
|
|
EnforceWorkspaceRules,
|
|
/// Identify an application that sends EVENT_OBJECT_NAMECHANGE on launch
|
|
#[clap(arg_required_else_help = true)]
|
|
IdentifyObjectNameChangeApplication(IdentifyObjectNameChangeApplication),
|
|
/// Identify an application that closes to the system tray
|
|
#[clap(arg_required_else_help = true)]
|
|
IdentifyTrayApplication(IdentifyTrayApplication),
|
|
/// Identify an application that has WS_EX_LAYERED, but should still be managed
|
|
#[clap(arg_required_else_help = true)]
|
|
IdentifyLayeredApplication(IdentifyLayeredApplication),
|
|
/// Whitelist an application for title bar removal
|
|
#[clap(arg_required_else_help = true)]
|
|
RemoveTitleBar(RemoveTitleBar),
|
|
/// Toggle title bars for whitelisted applications
|
|
ToggleTitleBars,
|
|
/// Identify an application that has overflowing borders
|
|
#[clap(hide = true)]
|
|
#[clap(alias = "identify-border-overflow")]
|
|
IdentifyBorderOverflowApplication(IdentifyBorderOverflowApplication),
|
|
/// Enable or disable borders
|
|
#[clap(arg_required_else_help = true)]
|
|
#[clap(alias = "active-window-border")]
|
|
Border(Border),
|
|
/// Set the colour for a window border kind
|
|
#[clap(arg_required_else_help = true)]
|
|
#[clap(alias = "active-window-border-colour")]
|
|
BorderColour(BorderColour),
|
|
/// Set the border width
|
|
#[clap(arg_required_else_help = true)]
|
|
#[clap(alias = "active-window-border-width")]
|
|
BorderWidth(BorderWidth),
|
|
/// Set the border offset
|
|
#[clap(arg_required_else_help = true)]
|
|
#[clap(alias = "active-window-border-offset")]
|
|
BorderOffset(BorderOffset),
|
|
/// Set the border style
|
|
#[clap(arg_required_else_help = true)]
|
|
BorderStyle(BorderStyle),
|
|
/// Set the border implementation
|
|
#[clap(arg_required_else_help = true)]
|
|
BorderImplementation(BorderImplementation),
|
|
/// Set the stackbar mode
|
|
#[clap(arg_required_else_help = true)]
|
|
StackbarMode(StackbarMode),
|
|
/// Enable or disable transparency for unfocused windows
|
|
#[clap(arg_required_else_help = true)]
|
|
Transparency(Transparency),
|
|
/// Set the alpha value for unfocused window transparency
|
|
#[clap(arg_required_else_help = true)]
|
|
TransparencyAlpha(TransparencyAlpha),
|
|
/// Toggle transparency for unfocused windows
|
|
ToggleTransparency,
|
|
/// Enable or disable movement animations
|
|
#[clap(arg_required_else_help = true)]
|
|
Animation(Animation),
|
|
/// Set the duration for movement animations in ms
|
|
#[clap(arg_required_else_help = true)]
|
|
AnimationDuration(AnimationDuration),
|
|
/// Set the frames per second for movement animations
|
|
#[clap(arg_required_else_help = true)]
|
|
AnimationFps(AnimationFps),
|
|
/// Set the ease function for movement animations
|
|
#[clap(arg_required_else_help = true)]
|
|
AnimationStyle(AnimationStyle),
|
|
/// Enable or disable focus follows mouse for the operating system
|
|
#[clap(hide = true)]
|
|
#[clap(arg_required_else_help = true)]
|
|
FocusFollowsMouse(FocusFollowsMouse),
|
|
/// Toggle focus follows mouse for the operating system
|
|
#[clap(hide = true)]
|
|
#[clap(arg_required_else_help = true)]
|
|
ToggleFocusFollowsMouse(ToggleFocusFollowsMouse),
|
|
/// Enable or disable mouse follows focus on all workspaces
|
|
#[clap(arg_required_else_help = true)]
|
|
MouseFollowsFocus(MouseFollowsFocus),
|
|
/// Toggle mouse follows focus on all workspaces
|
|
ToggleMouseFollowsFocus,
|
|
/// Generate common app-specific configurations and fixes to use in komorebi.ahk
|
|
#[clap(arg_required_else_help = true)]
|
|
#[clap(alias = "ahk-asc")]
|
|
AhkAppSpecificConfiguration(AhkAppSpecificConfiguration),
|
|
/// Generate common app-specific configurations and fixes in a PowerShell script
|
|
#[clap(arg_required_else_help = true)]
|
|
#[clap(alias = "pwsh-asc")]
|
|
PwshAppSpecificConfiguration(PwshAppSpecificConfiguration),
|
|
/// Convert a v1 ASC YAML file to a v2 ASC JSON file
|
|
#[clap(arg_required_else_help = true)]
|
|
#[clap(alias = "convert-asc")]
|
|
ConvertAppSpecificConfiguration(ConvertAppSpecificConfiguration),
|
|
/// Format a YAML file for use with the `app-specific-configuration` command
|
|
#[clap(arg_required_else_help = true)]
|
|
#[clap(alias = "fmt-asc")]
|
|
#[clap(hide = true)]
|
|
FormatAppSpecificConfiguration(FormatAppSpecificConfiguration),
|
|
/// Fetch the latest version of applications.json from komorebi-application-specific-configuration
|
|
#[clap(alias = "fetch-asc")]
|
|
FetchAppSpecificConfiguration,
|
|
/// Generate a JSON Schema for applications.json
|
|
#[clap(alias = "asc-schema")]
|
|
ApplicationSpecificConfigurationSchema,
|
|
/// Generate a JSON Schema of subscription notifications
|
|
NotificationSchema,
|
|
/// Generate a JSON Schema of socket messages
|
|
SocketSchema,
|
|
/// Generate a JSON Schema of the static configuration file
|
|
StaticConfigSchema,
|
|
/// Generates a static configuration JSON file based on the current window manager state
|
|
GenerateStaticConfig,
|
|
/// Generates the komorebi.lnk shortcut in shell:startup to autostart komorebi
|
|
EnableAutostart(EnableAutostart),
|
|
/// Deletes the komorebi.lnk shortcut in shell:startup to disable autostart
|
|
DisableAutostart,
|
|
}
|
|
|
|
// print_query is a helper that queries komorebi and prints the response.
|
|
// panics on error.
|
|
fn print_query(message: &SocketMessage) {
|
|
match send_query(message) {
|
|
Ok(response) => println!("{response}"),
|
|
Err(error) => panic!("{}", error),
|
|
}
|
|
}
|
|
|
|
fn startup_dir() -> eyre::Result<PathBuf> {
|
|
let startup = dirs::home_dir()
|
|
.expect("unable to obtain user's home folder")
|
|
.join("AppData")
|
|
.join("Roaming")
|
|
.join("Microsoft")
|
|
.join("Windows")
|
|
.join("Start Menu")
|
|
.join("Programs")
|
|
.join("Startup");
|
|
|
|
if !startup.is_dir() {
|
|
std::fs::create_dir_all(&startup)?;
|
|
}
|
|
|
|
Ok(startup)
|
|
}
|
|
|
|
#[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
|
|
fn main() -> eyre::Result<()> {
|
|
let opts: Opts = Opts::parse();
|
|
|
|
match opts.subcmd {
|
|
SubCommand::Docgen(args) => {
|
|
let mut cli = Opts::command();
|
|
let subcommands = cli.get_subcommands_mut();
|
|
|
|
let output_dir = args.output.unwrap_or_else(|| PathBuf::from("docs/cli"));
|
|
std::fs::create_dir_all(&output_dir)?;
|
|
|
|
let ignore = [
|
|
"docgen",
|
|
"splash",
|
|
"alt-focus-hack",
|
|
"identify-border-overflow-application",
|
|
"load-custom-layout",
|
|
"workspace-custom-layout",
|
|
"named-workspace-custom-layout",
|
|
"workspace-custom-layout-rule",
|
|
"named-workspace-custom-layout-rule",
|
|
"focus-follows-mouse",
|
|
"toggle-focus-follows-mouse",
|
|
"format-app-specific-configuration",
|
|
];
|
|
|
|
for cmd in subcommands {
|
|
let name = cmd.get_name().to_string();
|
|
if !ignore.contains(&name.as_str()) {
|
|
let help_text = cmd.render_long_help().to_string();
|
|
let outpath = output_dir.join(format!("{name}.md"));
|
|
let markdown = format!("```\n{help_text}\n```");
|
|
std::fs::write(&outpath, markdown)?;
|
|
println!("{}", outpath.display());
|
|
}
|
|
}
|
|
}
|
|
SubCommand::Splash(args) => {
|
|
let informative_text = match args.mdm_server {
|
|
None => {
|
|
"It looks like you are using a corporate device enrolled in mobile device management\n\n\
|
|
The Komorebi License does not permit any kind of commercial use\n\n\
|
|
A dedicated Individual Commercial Use License is available if you wish to use this software at work\n\n\
|
|
You are strongly encouraged to make your employer pay for your license, either directly or via reimbursement\n\n\
|
|
To remove this popup in the future, run \"komorebic license <email>\" using the email address associated with your license\n\n\
|
|
If you are a student using komorebi on a laptop provided by your school, you can email me from your educational email account to remove this popup".to_string()
|
|
}
|
|
Some(server) => {
|
|
format!(
|
|
"It looks like you are using a corporate device enrolled in mobile device management ({server})\n\n\
|
|
The Komorebi License does not permit any kind of commercial use\n\n\
|
|
A dedicated Individual Commercial Use License is available if you wish to use this software at work\n\n\
|
|
You are strongly encouraged to make your employer pay for your license, either directly or via reimbursement\n\n\
|
|
To remove this popup in the future you can run \"komorebic license <email>\" using the email address associated with your license\n\n
|
|
If you are a student using komorebi on a laptop provided by your school, you can email me from your educational email account to remove this popup"
|
|
)
|
|
}
|
|
};
|
|
|
|
if let Ok(response) = win_msgbox::error::<OkayCancel>(&informative_text)
|
|
.title("MDM Enrollment Detected")
|
|
.topmost()
|
|
.icon(win_msgbox::Icon::Warning)
|
|
.set_foreground()
|
|
.show()
|
|
{
|
|
match response {
|
|
OkayCancel::Okay => {
|
|
open::that("https://lgug2z.com/software/komorebi")?;
|
|
}
|
|
OkayCancel::Cancel => {}
|
|
}
|
|
}
|
|
}
|
|
SubCommand::License(args) => {
|
|
let _ = std::fs::remove_file(DATA_DIR.join("icul.validation"));
|
|
std::fs::write(DATA_DIR.join("icul"), &args.email)?;
|
|
match splash::should()? {
|
|
ValidationFeedback::Successful(icul_validation) => {
|
|
println!("Individual commercial use license validation successful");
|
|
println!(
|
|
"Local validation file saved to {}",
|
|
icul_validation.display()
|
|
);
|
|
println!("\n{}", std::fs::read_to_string(&icul_validation)?);
|
|
}
|
|
ValidationFeedback::Unsuccessful(invalid_payload) => {
|
|
println!(
|
|
"No active individual commercial use license found for {}",
|
|
args.email
|
|
);
|
|
println!("\n{invalid_payload}");
|
|
println!(
|
|
"\nYou can purchase an individual commercial use license at https://lgug2z.com/software/komorebi"
|
|
);
|
|
}
|
|
ValidationFeedback::NoEmail => {}
|
|
ValidationFeedback::NoConnectivity => {
|
|
println!(
|
|
"Could not make a connection to validate an individual commercial use license for {}",
|
|
args.email
|
|
);
|
|
}
|
|
}
|
|
}
|
|
SubCommand::Quickstart => {
|
|
fn write_file_with_prompt(
|
|
path: &PathBuf,
|
|
content: &str,
|
|
created_files: &mut Vec<String>,
|
|
) -> eyre::Result<()> {
|
|
if path.exists() {
|
|
print!(
|
|
"{} will be overwritten, do you want to continue? (y/N): ",
|
|
path.display()
|
|
);
|
|
io::stdout().flush()?;
|
|
let mut input = String::new();
|
|
io::stdin().read_line(&mut input)?;
|
|
let trimmed = input.trim().to_lowercase();
|
|
if trimmed == "y" || trimmed == "yes" {
|
|
std::fs::write(path, content)?;
|
|
created_files.push(path.display().to_string());
|
|
} else {
|
|
println!("Skipping {}", path.display());
|
|
}
|
|
} else {
|
|
std::fs::write(path, content)?;
|
|
created_files.push(path.display().to_string());
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
let local_appdata_dir = dirs::data_local_dir().expect("could not find localdata dir");
|
|
let data_dir = local_appdata_dir.join("komorebi");
|
|
std::fs::create_dir_all(&*WHKD_CONFIG_DIR)?;
|
|
std::fs::create_dir_all(&*HOME_DIR)?;
|
|
std::fs::create_dir_all(data_dir)?;
|
|
|
|
let mut komorebi_json = include_str!("../../docs/komorebi.example.json").to_string();
|
|
let komorebi_bar_json =
|
|
include_str!("../../docs/komorebi.bar.example.json").to_string();
|
|
|
|
if std::env::var("KOMOREBI_CONFIG_HOME").is_ok() {
|
|
komorebi_json =
|
|
komorebi_json.replace("Env:USERPROFILE", "Env:KOMOREBI_CONFIG_HOME");
|
|
}
|
|
|
|
let komorebi_path = HOME_DIR.join("komorebi.json");
|
|
let bar_path = HOME_DIR.join("komorebi.bar.json");
|
|
let applications_path = HOME_DIR.join("applications.json");
|
|
let whkdrc_path = WHKD_CONFIG_DIR.join("whkdrc");
|
|
|
|
let mut written_files = Vec::new();
|
|
|
|
write_file_with_prompt(&komorebi_path, &komorebi_json, &mut written_files)?;
|
|
write_file_with_prompt(&bar_path, &komorebi_bar_json, &mut written_files)?;
|
|
|
|
let applications_json = include_str!("../applications.json");
|
|
write_file_with_prompt(&applications_path, applications_json, &mut written_files)?;
|
|
|
|
let whkdrc = include_str!("../../docs/whkdrc.sample");
|
|
write_file_with_prompt(&whkdrc_path, whkdrc, &mut written_files)?;
|
|
if written_files.is_empty() {
|
|
println!("\nNo files were written.")
|
|
} else {
|
|
println!(
|
|
"\nThe following example files were written:\n{}",
|
|
written_files.join("\n")
|
|
);
|
|
}
|
|
|
|
if let Ok((mdm, server)) = splash::mdm_enrollment()
|
|
&& mdm
|
|
{
|
|
if let Some(server) = server {
|
|
println!(
|
|
"\nIt looks like you are using a corporate device enrolled in mobile device management ({server})"
|
|
);
|
|
} else {
|
|
println!(
|
|
"\nIt looks like you are using a corporate device enrolled in mobile device management"
|
|
);
|
|
}
|
|
println!("The Komorebi License does not permit any kind of commercial use");
|
|
println!(
|
|
"A dedicated Individual Commercial Use License is available if you wish to use this software at work"
|
|
);
|
|
println!(
|
|
"You are strongly encouraged to make your employer pay for your license, either directly or via reimbursement"
|
|
);
|
|
println!(
|
|
"If you already have a license, you can run \"komorebic license <email>\" with the email address your license is associated with"
|
|
);
|
|
}
|
|
|
|
println!("\nYou can now run komorebic start --whkd --bar");
|
|
}
|
|
SubCommand::EnableAutostart(args) => {
|
|
if args.ahk {
|
|
println!(
|
|
"EOL: The --ahk flag is now end-of-life and will not receive any further updates or bug fixes"
|
|
);
|
|
}
|
|
|
|
let mut current_exe = std::env::current_exe().expect("unable to get exec path");
|
|
current_exe.pop();
|
|
let komorebic_exe = current_exe.join("komorebic-no-console.exe");
|
|
let komorebic_exe = dunce::simplified(&komorebic_exe);
|
|
|
|
let startup_dir = startup_dir()?;
|
|
let shortcut_file = startup_dir.join("komorebi.lnk");
|
|
let shortcut_file = dunce::simplified(&shortcut_file);
|
|
|
|
let mut arguments = String::from("start");
|
|
|
|
if let Some(config) = args.config {
|
|
arguments.push_str(" --config ");
|
|
arguments.push_str(&config.to_string_lossy());
|
|
}
|
|
|
|
if args.ffm {
|
|
arguments.push_str(" --ffm");
|
|
}
|
|
|
|
if args.bar {
|
|
arguments.push_str(" --bar");
|
|
}
|
|
|
|
if args.whkd {
|
|
arguments.push_str(" --whkd");
|
|
} else if args.ahk {
|
|
arguments.push_str(" --ahk");
|
|
}
|
|
|
|
if args.masir {
|
|
arguments.push_str(" --masir");
|
|
}
|
|
|
|
Command::new("powershell")
|
|
.arg("-c")
|
|
.arg("$WshShell = New-Object -comObject WScript.Shell; $Shortcut = $WshShell.CreateShortcut($env:SHORTCUT_PATH); $Shortcut.TargetPath = $env:TARGET_PATH; $Shortcut.Arguments = $env:TARGET_ARGS; $Shortcut.Save()")
|
|
.env("SHORTCUT_PATH", shortcut_file.as_os_str())
|
|
.env("TARGET_PATH", komorebic_exe.as_os_str())
|
|
.env("TARGET_ARGS", arguments)
|
|
.output()?;
|
|
|
|
println!(
|
|
"NOTE: If your komorebi.json file contains a reference to $Env:KOMOREBI_CONFIG_HOME,"
|
|
);
|
|
println!(
|
|
"you need to add this to System Properties > Environment Variables > User Variables"
|
|
);
|
|
println!("in order for the autostart command to work properly");
|
|
}
|
|
SubCommand::DisableAutostart => {
|
|
let startup_dir = startup_dir()?;
|
|
let shortcut_file = startup_dir.join("komorebi.lnk");
|
|
|
|
if shortcut_file.is_file() {
|
|
std::fs::remove_file(shortcut_file)?;
|
|
}
|
|
}
|
|
SubCommand::Check(args) => {
|
|
let home_display = HOME_DIR.display();
|
|
if HAS_CUSTOM_CONFIG_HOME.load(Ordering::SeqCst) {
|
|
println!("KOMOREBI_CONFIG_HOME detected: {home_display}\n");
|
|
} else {
|
|
println!(
|
|
"No KOMOREBI_CONFIG_HOME detected, defaulting to {}\n",
|
|
dirs::home_dir()
|
|
.expect("could not find home dir")
|
|
.to_string_lossy()
|
|
);
|
|
}
|
|
|
|
println!("Looking for configuration files in {home_display}\n");
|
|
|
|
let static_config = if let Some(static_config) = args.komorebi_config {
|
|
println!(
|
|
"Using an arbitrary configuration file passed to --komorebi-config flag\n"
|
|
);
|
|
static_config
|
|
} else {
|
|
HOME_DIR.join("komorebi.json")
|
|
};
|
|
|
|
let config_pwsh = HOME_DIR.join("komorebi.ps1");
|
|
let config_ahk = HOME_DIR.join("komorebi.ahk");
|
|
let config_whkd = WHKD_CONFIG_DIR.join("whkdrc");
|
|
|
|
if static_config.exists() {
|
|
let config_source = std::fs::read_to_string(&static_config)?;
|
|
let lines: Vec<_> = config_source.lines().collect();
|
|
let parsed_config = serde_json::from_str::<serde_json::Value>(&config_source);
|
|
if let Err(serde_error) = &parsed_config {
|
|
let line = lines[serde_error.line() - 2];
|
|
|
|
let offset = SourceOffset::from_location(
|
|
config_source.clone(),
|
|
serde_error.line() - 1,
|
|
line.len(),
|
|
);
|
|
|
|
let error_string = serde_error.to_string();
|
|
let msgs: Vec<_> = error_string.split(" at ").collect();
|
|
|
|
let diagnostic = ConfigurationError {
|
|
message: msgs[0].to_string(),
|
|
src: NamedSource::new("komorebi.json", config_source.clone()),
|
|
bad_bit: SourceSpan::new(offset, 2),
|
|
};
|
|
|
|
println!("{:?}", Report::new(diagnostic));
|
|
}
|
|
|
|
println!(
|
|
"Found komorebi.json; this file can be passed to the start command with the --config flag\n"
|
|
);
|
|
|
|
if let Ok(config) = StaticConfig::read(&static_config) {
|
|
match config.app_specific_configuration_path {
|
|
None => {
|
|
println!(
|
|
"Application specific configuration file path has not been set. Try running 'komorebic fetch-asc'\n"
|
|
);
|
|
}
|
|
Some(AppSpecificConfigurationPath::Single(path)) => {
|
|
if !path.exists() {
|
|
println!(
|
|
"Application specific configuration file path '{}' does not exist. Try running 'komorebic fetch-asc'\n",
|
|
path.display()
|
|
);
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
// Check that this file adheres to the schema static config schema as the last step,
|
|
// so that more basic errors above can be shown to the error before schema-specific
|
|
// errors
|
|
let _ = serde_json::from_str::<StaticConfig>(&config_source)?;
|
|
|
|
let raw = std::fs::read_to_string(static_config)?;
|
|
StaticConfig::aliases(&raw);
|
|
StaticConfig::deprecated(&raw);
|
|
StaticConfig::end_of_life(&raw);
|
|
|
|
if config_whkd.exists() {
|
|
println!(
|
|
"Found {}; key bindings will be loaded from here when whkd is started, and you can start it automatically using the --whkd flag\n",
|
|
config_whkd.to_string_lossy()
|
|
);
|
|
} else {
|
|
println!(
|
|
"No ~/.config/whkdrc found; you may not be able to control komorebi with your keyboard\n"
|
|
);
|
|
}
|
|
} else if config_pwsh.exists() {
|
|
println!("Found komorebi.ps1; this file will be autoloaded by komorebi\n");
|
|
if config_whkd.exists() {
|
|
println!(
|
|
"Found {}; key bindings will be loaded from here when whkd is started\n",
|
|
config_whkd.to_string_lossy()
|
|
);
|
|
} else {
|
|
println!(
|
|
"No ~/.config/whkdrc found; you may not be able to control komorebi with your keyboard\n"
|
|
);
|
|
}
|
|
} else if config_ahk.exists() {
|
|
println!("Found komorebi.ahk; this file will be autoloaded by komorebi\n");
|
|
} else {
|
|
println!("No komorebi configuration found in {home_display}\n");
|
|
println!(
|
|
"If running 'komorebic start --await-configuration', you will manually have to call the following command to begin tiling: komorebic complete-configuration\n"
|
|
);
|
|
}
|
|
|
|
let client = reqwest::blocking::Client::new();
|
|
|
|
if let Ok(response) = client
|
|
.get("https://api.github.com/repos/LGUG2Z/komorebi/releases/latest")
|
|
.header("User-Agent", "komorebic-version-checker")
|
|
.send()
|
|
{
|
|
let version = env!("CARGO_PKG_VERSION");
|
|
|
|
#[derive(Deserialize)]
|
|
struct Release {
|
|
tag_name: String,
|
|
}
|
|
|
|
if let Ok(release) =
|
|
serde_json::from_str::<Release>(&response.text().unwrap_or_default())
|
|
{
|
|
let trimmed = release.tag_name.trim_start_matches("v");
|
|
if trimmed > version {
|
|
println!(
|
|
"An updated version of komorebi is available! https://github.com/LGUG2Z/komorebi/releases/v{trimmed}"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
SubCommand::Configuration => {
|
|
let static_config = HOME_DIR.join("komorebi.json");
|
|
|
|
if static_config.exists() {
|
|
println!("{}", static_config.display());
|
|
}
|
|
}
|
|
SubCommand::BarConfiguration => {
|
|
let static_config = HOME_DIR.join("komorebi.bar.json");
|
|
|
|
if static_config.exists() {
|
|
println!("{}", static_config.display());
|
|
}
|
|
}
|
|
SubCommand::Whkdrc => {
|
|
let whkdrc = WHKD_CONFIG_DIR.join("whkdrc");
|
|
|
|
if whkdrc.exists() {
|
|
println!("{}", whkdrc.display());
|
|
}
|
|
}
|
|
SubCommand::DataDirectory => {
|
|
let dir = &*DATA_DIR;
|
|
if dir.exists() {
|
|
println!("{}", dir.display());
|
|
}
|
|
}
|
|
SubCommand::Log => {
|
|
let timestamp = Utc::now().format("%Y-%m-%d").to_string();
|
|
let color_log = std::env::temp_dir().join(format!("komorebi.log.{timestamp}"));
|
|
let file = TailedFile::new(File::open(color_log)?);
|
|
let locked = file.lock();
|
|
#[allow(clippy::significant_drop_in_scrutinee, clippy::lines_filter_map_ok)]
|
|
for line in locked.lines().flatten() {
|
|
println!("{line}");
|
|
}
|
|
}
|
|
SubCommand::Focus(args) => {
|
|
send_message(&SocketMessage::FocusWindow(args.operation_direction))?;
|
|
}
|
|
SubCommand::ForceFocus => {
|
|
send_message(&SocketMessage::ForceFocus)?;
|
|
}
|
|
SubCommand::Close => {
|
|
send_message(&SocketMessage::Close)?;
|
|
}
|
|
SubCommand::Minimize => {
|
|
send_message(&SocketMessage::Minimize)?;
|
|
}
|
|
SubCommand::Promote => {
|
|
send_message(&SocketMessage::Promote)?;
|
|
}
|
|
SubCommand::PromoteSwap => {
|
|
send_message(&SocketMessage::PromoteSwap)?;
|
|
}
|
|
SubCommand::PromoteFocus => {
|
|
send_message(&SocketMessage::PromoteFocus)?;
|
|
}
|
|
SubCommand::PromoteWindow(args) => {
|
|
send_message(&SocketMessage::PromoteWindow(args.operation_direction))?;
|
|
}
|
|
SubCommand::TogglePause => {
|
|
send_message(&SocketMessage::TogglePause)?;
|
|
}
|
|
SubCommand::Retile => {
|
|
send_message(&SocketMessage::Retile)?;
|
|
}
|
|
SubCommand::Move(args) => {
|
|
send_message(&SocketMessage::MoveWindow(args.operation_direction))?;
|
|
}
|
|
SubCommand::PreselectDirection(args) => {
|
|
send_message(&SocketMessage::PreselectDirection(args.operation_direction))?;
|
|
}
|
|
SubCommand::CancelPreselect => {
|
|
send_message(&SocketMessage::CancelPreselect)?;
|
|
}
|
|
SubCommand::CycleFocus(args) => {
|
|
send_message(&SocketMessage::CycleFocusWindow(args.cycle_direction))?;
|
|
}
|
|
SubCommand::CycleMove(args) => {
|
|
send_message(&SocketMessage::CycleMoveWindow(args.cycle_direction))?;
|
|
}
|
|
SubCommand::EagerFocus(args) => {
|
|
send_message(&SocketMessage::EagerFocus(args.exe))?;
|
|
}
|
|
SubCommand::MoveToMonitor(args) => {
|
|
send_message(&SocketMessage::MoveContainerToMonitorNumber(args.target))?;
|
|
}
|
|
SubCommand::CycleMoveToMonitor(args) => {
|
|
send_message(&SocketMessage::CycleMoveContainerToMonitor(
|
|
args.cycle_direction,
|
|
))?;
|
|
}
|
|
SubCommand::MoveToWorkspace(args) => {
|
|
send_message(&SocketMessage::MoveContainerToWorkspaceNumber(args.target))?;
|
|
}
|
|
SubCommand::MoveToNamedWorkspace(args) => {
|
|
send_message(&SocketMessage::MoveContainerToNamedWorkspace(
|
|
args.workspace,
|
|
))?;
|
|
}
|
|
SubCommand::CycleMoveToWorkspace(args) => {
|
|
send_message(&SocketMessage::CycleMoveContainerToWorkspace(
|
|
args.cycle_direction,
|
|
))?;
|
|
}
|
|
SubCommand::SendToMonitor(args) => {
|
|
send_message(&SocketMessage::SendContainerToMonitorNumber(args.target))?;
|
|
}
|
|
SubCommand::CycleSendToMonitor(args) => {
|
|
send_message(&SocketMessage::CycleSendContainerToMonitor(
|
|
args.cycle_direction,
|
|
))?;
|
|
}
|
|
SubCommand::SendToWorkspace(args) => {
|
|
send_message(&SocketMessage::SendContainerToWorkspaceNumber(args.target))?;
|
|
}
|
|
SubCommand::SendToNamedWorkspace(args) => {
|
|
send_message(&SocketMessage::SendContainerToNamedWorkspace(
|
|
args.workspace,
|
|
))?;
|
|
}
|
|
SubCommand::CycleSendToWorkspace(args) => {
|
|
send_message(&SocketMessage::CycleSendContainerToWorkspace(
|
|
args.cycle_direction,
|
|
))?;
|
|
}
|
|
SubCommand::SendToMonitorWorkspace(args) => {
|
|
send_message(&SocketMessage::SendContainerToMonitorWorkspaceNumber(
|
|
args.target_monitor,
|
|
args.target_workspace,
|
|
))?;
|
|
}
|
|
SubCommand::MoveToMonitorWorkspace(args) => {
|
|
send_message(&SocketMessage::MoveContainerToMonitorWorkspaceNumber(
|
|
args.target_monitor,
|
|
args.target_workspace,
|
|
))?;
|
|
}
|
|
SubCommand::MoveWorkspaceToMonitor(args) => {
|
|
send_message(&SocketMessage::MoveWorkspaceToMonitorNumber(args.target))?;
|
|
}
|
|
SubCommand::CycleMoveWorkspaceToMonitor(args) => {
|
|
send_message(&SocketMessage::CycleMoveWorkspaceToMonitor(
|
|
args.cycle_direction,
|
|
))?;
|
|
}
|
|
SubCommand::MoveToLastWorkspace => {
|
|
send_message(&SocketMessage::MoveContainerToLastWorkspace)?;
|
|
}
|
|
SubCommand::SendToLastWorkspace => {
|
|
send_message(&SocketMessage::SendContainerToLastWorkspace)?;
|
|
}
|
|
SubCommand::SwapWorkspacesWithMonitor(args) => {
|
|
send_message(&SocketMessage::SwapWorkspacesToMonitorNumber(args.target))?;
|
|
}
|
|
SubCommand::InvisibleBorders(args) => {
|
|
send_message(&SocketMessage::InvisibleBorders(Rect {
|
|
left: args.left,
|
|
top: args.top,
|
|
right: args.right,
|
|
bottom: args.bottom,
|
|
}))?;
|
|
}
|
|
SubCommand::MonitorWorkAreaOffset(args) => {
|
|
send_message(&SocketMessage::MonitorWorkAreaOffset(
|
|
args.monitor,
|
|
Rect {
|
|
left: args.left,
|
|
top: args.top,
|
|
right: args.right,
|
|
bottom: args.bottom,
|
|
},
|
|
))?;
|
|
}
|
|
SubCommand::GlobalWorkAreaOffset(args) => {
|
|
send_message(&SocketMessage::WorkAreaOffset(Rect {
|
|
left: args.left,
|
|
top: args.top,
|
|
right: args.right,
|
|
bottom: args.bottom,
|
|
}))?;
|
|
}
|
|
|
|
SubCommand::WorkspaceWorkAreaOffset(args) => {
|
|
send_message(&SocketMessage::WorkspaceWorkAreaOffset(
|
|
args.monitor,
|
|
args.workspace,
|
|
Rect {
|
|
left: args.left,
|
|
top: args.top,
|
|
right: args.right,
|
|
bottom: args.bottom,
|
|
},
|
|
))?;
|
|
}
|
|
|
|
SubCommand::ToggleWindowBasedWorkAreaOffset => {
|
|
send_message(&SocketMessage::ToggleWindowBasedWorkAreaOffset)?;
|
|
}
|
|
SubCommand::ContainerPadding(args) => {
|
|
send_message(&SocketMessage::ContainerPadding(
|
|
args.monitor,
|
|
args.workspace,
|
|
args.size,
|
|
))?;
|
|
}
|
|
SubCommand::NamedWorkspaceContainerPadding(args) => {
|
|
send_message(&SocketMessage::NamedWorkspaceContainerPadding(
|
|
args.workspace,
|
|
args.size,
|
|
))?;
|
|
}
|
|
SubCommand::WorkspacePadding(args) => {
|
|
send_message(&SocketMessage::WorkspacePadding(
|
|
args.monitor,
|
|
args.workspace,
|
|
args.size,
|
|
))?;
|
|
}
|
|
SubCommand::NamedWorkspacePadding(args) => {
|
|
send_message(&SocketMessage::NamedWorkspacePadding(
|
|
args.workspace,
|
|
args.size,
|
|
))?;
|
|
}
|
|
SubCommand::FocusedWorkspacePadding(args) => {
|
|
send_message(&SocketMessage::FocusedWorkspacePadding(args.size))?;
|
|
}
|
|
SubCommand::FocusedWorkspaceContainerPadding(args) => {
|
|
send_message(&SocketMessage::FocusedWorkspaceContainerPadding(args.size))?;
|
|
}
|
|
SubCommand::AdjustWorkspacePadding(args) => {
|
|
send_message(&SocketMessage::AdjustWorkspacePadding(
|
|
args.sizing,
|
|
args.adjustment,
|
|
))?;
|
|
}
|
|
SubCommand::AdjustContainerPadding(args) => {
|
|
send_message(&SocketMessage::AdjustContainerPadding(
|
|
args.sizing,
|
|
args.adjustment,
|
|
))?;
|
|
}
|
|
SubCommand::ToggleFocusFollowsMouse(args) => {
|
|
send_message(&SocketMessage::ToggleFocusFollowsMouse(args.implementation))?;
|
|
}
|
|
SubCommand::ToggleTiling => {
|
|
send_message(&SocketMessage::ToggleTiling)?;
|
|
}
|
|
SubCommand::ToggleFloat => {
|
|
send_message(&SocketMessage::ToggleFloat)?;
|
|
}
|
|
SubCommand::ToggleMonocle => {
|
|
send_message(&SocketMessage::ToggleMonocle)?;
|
|
}
|
|
SubCommand::ToggleMaximize => {
|
|
send_message(&SocketMessage::ToggleMaximize)?;
|
|
}
|
|
SubCommand::ToggleLock => {
|
|
send_message(&SocketMessage::ToggleLock)?;
|
|
}
|
|
SubCommand::WorkspaceLayout(args) => {
|
|
send_message(&SocketMessage::WorkspaceLayout(
|
|
args.monitor,
|
|
args.workspace,
|
|
args.value,
|
|
))?;
|
|
}
|
|
SubCommand::NamedWorkspaceLayout(args) => {
|
|
send_message(&SocketMessage::NamedWorkspaceLayout(
|
|
args.workspace,
|
|
args.value,
|
|
))?;
|
|
}
|
|
SubCommand::WorkspaceCustomLayout(args) => {
|
|
send_message(&SocketMessage::WorkspaceLayoutCustom(
|
|
args.monitor,
|
|
args.workspace,
|
|
args.path,
|
|
))?;
|
|
}
|
|
SubCommand::NamedWorkspaceCustomLayout(args) => {
|
|
send_message(&SocketMessage::NamedWorkspaceLayoutCustom(
|
|
args.workspace,
|
|
args.path,
|
|
))?;
|
|
}
|
|
SubCommand::WorkspaceLayoutRule(args) => {
|
|
send_message(&SocketMessage::WorkspaceLayoutRule(
|
|
args.monitor,
|
|
args.workspace,
|
|
args.at_container_count,
|
|
args.layout,
|
|
))?;
|
|
}
|
|
SubCommand::NamedWorkspaceLayoutRule(args) => {
|
|
send_message(&SocketMessage::NamedWorkspaceLayoutRule(
|
|
args.workspace,
|
|
args.at_container_count,
|
|
args.layout,
|
|
))?;
|
|
}
|
|
SubCommand::WorkspaceCustomLayoutRule(args) => {
|
|
send_message(&SocketMessage::WorkspaceLayoutCustomRule(
|
|
args.monitor,
|
|
args.workspace,
|
|
args.at_container_count,
|
|
args.path,
|
|
))?;
|
|
}
|
|
SubCommand::NamedWorkspaceCustomLayoutRule(args) => {
|
|
send_message(&SocketMessage::NamedWorkspaceLayoutCustomRule(
|
|
args.workspace,
|
|
args.at_container_count,
|
|
args.path,
|
|
))?;
|
|
}
|
|
SubCommand::ClearWorkspaceLayoutRules(args) => {
|
|
send_message(&SocketMessage::ClearWorkspaceLayoutRules(
|
|
args.monitor,
|
|
args.workspace,
|
|
))?;
|
|
}
|
|
SubCommand::ClearNamedWorkspaceLayoutRules(args) => {
|
|
send_message(&SocketMessage::ClearNamedWorkspaceLayoutRules(
|
|
args.workspace,
|
|
))?;
|
|
}
|
|
SubCommand::WorkspaceTiling(args) => {
|
|
send_message(&SocketMessage::WorkspaceTiling(
|
|
args.monitor,
|
|
args.workspace,
|
|
args.value.into(),
|
|
))?;
|
|
}
|
|
SubCommand::NamedWorkspaceTiling(args) => {
|
|
send_message(&SocketMessage::NamedWorkspaceTiling(
|
|
args.workspace,
|
|
args.value.into(),
|
|
))?;
|
|
}
|
|
SubCommand::Start(args) => {
|
|
if args.ahk {
|
|
println!(
|
|
"EOL: The --ahk flag is now end-of-life and will not receive any further updates or bug fixes"
|
|
);
|
|
}
|
|
|
|
let mut ahk: String = String::from("autohotkey.exe");
|
|
|
|
if let Ok(komorebi_ahk_exe) = std::env::var("KOMOREBI_AHK_EXE")
|
|
&& which(&komorebi_ahk_exe).is_ok()
|
|
{
|
|
ahk = komorebi_ahk_exe;
|
|
}
|
|
|
|
if args.whkd && which("whkd").is_err() {
|
|
bail!(
|
|
"could not find whkd, please make sure it is installed before using the --whkd flag"
|
|
);
|
|
}
|
|
|
|
if args.masir && which("masir").is_err() {
|
|
bail!(
|
|
"could not find masir, please make sure it is installed before using the --masir flag"
|
|
);
|
|
}
|
|
|
|
if args.ahk && which(&ahk).is_err() {
|
|
bail!(
|
|
"could not find autohotkey, please make sure it is installed before using the --ahk flag"
|
|
);
|
|
}
|
|
|
|
let mut buf: PathBuf;
|
|
|
|
// The komorebi.ps1 shim will only exist in the Path if installed by Scoop
|
|
let exec =
|
|
if let Ok(output) = Command::new("where.exe").arg("komorebi.ps1").output() {
|
|
let stdout = String::from_utf8(output.stdout)?;
|
|
match stdout.trim() {
|
|
"" => None,
|
|
// It's possible that a komorebi.ps1 config will be in %USERPROFILE% - ignore this
|
|
stdout if !stdout.contains("scoop") => None,
|
|
stdout => {
|
|
buf = PathBuf::from(stdout);
|
|
buf.pop(); // %USERPROFILE%\scoop\shims
|
|
buf.pop(); // %USERPROFILE%\scoop
|
|
buf.push("apps\\komorebi\\current\\komorebi.exe"); //%USERPROFILE%\scoop\komorebi\current\komorebi.exe
|
|
Some(buf.to_str().ok_or_eyre(
|
|
"cannot create a string from the scoop komorebi path",
|
|
)?)
|
|
}
|
|
}
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let mut flags = vec![];
|
|
if let Some(config) = &args.config {
|
|
if !config.is_file() {
|
|
bail!("could not find file: {}", config.display());
|
|
}
|
|
|
|
let config = dunce::simplified(config);
|
|
flags.push(format!("'--config=\"{}\"'", config.display()));
|
|
}
|
|
|
|
if args.ffm {
|
|
flags.push("'--ffm'".to_string());
|
|
}
|
|
|
|
if args.await_configuration {
|
|
flags.push("'--await-configuration'".to_string());
|
|
}
|
|
|
|
if let Some(port) = args.tcp_port {
|
|
flags.push(format!("'--tcp-port={port}'"));
|
|
}
|
|
|
|
if args.clean_state {
|
|
flags.push("'--clean-state'".to_string());
|
|
}
|
|
|
|
let exec = exec.unwrap_or("komorebi.exe");
|
|
let script = if flags.is_empty() {
|
|
format!("Start-Process '{exec}' -WindowStyle hidden",)
|
|
} else {
|
|
let argument_list = flags.join(",");
|
|
format!("Start-Process '{exec}' -ArgumentList {argument_list} -WindowStyle hidden",)
|
|
};
|
|
|
|
let mut system = sysinfo::System::new_all();
|
|
system.refresh_processes(ProcessesToUpdate::All, true);
|
|
|
|
let mut attempts = 0;
|
|
let mut running = system
|
|
.processes_by_name("komorebi.exe".as_ref())
|
|
.next()
|
|
.is_some();
|
|
|
|
while !running && attempts <= 2 {
|
|
match powershell_script::run(&script) {
|
|
Ok(_) => {
|
|
println!("{script}");
|
|
}
|
|
Err(error) => {
|
|
println!("Error: {error}");
|
|
}
|
|
}
|
|
|
|
print!("Waiting for komorebi.exe to start...");
|
|
std::thread::sleep(Duration::from_secs(3));
|
|
|
|
system.refresh_processes(ProcessesToUpdate::All, true);
|
|
|
|
if system
|
|
.processes_by_name("komorebi.exe".as_ref())
|
|
.next()
|
|
.is_some()
|
|
{
|
|
println!("Started!");
|
|
running = true;
|
|
} else {
|
|
println!("komorebi.exe did not start... Trying again");
|
|
attempts += 1;
|
|
}
|
|
}
|
|
|
|
if !running {
|
|
println!("\nRunning komorebi.exe directly for detailed error output\n");
|
|
if let Some(config) = args.config {
|
|
if let Ok(output) = Command::new("komorebi.exe")
|
|
.arg(format!("'--config=\"{}\"'", config.display()))
|
|
.output()
|
|
{
|
|
println!("{}", String::from_utf8(output.stderr)?);
|
|
}
|
|
} else if let Ok(output) = Command::new("komorebi.exe").output() {
|
|
println!("{}", String::from_utf8(output.stderr)?);
|
|
}
|
|
|
|
return Ok(());
|
|
}
|
|
|
|
if args.whkd {
|
|
let script = r"
|
|
if (!(Get-Process whkd -ErrorAction SilentlyContinue))
|
|
{
|
|
Start-Process whkd -WindowStyle hidden
|
|
}
|
|
";
|
|
match powershell_script::run(script) {
|
|
Ok(_) => {
|
|
println!("{script}");
|
|
}
|
|
Err(error) => {
|
|
println!("Error: {error}");
|
|
}
|
|
}
|
|
}
|
|
|
|
if args.ahk {
|
|
let config_ahk = HOME_DIR.join("komorebi.ahk");
|
|
let config_ahk = dunce::simplified(&config_ahk);
|
|
|
|
let script = format!(
|
|
r#"
|
|
Start-Process '"{ahk}"' '"{config}"' -WindowStyle hidden
|
|
"#,
|
|
config = config_ahk.display()
|
|
);
|
|
|
|
match powershell_script::run(&script) {
|
|
Ok(_) => {
|
|
println!("{script}");
|
|
}
|
|
Err(error) => {
|
|
println!("Error: {error}");
|
|
}
|
|
}
|
|
}
|
|
|
|
let static_config = args.config.clone().or_else(|| {
|
|
let komorebi_json = HOME_DIR.join("komorebi.json");
|
|
komorebi_json.is_file().then_some(komorebi_json)
|
|
});
|
|
|
|
if args.bar {
|
|
let mut fallthrough = false;
|
|
match static_config {
|
|
None => {
|
|
fallthrough = true;
|
|
}
|
|
Some(ref config) => {
|
|
let mut config = StaticConfig::read(config)?;
|
|
if let Some(display_bar_configurations) = &mut config.bar_configurations {
|
|
for config_file_path in &mut *display_bar_configurations {
|
|
let script = format!(
|
|
r#"Start-Process "komorebi-bar" '"--config" "{}"' -WindowStyle hidden"#,
|
|
config_file_path.to_string_lossy()
|
|
);
|
|
|
|
match powershell_script::run(&script) {
|
|
Ok(_) => {
|
|
println!("{script}");
|
|
}
|
|
Err(error) => {
|
|
println!("Error: {error}");
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
fallthrough = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if fallthrough {
|
|
let script = r"
|
|
if (!(Get-Process komorebi-bar -ErrorAction SilentlyContinue))
|
|
{
|
|
Start-Process komorebi-bar -WindowStyle hidden
|
|
}
|
|
";
|
|
match powershell_script::run(script) {
|
|
Ok(_) => {
|
|
println!("{script}");
|
|
}
|
|
Err(error) => {
|
|
println!("Error: {error}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if args.masir {
|
|
let script = r"
|
|
if (!(Get-Process masir -ErrorAction SilentlyContinue))
|
|
{
|
|
Start-Process masir -WindowStyle hidden
|
|
}
|
|
";
|
|
match powershell_script::run(script) {
|
|
Ok(_) => {
|
|
println!("{script}");
|
|
}
|
|
Err(error) => {
|
|
println!("Error: {error}");
|
|
}
|
|
}
|
|
}
|
|
|
|
println!("\nThank you for using komorebi!\n");
|
|
println!("# Commercial Use License");
|
|
println!(
|
|
"* View licensing options https://lgug2z.com/software/komorebi - A commercial use license is required to use komorebi at work"
|
|
);
|
|
println!("\n# Personal Use Sponsorship");
|
|
println!(
|
|
"* Become a sponsor https://github.com/sponsors/LGUG2Z - $5/month makes a big difference"
|
|
);
|
|
println!("* Leave a tip https://ko-fi.com/lgug2z - An alternative to GitHub Sponsors");
|
|
println!("\n# Community");
|
|
println!(
|
|
"* Join the Discord https://discord.gg/mGkn66PHkx - Chat, ask questions, share your desktops"
|
|
);
|
|
println!(
|
|
"* Subscribe to https://youtube.com/@LGUG2Z - Development videos, feature previews and release overviews"
|
|
);
|
|
println!(
|
|
"* Explore the Awesome Komorebi list https://github.com/LGUG2Z/awesome-komorebi - Projects in the komorebi ecosystem"
|
|
);
|
|
println!("\n# Documentation");
|
|
println!(
|
|
"* Read the docs https://lgug2z.github.io/komorebi - Quickly search through all komorebic commands"
|
|
);
|
|
|
|
let bar_config = args.config.or_else(|| {
|
|
let bar_json = HOME_DIR.join("komorebi.bar.json");
|
|
bar_json.is_file().then_some(bar_json)
|
|
});
|
|
|
|
if let Some(config) = &static_config {
|
|
let raw = std::fs::read_to_string(config)?;
|
|
StaticConfig::aliases(&raw);
|
|
StaticConfig::deprecated(&raw);
|
|
StaticConfig::end_of_life(&raw);
|
|
}
|
|
|
|
if bar_config.is_some() {
|
|
let output = Command::new("komorebi-bar.exe").arg("--aliases").output()?;
|
|
let stdout = String::from_utf8(output.stdout)?;
|
|
println!("{stdout}");
|
|
}
|
|
|
|
let client = reqwest::blocking::Client::new();
|
|
|
|
if let Ok(response) = client
|
|
.get("https://api.github.com/repos/LGUG2Z/komorebi/releases/latest")
|
|
.header("User-Agent", "komorebic-version-checker")
|
|
.send()
|
|
{
|
|
let version = env!("CARGO_PKG_VERSION");
|
|
|
|
#[derive(Deserialize)]
|
|
struct Release {
|
|
tag_name: String,
|
|
}
|
|
|
|
if let Ok(release) =
|
|
serde_json::from_str::<Release>(&response.text().unwrap_or_default())
|
|
{
|
|
let trimmed = release.tag_name.trim_start_matches("v");
|
|
if trimmed > version {
|
|
println!(
|
|
"An updated version of komorebi is available! https://github.com/LGUG2Z/komorebi/releases/v{trimmed}"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
SubCommand::Stop(args) => {
|
|
if args.ahk {
|
|
println!(
|
|
"EOL: The --ahk flag is now end-of-life and will not receive any further updates or bug fixes"
|
|
);
|
|
}
|
|
|
|
if args.whkd {
|
|
let script = r"
|
|
Stop-Process -Name:whkd -ErrorAction SilentlyContinue
|
|
";
|
|
match powershell_script::run(script) {
|
|
Ok(_) => {
|
|
println!("{script}");
|
|
}
|
|
Err(error) => {
|
|
println!("Error: {error}");
|
|
}
|
|
}
|
|
}
|
|
|
|
if args.bar {
|
|
let script = r"
|
|
Stop-Process -Name:komorebi-bar -ErrorAction SilentlyContinue
|
|
";
|
|
match powershell_script::run(script) {
|
|
Ok(_) => {
|
|
println!("{script}");
|
|
}
|
|
Err(error) => {
|
|
println!("Error: {error}");
|
|
}
|
|
}
|
|
}
|
|
|
|
if args.masir {
|
|
let script = r"
|
|
Stop-Process -Name:masir -ErrorAction SilentlyContinue
|
|
";
|
|
match powershell_script::run(script) {
|
|
Ok(_) => {
|
|
println!("{script}");
|
|
}
|
|
Err(error) => {
|
|
println!("Error: {error}");
|
|
}
|
|
}
|
|
}
|
|
|
|
if args.ahk {
|
|
let script = r#"
|
|
if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
|
|
(Get-CimInstance Win32_Process | Where-Object {
|
|
($_.CommandLine -like '*komorebi.ahk"') -and
|
|
($_.Name -in @('AutoHotkey.exe', 'AutoHotkey64.exe', 'AutoHotkey32.exe', 'AutoHotkeyUX.exe'))
|
|
} | Select-Object -First 1) | ForEach-Object {
|
|
Stop-Process -Id $_.ProcessId -ErrorAction SilentlyContinue
|
|
}
|
|
} else {
|
|
(Get-WmiObject Win32_Process | Where-Object {
|
|
($_.CommandLine -like '*komorebi.ahk"') -and
|
|
($_.Name -in @('AutoHotkey.exe', 'AutoHotkey64.exe', 'AutoHotkey32.exe', 'AutoHotkeyUX.exe'))
|
|
} | Select-Object -First 1) | ForEach-Object {
|
|
Stop-Process -Id $_.ProcessId -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
"#;
|
|
|
|
match powershell_script::run(script) {
|
|
Ok(_) => {
|
|
println!("{script}");
|
|
}
|
|
Err(error) => {
|
|
println!("Error: {error}");
|
|
}
|
|
}
|
|
}
|
|
|
|
if args.ignore_restore {
|
|
send_message(&SocketMessage::StopIgnoreRestore)?;
|
|
} else {
|
|
send_message(&SocketMessage::Stop)?;
|
|
}
|
|
let mut system = sysinfo::System::new_all();
|
|
system.refresh_processes(ProcessesToUpdate::All, true);
|
|
|
|
if system.processes_by_name("komorebi.exe".as_ref()).count() >= 1 {
|
|
println!("komorebi is still running, attempting to force-quit");
|
|
|
|
let script = r"
|
|
Stop-Process -Name:komorebi -ErrorAction SilentlyContinue
|
|
";
|
|
match powershell_script::run(script) {
|
|
Ok(_) => {
|
|
println!("{script}");
|
|
|
|
let hwnd_json = DATA_DIR.join("komorebi.hwnd.json");
|
|
|
|
let file = File::open(hwnd_json)?;
|
|
let reader = BufReader::new(file);
|
|
let hwnds: Vec<isize> = serde_json::from_reader(reader)?;
|
|
|
|
for hwnd in hwnds {
|
|
restore_window(hwnd);
|
|
}
|
|
}
|
|
Err(error) => {
|
|
println!("Error: {error}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
SubCommand::Kill(args) => {
|
|
if args.ahk {
|
|
println!(
|
|
"EOL: The --ahk flag is now end-of-life and will not receive any further updates or bug fixes"
|
|
);
|
|
}
|
|
|
|
if args.whkd {
|
|
let script = r"
|
|
Stop-Process -Name:whkd -ErrorAction SilentlyContinue
|
|
";
|
|
match powershell_script::run(script) {
|
|
Ok(_) => {
|
|
println!("{script}");
|
|
}
|
|
Err(error) => {
|
|
println!("Error: {error}");
|
|
}
|
|
}
|
|
}
|
|
|
|
if args.bar {
|
|
let script = r"
|
|
Stop-Process -Name:komorebi-bar -ErrorAction SilentlyContinue
|
|
";
|
|
match powershell_script::run(script) {
|
|
Ok(_) => {
|
|
println!("{script}");
|
|
}
|
|
Err(error) => {
|
|
println!("Error: {error}");
|
|
}
|
|
}
|
|
}
|
|
|
|
if args.masir {
|
|
let script = r"
|
|
Stop-Process -Name:masir -ErrorAction SilentlyContinue
|
|
";
|
|
match powershell_script::run(script) {
|
|
Ok(_) => {
|
|
println!("{script}");
|
|
}
|
|
Err(error) => {
|
|
println!("Error: {error}");
|
|
}
|
|
}
|
|
}
|
|
|
|
if args.ahk {
|
|
let script = r#"
|
|
if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
|
|
(Get-CimInstance Win32_Process | Where-Object {
|
|
($_.CommandLine -like '*komorebi.ahk"') -and
|
|
($_.Name -in @('AutoHotkey.exe', 'AutoHotkey64.exe', 'AutoHotkey32.exe', 'AutoHotkeyUX.exe'))
|
|
} | Select-Object -First 1) | ForEach-Object {
|
|
Stop-Process -Id $_.ProcessId -ErrorAction SilentlyContinue
|
|
}
|
|
} else {
|
|
(Get-WmiObject Win32_Process | Where-Object {
|
|
($_.CommandLine -like '*komorebi.ahk"') -and
|
|
($_.Name -in @('AutoHotkey.exe', 'AutoHotkey64.exe', 'AutoHotkey32.exe', 'AutoHotkeyUX.exe'))
|
|
} | Select-Object -First 1) | ForEach-Object {
|
|
Stop-Process -Id $_.ProcessId -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
"#;
|
|
|
|
match powershell_script::run(script) {
|
|
Ok(_) => {
|
|
println!("{script}");
|
|
}
|
|
Err(error) => {
|
|
println!("Error: {error}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
SubCommand::SessionFloatRule => {
|
|
send_message(&SocketMessage::SessionFloatRule)?;
|
|
}
|
|
SubCommand::SessionFloatRules => {
|
|
print_query(&SocketMessage::SessionFloatRules);
|
|
}
|
|
SubCommand::ClearSessionFloatRules => {
|
|
send_message(&SocketMessage::ClearSessionFloatRules)?;
|
|
}
|
|
SubCommand::IgnoreRule(args) => {
|
|
send_message(&SocketMessage::IgnoreRule(args.identifier, args.id))?;
|
|
}
|
|
SubCommand::ManageRule(args) => {
|
|
send_message(&SocketMessage::ManageRule(args.identifier, args.id))?;
|
|
}
|
|
SubCommand::InitialWorkspaceRule(args) => {
|
|
send_message(&SocketMessage::InitialWorkspaceRule(
|
|
args.identifier,
|
|
args.id,
|
|
args.monitor,
|
|
args.workspace,
|
|
))?;
|
|
}
|
|
SubCommand::InitialNamedWorkspaceRule(args) => {
|
|
send_message(&SocketMessage::InitialNamedWorkspaceRule(
|
|
args.identifier,
|
|
args.id,
|
|
args.workspace,
|
|
))?;
|
|
}
|
|
SubCommand::WorkspaceRule(args) => {
|
|
send_message(&SocketMessage::WorkspaceRule(
|
|
args.identifier,
|
|
args.id,
|
|
args.monitor,
|
|
args.workspace,
|
|
))?;
|
|
}
|
|
SubCommand::NamedWorkspaceRule(args) => {
|
|
send_message(&SocketMessage::NamedWorkspaceRule(
|
|
args.identifier,
|
|
args.id,
|
|
args.workspace,
|
|
))?;
|
|
}
|
|
SubCommand::ClearWorkspaceRules(args) => {
|
|
send_message(&SocketMessage::ClearWorkspaceRules(
|
|
args.monitor,
|
|
args.workspace,
|
|
))?;
|
|
}
|
|
SubCommand::ClearNamedWorkspaceRules(args) => {
|
|
send_message(&SocketMessage::ClearNamedWorkspaceRules(args.workspace))?;
|
|
}
|
|
SubCommand::ClearAllWorkspaceRules => {
|
|
send_message(&SocketMessage::ClearAllWorkspaceRules)?;
|
|
}
|
|
SubCommand::EnforceWorkspaceRules => {
|
|
send_message(&SocketMessage::EnforceWorkspaceRules)?;
|
|
}
|
|
SubCommand::Stack(args) => {
|
|
send_message(&SocketMessage::StackWindow(args.operation_direction))?;
|
|
}
|
|
SubCommand::StackAll => {
|
|
send_message(&SocketMessage::StackAll)?;
|
|
}
|
|
SubCommand::Unstack => {
|
|
send_message(&SocketMessage::UnstackWindow)?;
|
|
}
|
|
SubCommand::UnstackAll => {
|
|
send_message(&SocketMessage::UnstackAll)?;
|
|
}
|
|
SubCommand::FocusStackWindow(args) => {
|
|
send_message(&SocketMessage::FocusStackWindow(args.target))?;
|
|
}
|
|
SubCommand::CycleStack(args) => {
|
|
send_message(&SocketMessage::CycleStack(args.cycle_direction))?;
|
|
}
|
|
SubCommand::CycleStackIndex(args) => {
|
|
send_message(&SocketMessage::CycleStackIndex(args.cycle_direction))?;
|
|
}
|
|
SubCommand::ChangeLayout(args) => {
|
|
send_message(&SocketMessage::ChangeLayout(args.default_layout))?;
|
|
}
|
|
SubCommand::CycleLayout(args) => {
|
|
send_message(&SocketMessage::CycleLayout(args.cycle_direction))?;
|
|
}
|
|
SubCommand::ScrollingLayoutColumns(args) => {
|
|
send_message(&SocketMessage::ScrollingLayoutColumns(args.count))?;
|
|
}
|
|
SubCommand::LoadCustomLayout(args) => {
|
|
send_message(&SocketMessage::ChangeLayoutCustom(args.path))?;
|
|
}
|
|
SubCommand::FlipLayout(args) => {
|
|
send_message(&SocketMessage::FlipLayout(args.axis))?;
|
|
}
|
|
SubCommand::FocusMonitor(args) => {
|
|
send_message(&SocketMessage::FocusMonitorNumber(args.target))?;
|
|
}
|
|
SubCommand::FocusMonitorAtCursor => {
|
|
send_message(&SocketMessage::FocusMonitorAtCursor)?;
|
|
}
|
|
SubCommand::FocusLastWorkspace => {
|
|
send_message(&SocketMessage::FocusLastWorkspace)?;
|
|
}
|
|
SubCommand::FocusWorkspace(args) => {
|
|
send_message(&SocketMessage::FocusWorkspaceNumber(args.target))?;
|
|
}
|
|
SubCommand::FocusWorkspaces(args) => {
|
|
send_message(&SocketMessage::FocusWorkspaceNumbers(args.target))?;
|
|
}
|
|
SubCommand::FocusMonitorWorkspace(args) => {
|
|
send_message(&SocketMessage::FocusMonitorWorkspaceNumber(
|
|
args.target_monitor,
|
|
args.target_workspace,
|
|
))?;
|
|
}
|
|
SubCommand::FocusNamedWorkspace(args) => {
|
|
send_message(&SocketMessage::FocusNamedWorkspace(args.workspace))?;
|
|
}
|
|
SubCommand::CloseWorkspace => {
|
|
send_message(&SocketMessage::CloseWorkspace)?;
|
|
}
|
|
SubCommand::CycleMonitor(args) => {
|
|
send_message(&SocketMessage::CycleFocusMonitor(args.cycle_direction))?;
|
|
}
|
|
SubCommand::CycleWorkspace(args) => {
|
|
send_message(&SocketMessage::CycleFocusWorkspace(args.cycle_direction))?;
|
|
}
|
|
SubCommand::CycleEmptyWorkspace(args) => {
|
|
send_message(&SocketMessage::CycleFocusEmptyWorkspace(
|
|
args.cycle_direction,
|
|
))?;
|
|
}
|
|
SubCommand::NewWorkspace => {
|
|
send_message(&SocketMessage::NewWorkspace)?;
|
|
}
|
|
SubCommand::WorkspaceName(name) => {
|
|
send_message(&SocketMessage::WorkspaceName(
|
|
name.monitor,
|
|
name.workspace,
|
|
name.value,
|
|
))?;
|
|
}
|
|
SubCommand::MonitorIndexPreference(args) => {
|
|
send_message(&SocketMessage::MonitorIndexPreference(
|
|
args.index_preference,
|
|
args.left,
|
|
args.top,
|
|
args.right,
|
|
args.bottom,
|
|
))?;
|
|
}
|
|
SubCommand::DisplayIndexPreference(args) => {
|
|
send_message(&SocketMessage::DisplayIndexPreference(
|
|
args.index_preference,
|
|
args.display,
|
|
))?;
|
|
}
|
|
SubCommand::EnsureWorkspaces(workspaces) => {
|
|
send_message(&SocketMessage::EnsureWorkspaces(
|
|
workspaces.monitor,
|
|
workspaces.workspace_count,
|
|
))?;
|
|
}
|
|
SubCommand::EnsureNamedWorkspaces(args) => {
|
|
send_message(&SocketMessage::EnsureNamedWorkspaces(
|
|
args.monitor,
|
|
args.names,
|
|
))?;
|
|
}
|
|
SubCommand::State => {
|
|
print_query(&SocketMessage::State);
|
|
}
|
|
SubCommand::GlobalState => {
|
|
print_query(&SocketMessage::GlobalState);
|
|
}
|
|
SubCommand::Gui => {
|
|
Command::new("komorebi-gui").spawn()?;
|
|
}
|
|
SubCommand::ToggleShortcuts => {
|
|
let output = Command::new("taskkill")
|
|
.args(["/F", "/IM", "komorebi-shortcuts.exe"])
|
|
.output()?;
|
|
|
|
if !output.status.success() {
|
|
Command::new("komorebi-shortcuts.exe").spawn()?;
|
|
}
|
|
}
|
|
SubCommand::VisibleWindows => {
|
|
print_query(&SocketMessage::VisibleWindows);
|
|
}
|
|
SubCommand::MonitorInformation => {
|
|
print_query(&SocketMessage::MonitorInformation);
|
|
}
|
|
SubCommand::Query(args) => {
|
|
print_query(&SocketMessage::Query(args.state_query));
|
|
}
|
|
SubCommand::RestoreWindows => {
|
|
let hwnd_json = DATA_DIR.join("komorebi.hwnd.json");
|
|
|
|
let file = File::open(hwnd_json)?;
|
|
let reader = BufReader::new(file);
|
|
let hwnds: Vec<isize> = serde_json::from_reader(reader)?;
|
|
|
|
for hwnd in hwnds {
|
|
restore_window(hwnd);
|
|
}
|
|
}
|
|
SubCommand::ResizeEdge(resize) => {
|
|
send_message(&SocketMessage::ResizeWindowEdge(resize.edge, resize.sizing))?;
|
|
}
|
|
SubCommand::ResizeAxis(args) => {
|
|
send_message(&SocketMessage::ResizeWindowAxis(args.axis, args.sizing))?;
|
|
}
|
|
SubCommand::FocusFollowsMouse(args) => {
|
|
send_message(&SocketMessage::FocusFollowsMouse(
|
|
args.implementation,
|
|
args.boolean_state.into(),
|
|
))?;
|
|
}
|
|
SubCommand::ReplaceConfiguration(args) => {
|
|
send_message(&SocketMessage::ReplaceConfiguration(args.path))?;
|
|
}
|
|
SubCommand::ReloadConfiguration => {
|
|
send_message(&SocketMessage::ReloadConfiguration)?;
|
|
}
|
|
SubCommand::WatchConfiguration(args) => {
|
|
send_message(&SocketMessage::WatchConfiguration(
|
|
args.boolean_state.into(),
|
|
))?;
|
|
}
|
|
SubCommand::CompleteConfiguration => {
|
|
send_message(&SocketMessage::CompleteConfiguration)?;
|
|
}
|
|
SubCommand::IdentifyObjectNameChangeApplication(target) => {
|
|
send_message(&SocketMessage::IdentifyObjectNameChangeApplication(
|
|
target.identifier,
|
|
target.id,
|
|
))?;
|
|
}
|
|
SubCommand::IdentifyTrayApplication(target) => {
|
|
send_message(&SocketMessage::IdentifyTrayApplication(
|
|
target.identifier,
|
|
target.id,
|
|
))?;
|
|
}
|
|
SubCommand::IdentifyLayeredApplication(target) => {
|
|
send_message(&SocketMessage::IdentifyLayeredApplication(
|
|
target.identifier,
|
|
target.id,
|
|
))?;
|
|
}
|
|
SubCommand::RemoveTitleBar(target) => {
|
|
match target.identifier {
|
|
ApplicationIdentifier::Exe => {}
|
|
_ => {
|
|
bail!("this command requires applications to be identified by their exe");
|
|
}
|
|
}
|
|
|
|
send_message(&SocketMessage::RemoveTitleBar(target.identifier, target.id))?;
|
|
}
|
|
SubCommand::ToggleTitleBars => {
|
|
send_message(&SocketMessage::ToggleTitleBars)?;
|
|
}
|
|
SubCommand::Manage => {
|
|
send_message(&SocketMessage::ManageFocusedWindow)?;
|
|
}
|
|
SubCommand::Unmanage => {
|
|
send_message(&SocketMessage::UnmanageFocusedWindow)?;
|
|
}
|
|
SubCommand::QuickSaveResize => {
|
|
send_message(&SocketMessage::QuickSave)?;
|
|
}
|
|
SubCommand::QuickLoadResize => {
|
|
send_message(&SocketMessage::QuickLoad)?;
|
|
}
|
|
SubCommand::SaveResize(args) => {
|
|
send_message(&SocketMessage::Save(args.path))?;
|
|
}
|
|
SubCommand::LoadResize(args) => {
|
|
send_message(&SocketMessage::Load(args.path))?;
|
|
}
|
|
SubCommand::SubscribeSocket(args) => {
|
|
send_message(&SocketMessage::AddSubscriberSocket(args.socket))?;
|
|
}
|
|
SubCommand::UnsubscribeSocket(args) => {
|
|
send_message(&SocketMessage::RemoveSubscriberSocket(args.socket))?;
|
|
}
|
|
SubCommand::SubscribePipe(args) => {
|
|
send_message(&SocketMessage::AddSubscriberPipe(args.named_pipe))?;
|
|
}
|
|
SubCommand::UnsubscribePipe(args) => {
|
|
send_message(&SocketMessage::RemoveSubscriberPipe(args.named_pipe))?;
|
|
}
|
|
SubCommand::ToggleMouseFollowsFocus => {
|
|
send_message(&SocketMessage::ToggleMouseFollowsFocus)?;
|
|
}
|
|
SubCommand::MouseFollowsFocus(args) => {
|
|
send_message(&SocketMessage::MouseFollowsFocus(args.boolean_state.into()))?;
|
|
}
|
|
SubCommand::Border(args) => {
|
|
send_message(&SocketMessage::Border(args.boolean_state.into()))?;
|
|
}
|
|
SubCommand::BorderColour(args) => {
|
|
send_message(&SocketMessage::BorderColour(
|
|
args.window_kind,
|
|
args.r,
|
|
args.g,
|
|
args.b,
|
|
))?;
|
|
}
|
|
SubCommand::BorderWidth(args) => {
|
|
send_message(&SocketMessage::BorderWidth(args.width))?;
|
|
}
|
|
SubCommand::BorderOffset(args) => {
|
|
send_message(&SocketMessage::BorderOffset(args.offset))?;
|
|
}
|
|
SubCommand::BorderStyle(args) => {
|
|
send_message(&SocketMessage::BorderStyle(args.style))?;
|
|
}
|
|
SubCommand::BorderImplementation(args) => {
|
|
send_message(&SocketMessage::BorderImplementation(args.style))?;
|
|
}
|
|
SubCommand::StackbarMode(args) => {
|
|
send_message(&SocketMessage::StackbarMode(args.mode))?;
|
|
}
|
|
SubCommand::Transparency(args) => {
|
|
send_message(&SocketMessage::Transparency(args.boolean_state.into()))?;
|
|
}
|
|
SubCommand::TransparencyAlpha(args) => {
|
|
send_message(&SocketMessage::TransparencyAlpha(args.alpha))?;
|
|
}
|
|
SubCommand::ToggleTransparency => {
|
|
send_message(&SocketMessage::ToggleTransparency)?;
|
|
}
|
|
SubCommand::Animation(args) => {
|
|
send_message(&SocketMessage::Animation(
|
|
args.boolean_state.into(),
|
|
args.animation_type,
|
|
))?;
|
|
}
|
|
SubCommand::AnimationDuration(args) => {
|
|
send_message(&SocketMessage::AnimationDuration(
|
|
args.duration,
|
|
args.animation_type,
|
|
))?;
|
|
}
|
|
SubCommand::AnimationFps(args) => {
|
|
send_message(&SocketMessage::AnimationFps(args.fps))?;
|
|
}
|
|
SubCommand::AnimationStyle(args) => {
|
|
send_message(&SocketMessage::AnimationStyle(
|
|
args.style,
|
|
args.animation_type,
|
|
))?;
|
|
}
|
|
|
|
SubCommand::ResizeDelta(args) => {
|
|
send_message(&SocketMessage::ResizeDelta(args.pixels))?;
|
|
}
|
|
SubCommand::ToggleWindowContainerBehaviour => {
|
|
send_message(&SocketMessage::ToggleWindowContainerBehaviour)?;
|
|
}
|
|
SubCommand::ToggleFloatOverride => {
|
|
send_message(&SocketMessage::ToggleFloatOverride)?;
|
|
}
|
|
SubCommand::ToggleWorkspaceWindowContainerBehaviour => {
|
|
send_message(&SocketMessage::ToggleWorkspaceWindowContainerBehaviour)?;
|
|
}
|
|
SubCommand::ToggleWorkspaceFloatOverride => {
|
|
send_message(&SocketMessage::ToggleWorkspaceFloatOverride)?;
|
|
}
|
|
SubCommand::ToggleWorkspaceLayer => {
|
|
send_message(&SocketMessage::ToggleWorkspaceLayer)?;
|
|
}
|
|
SubCommand::WindowHidingBehaviour(args) => {
|
|
send_message(&SocketMessage::WindowHidingBehaviour(args.hiding_behaviour))?;
|
|
}
|
|
SubCommand::CrossMonitorMoveBehaviour(args) => {
|
|
send_message(&SocketMessage::CrossMonitorMoveBehaviour(
|
|
args.move_behaviour,
|
|
))?;
|
|
}
|
|
SubCommand::ToggleCrossMonitorMoveBehaviour => {
|
|
send_message(&SocketMessage::ToggleCrossMonitorMoveBehaviour)?;
|
|
}
|
|
SubCommand::UnmanagedWindowOperationBehaviour(args) => {
|
|
send_message(&SocketMessage::UnmanagedWindowOperationBehaviour(
|
|
args.operation_behaviour,
|
|
))?;
|
|
}
|
|
SubCommand::AhkAppSpecificConfiguration(args) => {
|
|
let content = std::fs::read_to_string(args.path)?;
|
|
let lines = if let Some(override_path) = args.override_path {
|
|
let override_content = std::fs::read_to_string(override_path)?;
|
|
|
|
ApplicationConfigurationGenerator::generate_ahk(
|
|
&content,
|
|
Option::from(override_content.as_str()),
|
|
)?
|
|
} else {
|
|
ApplicationConfigurationGenerator::generate_ahk(&content, None)?
|
|
};
|
|
|
|
let generated_config = HOME_DIR.join("komorebi.generated.ahk");
|
|
let mut file = OpenOptions::new()
|
|
.write(true)
|
|
.create(true)
|
|
.truncate(true)
|
|
.open(&generated_config)?;
|
|
|
|
file.write_all(lines.join("\n").as_bytes())?;
|
|
|
|
println!(
|
|
"\nApplication-specific generated configuration written to {}",
|
|
generated_config.display()
|
|
);
|
|
}
|
|
SubCommand::PwshAppSpecificConfiguration(args) => {
|
|
let content = std::fs::read_to_string(args.path)?;
|
|
let lines = if let Some(override_path) = args.override_path {
|
|
let override_content = std::fs::read_to_string(override_path)?;
|
|
|
|
ApplicationConfigurationGenerator::generate_pwsh(
|
|
&content,
|
|
Option::from(override_content.as_str()),
|
|
)?
|
|
} else {
|
|
ApplicationConfigurationGenerator::generate_pwsh(&content, None)?
|
|
};
|
|
|
|
let generated_config = HOME_DIR.join("komorebi.generated.ps1");
|
|
let mut file = OpenOptions::new()
|
|
.write(true)
|
|
.create(true)
|
|
.truncate(true)
|
|
.open(&generated_config)?;
|
|
|
|
file.write_all(lines.join("\n").as_bytes())?;
|
|
|
|
println!(
|
|
"\nApplication-specific generated configuration written to {}",
|
|
generated_config.display()
|
|
);
|
|
}
|
|
SubCommand::ConvertAppSpecificConfiguration(args) => {
|
|
let content = std::fs::read_to_string(args.path)?;
|
|
let mut asc = ApplicationConfigurationGenerator::load(&content)?;
|
|
asc.sort_by(|a, b| a.name.cmp(&b.name));
|
|
let v2 = ApplicationSpecificConfiguration::from(asc);
|
|
println!("{}", serde_json::to_string_pretty(&v2)?);
|
|
}
|
|
SubCommand::FormatAppSpecificConfiguration(args) => {
|
|
let content = std::fs::read_to_string(&args.path)?;
|
|
let formatted_content = ApplicationConfigurationGenerator::format(&content)?;
|
|
|
|
let mut file = OpenOptions::new()
|
|
.write(true)
|
|
.create(true)
|
|
.truncate(true)
|
|
.open(args.path)?;
|
|
|
|
file.write_all(formatted_content.as_bytes())?;
|
|
|
|
println!(
|
|
"File successfully formatted for PRs to https://github.com/LGUG2Z/komorebi-application-specific-configuration"
|
|
);
|
|
}
|
|
SubCommand::FetchAppSpecificConfiguration => {
|
|
let content = reqwest::blocking::get("https://raw.githubusercontent.com/LGUG2Z/komorebi-application-specific-configuration/master/applications.json")?
|
|
.text()?;
|
|
|
|
let output_file = HOME_DIR.join("applications.json");
|
|
|
|
let mut file = OpenOptions::new()
|
|
.write(true)
|
|
.create(true)
|
|
.truncate(true)
|
|
.open(&output_file)?;
|
|
|
|
file.write_all(content.as_bytes())?;
|
|
|
|
println!(
|
|
"Latest version of applications.json from https://github.com/LGUG2Z/komorebi-application-specific-configuration downloaded\n"
|
|
);
|
|
println!(
|
|
"You can add this to your komorebi.json static configuration file like this: \n\n\"app_specific_configuration_path\": \"{}\"",
|
|
output_file.display().to_string().replace("\\", "/")
|
|
);
|
|
}
|
|
SubCommand::ApplicationSpecificConfigurationSchema => {
|
|
#[cfg(feature = "schemars")]
|
|
{
|
|
let asc = schemars::schema_for!(ApplicationSpecificConfiguration);
|
|
let schema = serde_json::to_string_pretty(&asc)?;
|
|
println!("{schema}");
|
|
}
|
|
}
|
|
SubCommand::NotificationSchema => {
|
|
#[cfg(feature = "schemars")]
|
|
{
|
|
let notification = schemars::schema_for!(komorebi_client::Notification);
|
|
let schema = serde_json::to_string_pretty(¬ification)?;
|
|
println!("{schema}");
|
|
}
|
|
}
|
|
SubCommand::SocketSchema => {
|
|
#[cfg(feature = "schemars")]
|
|
{
|
|
let socket_message = schemars::schema_for!(SocketMessage);
|
|
let schema = serde_json::to_string_pretty(&socket_message)?;
|
|
println!("{schema}");
|
|
}
|
|
}
|
|
SubCommand::StaticConfigSchema => {
|
|
#[cfg(feature = "schemars")]
|
|
{
|
|
let static_config = schemars::schema_for!(StaticConfig);
|
|
let schema = serde_json::to_string_pretty(&static_config)?;
|
|
println!("{schema}");
|
|
}
|
|
}
|
|
SubCommand::GenerateStaticConfig => {
|
|
print_query(&SocketMessage::GenerateStaticConfig);
|
|
}
|
|
// Deprecated
|
|
#[allow(deprecated)]
|
|
SubCommand::AltFocusHack(_) | SubCommand::IdentifyBorderOverflowApplication(_) => {
|
|
println!("Command deprecated - this is now automatically handled by komorebi! 🎉");
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn show_window(hwnd: HWND, command: SHOW_WINDOW_CMD) {
|
|
// BOOL is returned but does not signify whether or not the operation was succesful
|
|
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow
|
|
// TODO: error handling
|
|
unsafe {
|
|
let _ = ShowWindow(hwnd, command);
|
|
};
|
|
}
|
|
|
|
fn remove_transparency(hwnd: isize) {
|
|
let _ = komorebi_client::Window::from(hwnd).opaque();
|
|
}
|
|
|
|
fn restore_window(hwnd: isize) {
|
|
show_window(HWND(hwnd as *mut core::ffi::c_void), SW_RESTORE);
|
|
remove_transparency(hwnd);
|
|
}
|