Skip to content

Commit

Permalink
refactor(ffm): add selection of ffm implementation
Browse files Browse the repository at this point in the history
This commit adds an optional flag to allow users to select the focus
follows mouse implementation that they wish to use (komorebi or
windows). The flag defaults to komorebi.

The ahk-derive crate has been updated to enable the generation of
wrappers fns that require flags.

I pushed the ffm check up to listen_for_movements() so that we don't
even try to listen to the next event from the message loop unless
komorebi-flavoured ffm is enabled.

re #7
  • Loading branch information
LGUG2Z committed Sep 7, 2021
1 parent ce3c742 commit 2b7c51b
Show file tree
Hide file tree
Showing 9 changed files with 258 additions and 55 deletions.
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,21 @@ komorebic.exe identify-tray-application exe Discord.exe
# komorebic.exe identify-tray-application title [TITLE]
```

#### Focus Follows Mouse

Komorebi supports two focus-follows-mouse implementations; the native Windows X-Mouse implementation, which treats the
desktop, the task bar and the system tray as windows and switches focus to them eagerly, and a custom `komorebi`
implementation which only considers windows managed by `komorebi` as valid targets to switch focus to when moving the
mouse.

When calling any of the `komorebic` commands related to focus-follows-mouse functionality, the `komorebi`
implementation will be chosen as the default implementation. You can optionally specify the `windows` implementation by
passing it as an argument to the `--implementation` flag:

```powershell
komorebic.exe toggle-focus-follows-mouse --implementation windows
```

## Configuration with `komorebic`

As previously mentioned, this project does not handle anything related to keybindings and shortcuts directly. I
Expand Down Expand Up @@ -263,7 +278,8 @@ used [is available here](komorebi.sample.with.lib.ahk).
- [x] Toggle floating windows
- [x] Toggle monocle window
- [x] Toggle native maximization
- [x] Toggle focus follows mouse
- [x] Toggle X-Mouse (Native Windows) focus follows mouse
- [x] Toggle custom Komorebi focus follows mouse (desktop and system tray-aware)
- [x] Toggle automatic tiling
- [x] Pause all window management
- [x] Load configuration on startup
Expand Down
120 changes: 106 additions & 14 deletions derive-ahk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ use ::syn::DeriveInput;
use ::syn::Fields;
use ::syn::FieldsNamed;
use ::syn::FieldsUnnamed;
use ::syn::Meta;
use ::syn::NestedMeta;

#[allow(clippy::too_many_lines)]
#[proc_macro_derive(AhkFunction)]
pub fn ahk_function(input: ::proc_macro::TokenStream) -> ::proc_macro::TokenStream {
let input = parse_macro_input!(input as DeriveInput);
Expand All @@ -29,29 +32,118 @@ pub fn ahk_function(input: ::proc_macro::TokenStream) -> ::proc_macro::TokenStre
match input.data {
Data::Struct(s) => match s.fields {
Fields::Named(FieldsNamed { named, .. }) => {
let idents = named.iter().map(|f| &f.ident);
let arguments = quote! {#(#idents), *}.to_string();
let argument_idents = named
.iter()
// Filter out the flags
.filter(|&f| {
let mut include = true;
for attribute in &f.attrs {
if let ::std::result::Result::Ok(Meta::List(list)) =
attribute.parse_meta()
{
for nested in list.nested {
if let NestedMeta::Meta(Meta::Path(path)) = nested {
if path.is_ident("long") {
include = false;
}
}
}
}
}

include
})
.map(|f| &f.ident);

let argument_idents_clone = argument_idents.clone();

let idents = named.iter().map(|f| &f.ident);
let called_arguments = quote! {#(%#idents%) *}
let called_arguments = quote! {#(%#argument_idents_clone%) *}
.to_string()
.replace(" %", "%")
.replace("% ", "%")
.replace("%%", "% %");

quote! {
impl AhkFunction for #name {
fn generate_ahk_function() -> String {
::std::format!(r#"
let flag_idents = named
.iter()
// Filter only the flags
.filter(|f| {
let mut include = false;

for attribute in &f.attrs {
if let ::std::result::Result::Ok(Meta::List(list)) =
attribute.parse_meta()
{
for nested in list.nested {
if let NestedMeta::Meta(Meta::Path(path)) = nested {
// Identify them using the --long flag name
if path.is_ident("long") {
include = true;
}
}
}
}
}

include
})
.map(|f| &f.ident);

let has_flags = flag_idents.clone().count() != 0;

if has_flags {
let flag_idents_concat = flag_idents.clone();
let argument_idents_concat = argument_idents.clone();

// Concat the args and flag args if there are flags
let all_arguments =
quote! {#(#argument_idents_concat,) * #(#flag_idents_concat), *}
.to_string();

let flag_idents_clone = flag_idents.clone();
let flags = quote! {#(--#flag_idents_clone) *}
.to_string()
.replace("- - ", "--");

let called_flag_arguments = quote! {#(%#flag_idents%) *}
.to_string()
.replace(" %", "%")
.replace("% ", "%")
.replace("%%", "% %");

quote! {
impl AhkFunction for #name {
fn generate_ahk_function() -> String {
::std::format!(r#"
{}({}) {{
Run, komorebic.exe {} {} {} {}, , Hide
}}"#,
::std::stringify!(#name),
#all_arguments,
::std::stringify!(#name).to_kebab_case(),
#called_arguments,
#flags,
#called_flag_arguments
)
}
}
}
} else {
let arguments = quote! {#(#argument_idents), *}.to_string();

quote! {
impl AhkFunction for #name {
fn generate_ahk_function() -> String {
::std::format!(r#"
{}({}) {{
Run, komorebic.exe {} {}, , Hide
}}"#,
::std::stringify!(#name),
#arguments,
::std::stringify!(#name).to_kebab_case(),
#called_arguments
)
}
::std::stringify!(#name),
#arguments,
::std::stringify!(#name).to_kebab_case(),
#called_arguments
)
}
}
}
}
}
Expand Down
11 changes: 9 additions & 2 deletions komorebi-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ pub enum SocketMessage {
IdentifyTrayApplication(ApplicationIdentifier, String),
State,
Query(StateQuery),
FocusFollowsMouse(bool),
ToggleFocusFollowsMouse,
FocusFollowsMouse(FocusFollowsMouseImplementation, bool),
ToggleFocusFollowsMouse(FocusFollowsMouseImplementation),
}

impl SocketMessage {
Expand Down Expand Up @@ -107,6 +107,13 @@ pub enum ApplicationIdentifier {
Title,
}

#[derive(Clone, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum)]
#[strum(serialize_all = "snake_case")]
pub enum FocusFollowsMouseImplementation {
Komorebi,
Windows,
}

#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum)]
#[strum(serialize_all = "snake_case")]
pub enum Sizing {
Expand Down
5 changes: 3 additions & 2 deletions komorebi.sample.with.lib.ahk
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ FloatRule("exe", "Wox.exe")

; Identify Minimize-to-Tray Applications
IdentifyTrayApplication("exe", "Discord.exe")
IdentifyTrayApplication("exe", "Spotify.exe")

; Change the focused window, Alt + Vim direction keys
!h::
Expand Down Expand Up @@ -170,9 +171,9 @@ return
TogglePause()
return

; Toggle focus follows mouse
; Enable focus follows mouse
!0::
ToggleFocusFollowsMouse()
ToggleFocusFollowsMouse("komorebi")
return

; Switch to workspace
Expand Down
82 changes: 75 additions & 7 deletions komorebi/src/process_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ use parking_lot::Mutex;
use uds_windows::UnixStream;

use komorebi_core::ApplicationIdentifier;
use komorebi_core::FocusFollowsMouseImplementation;
use komorebi_core::SocketMessage;
use komorebi_core::StateQuery;

use crate::window_manager;
use crate::window_manager::WindowManager;
use crate::windows_api::WindowsApi;
use crate::FLOAT_IDENTIFIERS;
use crate::MANAGE_IDENTIFIERS;
use crate::TRAY_AND_MULTI_WINDOW_CLASSES;
Expand Down Expand Up @@ -211,14 +213,80 @@ impl WindowManager {
SocketMessage::ResizeWindow(direction, sizing) => {
self.resize_window(direction, sizing, Option::from(50))?;
}
SocketMessage::FocusFollowsMouse(enable) => {
if enable {
self.autoraise = true;
} else {
self.autoraise = false;
SocketMessage::FocusFollowsMouse(implementation, enable) => match implementation {
FocusFollowsMouseImplementation::Komorebi => {
if WindowsApi::focus_follows_mouse()? {
tracing::warn!(
"the komorebi implementation of focus follows mouse cannot be enabled while the windows implementation is enabled"
);
} else if enable {
self.focus_follows_mouse = Option::from(implementation);
} else {
self.focus_follows_mouse = None;
}
}
}
SocketMessage::ToggleFocusFollowsMouse => self.autoraise = !self.autoraise,
FocusFollowsMouseImplementation::Windows => {
if let Some(FocusFollowsMouseImplementation::Komorebi) =
self.focus_follows_mouse
{
tracing::warn!(
"the windows implementation of focus follows mouse cannot be enabled while the komorebi implementation is enabled"
);
} else if enable {
WindowsApi::enable_focus_follows_mouse()?;
self.focus_follows_mouse =
Option::from(FocusFollowsMouseImplementation::Windows);
} else {
WindowsApi::disable_focus_follows_mouse()?;
self.focus_follows_mouse = None;
}
}
},
SocketMessage::ToggleFocusFollowsMouse(implementation) => match implementation {
FocusFollowsMouseImplementation::Komorebi => {
if WindowsApi::focus_follows_mouse()? {
tracing::warn!(
"the komorebi implementation of focus follows mouse cannot be toggled while the windows implementation is enabled"
);
} else {
match self.focus_follows_mouse {
None => self.focus_follows_mouse = Option::from(implementation),
Some(FocusFollowsMouseImplementation::Komorebi) => {
self.focus_follows_mouse = None;
}
Some(FocusFollowsMouseImplementation::Windows) => {
tracing::warn!("ignoring command that could mix different focus follow mouse implementations");
}
}
}
}
FocusFollowsMouseImplementation::Windows => {
if let Some(FocusFollowsMouseImplementation::Komorebi) =
self.focus_follows_mouse
{
tracing::warn!(
"the windows implementation of focus follows mouse cannot be toggled while the komorebi implementation is enabled"
);
} else {
match self.focus_follows_mouse {
None => {
self.focus_follows_mouse = {
WindowsApi::enable_focus_follows_mouse()?;
Option::from(implementation)
}
}
Some(FocusFollowsMouseImplementation::Windows) => {
WindowsApi::disable_focus_follows_mouse()?;
self.focus_follows_mouse = None;
}
Some(FocusFollowsMouseImplementation::Komorebi) => {
tracing::warn!("ignoring command that could mix different focus follow mouse implementations");
}
}
}
}
},

SocketMessage::ReloadConfiguration => {
Self::reload_configuration();
}
Expand Down
29 changes: 17 additions & 12 deletions komorebi/src/process_movement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use winput::message_loop;
use winput::message_loop::Event;
use winput::Action;

use komorebi_core::FocusFollowsMouseImplementation;

use crate::window_manager::WindowManager;

#[tracing::instrument]
Expand All @@ -15,21 +17,24 @@ pub fn listen_for_movements(wm: Arc<Mutex<WindowManager>>) {
let receiver = message_loop::start().expect("could not start winput message loop");

loop {
match receiver.next_event() {
// Don't want to send any raise events while we are dragging or resizing
Event::MouseButton { action, .. } => match action {
Action::Press => ignore_movement = true,
Action::Release => ignore_movement = false,
},
Event::MouseMoveRelative { .. } => {
if !ignore_movement {
match wm.lock().raise_window_at_cursor_pos() {
Ok(_) => {}
Err(error) => tracing::error!("{}", error),
let focus_follows_mouse = wm.lock().focus_follows_mouse.clone();
if let Some(FocusFollowsMouseImplementation::Komorebi) = focus_follows_mouse {
match receiver.next_event() {
// Don't want to send any raise events while we are dragging or resizing
Event::MouseButton { action, .. } => match action {
Action::Press => ignore_movement = true,
Action::Release => ignore_movement = false,
},
Event::MouseMoveRelative { .. } => {
if !ignore_movement {
match wm.lock().raise_window_at_cursor_pos() {
Ok(_) => {}
Err(error) => tracing::error!("{}", error),
}
}
}
_ => {}
}
_ => {}
}
}
});
Expand Down
Loading

0 comments on commit 2b7c51b

Please sign in to comment.