feat(ahk): add cmd to generate helper lib

Woke up today and thought this would be a cool way to learn more about
deriving functionality with proc macros.

Hopefully having this wrapper/helper library will make first time
configuration for new users easier.
This commit is contained in:
LGUG2Z
2021-08-22 18:52:06 -07:00
parent c42739591f
commit 2c876701d8
9 changed files with 415 additions and 38 deletions
Generated
+11
View File
@@ -257,6 +257,15 @@ dependencies = [
"winapi 0.3.9", "winapi 0.3.9",
] ]
[[package]]
name = "derive-ahk"
version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "dirs" name = "dirs"
version = "3.0.2" version = "3.0.2"
@@ -544,8 +553,10 @@ dependencies = [
"bindings", "bindings",
"clap", "clap",
"color-eyre", "color-eyre",
"derive-ahk",
"dirs", "dirs",
"fs-tail", "fs-tail",
"heck",
"komorebi-core", "komorebi-core",
"paste", "paste",
"powershell_script", "powershell_script",
+1
View File
@@ -2,6 +2,7 @@
members = [ members = [
"bindings", "bindings",
"derive-ahk",
"komorebi", "komorebi",
"komorebi-core", "komorebi-core",
"komorebic" "komorebic"
+8
View File
@@ -213,9 +213,16 @@ manage-rule Add a rule to always manage the specified applicati
workspace-rule Add a rule to associate an application with a workspace workspace-rule Add a rule to associate an application with a workspace
identify-tray-application Identify an application that closes to the system tray identify-tray-application Identify an application that closes to the system tray
focus-follows-mouse Enable or disable focus follows mouse for the operating system focus-follows-mouse Enable or disable focus follows mouse for the operating system
ahk-lib Generate a library of AutoHotKey helper functions
help Print this message or the help of the given subcommand(s) help Print this message or the help of the given subcommand(s)
``` ```
Additionally, you may run `komorebic.exe ahk-lib` to generate [a helper library for AutoHotKey](komorebic.lib.sample.ahk)
which wraps every `komorebic` command in a native AHK function.
If you include the generated library at the top of your `~/komorebi.ahk` configuration file, you will be able to call
any of the functions that it contains.
## Features ## Features
- [x] Multi-monitor - [x] Multi-monitor
@@ -248,6 +255,7 @@ help Print this message or the help of the given subcomm
- [x] Load configuration on startup - [x] Load configuration on startup
- [x] Manually reload configuration - [x] Manually reload configuration
- [x] Watch configuration for changes - [x] Watch configuration for changes
- [x] Helper library for AutoHotKey
- [x] View window manager state - [x] View window manager state
## Development ## Development
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "derive-ahk"
version = "0.1.0"
edition = "2018"
[lib]
proc-macro = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
proc-macro2 = "1.0"
syn = "1.0"
quote = "1.0"
+100
View File
@@ -0,0 +1,100 @@
#![warn(clippy::all, clippy::nursery, clippy::pedantic)]
#![allow(clippy::missing_errors_doc)]
use std::stringify;
use quote::quote;
use syn::parse_macro_input;
use syn::Data;
use syn::DataEnum;
use syn::DeriveInput;
use syn::Fields;
use syn::FieldsNamed;
use syn::FieldsUnnamed;
#[proc_macro_derive(Ahk)]
pub fn ahk(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = input.ident;
match input.data {
Data::Struct(s) => match s.fields {
Fields::Named(FieldsNamed { named, .. }) => {
let idents = named.iter().map(|f| &f.ident);
let arguments = format!("{}", quote! {#(#idents), *});
let idents = named.iter().map(|f| &f.ident);
let called_arguments = format!("{}", quote! {#(%#idents%) *})
.replace(" %", "%")
.replace("% ", "%")
.replace("%%", "% %");
quote! {
impl #name {
fn ahk_function() -> String {
format!(r#"
{}({}) {{
Run, komorebic.exe {} {}, , Hide
}}"#,
stringify!(#name),
#arguments,
stringify!(#name).to_kebab_case(),
#called_arguments
)
}
}
}
}
_ => unreachable!("only to be used on structs with named fields"),
},
Data::Enum(DataEnum { variants, .. }) => {
let enums = variants.iter().filter(|&v| {
matches!(v.fields, Fields::Unit) || matches!(v.fields, Fields::Unnamed(..))
});
let mut stream = proc_macro2::TokenStream::new();
for variant in enums.clone() {
match &variant.fields {
Fields::Unnamed(FieldsUnnamed { unnamed, .. }) => {
for field in unnamed {
stream.extend(quote! {
v.push(#field::ahk_function());
});
}
}
Fields::Unit => {
let name = &variant.ident;
stream.extend(quote! {
v.push(format!(r#"
{}() {{
Run, komorebic.exe {}, , Hide
}}"#,
stringify!(#name),
stringify!(#name).to_kebab_case()
));
});
}
Fields::Named(_) => {
unreachable!("only to be used with unnamed and unit fields");
}
}
}
quote! {
impl #name {
fn ahk_functions() -> Vec<String> {
let mut v: Vec<String> = vec![];
v.push(String::from("; Generated by komorebic.exe ahk-lib"));
#stream
v
}
}
}
}
Data::Union(_) => unreachable!("only to be used on enums and structs"),
}
.into()
}
+173
View File
@@ -0,0 +1,173 @@
; Generated by komorebic.exe generate-ahk-library
Start() {
Run, komorebic.exe start, , Hide
}
Stop() {
Run, komorebic.exe stop, , Hide
}
State() {
Run, komorebic.exe state, , Hide
}
Log() {
Run, komorebic.exe log, , Hide
}
Focus(operation_direction) {
Run, komorebic.exe focus %operation_direction%, , Hide
}
Move(operation_direction) {
Run, komorebic.exe move %operation_direction%, , Hide
}
Stack(operation_direction) {
Run, komorebic.exe stack %operation_direction%, , Hide
}
Resize(edge, sizing) {
Run, komorebic.exe resize %edge% %sizing%, , Hide
}
Unstack() {
Run, komorebic.exe unstack, , Hide
}
CycleStack(cycle_direction) {
Run, komorebic.exe cycle-stack %cycle_direction%, , Hide
}
MoveToMonitor(target) {
Run, komorebic.exe move-to-monitor %target%, , Hide
}
MoveToWorkspace(target) {
Run, komorebic.exe move-to-workspace %target%, , Hide
}
FocusMonitor(target) {
Run, komorebic.exe focus-monitor %target%, , Hide
}
FocusWorkspace(target) {
Run, komorebic.exe focus-workspace %target%, , Hide
}
NewWorkspace() {
Run, komorebic.exe new-workspace, , Hide
}
AdjustContainerPadding(sizing, adjustment) {
Run, komorebic.exe adjust-container-padding %sizing% %adjustment%, , Hide
}
AdjustWorkspacePadding(sizing, adjustment) {
Run, komorebic.exe adjust-workspace-padding %sizing% %adjustment%, , Hide
}
ChangeLayout(layout) {
Run, komorebic.exe change-layout %layout%, , Hide
}
FlipLayout(flip) {
Run, komorebic.exe flip-layout %flip%, , Hide
}
Promote() {
Run, komorebic.exe promote, , Hide
}
Retile() {
Run, komorebic.exe retile, , Hide
}
EnsureWorkspaces(monitor, workspace_count) {
Run, komorebic.exe ensure-workspaces %monitor% %workspace_count%, , Hide
}
ContainerPadding(monitor, workspace, size) {
Run, komorebic.exe container-padding %monitor% %workspace% %size%, , Hide
}
WorkspacePadding(monitor, workspace, size) {
Run, komorebic.exe workspace-padding %monitor% %workspace% %size%, , Hide
}
WorkspaceLayout(monitor, workspace, value) {
Run, komorebic.exe workspace-layout %monitor% %workspace% %value%, , Hide
}
WorkspaceTiling(monitor, workspace, value) {
Run, komorebic.exe workspace-tiling %monitor% %workspace% %value%, , Hide
}
WorkspaceName(monitor, workspace, value) {
Run, komorebic.exe workspace-name %monitor% %workspace% %value%, , Hide
}
TogglePause() {
Run, komorebic.exe toggle-pause, , Hide
}
ToggleTiling() {
Run, komorebic.exe toggle-tiling, , Hide
}
ToggleFloat() {
Run, komorebic.exe toggle-float, , Hide
}
ToggleMonocle() {
Run, komorebic.exe toggle-monocle, , Hide
}
ToggleMaximize() {
Run, komorebic.exe toggle-maximize, , Hide
}
RestoreWindows() {
Run, komorebic.exe restore-windows, , Hide
}
Manage() {
Run, komorebic.exe manage, , Hide
}
Unmanage() {
Run, komorebic.exe unmanage, , Hide
}
ReloadConfiguration() {
Run, komorebic.exe reload-configuration, , Hide
}
WatchConfiguration(boolean_state) {
Run, komorebic.exe watch-configuration %boolean_state%, , Hide
}
FloatRule(identifier, id) {
Run, komorebic.exe float-rule %identifier% %id%, , Hide
}
ManageRule(identifier, id) {
Run, komorebic.exe manage-rule %identifier% %id%, , Hide
}
WorkspaceRule(identifier, id, monitor, workspace) {
Run, komorebic.exe workspace-rule %identifier% %id% %monitor% %workspace%, , Hide
}
IdentifyTrayApplication(identifier, id) {
Run, komorebic.exe identify-tray-application %identifier% %id%, , Hide
}
FocusFollowsMouse(boolean_state) {
Run, komorebic.exe focus-follows-mouse %boolean_state%, , Hide
}
AhkLib() {
Run, komorebic.exe ahk-lib, , Hide
}
+2
View File
@@ -12,12 +12,14 @@ edition = "2018"
[dependencies] [dependencies]
bindings = { package = "bindings", path = "../bindings" } bindings = { package = "bindings", path = "../bindings" }
derive-ahk = { path = "../derive-ahk" }
komorebi-core = { path = "../komorebi-core" } komorebi-core = { path = "../komorebi-core" }
clap = "3.0.0-beta.4" clap = "3.0.0-beta.4"
color-eyre = "0.5" color-eyre = "0.5"
dirs = "3" dirs = "3"
fs-tail = "0.1" fs-tail = "0.1"
heck = "0.3"
paste = "1" paste = "1"
powershell_script = "0.2" powershell_script = "0.2"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
+104 -36
View File
@@ -2,12 +2,14 @@
#![allow(clippy::missing_errors_doc)] #![allow(clippy::missing_errors_doc)]
use std::fs::File; use std::fs::File;
use std::fs::OpenOptions;
use std::io::BufRead; use std::io::BufRead;
use std::io::BufReader; use std::io::BufReader;
use std::io::ErrorKind; use std::io::ErrorKind;
use std::io::Write; use std::io::Write;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::Command; use std::process::Command;
use std::stringify;
use clap::AppSettings; use clap::AppSettings;
use clap::ArgEnum; use clap::ArgEnum;
@@ -15,6 +17,7 @@ use clap::Clap;
use color_eyre::eyre::ContextCompat; use color_eyre::eyre::ContextCompat;
use color_eyre::Result; use color_eyre::Result;
use fs_tail::TailedFile; use fs_tail::TailedFile;
use heck::KebabCase;
use paste::paste; use paste::paste;
use uds_windows::UnixListener; use uds_windows::UnixListener;
use uds_windows::UnixStream; use uds_windows::UnixStream;
@@ -23,6 +26,7 @@ use bindings::Windows::Win32::Foundation::HWND;
use bindings::Windows::Win32::UI::WindowsAndMessaging::ShowWindow; use bindings::Windows::Win32::UI::WindowsAndMessaging::ShowWindow;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SHOW_WINDOW_CMD; use bindings::Windows::Win32::UI::WindowsAndMessaging::SHOW_WINDOW_CMD;
use bindings::Windows::Win32::UI::WindowsAndMessaging::SW_RESTORE; use bindings::Windows::Win32::UI::WindowsAndMessaging::SW_RESTORE;
use derive_ahk::Ahk;
use komorebi_core::ApplicationIdentifier; use komorebi_core::ApplicationIdentifier;
use komorebi_core::CycleDirection; use komorebi_core::CycleDirection;
use komorebi_core::Flip; use komorebi_core::Flip;
@@ -51,7 +55,7 @@ macro_rules! gen_enum_subcommand_args {
( $( $name:ident: $element:ty ),+ ) => { ( $( $name:ident: $element:ty ),+ ) => {
$( $(
paste! { paste! {
#[derive(clap::Clap)] #[derive(clap::Clap, derive_ahk::Ahk)]
pub struct $name { pub struct $name {
#[clap(arg_enum)] #[clap(arg_enum)]
[<$element:snake>]: $element [<$element:snake>]: $element
@@ -67,7 +71,7 @@ gen_enum_subcommand_args! {
Stack: OperationDirection, Stack: OperationDirection,
CycleStack: CycleDirection, CycleStack: CycleDirection,
FlipLayout: Flip, FlipLayout: Flip,
SetLayout: Layout, ChangeLayout: Layout,
WatchConfiguration: BooleanState, WatchConfiguration: BooleanState,
FocusFollowsMouse: BooleanState FocusFollowsMouse: BooleanState
} }
@@ -76,7 +80,7 @@ macro_rules! gen_target_subcommand_args {
// SubCommand Pattern // SubCommand Pattern
( $( $name:ident ),+ ) => { ( $( $name:ident ),+ ) => {
$( $(
#[derive(clap::Clap)] #[derive(clap::Clap, derive_ahk::Ahk)]
pub struct $name { pub struct $name {
/// Target index (zero-indexed) /// Target index (zero-indexed)
target: usize, target: usize,
@@ -100,7 +104,7 @@ macro_rules! gen_workspace_subcommand_args {
( $( $name:ident: $(#[enum] $(@$arg_enum:tt)?)? $value:ty ),+ ) => ( ( $( $name:ident: $(#[enum] $(@$arg_enum:tt)?)? $value:ty ),+ ) => (
paste! { paste! {
$( $(
#[derive(clap::Clap)] #[derive(clap::Clap, derive_ahk::Ahk)]
pub struct [<Workspace $name>] { pub struct [<Workspace $name>] {
/// Monitor index (zero-indexed) /// Monitor index (zero-indexed)
monitor: usize, monitor: usize,
@@ -126,7 +130,7 @@ gen_workspace_subcommand_args! {
Tiling: #[enum] BooleanState Tiling: #[enum] BooleanState
} }
#[derive(Clap)] #[derive(Clap, Ahk)]
struct Resize { struct Resize {
#[clap(arg_enum)] #[clap(arg_enum)]
edge: OperationDirection, edge: OperationDirection,
@@ -134,7 +138,7 @@ struct Resize {
sizing: Sizing, sizing: Sizing,
} }
#[derive(Clap)] #[derive(Clap, Ahk)]
struct EnsureWorkspaces { struct EnsureWorkspaces {
/// Monitor index (zero-indexed) /// Monitor index (zero-indexed)
monitor: usize, monitor: usize,
@@ -142,33 +146,70 @@ struct EnsureWorkspaces {
workspace_count: usize, workspace_count: usize,
} }
#[derive(Clap)] macro_rules! gen_padding_subcommand_args {
struct Padding { // SubCommand Pattern
/// Monitor index (zero-indexed) ( $( $name:ident ),+ ) => {
monitor: usize, $(
/// Workspace index on the specified monitor (zero-indexed) #[derive(clap::Clap, derive_ahk::Ahk)]
workspace: usize, pub struct $name {
/// Pixels to pad with as an integer /// Monitor index (zero-indexed)
size: i32, monitor: usize,
/// Workspace index on the specified monitor (zero-indexed)
workspace: usize,
/// Pixels to pad with as an integer
size: i32,
}
)+
};
} }
#[derive(Clap)] gen_padding_subcommand_args! {
struct PaddingAdjustment { ContainerPadding,
#[clap(arg_enum)] WorkspacePadding
sizing: Sizing,
/// Pixels to adjust by as an integer
adjustment: i32,
} }
#[derive(Clap)] macro_rules! gen_padding_adjustment_subcommand_args {
struct ApplicationTarget { // SubCommand Pattern
#[clap(arg_enum)] ( $( $name:ident ),+ ) => {
identifier: ApplicationIdentifier, $(
/// Identifier as a string #[derive(clap::Clap, derive_ahk::Ahk)]
id: String, pub struct $name {
#[clap(arg_enum)]
sizing: Sizing,
/// Pixels to adjust by as an integer
adjustment: i32,
}
)+
};
} }
#[derive(Clap)] gen_padding_adjustment_subcommand_args! {
AdjustContainerPadding,
AdjustWorkspacePadding
}
macro_rules! gen_application_target_subcommand_args {
// SubCommand Pattern
( $( $name:ident ),+ ) => {
$(
#[derive(clap::Clap, derive_ahk::Ahk)]
pub struct $name {
#[clap(arg_enum)]
identifier: ApplicationIdentifier,
/// Identifier as a string
id: String,
}
)+
};
}
gen_application_target_subcommand_args! {
FloatRule,
ManageRule,
IdentifyTrayApplication
}
#[derive(Clap, Ahk)]
struct WorkspaceRule { struct WorkspaceRule {
#[clap(arg_enum)] #[clap(arg_enum)]
identifier: ApplicationIdentifier, identifier: ApplicationIdentifier,
@@ -187,7 +228,7 @@ struct Opts {
subcmd: SubCommand, subcmd: SubCommand,
} }
#[derive(Clap)] #[derive(Clap, Ahk)]
enum SubCommand { enum SubCommand {
/// Start komorebi.exe as a background process /// Start komorebi.exe as a background process
Start, Start,
@@ -230,12 +271,12 @@ enum SubCommand {
NewWorkspace, NewWorkspace,
/// Adjust container padding on the focused workspace /// Adjust container padding on the focused workspace
#[clap(setting = AppSettings::ArgRequiredElseHelp)] #[clap(setting = AppSettings::ArgRequiredElseHelp)]
AdjustContainerPadding(PaddingAdjustment), AdjustContainerPadding(AdjustContainerPadding),
/// Adjust workspace padding on the focused workspace /// Adjust workspace padding on the focused workspace
#[clap(setting = AppSettings::ArgRequiredElseHelp)] #[clap(setting = AppSettings::ArgRequiredElseHelp)]
AdjustWorkspacePadding(PaddingAdjustment), AdjustWorkspacePadding(AdjustWorkspacePadding),
/// Set the layout on the focused workspace /// Set the layout on the focused workspace
ChangeLayout(SetLayout), ChangeLayout(ChangeLayout),
/// Flip the layout on the focused workspace (BSP only) /// Flip the layout on the focused workspace (BSP only)
FlipLayout(FlipLayout), FlipLayout(FlipLayout),
/// Promote the focused window to the top of the tree /// Promote the focused window to the top of the tree
@@ -247,10 +288,10 @@ enum SubCommand {
EnsureWorkspaces(EnsureWorkspaces), EnsureWorkspaces(EnsureWorkspaces),
/// Set the container padding for the specified workspace /// Set the container padding for the specified workspace
#[clap(setting = AppSettings::ArgRequiredElseHelp)] #[clap(setting = AppSettings::ArgRequiredElseHelp)]
ContainerPadding(Padding), ContainerPadding(ContainerPadding),
/// Set the workspace padding for the specified workspace /// Set the workspace padding for the specified workspace
#[clap(setting = AppSettings::ArgRequiredElseHelp)] #[clap(setting = AppSettings::ArgRequiredElseHelp)]
WorkspacePadding(Padding), WorkspacePadding(WorkspacePadding),
/// Set the layout for the specified workspace /// Set the layout for the specified workspace
#[clap(setting = AppSettings::ArgRequiredElseHelp)] #[clap(setting = AppSettings::ArgRequiredElseHelp)]
WorkspaceLayout(WorkspaceLayout), WorkspaceLayout(WorkspaceLayout),
@@ -283,18 +324,20 @@ enum SubCommand {
WatchConfiguration(WatchConfiguration), WatchConfiguration(WatchConfiguration),
/// Add a rule to always float the specified application /// Add a rule to always float the specified application
#[clap(setting = AppSettings::ArgRequiredElseHelp)] #[clap(setting = AppSettings::ArgRequiredElseHelp)]
FloatRule(ApplicationTarget), FloatRule(FloatRule),
/// Add a rule to always manage the specified application /// Add a rule to always manage the specified application
#[clap(setting = AppSettings::ArgRequiredElseHelp)] #[clap(setting = AppSettings::ArgRequiredElseHelp)]
ManageRule(ApplicationTarget), ManageRule(ManageRule),
/// Add a rule to associate an application with a workspace /// Add a rule to associate an application with a workspace
#[clap(setting = AppSettings::ArgRequiredElseHelp)] #[clap(setting = AppSettings::ArgRequiredElseHelp)]
WorkspaceRule(WorkspaceRule), WorkspaceRule(WorkspaceRule),
/// Identify an application that closes to the system tray /// Identify an application that closes to the system tray
#[clap(setting = AppSettings::ArgRequiredElseHelp)] #[clap(setting = AppSettings::ArgRequiredElseHelp)]
IdentifyTrayApplication(ApplicationTarget), IdentifyTrayApplication(IdentifyTrayApplication),
/// Enable or disable focus follows mouse for the operating system /// Enable or disable focus follows mouse for the operating system
FocusFollowsMouse(FocusFollowsMouse), FocusFollowsMouse(FocusFollowsMouse),
/// Generate a library of AutoHotKey helper functions
AhkLib,
} }
pub fn send_message(bytes: &[u8]) -> Result<()> { pub fn send_message(bytes: &[u8]) -> Result<()> {
@@ -311,6 +354,31 @@ fn main() -> Result<()> {
let opts: Opts = Opts::parse(); let opts: Opts = Opts::parse();
match opts.subcmd { match opts.subcmd {
SubCommand::AhkLib => {
let mut library = dirs::home_dir().context("there is no home directory")?;
library.push("komorebic.lib.ahk");
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(library.clone())?;
let library_text: String = SubCommand::ahk_functions().join("\n");
file.write_all(library_text.as_bytes())?;
println!(
"\nAHK helper library for komorebic written to {}",
library
.to_str()
.context("could not find the path to the generated ahk lib file")?
);
println!(
"\nYou can include the library at the top of your ~/komorebi.ahk config with this line:"
);
println!("\n#Include %A_ScriptDir%\\komorebic.lib.ahk");
}
SubCommand::Log => { SubCommand::Log => {
let mut color_log = std::env::temp_dir(); let mut color_log = std::env::temp_dir();
color_log.push("komorebi.log"); color_log.push("komorebi.log");