Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mouse movement constant factor/multiplier keys to instantaneously change speed #2206

Open
seansfkelley opened this issue Mar 16, 2024 · 8 comments

Comments

@seansfkelley
Copy link

seansfkelley commented Mar 16, 2024

Background and Use Case

I currently use a fork of ZMK that includes #2027. I use constant-speed mouse/scroll movement (exponent 0, time-to-max 0), which I find much easier to control. The downside is that it's a poor fit for handling both precise movement and large screen movements.

In QMK, I resolved this problem using the different cursor speeds offered by the KC_ACL{0,1,2} bindings. The cursor had four speeds (including the default, unmodified one) and holding a key for the alternate speed took effect immediately, without moving any of your other fingers or lifting them off their keys.

In the version of mouse movement in #2027 and (I think) #778, it seems like the only way to get different mouse/scroll speeds at different times is to use different keys, possibly on different layers. This is pretty awkward when you're used to using just WASD to move the mouse around, and doubly so if you choose to put those alternate speeds on a different layer -- both methods require lifting and moving the fingers being actively used for the movement, which feels a bit like having multiple gas pedals in your car for different speeds.

If I've misunderstood, and there is a way to get my desired behavior with some clever usage of macros, I'd love to hear it! Composing a solution out of the existing parts is preferable.

Assuming not, here is a proposal. I would be happy to open a PR as well if you think this is a good idea. My fork already has more primitive prototype of this behavior implemented and working.

Proposal: Speed Multiplier Keys

#define a new type of thing that can be provided to any zmk,behavior-input-two-axis (but I'll use &mmv as an example for the rest of this):

&mmv MOVE_MULTIPLIER(2)

This would double the speed of any other &mmv keys that are held while this key is held. Multipliers would combine... multiplicatively... so if you held more than one such modifier you would get a speedup proportional to their product. This factor would be applied as the last step, after all other acceleration, exponent, etc. math has been performed to compute the current tick's delta x/y.

The lack of floating point (and please correct me if I'm wrong about that) makes defining slower-than-default speeds awkward. Instead of defining a multiplier [0, 1) and being done with it, you could instead define a "base" multiplier that is ignored if other multipliers are active:

&mmv {
    base-multiplier = <4>;
};

This means that all speeds are multiplied by 4 by default, unless any other modifiers are held, in which case those held values are used instead. In the case of 4, this means slower speeds could be 1/4, 2/4 or 3/4 of the "base" speed. It also means that the numbers in your keymap have to be 1/4 as large as you think. (This value could be a regular constant instead of a behavior property as well.)

As an implementation detail, it would probably be best to change the underlying two-axis behavior to accept two parameters instead of one. The speeds could remain bit-packed into the first, and the second would be used to communicate the multiplier, if present. The MOVE_X (etc.) macros would change to hardcode a 0 or sentinel value for the second parameter, so you could still do e.g. &mmv MOVE_X(500) and it would continue to work.

It seems unlikely that this behavior would be composed with non-linear or non-constant two-axis movement configurations, but it would have well-defined semantics if so.

@seansfkelley
Copy link
Author

seansfkelley commented Mar 16, 2024

I just noticed that an interesting property falls out of this design if you set something like

#define ZMK_MOUSE_DEFAULT_MOVE_VAL 1

and

&mmv {
    base-multiplier = <500>;
}

Now, if you only hold one speed-modifier key at a time, it effectively sets the speed to its parameter

&mmv MOVE_MULTIPLIER(200) // slow mode

which is generally easier to reason about, though the total speed gets enormous if you hold multiple of these at once.

Perhaps an alternate proposal could be to make the speeds additive instead of multiplicative, so you would have a base-speed property instead of a multiplier (and no more ZMK_MOUSE_DEFAULT_MOVE_VAL at all), and the speed-modifier keys could have negative values if you want to slow down. This resolves the weird and unpleasant semantics of base-multiplier being used to fake values < 1. In exchange, it makes the whole system clash awkwardly with varying per-key speeds, since an arbitrary addition/subtraction is going to be more annoying to tailor well to said varying values than a simple multiplicative factor. (As someone who does not use multiple speed values, this does not matter to me personally.)

@caksoylar
Copy link
Contributor

caksoylar commented Mar 16, 2024

I think multiplier makes more sense to me as well, and is easier to reason about in general. In order to solve the slow down issue with integer params, I'd propose a fractional approach:

// 1/4 speed
multiplier = <1>; // default
divisor = <4>;

// 2x speed
multiplier = <2>;
divisor = <1>; // default

I don't know if it makes sense to fold this into &mmv as well -- ideally these modifiers should apply to other pointing devices besides mouse keys as well, somehow. I guess it could interact with the input listener instead, but am not familiar enough with the current design to say how that'd be feasible.

@seansfkelley
Copy link
Author

Can you expand more on the fractional idea? It sounds likely to be clearer than mine but I don't understand the specifics enough. Are those properties set once on e.g. &mmv, and how do they relate to/replace MOVE_MULTIPLIER?

I don't know if it makes sense to fold this into &mmv as well -- ideally these modifiers should apply to other pointing devices besides mouse keys as well, somehow.

I just edited the description slightly to clarify that this really applies to zmk,behavior-input-two-axis, of which &mmv is a standard example. In my prototype, I use a macro to set the mouse-move and scroll speeds simultaneously with the same single key:

mouse_fast: mouse_fast {
    compatible = "zmk,behavior-macro";
    #binding-cells = <0>;
    bindings
        = <&macro_press &mmv MOVE_MULTIPLIER(12)>
        , <&macro_press &msc MOVE_MULTIPLIER(12)>
        , <&macro_pause_for_release>
        , <&macro_release &msc MOVE_MULTIPLIER(12)>
        , <&macro_release &mmv MOVE_MULTIPLIER(12)>
        ;
};

Composition! We love to see it. Does this address your concern?

I'm also not terribly familiar with how the zmk,input-listener behavior factors in here, though I see it already has a multiplier/divisor concept and is 1:1 with the relevant zmk,behavior-input-two-axis behaviors. I suppose that means this feature could be implemented there instead in a similar fashion.

@caksoylar
Copy link
Contributor

I just edited the description slightly to clarify that this really applies to zmk,behavior-input-two-axis, of which &mmv is a standard example.

My concern is more with pointing devices that you don't use zmk,behavior-input-two-axis (or behaviors at all), like trackpoints. They seem to tie the device driver directly to movements via the zmk,input-listener device (example, example). So my point was that such modifiers should be able to affect the behavior of those as well.

Can you expand more on the fractional idea? It sounds likely to be clearer than mine but I don't understand the specifics enough.

I propose this as an alternative to setting a base multiplier like base-multiplier = <4>; that would allow you to slow down the movement (e.g. to half, with MOVE_MULTIPLIER(2), if I understand your suggestion correctly). You could imagine a new behavior like below:

slow_down: slow_down {
    compatible = "zmk,behavior-pointer-speed";
    multiplier = <2>;
    divisor = <4>;
};

// use in keymap with `&slow_down`

Which would also multiply pointer speed by 2/4=1/2.

But there is more intricacy here, you probably want this to affect only one device. If you want to change the speed of &mmv, maybe you also pass a reference to its listener (assume this node also has the label mkp_input_listener):

slow_down_mmv: slow_down_mmv {
    compatible = "zmk,behavior-pointer-speed";
    listener = <&mkp_input_listener>;
    multiplier = <2>;
    divisor = <4>;
};

@caksoylar
Copy link
Contributor

I missed that such multiplier/divisors already exist in the input listener device, so the values from a new "modifier" behavior might simply compose with the existing listener settings during runtime.

@seansfkelley
Copy link
Author

If I understand correctly, the input-listener multiplier/divisor is applied right at the beginning of the process (via input_handler calling filter_with_input_config), before any acceleration math (etc.) is applied. That makes it a little harder to reason about the end effect of changing the multiplier/divisor, but I really like the idea of reusing the existing fractional definitions to express this feature. Also, it shouldn't make a practical difference when using constant (and maybe linear?) acceleration modes, which is really when I expect this to be used.

I'll work through more specifics later, but the short version of an updated proposal would be: define new codes like INPUT_MULT(n) or INPUT_DIV(n) for input-listeners. Bind keys to (e.g.) &mmv_input_listener INPUT_MULT(2). Multiplier/divisors combine multiplicatively with each other (if more than one are held) as well as the base multiplier/divisor properties. This means that INPUT_MULT(2) or INPUT_DIV(2) would always double or half (respectively) the speed regardless of what other keys are held or what the "base" properties are set as. Sounds... simple?

@badjeff
Copy link

badjeff commented Mar 17, 2024

would always double or half (respectively) the speed regardless of what other keys are held or what the "base" properties are set as.

Here is my version of zmk,input-listener with behavior bindings. It is a module that can added on top of #2027 and coexists to original listener features. I added extra layers, bindings, etc, properties as I need them for my daily use. You can set a static speed in each layers. Or implement &braking behavior with your acceleration favor regarding what other keys are held. I guess you can do whatever you want while the event pointer struct input_event * is one manipulatable parameter in behavior.

@claybford
Copy link

I tried @badjeff 's implementation and it resulted in odd behavior with my cirque/glidepoint/pinnacle, the motion was off-axis and not scaled as expected. Since @seansfkelley your version was mouse keys only, I put together this behavior hack into input_listener.c, I doubt it follows any style guidelines at all and won't generalize to mouse keys, but it works for defining a behavior in your keymap with replacement multiplier/divisor properties.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants