feat(input): configure mice and left-handed pointers (#108)
* refactor(input): share pointer settings between touchpad and mouse Move natural-scroll, accel and left-handed into PointerConfig, embedded by TouchpadConfig and used by the new mouse.* keys. Add InputDevicesConfig for the input adapter. * refactor(app): send latest config and scenes through one helper Relay the touchpad and mouse config as one InputDevicesConfig so a change to either reaches the input adapter. Share the keep-newest channel send between the config relay and the scene consumer. * feat(input): configure mice and left-handed pointers Configure devices through an unexported deviceConfig seam (zero-size libinputDevices implementation, Mockery mock) and pick settings by device type: touchpads take touchpad.* and tapping, other pointers take mouse.*, keyboards and the like are left alone. This stops touchpad.natural-scroll from being applied to mice. Left-handed is set through libinput when the device offers it. Refs #98 * docs(input): document mouse natural scroll in the overview and trackpoint scope * refactor(input): embed device config in Config and tighten pointer tests
- commit
- 819389ed0febe5d2a23cf495e771044e63e22c10
- author
- bnema · · 2026-10-03 13:25 UTC
- committer
- GitHub · · 2026-10-03 13:25 UTC
- parent
- f28b0f0
- signature
- GPG Unverified
B5690EEEBB952194The key is unknown
12 files · +938 -176
M .mockery.yml +7 -0
| @@ -31,6 +31,13 @@ packages: | |||
| 31 | 31 | dir: internal/adapters/drm | |
| 32 | 32 | pkgname: drm | |
| 33 | 33 | filename: kms_mock_test.go | |
| 34 | + | github.com/bnema/neferwl/internal/adapters/libinput: | |
| 35 | + | interfaces: | |
| 36 | + | deviceConfig: | |
| 37 | + | config: | |
| 38 | + | dir: internal/adapters/libinput | |
| 39 | + | pkgname: libinput | |
| 40 | + | filename: device_config_mock_test.go | |
| 34 | 41 | github.com/bnema/neferwl/internal/adapters/vulkan: | |
| 35 | 42 | interfaces: | |
| 36 | 43 | captureSync: | |
| @@ -31,6 +31,13 @@ packages: | |||||
| 31 | dir: internal/adapters/drm | 31 | dir: internal/adapters/drm | ||
| 32 | pkgname: drm | 32 | pkgname: drm | ||
| 33 | filename: kms_mock_test.go | 33 | filename: kms_mock_test.go | ||
| 34 | + | github.com/bnema/neferwl/internal/adapters/libinput: | |||
| 35 | + | interfaces: | |||
| 36 | + | deviceConfig: | |||
| 37 | + | config: | |||
| 38 | + | dir: internal/adapters/libinput | |||
| 39 | + | pkgname: libinput | |||
| 40 | + | filename: device_config_mock_test.go | |||
| 34 | github.com/bnema/neferwl/internal/adapters/vulkan: | 41 | github.com/bnema/neferwl/internal/adapters/vulkan: | ||
| 35 | interfaces: | 42 | interfaces: | ||
| 36 | captureSync: | 43 | captureSync: | ||
M docs/config.md +6 -1
| @@ -37,7 +37,12 @@ NeferWL reads `$XDG_CONFIG_HOME/neferwl/config` (or `~/.config/neferwl/config`). | |||
| 37 | 37 | | `touchpad.tap` | `on` | Tap to click: one finger left, two right, three middle | | |
| 38 | 38 | | `touchpad.accel-speed` | `0` | Touchpad pointer speed, from `-1` (slowest) to `1` (fastest) | | |
| 39 | 39 | | `touchpad.accel-profile` | `adaptive` | `adaptive`: faster finger moves go further; `flat`: constant speed | | |
| 40 | + | | `touchpad.left-handed` | `off` | Swaps the left and right touchpad buttons | | |
| 40 | 41 | | `touchpad.scroll-factor` | `1` | Multiplies two-finger scroll distance, above `0` up to `10` | | |
| 42 | + | | `mouse.natural-scroll` | `off` | Wheel scrolling moves the content with the wheel instead of against it. `mouse.*` applies to every pointer that is not a touchpad: mice, trackballs, trackpoints | | |
| 43 | + | | `mouse.accel-speed` | `0` | Mouse pointer speed, from `-1` (slowest) to `1` (fastest) | | |
| 44 | + | | `mouse.accel-profile` | `adaptive` | `adaptive`: faster moves go further; `flat`: constant speed | | |
| 45 | + | | `mouse.left-handed` | `off` | Swaps the left and right mouse buttons | | |
| 41 | 46 | | `cursor.hide-after` | `5s` | Hides the pointer cursor after this long without motion; the next motion shows it. 100ms to 1h, or `off` | | |
| 42 | 47 | | `focus.follow-move` | `off` | Follow a column moved to another workspace | | |
| 43 | 48 | | `focus.animation` | `pulse` | Focus indicator on a window that keeps the focus for 150 ms: `pulse` (brief rise and fall) or `off`. Skipped for fullscreen windows, the overview and a window focused as it opens | | |
| @@ -189,7 +194,7 @@ The focus binds (`focus-column-left/right`, `focus-window-up/down`, `cmd+arrows` | |||
| 189 | 194 | ||
| 190 | 195 | Each workspace's [stash](#stash), hidden or not, shows as a pile of cards on the left of its row, which stays centred unless it would overlap the pile: its selected window in front, up to three others behind it, dimmed. In the pile, `h` / `l` browse the stash and `l` past its last window returns to the front card. A successful up/down move through the stack leaves the stash and selects the new front card. `return` or a click on a card closes the overview with the stash shown on that window. | |
| 191 | 196 | ||
| 192 | - | A four-finger swipe up opens the overview and a swipe down closes it on the selection, whatever `touchpad.natural-scroll` says. Two-finger scrolling and the mouse wheel move the selection: left and right through the columns, up and down through stack cards before crossing workspaces, following `touchpad.natural-scroll`. A two-finger scroll sideways moves it to the next column after a short distance, then one column per longer distance, and stays on the columns until the fingers lift; up or down it moves one step per scroll, so a card in a stack is easy to pick. The wheel moves one step per notch. A three-finger swipe moves it one step when the fingers lift. | |
| 197 | + | A four-finger swipe up opens the overview and a swipe down closes it on the selection, whatever `touchpad.natural-scroll` says. Two-finger scrolling and the mouse wheel move the selection: left and right through the columns, up and down through stack cards before crossing workspaces, following `touchpad.natural-scroll` for two-finger scrolling and `mouse.natural-scroll` for the wheel. A two-finger scroll sideways moves it to the next column after a short distance, then one column per longer distance, and stays on the columns until the fingers lift; up or down it moves one step per scroll, so a card in a stack is easy to pick. The wheel moves one step per notch. A three-finger swipe moves it one step when the fingers lift. | |
| 193 | 198 | ||
| 194 | 199 | Return or a click on a front tile or peeking card commits it; card changes remain provisional until then. `close-window` targets the selected preview. Move-to-workspace binds (`move-column-to-workspace*`, `move-window-to-workspace*`) move the selected preview at once and keep the overview open; with `focus.follow-move`, the selection follows it. Escape does not undo a move, and the row that received the window keeps it focused. A selected stash card, or a row with no preview, does not move. Other window mutation binds (moving within the row, resizing, maximizing, fullscreen and stash toggles) and moving a workspace to another monitor are disabled while the overview is open. Workspace and monitor navigation, launch and quit binds remain active. | |
| 195 | 200 | ||
| @@ -37,7 +37,12 @@ NeferWL reads `$XDG_CONFIG_HOME/neferwl/config` (or `~/.config/neferwl/config`). | |||||
| 37 | | `touchpad.tap` | `on` | Tap to click: one finger left, two right, three middle | | 37 | | `touchpad.tap` | `on` | Tap to click: one finger left, two right, three middle | | ||
| 38 | | `touchpad.accel-speed` | `0` | Touchpad pointer speed, from `-1` (slowest) to `1` (fastest) | | 38 | | `touchpad.accel-speed` | `0` | Touchpad pointer speed, from `-1` (slowest) to `1` (fastest) | | ||
| 39 | | `touchpad.accel-profile` | `adaptive` | `adaptive`: faster finger moves go further; `flat`: constant speed | | 39 | | `touchpad.accel-profile` | `adaptive` | `adaptive`: faster finger moves go further; `flat`: constant speed | | ||
| 40 | + | | `touchpad.left-handed` | `off` | Swaps the left and right touchpad buttons | | |||
| 40 | | `touchpad.scroll-factor` | `1` | Multiplies two-finger scroll distance, above `0` up to `10` | | 41 | | `touchpad.scroll-factor` | `1` | Multiplies two-finger scroll distance, above `0` up to `10` | | ||
| 42 | + | | `mouse.natural-scroll` | `off` | Wheel scrolling moves the content with the wheel instead of against it. `mouse.*` applies to every pointer that is not a touchpad: mice, trackballs, trackpoints | | |||
| 43 | + | | `mouse.accel-speed` | `0` | Mouse pointer speed, from `-1` (slowest) to `1` (fastest) | | |||
| 44 | + | | `mouse.accel-profile` | `adaptive` | `adaptive`: faster moves go further; `flat`: constant speed | | |||
| 45 | + | | `mouse.left-handed` | `off` | Swaps the left and right mouse buttons | | |||
| 41 | | `cursor.hide-after` | `5s` | Hides the pointer cursor after this long without motion; the next motion shows it. 100ms to 1h, or `off` | | 46 | | `cursor.hide-after` | `5s` | Hides the pointer cursor after this long without motion; the next motion shows it. 100ms to 1h, or `off` | | ||
| 42 | | `focus.follow-move` | `off` | Follow a column moved to another workspace | | 47 | | `focus.follow-move` | `off` | Follow a column moved to another workspace | | ||
| 43 | | `focus.animation` | `pulse` | Focus indicator on a window that keeps the focus for 150 ms: `pulse` (brief rise and fall) or `off`. Skipped for fullscreen windows, the overview and a window focused as it opens | | 48 | | `focus.animation` | `pulse` | Focus indicator on a window that keeps the focus for 150 ms: `pulse` (brief rise and fall) or `off`. Skipped for fullscreen windows, the overview and a window focused as it opens | | ||
| @@ -189,7 +194,7 @@ The focus binds (`focus-column-left/right`, `focus-window-up/down`, `cmd+arrows` | |||||
| 189 | 194 | ||||
| 190 | Each workspace's [stash](#stash), hidden or not, shows as a pile of cards on the left of its row, which stays centred unless it would overlap the pile: its selected window in front, up to three others behind it, dimmed. In the pile, `h` / `l` browse the stash and `l` past its last window returns to the front card. A successful up/down move through the stack leaves the stash and selects the new front card. `return` or a click on a card closes the overview with the stash shown on that window. | 195 | Each workspace's [stash](#stash), hidden or not, shows as a pile of cards on the left of its row, which stays centred unless it would overlap the pile: its selected window in front, up to three others behind it, dimmed. In the pile, `h` / `l` browse the stash and `l` past its last window returns to the front card. A successful up/down move through the stack leaves the stash and selects the new front card. `return` or a click on a card closes the overview with the stash shown on that window. | ||
| 191 | 196 | ||||
| 192 | - | A four-finger swipe up opens the overview and a swipe down closes it on the selection, whatever `touchpad.natural-scroll` says. Two-finger scrolling and the mouse wheel move the selection: left and right through the columns, up and down through stack cards before crossing workspaces, following `touchpad.natural-scroll`. A two-finger scroll sideways moves it to the next column after a short distance, then one column per longer distance, and stays on the columns until the fingers lift; up or down it moves one step per scroll, so a card in a stack is easy to pick. The wheel moves one step per notch. A three-finger swipe moves it one step when the fingers lift. | 197 | + | A four-finger swipe up opens the overview and a swipe down closes it on the selection, whatever `touchpad.natural-scroll` says. Two-finger scrolling and the mouse wheel move the selection: left and right through the columns, up and down through stack cards before crossing workspaces, following `touchpad.natural-scroll` for two-finger scrolling and `mouse.natural-scroll` for the wheel. A two-finger scroll sideways moves it to the next column after a short distance, then one column per longer distance, and stays on the columns until the fingers lift; up or down it moves one step per scroll, so a card in a stack is easy to pick. The wheel moves one step per notch. A three-finger swipe moves it one step when the fingers lift. |
| 193 | 198 | ||||
| 194 | Return or a click on a front tile or peeking card commits it; card changes remain provisional until then. `close-window` targets the selected preview. Move-to-workspace binds (`move-column-to-workspace*`, `move-window-to-workspace*`) move the selected preview at once and keep the overview open; with `focus.follow-move`, the selection follows it. Escape does not undo a move, and the row that received the window keeps it focused. A selected stash card, or a row with no preview, does not move. Other window mutation binds (moving within the row, resizing, maximizing, fullscreen and stash toggles) and moving a workspace to another monitor are disabled while the overview is open. Workspace and monitor navigation, launch and quit binds remain active. | 199 | Return or a click on a front tile or peeking card commits it; card changes remain provisional until then. `close-window` targets the selected preview. Move-to-workspace binds (`move-column-to-workspace*`, `move-window-to-workspace*`) move the selected preview at once and keep the overview open; with `focus.follow-move`, the selection follows it. Escape does not undo a move, and the row that received the window keeps it focused. A selected stash card, or a row with no preview, does not move. Other window mutation binds (moving within the row, resizing, maximizing, fullscreen and stash toggles) and moving a workspace to another monitor are disabled while the overview is open. Workspace and monitor navigation, launch and quit binds remain active. | ||
| 195 | 200 | ||||
M examples/config +9 -0
| @@ -54,9 +54,18 @@ touchpad.tap = on | |||
| 54 | 54 | # Pointer speed from -1 to 1; adaptive speeds up quick moves, flat does not. | |
| 55 | 55 | touchpad.accel-speed = 0 | |
| 56 | 56 | touchpad.accel-profile = adaptive | |
| 57 | + | # left-handed = on swaps the left and right buttons. | |
| 58 | + | touchpad.left-handed = off | |
| 57 | 59 | # Two-finger scroll distance multiplier. | |
| 58 | 60 | touchpad.scroll-factor = 1 | |
| 59 | 61 | ||
| 62 | + | # Mouse. natural-scroll = on moves the content with the wheel; speed and | |
| 63 | + | # profile work as for the touchpad. | |
| 64 | + | mouse.natural-scroll = off | |
| 65 | + | mouse.accel-speed = 0 | |
| 66 | + | mouse.accel-profile = adaptive | |
| 67 | + | mouse.left-handed = off | |
| 68 | + | ||
| 60 | 69 | # Hide the pointer cursor after this long without motion (100ms to 1h, or | |
| 61 | 70 | # off); the next motion shows it. | |
| 62 | 71 | cursor.hide-after = 5s | |
| @@ -54,9 +54,18 @@ touchpad.tap = on | |||||
| 54 | # Pointer speed from -1 to 1; adaptive speeds up quick moves, flat does not. | 54 | # Pointer speed from -1 to 1; adaptive speeds up quick moves, flat does not. | ||
| 55 | touchpad.accel-speed = 0 | 55 | touchpad.accel-speed = 0 | ||
| 56 | touchpad.accel-profile = adaptive | 56 | touchpad.accel-profile = adaptive | ||
| 57 | + | # left-handed = on swaps the left and right buttons. | |||
| 58 | + | touchpad.left-handed = off | |||
| 57 | # Two-finger scroll distance multiplier. | 59 | # Two-finger scroll distance multiplier. | ||
| 58 | touchpad.scroll-factor = 1 | 60 | touchpad.scroll-factor = 1 | ||
| 59 | 61 | ||||
| 62 | + | # Mouse. natural-scroll = on moves the content with the wheel; speed and | |||
| 63 | + | # profile work as for the touchpad. | |||
| 64 | + | mouse.natural-scroll = off | |||
| 65 | + | mouse.accel-speed = 0 | |||
| 66 | + | mouse.accel-profile = adaptive | |||
| 67 | + | mouse.left-handed = off | |||
| 68 | + | ||||
| 60 | # Hide the pointer cursor after this long without motion (100ms to 1h, or | 69 | # Hide the pointer cursor after this long without motion (100ms to 1h, or | ||
| 61 | # off); the next motion shows it. | 70 | # off); the next motion shows it. | ||
| 62 | cursor.hide-after = 5s | 71 | cursor.hide-after = 5s | ||
M internal/adapters/config/config.go +41 -17
| @@ -125,6 +125,7 @@ func Defaults() ports.Config { | |||
| 125 | 125 | c.Touchpad.Tap = true | |
| 126 | 126 | c.Touchpad.AccelProfile = ports.AccelAdaptive | |
| 127 | 127 | c.Touchpad.ScrollFactor = 1 | |
| 128 | + | c.Mouse.AccelProfile = ports.AccelAdaptive | |
| 128 | 129 | c.Cursor.HideAfter = 5 * time.Second | |
| 129 | 130 | c.Focus.Animation = ports.FocusAnimationPulse | |
| 130 | 131 | c.Focus.Effect = ports.FocusEffectScreen | |
| @@ -557,29 +558,12 @@ func set(c *ports.Config, key, v string) error { | |||
| 557 | 558 | } | |
| 558 | 559 | } | |
| 559 | 560 | c.Layout.Presets = list | |
| 560 | - | case "touchpad.natural-scroll": | |
| 561 | - | b, err := onOff(v) | |
| 562 | - | if err != nil { | |
| 563 | - | return err | |
| 564 | - | } | |
| 565 | - | c.Touchpad.NaturalScroll = b | |
| 566 | 561 | case "touchpad.tap": | |
| 567 | 562 | b, err := onOff(v) | |
| 568 | 563 | if err != nil { | |
| 569 | 564 | return err | |
| 570 | 565 | } | |
| 571 | 566 | c.Touchpad.Tap = b | |
| 572 | - | case "touchpad.accel-speed": | |
| 573 | - | s, err := strconv.ParseFloat(v, 64) | |
| 574 | - | if err != nil || !(s >= -1 && s <= 1) { | |
| 575 | - | return fmt.Errorf("must be between -1 and 1") | |
| 576 | - | } | |
| 577 | - | c.Touchpad.AccelSpeed = s | |
| 578 | - | case "touchpad.accel-profile": | |
| 579 | - | if v != ports.AccelAdaptive && v != ports.AccelFlat { | |
| 580 | - | return fmt.Errorf("must be adaptive or flat") | |
| 581 | - | } | |
| 582 | - | c.Touchpad.AccelProfile = v | |
| 583 | 567 | case "touchpad.scroll-factor": | |
| 584 | 568 | f, err := strconv.ParseFloat(v, 64) | |
| 585 | 569 | if err != nil || !(f > 0 && f <= 10) { | |
| @@ -666,6 +650,12 @@ func set(c *ports.Config, key, v string) error { | |||
| 666 | 650 | } | |
| 667 | 651 | c.Log.Debug = list | |
| 668 | 652 | default: | |
| 653 | + | if rest, ok := strings.CutPrefix(key, "touchpad."); ok { | |
| 654 | + | return parsePointerKey(rest, v, &c.Touchpad.PointerConfig) | |
| 655 | + | } | |
| 656 | + | if rest, ok := strings.CutPrefix(key, "mouse."); ok { | |
| 657 | + | return parsePointerKey(rest, v, &c.Mouse) | |
| 658 | + | } | |
| 669 | 659 | if rest, ok := strings.CutPrefix(key, "layout."); ok { | |
| 670 | 660 | if i := strings.LastIndex(rest, "."); i > 0 { | |
| 671 | 661 | return setOutputLayout(c, rest[:i], rest[i+1:], v) | |
| @@ -676,6 +666,40 @@ func set(c *ports.Config, key, v string) error { | |||
| 676 | 666 | return nil | |
| 677 | 667 | } | |
| 678 | 668 | ||
| 669 | + | // parsePointerKey applies one pointer key shared by touchpads and mice, with | |
| 670 | + | // the touchpad. or mouse. prefix already removed. Touchpad-only keys are | |
| 671 | + | // handled before; anything else is unknown. On an error p is left unchanged. | |
| 672 | + | func parsePointerKey(key, v string, p *ports.PointerConfig) error { | |
| 673 | + | switch key { | |
| 674 | + | case "natural-scroll": | |
| 675 | + | b, err := onOff(v) | |
| 676 | + | if err != nil { | |
| 677 | + | return err | |
| 678 | + | } | |
| 679 | + | p.NaturalScroll = b | |
| 680 | + | case "left-handed": | |
| 681 | + | b, err := onOff(v) | |
| 682 | + | if err != nil { | |
| 683 | + | return err | |
| 684 | + | } | |
| 685 | + | p.LeftHanded = b | |
| 686 | + | case "accel-speed": | |
| 687 | + | s, err := strconv.ParseFloat(v, 64) | |
| 688 | + | if err != nil || !(s >= -1 && s <= 1) { | |
| 689 | + | return fmt.Errorf("must be between -1 and 1") | |
| 690 | + | } | |
| 691 | + | p.AccelSpeed = s | |
| 692 | + | case "accel-profile": | |
| 693 | + | if v != ports.AccelAdaptive && v != ports.AccelFlat { | |
| 694 | + | return fmt.Errorf("must be adaptive or flat") | |
| 695 | + | } | |
| 696 | + | p.AccelProfile = v | |
| 697 | + | default: | |
| 698 | + | return fmt.Errorf("unknown key") | |
| 699 | + | } | |
| 700 | + | return nil | |
| 701 | + | } | |
| 702 | + | ||
| 679 | 703 | // setOutputLayout applies one layout.<output>.<field> key. | |
| 680 | 704 | func setOutputLayout(c *ports.Config, output, field, v string) error { | |
| 681 | 705 | i := slices.IndexFunc(c.Layout.Outputs, func(o ports.OutputLayout) bool { return o.Output == output }) | |
| @@ -125,6 +125,7 @@ func Defaults() ports.Config { | |||||
| 125 | c.Touchpad.Tap = true | 125 | c.Touchpad.Tap = true | ||
| 126 | c.Touchpad.AccelProfile = ports.AccelAdaptive | 126 | c.Touchpad.AccelProfile = ports.AccelAdaptive | ||
| 127 | c.Touchpad.ScrollFactor = 1 | 127 | c.Touchpad.ScrollFactor = 1 | ||
| 128 | + | c.Mouse.AccelProfile = ports.AccelAdaptive | |||
| 128 | c.Cursor.HideAfter = 5 * time.Second | 129 | c.Cursor.HideAfter = 5 * time.Second | ||
| 129 | c.Focus.Animation = ports.FocusAnimationPulse | 130 | c.Focus.Animation = ports.FocusAnimationPulse | ||
| 130 | c.Focus.Effect = ports.FocusEffectScreen | 131 | c.Focus.Effect = ports.FocusEffectScreen | ||
| @@ -557,29 +558,12 @@ func set(c *ports.Config, key, v string) error { | |||||
| 557 | } | 558 | } | ||
| 558 | } | 559 | } | ||
| 559 | c.Layout.Presets = list | 560 | c.Layout.Presets = list | ||
| 560 | - | case "touchpad.natural-scroll": | |||
| 561 | - | b, err := onOff(v) | |||
| 562 | - | if err != nil { | |||
| 563 | - | return err | |||
| 564 | - | } | |||
| 565 | - | c.Touchpad.NaturalScroll = b | |||
| 566 | case "touchpad.tap": | 561 | case "touchpad.tap": | ||
| 567 | b, err := onOff(v) | 562 | b, err := onOff(v) | ||
| 568 | if err != nil { | 563 | if err != nil { | ||
| 569 | return err | 564 | return err | ||
| 570 | } | 565 | } | ||
| 571 | c.Touchpad.Tap = b | 566 | c.Touchpad.Tap = b | ||
| 572 | - | case "touchpad.accel-speed": | |||
| 573 | - | s, err := strconv.ParseFloat(v, 64) | |||
| 574 | - | if err != nil || !(s >= -1 && s <= 1) { | |||
| 575 | - | return fmt.Errorf("must be between -1 and 1") | |||
| 576 | - | } | |||
| 577 | - | c.Touchpad.AccelSpeed = s | |||
| 578 | - | case "touchpad.accel-profile": | |||
| 579 | - | if v != ports.AccelAdaptive && v != ports.AccelFlat { | |||
| 580 | - | return fmt.Errorf("must be adaptive or flat") | |||
| 581 | - | } | |||
| 582 | - | c.Touchpad.AccelProfile = v | |||
| 583 | case "touchpad.scroll-factor": | 567 | case "touchpad.scroll-factor": | ||
| 584 | f, err := strconv.ParseFloat(v, 64) | 568 | f, err := strconv.ParseFloat(v, 64) | ||
| 585 | if err != nil || !(f > 0 && f <= 10) { | 569 | if err != nil || !(f > 0 && f <= 10) { | ||
| @@ -666,6 +650,12 @@ func set(c *ports.Config, key, v string) error { | |||||
| 666 | } | 650 | } | ||
| 667 | c.Log.Debug = list | 651 | c.Log.Debug = list | ||
| 668 | default: | 652 | default: | ||
| 653 | + | if rest, ok := strings.CutPrefix(key, "touchpad."); ok { | |||
| 654 | + | return parsePointerKey(rest, v, &c.Touchpad.PointerConfig) | |||
| 655 | + | } | |||
| 656 | + | if rest, ok := strings.CutPrefix(key, "mouse."); ok { | |||
| 657 | + | return parsePointerKey(rest, v, &c.Mouse) | |||
| 658 | + | } | |||
| 669 | if rest, ok := strings.CutPrefix(key, "layout."); ok { | 659 | if rest, ok := strings.CutPrefix(key, "layout."); ok { | ||
| 670 | if i := strings.LastIndex(rest, "."); i > 0 { | 660 | if i := strings.LastIndex(rest, "."); i > 0 { | ||
| 671 | return setOutputLayout(c, rest[:i], rest[i+1:], v) | 661 | return setOutputLayout(c, rest[:i], rest[i+1:], v) | ||
| @@ -676,6 +666,40 @@ func set(c *ports.Config, key, v string) error { | |||||
| 676 | return nil | 666 | return nil | ||
| 677 | } | 667 | } | ||
| 678 | 668 | ||||
| 669 | + | // parsePointerKey applies one pointer key shared by touchpads and mice, with | |||
| 670 | + | // the touchpad. or mouse. prefix already removed. Touchpad-only keys are | |||
| 671 | + | // handled before; anything else is unknown. On an error p is left unchanged. | |||
| 672 | + | func parsePointerKey(key, v string, p *ports.PointerConfig) error { | |||
| 673 | + | switch key { | |||
| 674 | + | case "natural-scroll": | |||
| 675 | + | b, err := onOff(v) | |||
| 676 | + | if err != nil { | |||
| 677 | + | return err | |||
| 678 | + | } | |||
| 679 | + | p.NaturalScroll = b | |||
| 680 | + | case "left-handed": | |||
| 681 | + | b, err := onOff(v) | |||
| 682 | + | if err != nil { | |||
| 683 | + | return err | |||
| 684 | + | } | |||
| 685 | + | p.LeftHanded = b | |||
| 686 | + | case "accel-speed": | |||
| 687 | + | s, err := strconv.ParseFloat(v, 64) | |||
| 688 | + | if err != nil || !(s >= -1 && s <= 1) { | |||
| 689 | + | return fmt.Errorf("must be between -1 and 1") | |||
| 690 | + | } | |||
| 691 | + | p.AccelSpeed = s | |||
| 692 | + | case "accel-profile": | |||
| 693 | + | if v != ports.AccelAdaptive && v != ports.AccelFlat { | |||
| 694 | + | return fmt.Errorf("must be adaptive or flat") | |||
| 695 | + | } | |||
| 696 | + | p.AccelProfile = v | |||
| 697 | + | default: | |||
| 698 | + | return fmt.Errorf("unknown key") | |||
| 699 | + | } | |||
| 700 | + | return nil | |||
| 701 | + | } | |||
| 702 | + | ||||
| 679 | // setOutputLayout applies one layout.<output>.<field> key. | 703 | // setOutputLayout applies one layout.<output>.<field> key. | ||
| 680 | func setOutputLayout(c *ports.Config, output, field, v string) error { | 704 | func setOutputLayout(c *ports.Config, output, field, v string) error { | ||
| 681 | i := slices.IndexFunc(c.Layout.Outputs, func(o ports.OutputLayout) bool { return o.Output == output }) | 705 | i := slices.IndexFunc(c.Layout.Outputs, func(o ports.OutputLayout) bool { return o.Output == output }) | ||
M internal/adapters/config/config_test.go +56 -19
| @@ -95,20 +95,6 @@ func TestFocusIndicator(t *testing.T) { | |||
| 95 | 95 | } | |
| 96 | 96 | } | |
| 97 | 97 | ||
| 98 | - | func TestTouchpadNaturalScroll(t *testing.T) { | |
| 99 | - | if Defaults().Touchpad.NaturalScroll { | |
| 100 | - | t.Fatal("natural scroll on by default") | |
| 101 | - | } | |
| 102 | - | c, w := parseString(t, "touchpad.natural-scroll = on\n") | |
| 103 | - | if !c.Touchpad.NaturalScroll || len(w) != 0 { | |
| 104 | - | t.Fatal(c.Touchpad, w) | |
| 105 | - | } | |
| 106 | - | c, w = parseString(t, "touchpad.natural-scroll = maybe\n") | |
| 107 | - | if c.Touchpad.NaturalScroll || len(w) != 1 { | |
| 108 | - | t.Fatal(c.Touchpad, w) | |
| 109 | - | } | |
| 110 | - | } | |
| 111 | - | ||
| 112 | 98 | func TestTouchpadTap(t *testing.T) { | |
| 113 | 99 | if !Defaults().Touchpad.Tap { | |
| 114 | 100 | t.Fatal("tap to click off by default") | |
| @@ -128,14 +114,11 @@ func TestTouchpadSpeed(t *testing.T) { | |||
| 128 | 114 | if d.AccelSpeed != 0 || d.AccelProfile != ports.AccelAdaptive || d.ScrollFactor != 1 { | |
| 129 | 115 | t.Fatal(d) | |
| 130 | 116 | } | |
| 131 | - | c, w := parseString(t, "touchpad.accel-speed = -0.5\ntouchpad.accel-profile = flat\ntouchpad.scroll-factor = 0.5\n") | |
| 132 | - | if c.Touchpad.AccelSpeed != -0.5 || c.Touchpad.AccelProfile != ports.AccelFlat || c.Touchpad.ScrollFactor != 0.5 || len(w) != 0 { | |
| 117 | + | c, w := parseString(t, "touchpad.scroll-factor = 0.5\n") | |
| 118 | + | if c.Touchpad.ScrollFactor != 0.5 || len(w) != 0 { | |
| 133 | 119 | t.Fatal(c.Touchpad, w) | |
| 134 | 120 | } | |
| 135 | 121 | for _, bad := range []string{ | |
| 136 | - | "touchpad.accel-speed = 2", | |
| 137 | - | "touchpad.accel-speed = NaN", | |
| 138 | - | "touchpad.accel-profile = fast", | |
| 139 | 122 | "touchpad.scroll-factor = 0", | |
| 140 | 123 | "touchpad.scroll-factor = 11", | |
| 141 | 124 | } { | |
| @@ -146,6 +129,60 @@ func TestTouchpadSpeed(t *testing.T) { | |||
| 146 | 129 | } | |
| 147 | 130 | } | |
| 148 | 131 | ||
| 132 | + | func TestPointerKeys(t *testing.T) { | |
| 133 | + | d := Defaults() | |
| 134 | + | if d.Mouse != (ports.PointerConfig{AccelProfile: ports.AccelAdaptive}) { | |
| 135 | + | t.Fatal(d.Mouse) | |
| 136 | + | } | |
| 137 | + | if d.Touchpad.LeftHanded || d.Touchpad.NaturalScroll { | |
| 138 | + | t.Fatal(d.Touchpad) | |
| 139 | + | } | |
| 140 | + | touchpad := func(c ports.Config) ports.PointerConfig { return c.Touchpad.PointerConfig } | |
| 141 | + | mouse := func(c ports.Config) ports.PointerConfig { return c.Mouse } | |
| 142 | + | for _, tc := range []struct { | |
| 143 | + | prefix string | |
| 144 | + | pointer, other func(ports.Config) ports.PointerConfig | |
| 145 | + | }{ | |
| 146 | + | {"touchpad", touchpad, mouse}, | |
| 147 | + | {"mouse", mouse, touchpad}, | |
| 148 | + | } { | |
| 149 | + | t.Run(tc.prefix, func(t *testing.T) { | |
| 150 | + | prefix := tc.prefix | |
| 151 | + | c, w := parseString(t, prefix+".natural-scroll = on\n"+prefix+".left-handed = on\n"+prefix+".accel-speed = -0.5\n"+prefix+".accel-profile = flat\n") | |
| 152 | + | want := ports.PointerConfig{NaturalScroll: true, LeftHanded: true, AccelSpeed: -0.5, AccelProfile: ports.AccelFlat} | |
| 153 | + | if tc.pointer(c) != want || len(w) != 0 { | |
| 154 | + | t.Errorf("%+v %v", tc.pointer(c), w) | |
| 155 | + | } | |
| 156 | + | // The other device keeps its defaults. | |
| 157 | + | if other := tc.other(c); other != (ports.PointerConfig{AccelProfile: ports.AccelAdaptive}) { | |
| 158 | + | t.Errorf("leaked into the other device: %+v", other) | |
| 159 | + | } | |
| 160 | + | for _, bad := range []string{ | |
| 161 | + | ".natural-scroll = maybe", | |
| 162 | + | ".left-handed = 1", | |
| 163 | + | ".accel-speed = 2", | |
| 164 | + | ".accel-speed = -1.5", | |
| 165 | + | ".accel-speed = NaN", | |
| 166 | + | ".accel-speed = fast", | |
| 167 | + | ".accel-profile = fast", | |
| 168 | + | ".accel-profile = ", | |
| 169 | + | ".unknown = on", | |
| 170 | + | } { | |
| 171 | + | c, w := parseString(t, prefix+bad+"\n") | |
| 172 | + | if c.Mouse != d.Mouse || c.Touchpad != d.Touchpad || len(w) != 1 { | |
| 173 | + | t.Errorf("%s%s: %+v %v", prefix, bad, tc.pointer(c), w) | |
| 174 | + | } | |
| 175 | + | } | |
| 176 | + | }) | |
| 177 | + | } | |
| 178 | + | // Touchpad-only keys do not exist for mice. | |
| 179 | + | for _, bad := range []string{"mouse.tap = on", "mouse.scroll-factor = 2"} { | |
| 180 | + | if c, w := parseString(t, bad+"\n"); c.Mouse != d.Mouse || c.Touchpad != d.Touchpad || len(w) != 1 { | |
| 181 | + | t.Errorf("%s: %+v %v", bad, c.Mouse, w) | |
| 182 | + | } | |
| 183 | + | } | |
| 184 | + | } | |
| 185 | + | ||
| 149 | 186 | func TestParse(t *testing.T) { | |
| 150 | 187 | c, w := parseString(t, ` | |
| 151 | 188 | # comment | |
| @@ -95,20 +95,6 @@ func TestFocusIndicator(t *testing.T) { | |||||
| 95 | } | 95 | } | ||
| 96 | } | 96 | } | ||
| 97 | 97 | ||||
| 98 | - | func TestTouchpadNaturalScroll(t *testing.T) { | |||
| 99 | - | if Defaults().Touchpad.NaturalScroll { | |||
| 100 | - | t.Fatal("natural scroll on by default") | |||
| 101 | - | } | |||
| 102 | - | c, w := parseString(t, "touchpad.natural-scroll = on\n") | |||
| 103 | - | if !c.Touchpad.NaturalScroll || len(w) != 0 { | |||
| 104 | - | t.Fatal(c.Touchpad, w) | |||
| 105 | - | } | |||
| 106 | - | c, w = parseString(t, "touchpad.natural-scroll = maybe\n") | |||
| 107 | - | if c.Touchpad.NaturalScroll || len(w) != 1 { | |||
| 108 | - | t.Fatal(c.Touchpad, w) | |||
| 109 | - | } | |||
| 110 | - | } | |||
| 111 | - | ||||
| 112 | func TestTouchpadTap(t *testing.T) { | 98 | func TestTouchpadTap(t *testing.T) { | ||
| 113 | if !Defaults().Touchpad.Tap { | 99 | if !Defaults().Touchpad.Tap { | ||
| 114 | t.Fatal("tap to click off by default") | 100 | t.Fatal("tap to click off by default") | ||
| @@ -128,14 +114,11 @@ func TestTouchpadSpeed(t *testing.T) { | |||||
| 128 | if d.AccelSpeed != 0 || d.AccelProfile != ports.AccelAdaptive || d.ScrollFactor != 1 { | 114 | if d.AccelSpeed != 0 || d.AccelProfile != ports.AccelAdaptive || d.ScrollFactor != 1 { | ||
| 129 | t.Fatal(d) | 115 | t.Fatal(d) | ||
| 130 | } | 116 | } | ||
| 131 | - | c, w := parseString(t, "touchpad.accel-speed = -0.5\ntouchpad.accel-profile = flat\ntouchpad.scroll-factor = 0.5\n") | 117 | + | c, w := parseString(t, "touchpad.scroll-factor = 0.5\n") |
| 132 | - | if c.Touchpad.AccelSpeed != -0.5 || c.Touchpad.AccelProfile != ports.AccelFlat || c.Touchpad.ScrollFactor != 0.5 || len(w) != 0 { | 118 | + | if c.Touchpad.ScrollFactor != 0.5 || len(w) != 0 { |
| 133 | t.Fatal(c.Touchpad, w) | 119 | t.Fatal(c.Touchpad, w) | ||
| 134 | } | 120 | } | ||
| 135 | for _, bad := range []string{ | 121 | for _, bad := range []string{ | ||
| 136 | - | "touchpad.accel-speed = 2", | |||
| 137 | - | "touchpad.accel-speed = NaN", | |||
| 138 | - | "touchpad.accel-profile = fast", | |||
| 139 | "touchpad.scroll-factor = 0", | 122 | "touchpad.scroll-factor = 0", | ||
| 140 | "touchpad.scroll-factor = 11", | 123 | "touchpad.scroll-factor = 11", | ||
| 141 | } { | 124 | } { | ||
| @@ -146,6 +129,60 @@ func TestTouchpadSpeed(t *testing.T) { | |||||
| 146 | } | 129 | } | ||
| 147 | } | 130 | } | ||
| 148 | 131 | ||||
| 132 | + | func TestPointerKeys(t *testing.T) { | |||
| 133 | + | d := Defaults() | |||
| 134 | + | if d.Mouse != (ports.PointerConfig{AccelProfile: ports.AccelAdaptive}) { | |||
| 135 | + | t.Fatal(d.Mouse) | |||
| 136 | + | } | |||
| 137 | + | if d.Touchpad.LeftHanded || d.Touchpad.NaturalScroll { | |||
| 138 | + | t.Fatal(d.Touchpad) | |||
| 139 | + | } | |||
| 140 | + | touchpad := func(c ports.Config) ports.PointerConfig { return c.Touchpad.PointerConfig } | |||
| 141 | + | mouse := func(c ports.Config) ports.PointerConfig { return c.Mouse } | |||
| 142 | + | for _, tc := range []struct { | |||
| 143 | + | prefix string | |||
| 144 | + | pointer, other func(ports.Config) ports.PointerConfig | |||
| 145 | + | }{ | |||
| 146 | + | {"touchpad", touchpad, mouse}, | |||
| 147 | + | {"mouse", mouse, touchpad}, | |||
| 148 | + | } { | |||
| 149 | + | t.Run(tc.prefix, func(t *testing.T) { | |||
| 150 | + | prefix := tc.prefix | |||
| 151 | + | c, w := parseString(t, prefix+".natural-scroll = on\n"+prefix+".left-handed = on\n"+prefix+".accel-speed = -0.5\n"+prefix+".accel-profile = flat\n") | |||
| 152 | + | want := ports.PointerConfig{NaturalScroll: true, LeftHanded: true, AccelSpeed: -0.5, AccelProfile: ports.AccelFlat} | |||
| 153 | + | if tc.pointer(c) != want || len(w) != 0 { | |||
| 154 | + | t.Errorf("%+v %v", tc.pointer(c), w) | |||
| 155 | + | } | |||
| 156 | + | // The other device keeps its defaults. | |||
| 157 | + | if other := tc.other(c); other != (ports.PointerConfig{AccelProfile: ports.AccelAdaptive}) { | |||
| 158 | + | t.Errorf("leaked into the other device: %+v", other) | |||
| 159 | + | } | |||
| 160 | + | for _, bad := range []string{ | |||
| 161 | + | ".natural-scroll = maybe", | |||
| 162 | + | ".left-handed = 1", | |||
| 163 | + | ".accel-speed = 2", | |||
| 164 | + | ".accel-speed = -1.5", | |||
| 165 | + | ".accel-speed = NaN", | |||
| 166 | + | ".accel-speed = fast", | |||
| 167 | + | ".accel-profile = fast", | |||
| 168 | + | ".accel-profile = ", | |||
| 169 | + | ".unknown = on", | |||
| 170 | + | } { | |||
| 171 | + | c, w := parseString(t, prefix+bad+"\n") | |||
| 172 | + | if c.Mouse != d.Mouse || c.Touchpad != d.Touchpad || len(w) != 1 { | |||
| 173 | + | t.Errorf("%s%s: %+v %v", prefix, bad, tc.pointer(c), w) | |||
| 174 | + | } | |||
| 175 | + | } | |||
| 176 | + | }) | |||
| 177 | + | } | |||
| 178 | + | // Touchpad-only keys do not exist for mice. | |||
| 179 | + | for _, bad := range []string{"mouse.tap = on", "mouse.scroll-factor = 2"} { | |||
| 180 | + | if c, w := parseString(t, bad+"\n"); c.Mouse != d.Mouse || c.Touchpad != d.Touchpad || len(w) != 1 { | |||
| 181 | + | t.Errorf("%s: %+v %v", bad, c.Mouse, w) | |||
| 182 | + | } | |||
| 183 | + | } | |||
| 184 | + | } | |||
| 185 | + | ||||
| 149 | func TestParse(t *testing.T) { | 186 | func TestParse(t *testing.T) { | ||
| 150 | c, w := parseString(t, ` | 187 | c, w := parseString(t, ` | ||
| 151 | # comment | 188 | # comment | ||
A internal/adapters/libinput/configure_test.go +113 -0
| @@ -0,0 +1,113 @@ | |||
| 1 | + | package libinput | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "bytes" | |
| 5 | + | "strings" | |
| 6 | + | "testing" | |
| 7 | + | ||
| 8 | + | "github.com/bnema/neferwl/internal/ports" | |
| 9 | + | "github.com/bnema/zerowrap" | |
| 10 | + | ) | |
| 11 | + | ||
| 12 | + | const ( | |
| 13 | + | testPad uintptr = 1 | |
| 14 | + | testMouse uintptr = 2 | |
| 15 | + | testKeyboard uintptr = 3 | |
| 16 | + | ) | |
| 17 | + | ||
| 18 | + | // Every shared field differs between the two, so a mixed-up route fails. | |
| 19 | + | var ( | |
| 20 | + | padCfg = ports.TouchpadConfig{PointerConfig: ports.PointerConfig{NaturalScroll: true, AccelSpeed: 0.5, AccelProfile: ports.AccelFlat}, Tap: true} | |
| 21 | + | mouseCfg = ports.PointerConfig{AccelSpeed: -0.25, AccelProfile: ports.AccelAdaptive, LeftHanded: true} | |
| 22 | + | ) | |
| 23 | + | ||
| 24 | + | // newState returns an input state over a device config mock, and a logger | |
| 25 | + | // writing to the returned buffer. | |
| 26 | + | func newState(t *testing.T) (*inputState, *mockdeviceConfig, zerowrap.Logger, *bytes.Buffer) { | |
| 27 | + | t.Helper() | |
| 28 | + | dev := newMockdeviceConfig(t) | |
| 29 | + | buf := &bytes.Buffer{} | |
| 30 | + | log := zerowrap.New(zerowrap.Config{Level: "debug", Format: "json", Output: buf}).WithField("component", "input") | |
| 31 | + | return &inputState{cfg: ports.InputDevicesConfig{Touchpad: padCfg, Mouse: mouseCfg}, dev: dev}, dev, log, buf | |
| 32 | + | } | |
| 33 | + | ||
| 34 | + | func TestConfigure(t *testing.T) { | |
| 35 | + | for _, tc := range []struct { | |
| 36 | + | name string | |
| 37 | + | dev uintptr | |
| 38 | + | expect func(m *mockdeviceConfig) | |
| 39 | + | }{ | |
| 40 | + | {"touchpad", testPad, func(m *mockdeviceConfig) { | |
| 41 | + | m.EXPECT().IsTouchpad(testPad).Return(true) | |
| 42 | + | m.EXPECT().SetNaturalScroll(testPad, true).Return(nil).Once() | |
| 43 | + | m.EXPECT().SetAccel(testPad, 0.5, ports.AccelFlat).Return(nil).Once() | |
| 44 | + | m.EXPECT().SetLeftHanded(testPad, false).Return(nil).Once() | |
| 45 | + | m.EXPECT().SetTap(testPad, true).Return(nil).Once() | |
| 46 | + | }}, | |
| 47 | + | // The touchpad settings must not reach the mouse, and a mouse is | |
| 48 | + | // never given tapping: SetTap has no expectation. | |
| 49 | + | {"mouse", testMouse, func(m *mockdeviceConfig) { | |
| 50 | + | m.EXPECT().IsTouchpad(testMouse).Return(false) | |
| 51 | + | m.EXPECT().IsPointer(testMouse).Return(true) | |
| 52 | + | m.EXPECT().SetNaturalScroll(testMouse, false).Return(nil).Once() | |
| 53 | + | m.EXPECT().SetAccel(testMouse, -0.25, ports.AccelAdaptive).Return(nil).Once() | |
| 54 | + | m.EXPECT().SetLeftHanded(testMouse, true).Return(nil).Once() | |
| 55 | + | }}, | |
| 56 | + | {"keyboard", testKeyboard, func(m *mockdeviceConfig) { | |
| 57 | + | m.EXPECT().IsTouchpad(testKeyboard).Return(false) | |
| 58 | + | m.EXPECT().IsPointer(testKeyboard).Return(false) | |
| 59 | + | }}, | |
| 60 | + | } { | |
| 61 | + | t.Run(tc.name, func(t *testing.T) { | |
| 62 | + | in, dev, log, buf := newState(t) | |
| 63 | + | tc.expect(dev) | |
| 64 | + | in.configure(tc.dev, log) | |
| 65 | + | if buf.Len() != 0 { | |
| 66 | + | t.Fatalf("logged: %s", buf) | |
| 67 | + | } | |
| 68 | + | }) | |
| 69 | + | } | |
| 70 | + | } | |
| 71 | + | ||
| 72 | + | func TestConfigurable(t *testing.T) { | |
| 73 | + | for _, tc := range []struct { | |
| 74 | + | name string | |
| 75 | + | touchpad, ptr bool | |
| 76 | + | want, askedPtr bool | |
| 77 | + | }{ | |
| 78 | + | {"touchpad", true, false, true, false}, | |
| 79 | + | {"mouse", false, true, true, true}, | |
| 80 | + | {"keyboard", false, false, false, true}, | |
| 81 | + | } { | |
| 82 | + | t.Run(tc.name, func(t *testing.T) { | |
| 83 | + | in, dev, _, _ := newState(t) | |
| 84 | + | dev.EXPECT().IsTouchpad(testPad).Return(tc.touchpad) | |
| 85 | + | if tc.askedPtr { | |
| 86 | + | dev.EXPECT().IsPointer(testPad).Return(tc.ptr) | |
| 87 | + | } | |
| 88 | + | if got := in.configurable(testPad); got != tc.want { | |
| 89 | + | t.Fatalf("configurable = %v, want %v", got, tc.want) | |
| 90 | + | } | |
| 91 | + | }) | |
| 92 | + | } | |
| 93 | + | } | |
| 94 | + | ||
| 95 | + | func TestConfigureRejectedSettingLogsAndContinues(t *testing.T) { | |
| 96 | + | in, dev, log, buf := newState(t) | |
| 97 | + | dev.EXPECT().IsTouchpad(testPad).Return(true) | |
| 98 | + | dev.EXPECT().SetNaturalScroll(testPad, true).Return(errRejected).Once() | |
| 99 | + | dev.EXPECT().Name(testPad).Return("Synaptics").Once() | |
| 100 | + | dev.EXPECT().SetAccel(testPad, 0.5, ports.AccelFlat).Return(nil).Once() | |
| 101 | + | dev.EXPECT().SetLeftHanded(testPad, false).Return(nil).Once() | |
| 102 | + | dev.EXPECT().SetTap(testPad, true).Return(nil).Once() | |
| 103 | + | in.configure(testPad, log) | |
| 104 | + | out := buf.String() | |
| 105 | + | for _, want := range []string{`"device":"Synaptics"`, `"setting":`, `"component":"input"`, `"level":"warn"`} { | |
| 106 | + | if !strings.Contains(out, want) { | |
| 107 | + | t.Errorf("log %s lacks %s", out, want) | |
| 108 | + | } | |
| 109 | + | } | |
| 110 | + | if n := strings.Count(out, "\n"); n != 1 { | |
| 111 | + | t.Errorf("%d log lines, want 1: %s", n, out) | |
| 112 | + | } | |
| 113 | + | } | |
| @@ -0,0 +1,113 @@ | |||||
| 1 | + | package libinput | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "bytes" | |||
| 5 | + | "strings" | |||
| 6 | + | "testing" | |||
| 7 | + | ||||
| 8 | + | "github.com/bnema/neferwl/internal/ports" | |||
| 9 | + | "github.com/bnema/zerowrap" | |||
| 10 | + | ) | |||
| 11 | + | ||||
| 12 | + | const ( | |||
| 13 | + | testPad uintptr = 1 | |||
| 14 | + | testMouse uintptr = 2 | |||
| 15 | + | testKeyboard uintptr = 3 | |||
| 16 | + | ) | |||
| 17 | + | ||||
| 18 | + | // Every shared field differs between the two, so a mixed-up route fails. | |||
| 19 | + | var ( | |||
| 20 | + | padCfg = ports.TouchpadConfig{PointerConfig: ports.PointerConfig{NaturalScroll: true, AccelSpeed: 0.5, AccelProfile: ports.AccelFlat}, Tap: true} | |||
| 21 | + | mouseCfg = ports.PointerConfig{AccelSpeed: -0.25, AccelProfile: ports.AccelAdaptive, LeftHanded: true} | |||
| 22 | + | ) | |||
| 23 | + | ||||
| 24 | + | // newState returns an input state over a device config mock, and a logger | |||
| 25 | + | // writing to the returned buffer. | |||
| 26 | + | func newState(t *testing.T) (*inputState, *mockdeviceConfig, zerowrap.Logger, *bytes.Buffer) { | |||
| 27 | + | t.Helper() | |||
| 28 | + | dev := newMockdeviceConfig(t) | |||
| 29 | + | buf := &bytes.Buffer{} | |||
| 30 | + | log := zerowrap.New(zerowrap.Config{Level: "debug", Format: "json", Output: buf}).WithField("component", "input") | |||
| 31 | + | return &inputState{cfg: ports.InputDevicesConfig{Touchpad: padCfg, Mouse: mouseCfg}, dev: dev}, dev, log, buf | |||
| 32 | + | } | |||
| 33 | + | ||||
| 34 | + | func TestConfigure(t *testing.T) { | |||
| 35 | + | for _, tc := range []struct { | |||
| 36 | + | name string | |||
| 37 | + | dev uintptr | |||
| 38 | + | expect func(m *mockdeviceConfig) | |||
| 39 | + | }{ | |||
| 40 | + | {"touchpad", testPad, func(m *mockdeviceConfig) { | |||
| 41 | + | m.EXPECT().IsTouchpad(testPad).Return(true) | |||
| 42 | + | m.EXPECT().SetNaturalScroll(testPad, true).Return(nil).Once() | |||
| 43 | + | m.EXPECT().SetAccel(testPad, 0.5, ports.AccelFlat).Return(nil).Once() | |||
| 44 | + | m.EXPECT().SetLeftHanded(testPad, false).Return(nil).Once() | |||
| 45 | + | m.EXPECT().SetTap(testPad, true).Return(nil).Once() | |||
| 46 | + | }}, | |||
| 47 | + | // The touchpad settings must not reach the mouse, and a mouse is | |||
| 48 | + | // never given tapping: SetTap has no expectation. | |||
| 49 | + | {"mouse", testMouse, func(m *mockdeviceConfig) { | |||
| 50 | + | m.EXPECT().IsTouchpad(testMouse).Return(false) | |||
| 51 | + | m.EXPECT().IsPointer(testMouse).Return(true) | |||
| 52 | + | m.EXPECT().SetNaturalScroll(testMouse, false).Return(nil).Once() | |||
| 53 | + | m.EXPECT().SetAccel(testMouse, -0.25, ports.AccelAdaptive).Return(nil).Once() | |||
| 54 | + | m.EXPECT().SetLeftHanded(testMouse, true).Return(nil).Once() | |||
| 55 | + | }}, | |||
| 56 | + | {"keyboard", testKeyboard, func(m *mockdeviceConfig) { | |||
| 57 | + | m.EXPECT().IsTouchpad(testKeyboard).Return(false) | |||
| 58 | + | m.EXPECT().IsPointer(testKeyboard).Return(false) | |||
| 59 | + | }}, | |||
| 60 | + | } { | |||
| 61 | + | t.Run(tc.name, func(t *testing.T) { | |||
| 62 | + | in, dev, log, buf := newState(t) | |||
| 63 | + | tc.expect(dev) | |||
| 64 | + | in.configure(tc.dev, log) | |||
| 65 | + | if buf.Len() != 0 { | |||
| 66 | + | t.Fatalf("logged: %s", buf) | |||
| 67 | + | } | |||
| 68 | + | }) | |||
| 69 | + | } | |||
| 70 | + | } | |||
| 71 | + | ||||
| 72 | + | func TestConfigurable(t *testing.T) { | |||
| 73 | + | for _, tc := range []struct { | |||
| 74 | + | name string | |||
| 75 | + | touchpad, ptr bool | |||
| 76 | + | want, askedPtr bool | |||
| 77 | + | }{ | |||
| 78 | + | {"touchpad", true, false, true, false}, | |||
| 79 | + | {"mouse", false, true, true, true}, | |||
| 80 | + | {"keyboard", false, false, false, true}, | |||
| 81 | + | } { | |||
| 82 | + | t.Run(tc.name, func(t *testing.T) { | |||
| 83 | + | in, dev, _, _ := newState(t) | |||
| 84 | + | dev.EXPECT().IsTouchpad(testPad).Return(tc.touchpad) | |||
| 85 | + | if tc.askedPtr { | |||
| 86 | + | dev.EXPECT().IsPointer(testPad).Return(tc.ptr) | |||
| 87 | + | } | |||
| 88 | + | if got := in.configurable(testPad); got != tc.want { | |||
| 89 | + | t.Fatalf("configurable = %v, want %v", got, tc.want) | |||
| 90 | + | } | |||
| 91 | + | }) | |||
| 92 | + | } | |||
| 93 | + | } | |||
| 94 | + | ||||
| 95 | + | func TestConfigureRejectedSettingLogsAndContinues(t *testing.T) { | |||
| 96 | + | in, dev, log, buf := newState(t) | |||
| 97 | + | dev.EXPECT().IsTouchpad(testPad).Return(true) | |||
| 98 | + | dev.EXPECT().SetNaturalScroll(testPad, true).Return(errRejected).Once() | |||
| 99 | + | dev.EXPECT().Name(testPad).Return("Synaptics").Once() | |||
| 100 | + | dev.EXPECT().SetAccel(testPad, 0.5, ports.AccelFlat).Return(nil).Once() | |||
| 101 | + | dev.EXPECT().SetLeftHanded(testPad, false).Return(nil).Once() | |||
| 102 | + | dev.EXPECT().SetTap(testPad, true).Return(nil).Once() | |||
| 103 | + | in.configure(testPad, log) | |||
| 104 | + | out := buf.String() | |||
| 105 | + | for _, want := range []string{`"device":"Synaptics"`, `"setting":`, `"component":"input"`, `"level":"warn"`} { | |||
| 106 | + | if !strings.Contains(out, want) { | |||
| 107 | + | t.Errorf("log %s lacks %s", out, want) | |||
| 108 | + | } | |||
| 109 | + | } | |||
| 110 | + | if n := strings.Count(out, "\n"); n != 1 { | |||
| 111 | + | t.Errorf("%d log lines, want 1: %s", n, out) | |||
| 112 | + | } | |||
| 113 | + | } | |||
A internal/adapters/libinput/device_config_mock_test.go +432 -0
| @@ -0,0 +1,432 @@ | |||
| 1 | + | // Code generated by mockery; DO NOT EDIT. | |
| 2 | + | // github.com/vektra/mockery | |
| 3 | + | // template: testify | |
| 4 | + | ||
| 5 | + | package libinput | |
| 6 | + | ||
| 7 | + | import ( | |
| 8 | + | mock "github.com/stretchr/testify/mock" | |
| 9 | + | ) | |
| 10 | + | ||
| 11 | + | // newMockdeviceConfig creates a new instance of mockdeviceConfig. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | |
| 12 | + | // The first argument is typically a *testing.T value. | |
| 13 | + | func newMockdeviceConfig(t interface { | |
| 14 | + | mock.TestingT | |
| 15 | + | Cleanup(func()) | |
| 16 | + | }) *mockdeviceConfig { | |
| 17 | + | if helper, ok := t.(interface{ Helper() }); ok { | |
| 18 | + | helper.Helper() | |
| 19 | + | } | |
| 20 | + | ||
| 21 | + | mock := &mockdeviceConfig{} | |
| 22 | + | mock.Mock.Test(t) | |
| 23 | + | ||
| 24 | + | t.Cleanup(func() { | |
| 25 | + | if helper, ok := t.(interface{ Helper() }); ok { | |
| 26 | + | helper.Helper() | |
| 27 | + | } | |
| 28 | + | mock.AssertExpectations(t) | |
| 29 | + | }) | |
| 30 | + | ||
| 31 | + | return mock | |
| 32 | + | } | |
| 33 | + | ||
| 34 | + | // mockdeviceConfig is an autogenerated mock type for the deviceConfig type | |
| 35 | + | type mockdeviceConfig struct { | |
| 36 | + | mock.Mock | |
| 37 | + | } | |
| 38 | + | ||
| 39 | + | type mockdeviceConfig_Expecter struct { | |
| 40 | + | mock *mock.Mock | |
| 41 | + | } | |
| 42 | + | ||
| 43 | + | func (_m *mockdeviceConfig) EXPECT() *mockdeviceConfig_Expecter { | |
| 44 | + | return &mockdeviceConfig_Expecter{mock: &_m.Mock} | |
| 45 | + | } | |
| 46 | + | ||
| 47 | + | // IsPointer provides a mock function for the type mockdeviceConfig | |
| 48 | + | func (_mock *mockdeviceConfig) IsPointer(dev uintptr) bool { | |
| 49 | + | ret := _mock.Called(dev) | |
| 50 | + | ||
| 51 | + | if len(ret) == 0 { | |
| 52 | + | panic("no return value specified for IsPointer") | |
| 53 | + | } | |
| 54 | + | ||
| 55 | + | var r0 bool | |
| 56 | + | if returnFunc, ok := ret.Get(0).(func(uintptr) bool); ok { | |
| 57 | + | r0 = returnFunc(dev) | |
| 58 | + | } else { | |
| 59 | + | r0 = ret.Get(0).(bool) | |
| 60 | + | } | |
| 61 | + | return r0 | |
| 62 | + | } | |
| 63 | + | ||
| 64 | + | // mockdeviceConfig_IsPointer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsPointer' | |
| 65 | + | type mockdeviceConfig_IsPointer_Call struct { | |
| 66 | + | *mock.Call | |
| 67 | + | } | |
| 68 | + | ||
| 69 | + | // IsPointer is a helper method to define mock.On call | |
| 70 | + | // - dev uintptr | |
| 71 | + | func (_e *mockdeviceConfig_Expecter) IsPointer(dev any) *mockdeviceConfig_IsPointer_Call { | |
| 72 | + | return &mockdeviceConfig_IsPointer_Call{Call: _e.mock.On("IsPointer", dev)} | |
| 73 | + | } | |
| 74 | + | ||
| 75 | + | func (_c *mockdeviceConfig_IsPointer_Call) Run(run func(dev uintptr)) *mockdeviceConfig_IsPointer_Call { | |
| 76 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 77 | + | var arg0 uintptr | |
| 78 | + | if args[0] != nil { | |
| 79 | + | arg0 = args[0].(uintptr) | |
| 80 | + | } | |
| 81 | + | run( | |
| 82 | + | arg0, | |
| 83 | + | ) | |
| 84 | + | }) | |
| 85 | + | return _c | |
| 86 | + | } | |
| 87 | + | ||
| 88 | + | func (_c *mockdeviceConfig_IsPointer_Call) Return(b bool) *mockdeviceConfig_IsPointer_Call { | |
| 89 | + | _c.Call.Return(b) | |
| 90 | + | return _c | |
| 91 | + | } | |
| 92 | + | ||
| 93 | + | func (_c *mockdeviceConfig_IsPointer_Call) RunAndReturn(run func(dev uintptr) bool) *mockdeviceConfig_IsPointer_Call { | |
| 94 | + | _c.Call.Return(run) | |
| 95 | + | return _c | |
| 96 | + | } | |
| 97 | + | ||
| 98 | + | // IsTouchpad provides a mock function for the type mockdeviceConfig | |
| 99 | + | func (_mock *mockdeviceConfig) IsTouchpad(dev uintptr) bool { | |
| 100 | + | ret := _mock.Called(dev) | |
| 101 | + | ||
| 102 | + | if len(ret) == 0 { | |
| 103 | + | panic("no return value specified for IsTouchpad") | |
| 104 | + | } | |
| 105 | + | ||
| 106 | + | var r0 bool | |
| 107 | + | if returnFunc, ok := ret.Get(0).(func(uintptr) bool); ok { | |
| 108 | + | r0 = returnFunc(dev) | |
| 109 | + | } else { | |
| 110 | + | r0 = ret.Get(0).(bool) | |
| 111 | + | } | |
| 112 | + | return r0 | |
| 113 | + | } | |
| 114 | + | ||
| 115 | + | // mockdeviceConfig_IsTouchpad_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsTouchpad' | |
| 116 | + | type mockdeviceConfig_IsTouchpad_Call struct { | |
| 117 | + | *mock.Call | |
| 118 | + | } | |
| 119 | + | ||
| 120 | + | // IsTouchpad is a helper method to define mock.On call | |
| 121 | + | // - dev uintptr | |
| 122 | + | func (_e *mockdeviceConfig_Expecter) IsTouchpad(dev any) *mockdeviceConfig_IsTouchpad_Call { | |
| 123 | + | return &mockdeviceConfig_IsTouchpad_Call{Call: _e.mock.On("IsTouchpad", dev)} | |
| 124 | + | } | |
| 125 | + | ||
| 126 | + | func (_c *mockdeviceConfig_IsTouchpad_Call) Run(run func(dev uintptr)) *mockdeviceConfig_IsTouchpad_Call { | |
| 127 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 128 | + | var arg0 uintptr | |
| 129 | + | if args[0] != nil { | |
| 130 | + | arg0 = args[0].(uintptr) | |
| 131 | + | } | |
| 132 | + | run( | |
| 133 | + | arg0, | |
| 134 | + | ) | |
| 135 | + | }) | |
| 136 | + | return _c | |
| 137 | + | } | |
| 138 | + | ||
| 139 | + | func (_c *mockdeviceConfig_IsTouchpad_Call) Return(b bool) *mockdeviceConfig_IsTouchpad_Call { | |
| 140 | + | _c.Call.Return(b) | |
| 141 | + | return _c | |
| 142 | + | } | |
| 143 | + | ||
| 144 | + | func (_c *mockdeviceConfig_IsTouchpad_Call) RunAndReturn(run func(dev uintptr) bool) *mockdeviceConfig_IsTouchpad_Call { | |
| 145 | + | _c.Call.Return(run) | |
| 146 | + | return _c | |
| 147 | + | } | |
| 148 | + | ||
| 149 | + | // Name provides a mock function for the type mockdeviceConfig | |
| 150 | + | func (_mock *mockdeviceConfig) Name(dev uintptr) string { | |
| 151 | + | ret := _mock.Called(dev) | |
| 152 | + | ||
| 153 | + | if len(ret) == 0 { | |
| 154 | + | panic("no return value specified for Name") | |
| 155 | + | } | |
| 156 | + | ||
| 157 | + | var r0 string | |
| 158 | + | if returnFunc, ok := ret.Get(0).(func(uintptr) string); ok { | |
| 159 | + | r0 = returnFunc(dev) | |
| 160 | + | } else { | |
| 161 | + | r0 = ret.Get(0).(string) | |
| 162 | + | } | |
| 163 | + | return r0 | |
| 164 | + | } | |
| 165 | + | ||
| 166 | + | // mockdeviceConfig_Name_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Name' | |
| 167 | + | type mockdeviceConfig_Name_Call struct { | |
| 168 | + | *mock.Call | |
| 169 | + | } | |
| 170 | + | ||
| 171 | + | // Name is a helper method to define mock.On call | |
| 172 | + | // - dev uintptr | |
| 173 | + | func (_e *mockdeviceConfig_Expecter) Name(dev any) *mockdeviceConfig_Name_Call { | |
| 174 | + | return &mockdeviceConfig_Name_Call{Call: _e.mock.On("Name", dev)} | |
| 175 | + | } | |
| 176 | + | ||
| 177 | + | func (_c *mockdeviceConfig_Name_Call) Run(run func(dev uintptr)) *mockdeviceConfig_Name_Call { | |
| 178 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 179 | + | var arg0 uintptr | |
| 180 | + | if args[0] != nil { | |
| 181 | + | arg0 = args[0].(uintptr) | |
| 182 | + | } | |
| 183 | + | run( | |
| 184 | + | arg0, | |
| 185 | + | ) | |
| 186 | + | }) | |
| 187 | + | return _c | |
| 188 | + | } | |
| 189 | + | ||
| 190 | + | func (_c *mockdeviceConfig_Name_Call) Return(s string) *mockdeviceConfig_Name_Call { | |
| 191 | + | _c.Call.Return(s) | |
| 192 | + | return _c | |
| 193 | + | } | |
| 194 | + | ||
| 195 | + | func (_c *mockdeviceConfig_Name_Call) RunAndReturn(run func(dev uintptr) string) *mockdeviceConfig_Name_Call { | |
| 196 | + | _c.Call.Return(run) | |
| 197 | + | return _c | |
| 198 | + | } | |
| 199 | + | ||
| 200 | + | // SetAccel provides a mock function for the type mockdeviceConfig | |
| 201 | + | func (_mock *mockdeviceConfig) SetAccel(dev uintptr, speed float64, profile string) error { | |
| 202 | + | ret := _mock.Called(dev, speed, profile) | |
| 203 | + | ||
| 204 | + | if len(ret) == 0 { | |
| 205 | + | panic("no return value specified for SetAccel") | |
| 206 | + | } | |
| 207 | + | ||
| 208 | + | var r0 error | |
| 209 | + | if returnFunc, ok := ret.Get(0).(func(uintptr, float64, string) error); ok { | |
| 210 | + | r0 = returnFunc(dev, speed, profile) | |
| 211 | + | } else { | |
| 212 | + | r0 = ret.Error(0) | |
| 213 | + | } | |
| 214 | + | return r0 | |
| 215 | + | } | |
| 216 | + | ||
| 217 | + | // mockdeviceConfig_SetAccel_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetAccel' | |
| 218 | + | type mockdeviceConfig_SetAccel_Call struct { | |
| 219 | + | *mock.Call | |
| 220 | + | } | |
| 221 | + | ||
| 222 | + | // SetAccel is a helper method to define mock.On call | |
| 223 | + | // - dev uintptr | |
| 224 | + | // - speed float64 | |
| 225 | + | // - profile string | |
| 226 | + | func (_e *mockdeviceConfig_Expecter) SetAccel(dev any, speed any, profile any) *mockdeviceConfig_SetAccel_Call { | |
| 227 | + | return &mockdeviceConfig_SetAccel_Call{Call: _e.mock.On("SetAccel", dev, speed, profile)} | |
| 228 | + | } | |
| 229 | + | ||
| 230 | + | func (_c *mockdeviceConfig_SetAccel_Call) Run(run func(dev uintptr, speed float64, profile string)) *mockdeviceConfig_SetAccel_Call { | |
| 231 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 232 | + | var arg0 uintptr | |
| 233 | + | if args[0] != nil { | |
| 234 | + | arg0 = args[0].(uintptr) | |
| 235 | + | } | |
| 236 | + | var arg1 float64 | |
| 237 | + | if args[1] != nil { | |
| 238 | + | arg1 = args[1].(float64) | |
| 239 | + | } | |
| 240 | + | var arg2 string | |
| 241 | + | if args[2] != nil { | |
| 242 | + | arg2 = args[2].(string) | |
| 243 | + | } | |
| 244 | + | run( | |
| 245 | + | arg0, | |
| 246 | + | arg1, | |
| 247 | + | arg2, | |
| 248 | + | ) | |
| 249 | + | }) | |
| 250 | + | return _c | |
| 251 | + | } | |
| 252 | + | ||
| 253 | + | func (_c *mockdeviceConfig_SetAccel_Call) Return(err error) *mockdeviceConfig_SetAccel_Call { | |
| 254 | + | _c.Call.Return(err) | |
| 255 | + | return _c | |
| 256 | + | } | |
| 257 | + | ||
| 258 | + | func (_c *mockdeviceConfig_SetAccel_Call) RunAndReturn(run func(dev uintptr, speed float64, profile string) error) *mockdeviceConfig_SetAccel_Call { | |
| 259 | + | _c.Call.Return(run) | |
| 260 | + | return _c | |
| 261 | + | } | |
| 262 | + | ||
| 263 | + | // SetLeftHanded provides a mock function for the type mockdeviceConfig | |
| 264 | + | func (_mock *mockdeviceConfig) SetLeftHanded(dev uintptr, on bool) error { | |
| 265 | + | ret := _mock.Called(dev, on) | |
| 266 | + | ||
| 267 | + | if len(ret) == 0 { | |
| 268 | + | panic("no return value specified for SetLeftHanded") | |
| 269 | + | } | |
| 270 | + | ||
| 271 | + | var r0 error | |
| 272 | + | if returnFunc, ok := ret.Get(0).(func(uintptr, bool) error); ok { | |
| 273 | + | r0 = returnFunc(dev, on) | |
| 274 | + | } else { | |
| 275 | + | r0 = ret.Error(0) | |
| 276 | + | } | |
| 277 | + | return r0 | |
| 278 | + | } | |
| 279 | + | ||
| 280 | + | // mockdeviceConfig_SetLeftHanded_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetLeftHanded' | |
| 281 | + | type mockdeviceConfig_SetLeftHanded_Call struct { | |
| 282 | + | *mock.Call | |
| 283 | + | } | |
| 284 | + | ||
| 285 | + | // SetLeftHanded is a helper method to define mock.On call | |
| 286 | + | // - dev uintptr | |
| 287 | + | // - on bool | |
| 288 | + | func (_e *mockdeviceConfig_Expecter) SetLeftHanded(dev any, on any) *mockdeviceConfig_SetLeftHanded_Call { | |
| 289 | + | return &mockdeviceConfig_SetLeftHanded_Call{Call: _e.mock.On("SetLeftHanded", dev, on)} | |
| 290 | + | } | |
| 291 | + | ||
| 292 | + | func (_c *mockdeviceConfig_SetLeftHanded_Call) Run(run func(dev uintptr, on bool)) *mockdeviceConfig_SetLeftHanded_Call { | |
| 293 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 294 | + | var arg0 uintptr | |
| 295 | + | if args[0] != nil { | |
| 296 | + | arg0 = args[0].(uintptr) | |
| 297 | + | } | |
| 298 | + | var arg1 bool | |
| 299 | + | if args[1] != nil { | |
| 300 | + | arg1 = args[1].(bool) | |
| 301 | + | } | |
| 302 | + | run( | |
| 303 | + | arg0, | |
| 304 | + | arg1, | |
| 305 | + | ) | |
| 306 | + | }) | |
| 307 | + | return _c | |
| 308 | + | } | |
| 309 | + | ||
| 310 | + | func (_c *mockdeviceConfig_SetLeftHanded_Call) Return(err error) *mockdeviceConfig_SetLeftHanded_Call { | |
| 311 | + | _c.Call.Return(err) | |
| 312 | + | return _c | |
| 313 | + | } | |
| 314 | + | ||
| 315 | + | func (_c *mockdeviceConfig_SetLeftHanded_Call) RunAndReturn(run func(dev uintptr, on bool) error) *mockdeviceConfig_SetLeftHanded_Call { | |
| 316 | + | _c.Call.Return(run) | |
| 317 | + | return _c | |
| 318 | + | } | |
| 319 | + | ||
| 320 | + | // SetNaturalScroll provides a mock function for the type mockdeviceConfig | |
| 321 | + | func (_mock *mockdeviceConfig) SetNaturalScroll(dev uintptr, on bool) error { | |
| 322 | + | ret := _mock.Called(dev, on) | |
| 323 | + | ||
| 324 | + | if len(ret) == 0 { | |
| 325 | + | panic("no return value specified for SetNaturalScroll") | |
| 326 | + | } | |
| 327 | + | ||
| 328 | + | var r0 error | |
| 329 | + | if returnFunc, ok := ret.Get(0).(func(uintptr, bool) error); ok { | |
| 330 | + | r0 = returnFunc(dev, on) | |
| 331 | + | } else { | |
| 332 | + | r0 = ret.Error(0) | |
| 333 | + | } | |
| 334 | + | return r0 | |
| 335 | + | } | |
| 336 | + | ||
| 337 | + | // mockdeviceConfig_SetNaturalScroll_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetNaturalScroll' | |
| 338 | + | type mockdeviceConfig_SetNaturalScroll_Call struct { | |
| 339 | + | *mock.Call | |
| 340 | + | } | |
| 341 | + | ||
| 342 | + | // SetNaturalScroll is a helper method to define mock.On call | |
| 343 | + | // - dev uintptr | |
| 344 | + | // - on bool | |
| 345 | + | func (_e *mockdeviceConfig_Expecter) SetNaturalScroll(dev any, on any) *mockdeviceConfig_SetNaturalScroll_Call { | |
| 346 | + | return &mockdeviceConfig_SetNaturalScroll_Call{Call: _e.mock.On("SetNaturalScroll", dev, on)} | |
| 347 | + | } | |
| 348 | + | ||
| 349 | + | func (_c *mockdeviceConfig_SetNaturalScroll_Call) Run(run func(dev uintptr, on bool)) *mockdeviceConfig_SetNaturalScroll_Call { | |
| 350 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 351 | + | var arg0 uintptr | |
| 352 | + | if args[0] != nil { | |
| 353 | + | arg0 = args[0].(uintptr) | |
| 354 | + | } | |
| 355 | + | var arg1 bool | |
| 356 | + | if args[1] != nil { | |
| 357 | + | arg1 = args[1].(bool) | |
| 358 | + | } | |
| 359 | + | run( | |
| 360 | + | arg0, | |
| 361 | + | arg1, | |
| 362 | + | ) | |
| 363 | + | }) | |
| 364 | + | return _c | |
| 365 | + | } | |
| 366 | + | ||
| 367 | + | func (_c *mockdeviceConfig_SetNaturalScroll_Call) Return(err error) *mockdeviceConfig_SetNaturalScroll_Call { | |
| 368 | + | _c.Call.Return(err) | |
| 369 | + | return _c | |
| 370 | + | } | |
| 371 | + | ||
| 372 | + | func (_c *mockdeviceConfig_SetNaturalScroll_Call) RunAndReturn(run func(dev uintptr, on bool) error) *mockdeviceConfig_SetNaturalScroll_Call { | |
| 373 | + | _c.Call.Return(run) | |
| 374 | + | return _c | |
| 375 | + | } | |
| 376 | + | ||
| 377 | + | // SetTap provides a mock function for the type mockdeviceConfig | |
| 378 | + | func (_mock *mockdeviceConfig) SetTap(dev uintptr, on bool) error { | |
| 379 | + | ret := _mock.Called(dev, on) | |
| 380 | + | ||
| 381 | + | if len(ret) == 0 { | |
| 382 | + | panic("no return value specified for SetTap") | |
| 383 | + | } | |
| 384 | + | ||
| 385 | + | var r0 error | |
| 386 | + | if returnFunc, ok := ret.Get(0).(func(uintptr, bool) error); ok { | |
| 387 | + | r0 = returnFunc(dev, on) | |
| 388 | + | } else { | |
| 389 | + | r0 = ret.Error(0) | |
| 390 | + | } | |
| 391 | + | return r0 | |
| 392 | + | } | |
| 393 | + | ||
| 394 | + | // mockdeviceConfig_SetTap_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetTap' | |
| 395 | + | type mockdeviceConfig_SetTap_Call struct { | |
| 396 | + | *mock.Call | |
| 397 | + | } | |
| 398 | + | ||
| 399 | + | // SetTap is a helper method to define mock.On call | |
| 400 | + | // - dev uintptr | |
| 401 | + | // - on bool | |
| 402 | + | func (_e *mockdeviceConfig_Expecter) SetTap(dev any, on any) *mockdeviceConfig_SetTap_Call { | |
| 403 | + | return &mockdeviceConfig_SetTap_Call{Call: _e.mock.On("SetTap", dev, on)} | |
| 404 | + | } | |
| 405 | + | ||
| 406 | + | func (_c *mockdeviceConfig_SetTap_Call) Run(run func(dev uintptr, on bool)) *mockdeviceConfig_SetTap_Call { | |
| 407 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 408 | + | var arg0 uintptr | |
| 409 | + | if args[0] != nil { | |
| 410 | + | arg0 = args[0].(uintptr) | |
| 411 | + | } | |
| 412 | + | var arg1 bool | |
| 413 | + | if args[1] != nil { | |
| 414 | + | arg1 = args[1].(bool) | |
| 415 | + | } | |
| 416 | + | run( | |
| 417 | + | arg0, | |
| 418 | + | arg1, | |
| 419 | + | ) | |
| 420 | + | }) | |
| 421 | + | return _c | |
| 422 | + | } | |
| 423 | + | ||
| 424 | + | func (_c *mockdeviceConfig_SetTap_Call) Return(err error) *mockdeviceConfig_SetTap_Call { | |
| 425 | + | _c.Call.Return(err) | |
| 426 | + | return _c | |
| 427 | + | } | |
| 428 | + | ||
| 429 | + | func (_c *mockdeviceConfig_SetTap_Call) RunAndReturn(run func(dev uintptr, on bool) error) *mockdeviceConfig_SetTap_Call { | |
| 430 | + | _c.Call.Return(run) | |
| 431 | + | return _c | |
| 432 | + | } | |
| @@ -0,0 +1,432 @@ | |||||
| 1 | + | // Code generated by mockery; DO NOT EDIT. | |||
| 2 | + | // github.com/vektra/mockery | |||
| 3 | + | // template: testify | |||
| 4 | + | ||||
| 5 | + | package libinput | |||
| 6 | + | ||||
| 7 | + | import ( | |||
| 8 | + | mock "github.com/stretchr/testify/mock" | |||
| 9 | + | ) | |||
| 10 | + | ||||
| 11 | + | // newMockdeviceConfig creates a new instance of mockdeviceConfig. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | |||
| 12 | + | // The first argument is typically a *testing.T value. | |||
| 13 | + | func newMockdeviceConfig(t interface { | |||
| 14 | + | mock.TestingT | |||
| 15 | + | Cleanup(func()) | |||
| 16 | + | }) *mockdeviceConfig { | |||
| 17 | + | if helper, ok := t.(interface{ Helper() }); ok { | |||
| 18 | + | helper.Helper() | |||
| 19 | + | } | |||
| 20 | + | ||||
| 21 | + | mock := &mockdeviceConfig{} | |||
| 22 | + | mock.Mock.Test(t) | |||
| 23 | + | ||||
| 24 | + | t.Cleanup(func() { | |||
| 25 | + | if helper, ok := t.(interface{ Helper() }); ok { | |||
| 26 | + | helper.Helper() | |||
| 27 | + | } | |||
| 28 | + | mock.AssertExpectations(t) | |||
| 29 | + | }) | |||
| 30 | + | ||||
| 31 | + | return mock | |||
| 32 | + | } | |||
| 33 | + | ||||
| 34 | + | // mockdeviceConfig is an autogenerated mock type for the deviceConfig type | |||
| 35 | + | type mockdeviceConfig struct { | |||
| 36 | + | mock.Mock | |||
| 37 | + | } | |||
| 38 | + | ||||
| 39 | + | type mockdeviceConfig_Expecter struct { | |||
| 40 | + | mock *mock.Mock | |||
| 41 | + | } | |||
| 42 | + | ||||
| 43 | + | func (_m *mockdeviceConfig) EXPECT() *mockdeviceConfig_Expecter { | |||
| 44 | + | return &mockdeviceConfig_Expecter{mock: &_m.Mock} | |||
| 45 | + | } | |||
| 46 | + | ||||
| 47 | + | // IsPointer provides a mock function for the type mockdeviceConfig | |||
| 48 | + | func (_mock *mockdeviceConfig) IsPointer(dev uintptr) bool { | |||
| 49 | + | ret := _mock.Called(dev) | |||
| 50 | + | ||||
| 51 | + | if len(ret) == 0 { | |||
| 52 | + | panic("no return value specified for IsPointer") | |||
| 53 | + | } | |||
| 54 | + | ||||
| 55 | + | var r0 bool | |||
| 56 | + | if returnFunc, ok := ret.Get(0).(func(uintptr) bool); ok { | |||
| 57 | + | r0 = returnFunc(dev) | |||
| 58 | + | } else { | |||
| 59 | + | r0 = ret.Get(0).(bool) | |||
| 60 | + | } | |||
| 61 | + | return r0 | |||
| 62 | + | } | |||
| 63 | + | ||||
| 64 | + | // mockdeviceConfig_IsPointer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsPointer' | |||
| 65 | + | type mockdeviceConfig_IsPointer_Call struct { | |||
| 66 | + | *mock.Call | |||
| 67 | + | } | |||
| 68 | + | ||||
| 69 | + | // IsPointer is a helper method to define mock.On call | |||
| 70 | + | // - dev uintptr | |||
| 71 | + | func (_e *mockdeviceConfig_Expecter) IsPointer(dev any) *mockdeviceConfig_IsPointer_Call { | |||
| 72 | + | return &mockdeviceConfig_IsPointer_Call{Call: _e.mock.On("IsPointer", dev)} | |||
| 73 | + | } | |||
| 74 | + | ||||
| 75 | + | func (_c *mockdeviceConfig_IsPointer_Call) Run(run func(dev uintptr)) *mockdeviceConfig_IsPointer_Call { | |||
| 76 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 77 | + | var arg0 uintptr | |||
| 78 | + | if args[0] != nil { | |||
| 79 | + | arg0 = args[0].(uintptr) | |||
| 80 | + | } | |||
| 81 | + | run( | |||
| 82 | + | arg0, | |||
| 83 | + | ) | |||
| 84 | + | }) | |||
| 85 | + | return _c | |||
| 86 | + | } | |||
| 87 | + | ||||
| 88 | + | func (_c *mockdeviceConfig_IsPointer_Call) Return(b bool) *mockdeviceConfig_IsPointer_Call { | |||
| 89 | + | _c.Call.Return(b) | |||
| 90 | + | return _c | |||
| 91 | + | } | |||
| 92 | + | ||||
| 93 | + | func (_c *mockdeviceConfig_IsPointer_Call) RunAndReturn(run func(dev uintptr) bool) *mockdeviceConfig_IsPointer_Call { | |||
| 94 | + | _c.Call.Return(run) | |||
| 95 | + | return _c | |||
| 96 | + | } | |||
| 97 | + | ||||
| 98 | + | // IsTouchpad provides a mock function for the type mockdeviceConfig | |||
| 99 | + | func (_mock *mockdeviceConfig) IsTouchpad(dev uintptr) bool { | |||
| 100 | + | ret := _mock.Called(dev) | |||
| 101 | + | ||||
| 102 | + | if len(ret) == 0 { | |||
| 103 | + | panic("no return value specified for IsTouchpad") | |||
| 104 | + | } | |||
| 105 | + | ||||
| 106 | + | var r0 bool | |||
| 107 | + | if returnFunc, ok := ret.Get(0).(func(uintptr) bool); ok { | |||
| 108 | + | r0 = returnFunc(dev) | |||
| 109 | + | } else { | |||
| 110 | + | r0 = ret.Get(0).(bool) | |||
| 111 | + | } | |||
| 112 | + | return r0 | |||
| 113 | + | } | |||
| 114 | + | ||||
| 115 | + | // mockdeviceConfig_IsTouchpad_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsTouchpad' | |||
| 116 | + | type mockdeviceConfig_IsTouchpad_Call struct { | |||
| 117 | + | *mock.Call | |||
| 118 | + | } | |||
| 119 | + | ||||
| 120 | + | // IsTouchpad is a helper method to define mock.On call | |||
| 121 | + | // - dev uintptr | |||
| 122 | + | func (_e *mockdeviceConfig_Expecter) IsTouchpad(dev any) *mockdeviceConfig_IsTouchpad_Call { | |||
| 123 | + | return &mockdeviceConfig_IsTouchpad_Call{Call: _e.mock.On("IsTouchpad", dev)} | |||
| 124 | + | } | |||
| 125 | + | ||||
| 126 | + | func (_c *mockdeviceConfig_IsTouchpad_Call) Run(run func(dev uintptr)) *mockdeviceConfig_IsTouchpad_Call { | |||
| 127 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 128 | + | var arg0 uintptr | |||
| 129 | + | if args[0] != nil { | |||
| 130 | + | arg0 = args[0].(uintptr) | |||
| 131 | + | } | |||
| 132 | + | run( | |||
| 133 | + | arg0, | |||
| 134 | + | ) | |||
| 135 | + | }) | |||
| 136 | + | return _c | |||
| 137 | + | } | |||
| 138 | + | ||||
| 139 | + | func (_c *mockdeviceConfig_IsTouchpad_Call) Return(b bool) *mockdeviceConfig_IsTouchpad_Call { | |||
| 140 | + | _c.Call.Return(b) | |||
| 141 | + | return _c | |||
| 142 | + | } | |||
| 143 | + | ||||
| 144 | + | func (_c *mockdeviceConfig_IsTouchpad_Call) RunAndReturn(run func(dev uintptr) bool) *mockdeviceConfig_IsTouchpad_Call { | |||
| 145 | + | _c.Call.Return(run) | |||
| 146 | + | return _c | |||
| 147 | + | } | |||
| 148 | + | ||||
| 149 | + | // Name provides a mock function for the type mockdeviceConfig | |||
| 150 | + | func (_mock *mockdeviceConfig) Name(dev uintptr) string { | |||
| 151 | + | ret := _mock.Called(dev) | |||
| 152 | + | ||||
| 153 | + | if len(ret) == 0 { | |||
| 154 | + | panic("no return value specified for Name") | |||
| 155 | + | } | |||
| 156 | + | ||||
| 157 | + | var r0 string | |||
| 158 | + | if returnFunc, ok := ret.Get(0).(func(uintptr) string); ok { | |||
| 159 | + | r0 = returnFunc(dev) | |||
| 160 | + | } else { | |||
| 161 | + | r0 = ret.Get(0).(string) | |||
| 162 | + | } | |||
| 163 | + | return r0 | |||
| 164 | + | } | |||
| 165 | + | ||||
| 166 | + | // mockdeviceConfig_Name_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Name' | |||
| 167 | + | type mockdeviceConfig_Name_Call struct { | |||
| 168 | + | *mock.Call | |||
| 169 | + | } | |||
| 170 | + | ||||
| 171 | + | // Name is a helper method to define mock.On call | |||
| 172 | + | // - dev uintptr | |||
| 173 | + | func (_e *mockdeviceConfig_Expecter) Name(dev any) *mockdeviceConfig_Name_Call { | |||
| 174 | + | return &mockdeviceConfig_Name_Call{Call: _e.mock.On("Name", dev)} | |||
| 175 | + | } | |||
| 176 | + | ||||
| 177 | + | func (_c *mockdeviceConfig_Name_Call) Run(run func(dev uintptr)) *mockdeviceConfig_Name_Call { | |||
| 178 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 179 | + | var arg0 uintptr | |||
| 180 | + | if args[0] != nil { | |||
| 181 | + | arg0 = args[0].(uintptr) | |||
| 182 | + | } | |||
| 183 | + | run( | |||
| 184 | + | arg0, | |||
| 185 | + | ) | |||
| 186 | + | }) | |||
| 187 | + | return _c | |||
| 188 | + | } | |||
| 189 | + | ||||
| 190 | + | func (_c *mockdeviceConfig_Name_Call) Return(s string) *mockdeviceConfig_Name_Call { | |||
| 191 | + | _c.Call.Return(s) | |||
| 192 | + | return _c | |||
| 193 | + | } | |||
| 194 | + | ||||
| 195 | + | func (_c *mockdeviceConfig_Name_Call) RunAndReturn(run func(dev uintptr) string) *mockdeviceConfig_Name_Call { | |||
| 196 | + | _c.Call.Return(run) | |||
| 197 | + | return _c | |||
| 198 | + | } | |||
| 199 | + | ||||
| 200 | + | // SetAccel provides a mock function for the type mockdeviceConfig | |||
| 201 | + | func (_mock *mockdeviceConfig) SetAccel(dev uintptr, speed float64, profile string) error { | |||
| 202 | + | ret := _mock.Called(dev, speed, profile) | |||
| 203 | + | ||||
| 204 | + | if len(ret) == 0 { | |||
| 205 | + | panic("no return value specified for SetAccel") | |||
| 206 | + | } | |||
| 207 | + | ||||
| 208 | + | var r0 error | |||
| 209 | + | if returnFunc, ok := ret.Get(0).(func(uintptr, float64, string) error); ok { | |||
| 210 | + | r0 = returnFunc(dev, speed, profile) | |||
| 211 | + | } else { | |||
| 212 | + | r0 = ret.Error(0) | |||
| 213 | + | } | |||
| 214 | + | return r0 | |||
| 215 | + | } | |||
| 216 | + | ||||
| 217 | + | // mockdeviceConfig_SetAccel_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetAccel' | |||
| 218 | + | type mockdeviceConfig_SetAccel_Call struct { | |||
| 219 | + | *mock.Call | |||
| 220 | + | } | |||
| 221 | + | ||||
| 222 | + | // SetAccel is a helper method to define mock.On call | |||
| 223 | + | // - dev uintptr | |||
| 224 | + | // - speed float64 | |||
| 225 | + | // - profile string | |||
| 226 | + | func (_e *mockdeviceConfig_Expecter) SetAccel(dev any, speed any, profile any) *mockdeviceConfig_SetAccel_Call { | |||
| 227 | + | return &mockdeviceConfig_SetAccel_Call{Call: _e.mock.On("SetAccel", dev, speed, profile)} | |||
| 228 | + | } | |||
| 229 | + | ||||
| 230 | + | func (_c *mockdeviceConfig_SetAccel_Call) Run(run func(dev uintptr, speed float64, profile string)) *mockdeviceConfig_SetAccel_Call { | |||
| 231 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 232 | + | var arg0 uintptr | |||
| 233 | + | if args[0] != nil { | |||
| 234 | + | arg0 = args[0].(uintptr) | |||
| 235 | + | } | |||
| 236 | + | var arg1 float64 | |||
| 237 | + | if args[1] != nil { | |||
| 238 | + | arg1 = args[1].(float64) | |||
| 239 | + | } | |||
| 240 | + | var arg2 string | |||
| 241 | + | if args[2] != nil { | |||
| 242 | + | arg2 = args[2].(string) | |||
| 243 | + | } | |||
| 244 | + | run( | |||
| 245 | + | arg0, | |||
| 246 | + | arg1, | |||
| 247 | + | arg2, | |||
| 248 | + | ) | |||
| 249 | + | }) | |||
| 250 | + | return _c | |||
| 251 | + | } | |||
| 252 | + | ||||
| 253 | + | func (_c *mockdeviceConfig_SetAccel_Call) Return(err error) *mockdeviceConfig_SetAccel_Call { | |||
| 254 | + | _c.Call.Return(err) | |||
| 255 | + | return _c | |||
| 256 | + | } | |||
| 257 | + | ||||
| 258 | + | func (_c *mockdeviceConfig_SetAccel_Call) RunAndReturn(run func(dev uintptr, speed float64, profile string) error) *mockdeviceConfig_SetAccel_Call { | |||
| 259 | + | _c.Call.Return(run) | |||
| 260 | + | return _c | |||
| 261 | + | } | |||
| 262 | + | ||||
| 263 | + | // SetLeftHanded provides a mock function for the type mockdeviceConfig | |||
| 264 | + | func (_mock *mockdeviceConfig) SetLeftHanded(dev uintptr, on bool) error { | |||
| 265 | + | ret := _mock.Called(dev, on) | |||
| 266 | + | ||||
| 267 | + | if len(ret) == 0 { | |||
| 268 | + | panic("no return value specified for SetLeftHanded") | |||
| 269 | + | } | |||
| 270 | + | ||||
| 271 | + | var r0 error | |||
| 272 | + | if returnFunc, ok := ret.Get(0).(func(uintptr, bool) error); ok { | |||
| 273 | + | r0 = returnFunc(dev, on) | |||
| 274 | + | } else { | |||
| 275 | + | r0 = ret.Error(0) | |||
| 276 | + | } | |||
| 277 | + | return r0 | |||
| 278 | + | } | |||
| 279 | + | ||||
| 280 | + | // mockdeviceConfig_SetLeftHanded_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetLeftHanded' | |||
| 281 | + | type mockdeviceConfig_SetLeftHanded_Call struct { | |||
| 282 | + | *mock.Call | |||
| 283 | + | } | |||
| 284 | + | ||||
| 285 | + | // SetLeftHanded is a helper method to define mock.On call | |||
| 286 | + | // - dev uintptr | |||
| 287 | + | // - on bool | |||
| 288 | + | func (_e *mockdeviceConfig_Expecter) SetLeftHanded(dev any, on any) *mockdeviceConfig_SetLeftHanded_Call { | |||
| 289 | + | return &mockdeviceConfig_SetLeftHanded_Call{Call: _e.mock.On("SetLeftHanded", dev, on)} | |||
| 290 | + | } | |||
| 291 | + | ||||
| 292 | + | func (_c *mockdeviceConfig_SetLeftHanded_Call) Run(run func(dev uintptr, on bool)) *mockdeviceConfig_SetLeftHanded_Call { | |||
| 293 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 294 | + | var arg0 uintptr | |||
| 295 | + | if args[0] != nil { | |||
| 296 | + | arg0 = args[0].(uintptr) | |||
| 297 | + | } | |||
| 298 | + | var arg1 bool | |||
| 299 | + | if args[1] != nil { | |||
| 300 | + | arg1 = args[1].(bool) | |||
| 301 | + | } | |||
| 302 | + | run( | |||
| 303 | + | arg0, | |||
| 304 | + | arg1, | |||
| 305 | + | ) | |||
| 306 | + | }) | |||
| 307 | + | return _c | |||
| 308 | + | } | |||
| 309 | + | ||||
| 310 | + | func (_c *mockdeviceConfig_SetLeftHanded_Call) Return(err error) *mockdeviceConfig_SetLeftHanded_Call { | |||
| 311 | + | _c.Call.Return(err) | |||
| 312 | + | return _c | |||
| 313 | + | } | |||
| 314 | + | ||||
| 315 | + | func (_c *mockdeviceConfig_SetLeftHanded_Call) RunAndReturn(run func(dev uintptr, on bool) error) *mockdeviceConfig_SetLeftHanded_Call { | |||
| 316 | + | _c.Call.Return(run) | |||
| 317 | + | return _c | |||
| 318 | + | } | |||
| 319 | + | ||||
| 320 | + | // SetNaturalScroll provides a mock function for the type mockdeviceConfig | |||
| 321 | + | func (_mock *mockdeviceConfig) SetNaturalScroll(dev uintptr, on bool) error { | |||
| 322 | + | ret := _mock.Called(dev, on) | |||
| 323 | + | ||||
| 324 | + | if len(ret) == 0 { | |||
| 325 | + | panic("no return value specified for SetNaturalScroll") | |||
| 326 | + | } | |||
| 327 | + | ||||
| 328 | + | var r0 error | |||
| 329 | + | if returnFunc, ok := ret.Get(0).(func(uintptr, bool) error); ok { | |||
| 330 | + | r0 = returnFunc(dev, on) | |||
| 331 | + | } else { | |||
| 332 | + | r0 = ret.Error(0) | |||
| 333 | + | } | |||
| 334 | + | return r0 | |||
| 335 | + | } | |||
| 336 | + | ||||
| 337 | + | // mockdeviceConfig_SetNaturalScroll_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetNaturalScroll' | |||
| 338 | + | type mockdeviceConfig_SetNaturalScroll_Call struct { | |||
| 339 | + | *mock.Call | |||
| 340 | + | } | |||
| 341 | + | ||||
| 342 | + | // SetNaturalScroll is a helper method to define mock.On call | |||
| 343 | + | // - dev uintptr | |||
| 344 | + | // - on bool | |||
| 345 | + | func (_e *mockdeviceConfig_Expecter) SetNaturalScroll(dev any, on any) *mockdeviceConfig_SetNaturalScroll_Call { | |||
| 346 | + | return &mockdeviceConfig_SetNaturalScroll_Call{Call: _e.mock.On("SetNaturalScroll", dev, on)} | |||
| 347 | + | } | |||
| 348 | + | ||||
| 349 | + | func (_c *mockdeviceConfig_SetNaturalScroll_Call) Run(run func(dev uintptr, on bool)) *mockdeviceConfig_SetNaturalScroll_Call { | |||
| 350 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 351 | + | var arg0 uintptr | |||
| 352 | + | if args[0] != nil { | |||
| 353 | + | arg0 = args[0].(uintptr) | |||
| 354 | + | } | |||
| 355 | + | var arg1 bool | |||
| 356 | + | if args[1] != nil { | |||
| 357 | + | arg1 = args[1].(bool) | |||
| 358 | + | } | |||
| 359 | + | run( | |||
| 360 | + | arg0, | |||
| 361 | + | arg1, | |||
| 362 | + | ) | |||
| 363 | + | }) | |||
| 364 | + | return _c | |||
| 365 | + | } | |||
| 366 | + | ||||
| 367 | + | func (_c *mockdeviceConfig_SetNaturalScroll_Call) Return(err error) *mockdeviceConfig_SetNaturalScroll_Call { | |||
| 368 | + | _c.Call.Return(err) | |||
| 369 | + | return _c | |||
| 370 | + | } | |||
| 371 | + | ||||
| 372 | + | func (_c *mockdeviceConfig_SetNaturalScroll_Call) RunAndReturn(run func(dev uintptr, on bool) error) *mockdeviceConfig_SetNaturalScroll_Call { | |||
| 373 | + | _c.Call.Return(run) | |||
| 374 | + | return _c | |||
| 375 | + | } | |||
| 376 | + | ||||
| 377 | + | // SetTap provides a mock function for the type mockdeviceConfig | |||
| 378 | + | func (_mock *mockdeviceConfig) SetTap(dev uintptr, on bool) error { | |||
| 379 | + | ret := _mock.Called(dev, on) | |||
| 380 | + | ||||
| 381 | + | if len(ret) == 0 { | |||
| 382 | + | panic("no return value specified for SetTap") | |||
| 383 | + | } | |||
| 384 | + | ||||
| 385 | + | var r0 error | |||
| 386 | + | if returnFunc, ok := ret.Get(0).(func(uintptr, bool) error); ok { | |||
| 387 | + | r0 = returnFunc(dev, on) | |||
| 388 | + | } else { | |||
| 389 | + | r0 = ret.Error(0) | |||
| 390 | + | } | |||
| 391 | + | return r0 | |||
| 392 | + | } | |||
| 393 | + | ||||
| 394 | + | // mockdeviceConfig_SetTap_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetTap' | |||
| 395 | + | type mockdeviceConfig_SetTap_Call struct { | |||
| 396 | + | *mock.Call | |||
| 397 | + | } | |||
| 398 | + | ||||
| 399 | + | // SetTap is a helper method to define mock.On call | |||
| 400 | + | // - dev uintptr | |||
| 401 | + | // - on bool | |||
| 402 | + | func (_e *mockdeviceConfig_Expecter) SetTap(dev any, on any) *mockdeviceConfig_SetTap_Call { | |||
| 403 | + | return &mockdeviceConfig_SetTap_Call{Call: _e.mock.On("SetTap", dev, on)} | |||
| 404 | + | } | |||
| 405 | + | ||||
| 406 | + | func (_c *mockdeviceConfig_SetTap_Call) Run(run func(dev uintptr, on bool)) *mockdeviceConfig_SetTap_Call { | |||
| 407 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 408 | + | var arg0 uintptr | |||
| 409 | + | if args[0] != nil { | |||
| 410 | + | arg0 = args[0].(uintptr) | |||
| 411 | + | } | |||
| 412 | + | var arg1 bool | |||
| 413 | + | if args[1] != nil { | |||
| 414 | + | arg1 = args[1].(bool) | |||
| 415 | + | } | |||
| 416 | + | run( | |||
| 417 | + | arg0, | |||
| 418 | + | arg1, | |||
| 419 | + | ) | |||
| 420 | + | }) | |||
| 421 | + | return _c | |||
| 422 | + | } | |||
| 423 | + | ||||
| 424 | + | func (_c *mockdeviceConfig_SetTap_Call) Return(err error) *mockdeviceConfig_SetTap_Call { | |||
| 425 | + | _c.Call.Return(err) | |||
| 426 | + | return _c | |||
| 427 | + | } | |||
| 428 | + | ||||
| 429 | + | func (_c *mockdeviceConfig_SetTap_Call) RunAndReturn(run func(dev uintptr, on bool) error) *mockdeviceConfig_SetTap_Call { | |||
| 430 | + | _c.Call.Return(run) | |||
| 431 | + | return _c | |||
| 432 | + | } | |||
M internal/adapters/libinput/libinput.go +186 -95
| @@ -39,57 +39,59 @@ var ( | |||
| 39 | 39 | loadOnce sync.Once | |
| 40 | 40 | loadErr error | |
| 41 | 41 | ||
| 42 | - | udevNew func() uintptr | |
| 43 | - | udevUnref func(uintptr) uintptr | |
| 44 | - | createContext func(iface unsafe.Pointer, data uintptr, udev uintptr) uintptr | |
| 45 | - | assignSeat func(li uintptr, seat string) int32 | |
| 46 | - | unref func(li uintptr) uintptr | |
| 47 | - | getFD func(li uintptr) int32 | |
| 48 | - | dispatch func(li uintptr) int32 | |
| 49 | - | getEvent func(li uintptr) uintptr | |
| 50 | - | eventType func(ev uintptr) int32 | |
| 51 | - | eventDestroy func(ev uintptr) | |
| 52 | - | eventDevice func(ev uintptr) uintptr | |
| 53 | - | deviceName func(dev uintptr) string | |
| 54 | - | suspend func(li uintptr) | |
| 55 | - | resume func(li uintptr) int32 | |
| 56 | - | keyboardEvent func(ev uintptr) uintptr | |
| 57 | - | keyboardKey func(kev uintptr) uint32 | |
| 58 | - | keyboardState func(kev uintptr) int32 | |
| 59 | - | keyboardUsec func(kev uintptr) uint64 | |
| 60 | - | pointerEvent func(ev uintptr) uintptr | |
| 61 | - | pointerDX func(pev uintptr) float64 | |
| 62 | - | pointerDY func(pev uintptr) float64 | |
| 63 | - | pointerRawDX func(pev uintptr) float64 | |
| 64 | - | pointerRawDY func(pev uintptr) float64 | |
| 65 | - | pointerUsec func(pev uintptr) uint64 | |
| 66 | - | pointerAbsX func(pev uintptr, width uint32) float64 | |
| 67 | - | pointerAbsY func(pev uintptr, height uint32) float64 | |
| 68 | - | pointerButton func(pev uintptr) uint32 | |
| 69 | - | pointerBtnState func(pev uintptr) int32 | |
| 70 | - | pointerHasAxis func(pev uintptr, axis uint32) int32 | |
| 71 | - | scrollValue func(pev uintptr, axis uint32) float64 | |
| 72 | - | scrollV120 func(pev uintptr, axis uint32) float64 | |
| 73 | - | gestureEvent func(ev uintptr) uintptr | |
| 74 | - | gestureFingers func(gev uintptr) int32 | |
| 75 | - | gestureCanceled func(gev uintptr) int32 | |
| 76 | - | gestureDX func(gev uintptr) float64 | |
| 77 | - | gestureDY func(gev uintptr) float64 | |
| 78 | - | gestureUsec func(gev uintptr) uint64 | |
| 79 | - | deviceRef func(dev uintptr) uintptr | |
| 80 | - | deviceUnref func(dev uintptr) uintptr | |
| 81 | - | hasNatural func(dev uintptr) int32 | |
| 82 | - | setNatural func(dev uintptr, on int32) int32 | |
| 83 | - | tapFingers func(dev uintptr) int32 | |
| 84 | - | setTap func(dev uintptr, on int32) int32 | |
| 85 | - | setTapMap func(dev uintptr, m int32) int32 | |
| 86 | - | hasCapability func(dev uintptr, capability int32) int32 | |
| 87 | - | accelAvailable func(dev uintptr) int32 | |
| 88 | - | setAccelSpeed func(dev uintptr, speed float64) int32 | |
| 89 | - | accelProfiles func(dev uintptr) uint32 | |
| 90 | - | setAccelProfile func(dev uintptr, profile uint32) int32 | |
| 91 | - | iface [2]uintptr | |
| 92 | - | active ports.Seat | |
| 42 | + | udevNew func() uintptr | |
| 43 | + | udevUnref func(uintptr) uintptr | |
| 44 | + | createContext func(iface unsafe.Pointer, data uintptr, udev uintptr) uintptr | |
| 45 | + | assignSeat func(li uintptr, seat string) int32 | |
| 46 | + | unref func(li uintptr) uintptr | |
| 47 | + | getFD func(li uintptr) int32 | |
| 48 | + | dispatch func(li uintptr) int32 | |
| 49 | + | getEvent func(li uintptr) uintptr | |
| 50 | + | eventType func(ev uintptr) int32 | |
| 51 | + | eventDestroy func(ev uintptr) | |
| 52 | + | eventDevice func(ev uintptr) uintptr | |
| 53 | + | deviceName func(dev uintptr) string | |
| 54 | + | suspend func(li uintptr) | |
| 55 | + | resume func(li uintptr) int32 | |
| 56 | + | keyboardEvent func(ev uintptr) uintptr | |
| 57 | + | keyboardKey func(kev uintptr) uint32 | |
| 58 | + | keyboardState func(kev uintptr) int32 | |
| 59 | + | keyboardUsec func(kev uintptr) uint64 | |
| 60 | + | pointerEvent func(ev uintptr) uintptr | |
| 61 | + | pointerDX func(pev uintptr) float64 | |
| 62 | + | pointerDY func(pev uintptr) float64 | |
| 63 | + | pointerRawDX func(pev uintptr) float64 | |
| 64 | + | pointerRawDY func(pev uintptr) float64 | |
| 65 | + | pointerUsec func(pev uintptr) uint64 | |
| 66 | + | pointerAbsX func(pev uintptr, width uint32) float64 | |
| 67 | + | pointerAbsY func(pev uintptr, height uint32) float64 | |
| 68 | + | pointerButton func(pev uintptr) uint32 | |
| 69 | + | pointerBtnState func(pev uintptr) int32 | |
| 70 | + | pointerHasAxis func(pev uintptr, axis uint32) int32 | |
| 71 | + | scrollValue func(pev uintptr, axis uint32) float64 | |
| 72 | + | scrollV120 func(pev uintptr, axis uint32) float64 | |
| 73 | + | gestureEvent func(ev uintptr) uintptr | |
| 74 | + | gestureFingers func(gev uintptr) int32 | |
| 75 | + | gestureCanceled func(gev uintptr) int32 | |
| 76 | + | gestureDX func(gev uintptr) float64 | |
| 77 | + | gestureDY func(gev uintptr) float64 | |
| 78 | + | gestureUsec func(gev uintptr) uint64 | |
| 79 | + | deviceRef func(dev uintptr) uintptr | |
| 80 | + | deviceUnref func(dev uintptr) uintptr | |
| 81 | + | hasNatural func(dev uintptr) int32 | |
| 82 | + | setNatural func(dev uintptr, on int32) int32 | |
| 83 | + | tapFingers func(dev uintptr) int32 | |
| 84 | + | setTap func(dev uintptr, on int32) int32 | |
| 85 | + | setTapMap func(dev uintptr, m int32) int32 | |
| 86 | + | hasCapability func(dev uintptr, capability int32) int32 | |
| 87 | + | accelAvailable func(dev uintptr) int32 | |
| 88 | + | setAccelSpeed func(dev uintptr, speed float64) int32 | |
| 89 | + | accelProfiles func(dev uintptr) uint32 | |
| 90 | + | leftHandedAvailable func(dev uintptr) int32 | |
| 91 | + | setLeftHanded func(dev uintptr, on int32) int32 | |
| 92 | + | setAccelProfile func(dev uintptr, profile uint32) int32 | |
| 93 | + | iface [2]uintptr | |
| 94 | + | active ports.Seat | |
| 93 | 95 | ) | |
| 94 | 96 | ||
| 95 | 97 | func load() error { | |
| @@ -159,6 +161,8 @@ func load() error { | |||
| 159 | 161 | reg(&setAccelSpeed, "device_config_accel_set_speed") | |
| 160 | 162 | reg(&accelProfiles, "device_config_accel_get_profiles") | |
| 161 | 163 | reg(&setAccelProfile, "device_config_accel_set_profile") | |
| 164 | + | reg(&leftHandedAvailable, "device_config_left_handed_is_available") | |
| 165 | + | reg(&setLeftHanded, "device_config_left_handed_set") | |
| 162 | 166 | iface[0] = purego.NewCallback(func(path *byte, _, _ uintptr) uintptr { | |
| 163 | 167 | fd, err := active.OpenDevice(goString(path)) | |
| 164 | 168 | if err != nil { | |
| @@ -199,10 +203,12 @@ type Options struct { | |||
| 199 | 203 | // Constraints holds the pointer lock or confinement of the focused | |
| 200 | 204 | // window, in global logical coordinates. | |
| 201 | 205 | Constraints <-chan ports.PointerConstraint | |
| 202 | - | // Touchpad is the initial touchpad config; Touchpads replaces it live. | |
| 203 | - | Touchpad ports.TouchpadConfig | |
| 204 | - | Touchpads <-chan ports.TouchpadConfig | |
| 205 | - | Active <-chan bool | |
| 206 | + | // DeviceConfig is the initial config: Touchpad for touchpads, Mouse for | |
| 207 | + | // the other pointers (mice, trackballs, trackpoints); other devices are | |
| 208 | + | // left alone. DeviceConfigs replaces it live, on the devices already added. | |
| 209 | + | DeviceConfig ports.InputDevicesConfig | |
| 210 | + | DeviceConfigs <-chan ports.InputDevicesConfig | |
| 211 | + | Active <-chan bool | |
| 206 | 212 | // MoveCursor, when set, places the hardware cursor as soon as motion is | |
| 207 | 213 | // read, before core sees the event: the output under the pointer and | |
| 208 | 214 | // the physical position on it. motion is false when the pointer is only | |
| @@ -246,7 +252,7 @@ func Run(ctx context.Context, opts Options, input chan<- ports.InputEvent) error | |||
| 246 | 252 | } | |
| 247 | 253 | p := newPointer(opts.Layout) | |
| 248 | 254 | p.moved(opts.MoveCursor, false) | |
| 249 | - | in := &inputState{touchpad: opts.Touchpad, devices: map[uintptr]bool{}} | |
| 255 | + | in := &inputState{cfg: opts.DeviceConfig, dev: libinputDevices{}, devices: map[uintptr]bool{}} | |
| 250 | 256 | defer in.release() | |
| 251 | 257 | fd := getFD(li) | |
| 252 | 258 | fwd := newForwarder(opts.Log) | |
| @@ -283,12 +289,14 @@ func Run(ctx context.Context, opts Options, input chan<- ports.InputEvent) error | |||
| 283 | 289 | case c := <-opts.Constraints: | |
| 284 | 290 | p.constrain(c) | |
| 285 | 291 | p.moved(opts.MoveCursor, false) | |
| 286 | - | case t := <-opts.Touchpads: | |
| 287 | - | in.touchpad = t | |
| 292 | + | case c := <-opts.DeviceConfigs: | |
| 293 | + | in.cfg = c | |
| 288 | 294 | for dev := range in.devices { | |
| 289 | 295 | in.configure(dev, opts.Log) | |
| 290 | 296 | } | |
| 291 | - | opts.Log.Info().Bool("natural_scroll", t.NaturalScroll).Bool("tap", t.Tap).Float64("accel_speed", t.AccelSpeed).Str("accel_profile", t.AccelProfile).Float64("scroll_factor", t.ScrollFactor).Msg("touchpad reloaded") | |
| 297 | + | t, m := c.Touchpad, c.Mouse | |
| 298 | + | opts.Log.Info().Bool("natural_scroll", t.NaturalScroll).Bool("tap", t.Tap).Float64("accel_speed", t.AccelSpeed).Str("accel_profile", t.AccelProfile).Bool("left_handed", t.LeftHanded).Float64("scroll_factor", t.ScrollFactor). | |
| 299 | + | Bool("mouse_natural_scroll", m.NaturalScroll).Float64("mouse_accel_speed", m.AccelSpeed).Str("mouse_accel_profile", m.AccelProfile).Bool("mouse_left_handed", m.LeftHanded).Msg("input devices reloaded") | |
| 292 | 300 | default: | |
| 293 | 301 | } | |
| 294 | 302 | fds := []unix.PollFd{{Fd: fd, Events: unix.POLLIN}} | |
| @@ -316,51 +324,134 @@ func Run(ctx context.Context, opts Options, input chan<- ports.InputEvent) error | |||
| 316 | 324 | return nil | |
| 317 | 325 | } | |
| 318 | 326 | ||
| 319 | - | // inputState is the device state Run owns: touchpad config, the devices | |
| 327 | + | // inputState is the device state Run owns: the device config, the devices | |
| 320 | 328 | // it configures (referenced until removed) and the swipe in progress on | |
| 321 | - | // each touchpad. | |
| 329 | + | // each touchpad. dev applies the config to libinput; it is a zero-size | |
| 330 | + | // type, so storing it in the interface allocates nothing. | |
| 322 | 331 | type inputState struct { | |
| 323 | - | touchpad ports.TouchpadConfig | |
| 324 | - | devices map[uintptr]bool | |
| 325 | - | swipes swipes | |
| 332 | + | cfg ports.InputDevicesConfig | |
| 333 | + | dev deviceConfig | |
| 334 | + | devices map[uintptr]bool | |
| 335 | + | swipes swipes | |
| 326 | 336 | } | |
| 327 | 337 | ||
| 328 | - | // configurable reports whether the touchpad config applies to dev: it has | |
| 329 | - | // natural scroll or tapping, or is a touchpad. | |
| 330 | - | func configurable(dev uintptr) bool { | |
| 331 | - | return hasNatural(dev) != 0 || tapFingers(dev) > 0 || touchpad(dev) | |
| 338 | + | // deviceConfig is libinput's device configuration side: what a device is | |
| 339 | + | // and the settings it takes. libinputDevices is the real one; tests use its | |
| 340 | + | // Mockery mock. It serves device-added and config reload only, never the | |
| 341 | + | // event path. A setting the device lacks is skipped without error; one it | |
| 342 | + | // refuses returns a sentinel error. | |
| 343 | + | type deviceConfig interface { | |
| 344 | + | // IsTouchpad reports the gesture capability libinput gives touchpads. | |
| 345 | + | IsTouchpad(dev uintptr) bool | |
| 346 | + | // IsPointer reports the pointer capability: mice, trackballs, | |
| 347 | + | // trackpoints and touchpads. | |
| 348 | + | IsPointer(dev uintptr) bool | |
| 349 | + | // Name is the device name, for logs. | |
| 350 | + | Name(dev uintptr) string | |
| 351 | + | SetNaturalScroll(dev uintptr, on bool) error | |
| 352 | + | // SetAccel sets the pointer speed and, when the device offers it, the | |
| 353 | + | // profile (ports.AccelFlat or ports.AccelAdaptive). | |
| 354 | + | SetAccel(dev uintptr, speed float64, profile string) error | |
| 355 | + | SetLeftHanded(dev uintptr, on bool) error | |
| 356 | + | // SetTap enables tap to click, with one finger left, two right, three | |
| 357 | + | // middle. Devices that cannot tap skip it. | |
| 358 | + | SetTap(dev uintptr, on bool) error | |
| 332 | 359 | } | |
| 333 | 360 | ||
| 334 | - | // capGesture is LIBINPUT_DEVICE_CAP_GESTURE: libinput gives it to | |
| 335 | - | // touchpads, with or without tapping. | |
| 336 | - | const capGesture = 5 | |
| 361 | + | // errRejected is a setting the device refuses (libinput status not success). | |
| 362 | + | var errRejected = errors.New("rejected by libinput") | |
| 337 | 363 | ||
| 338 | - | func touchpad(dev uintptr) bool { return hasCapability(dev, capGesture) != 0 } | |
| 364 | + | // libinputDevices implements deviceConfig over the libinput functions. | |
| 365 | + | type libinputDevices struct{} | |
| 339 | 366 | ||
| 340 | - | // tapButtonMap is LIBINPUT_CONFIG_TAP_MAP_LRM: one finger taps left, two | |
| 341 | - | // right, three middle. | |
| 342 | - | const tapButtonMap = 0 | |
| 367 | + | const ( | |
| 368 | + | // capPointer is LIBINPUT_DEVICE_CAP_POINTER. | |
| 369 | + | capPointer = 1 | |
| 370 | + | // capGesture is LIBINPUT_DEVICE_CAP_GESTURE: libinput gives it to | |
| 371 | + | // touchpads, with or without tapping. | |
| 372 | + | capGesture = 5 | |
| 373 | + | // tapButtonMap is LIBINPUT_CONFIG_TAP_MAP_LRM. | |
| 374 | + | tapButtonMap = 0 | |
| 375 | + | ) | |
| 343 | 376 | ||
| 344 | - | func (s *inputState) configure(dev uintptr, log zerowrap.Logger) { | |
| 345 | - | if hasNatural(dev) != 0 && setNatural(dev, flag(s.touchpad.NaturalScroll)) != 0 { | |
| 346 | - | log.Warn().Str("device", deviceName(dev)).Msg("natural scroll rejected") | |
| 377 | + | func (libinputDevices) IsTouchpad(dev uintptr) bool { return hasCapability(dev, capGesture) != 0 } | |
| 378 | + | func (libinputDevices) IsPointer(dev uintptr) bool { return hasCapability(dev, capPointer) != 0 } | |
| 379 | + | func (libinputDevices) Name(dev uintptr) string { return deviceName(dev) } | |
| 380 | + | ||
| 381 | + | func (libinputDevices) SetNaturalScroll(dev uintptr, on bool) error { | |
| 382 | + | if hasNatural(dev) != 0 && setNatural(dev, flag(on)) != 0 { | |
| 383 | + | return errRejected | |
| 347 | 384 | } | |
| 348 | - | if tapFingers(dev) > 0 { | |
| 349 | - | if setTap(dev, flag(s.touchpad.Tap)) != 0 { | |
| 350 | - | log.Warn().Str("device", deviceName(dev)).Msg("tap to click rejected") | |
| 351 | - | } | |
| 352 | - | if setTapMap(dev, tapButtonMap) != 0 { | |
| 353 | - | log.Warn().Str("device", deviceName(dev)).Msg("tap button map rejected") | |
| 354 | - | } | |
| 385 | + | return nil | |
| 386 | + | } | |
| 387 | + | ||
| 388 | + | func (libinputDevices) SetAccel(dev uintptr, speed float64, profile string) error { | |
| 389 | + | if accelAvailable(dev) == 0 { | |
| 390 | + | return nil | |
| 355 | 391 | } | |
| 356 | - | // Pointer speed is a touchpad setting: mice keep libinput's own. | |
| 357 | - | if touchpad(dev) && accelAvailable(dev) != 0 { | |
| 358 | - | if setAccelSpeed(dev, s.touchpad.AccelSpeed) != 0 { | |
| 359 | - | log.Warn().Str("device", deviceName(dev)).Msg("accel speed rejected") | |
| 360 | - | } | |
| 361 | - | if p := accelProfile(s.touchpad.AccelProfile); accelProfiles(dev)&p != 0 && setAccelProfile(dev, p) != 0 { | |
| 362 | - | log.Warn().Str("device", deviceName(dev)).Msg("accel profile rejected") | |
| 363 | - | } | |
| 392 | + | var err error | |
| 393 | + | if setAccelSpeed(dev, speed) != 0 { | |
| 394 | + | err = errRejected | |
| 395 | + | } | |
| 396 | + | if p := accelProfile(profile); accelProfiles(dev)&p != 0 && setAccelProfile(dev, p) != 0 { | |
| 397 | + | err = errRejected | |
| 398 | + | } | |
| 399 | + | return err | |
| 400 | + | } | |
| 401 | + | ||
| 402 | + | func (libinputDevices) SetLeftHanded(dev uintptr, on bool) error { | |
| 403 | + | if leftHandedAvailable(dev) != 0 && setLeftHanded(dev, flag(on)) != 0 { | |
| 404 | + | return errRejected | |
| 405 | + | } | |
| 406 | + | return nil | |
| 407 | + | } | |
| 408 | + | ||
| 409 | + | func (libinputDevices) SetTap(dev uintptr, on bool) error { | |
| 410 | + | if tapFingers(dev) <= 0 { | |
| 411 | + | return nil | |
| 412 | + | } | |
| 413 | + | var err error | |
| 414 | + | if setTap(dev, flag(on)) != 0 { | |
| 415 | + | err = errRejected | |
| 416 | + | } | |
| 417 | + | if setTapMap(dev, tapButtonMap) != 0 { | |
| 418 | + | err = errRejected | |
| 419 | + | } | |
| 420 | + | return err | |
| 421 | + | } | |
| 422 | + | ||
| 423 | + | // configurable reports whether the device config applies to dev: a | |
| 424 | + | // touchpad or another pointer. Keyboards, switches and the like are left | |
| 425 | + | // alone and not referenced. | |
| 426 | + | func (s *inputState) configurable(dev uintptr) bool { | |
| 427 | + | return s.dev.IsTouchpad(dev) || s.dev.IsPointer(dev) | |
| 428 | + | } | |
| 429 | + | ||
| 430 | + | // configure applies the config to dev by type: touchpads take the touchpad | |
| 431 | + | // settings and tapping, other pointers (mice, trackballs, trackpoints) the | |
| 432 | + | // mouse settings, other devices nothing. A refused setting is logged and | |
| 433 | + | // the rest still apply. The success path logs and allocates nothing. | |
| 434 | + | func (s *inputState) configure(dev uintptr, log zerowrap.Logger) { | |
| 435 | + | switch { | |
| 436 | + | case s.dev.IsTouchpad(dev): | |
| 437 | + | s.applyPointer(dev, s.cfg.Touchpad.PointerConfig, log) | |
| 438 | + | s.check(dev, "tap to click", s.dev.SetTap(dev, s.cfg.Touchpad.Tap), log) | |
| 439 | + | case s.dev.IsPointer(dev): | |
| 440 | + | s.applyPointer(dev, s.cfg.Mouse, log) | |
| 441 | + | } | |
| 442 | + | } | |
| 443 | + | ||
| 444 | + | // applyPointer applies the settings touchpads and mice share. | |
| 445 | + | func (s *inputState) applyPointer(dev uintptr, c ports.PointerConfig, log zerowrap.Logger) { | |
| 446 | + | s.check(dev, "natural scroll", s.dev.SetNaturalScroll(dev, c.NaturalScroll), log) | |
| 447 | + | s.check(dev, "accel", s.dev.SetAccel(dev, c.AccelSpeed, c.AccelProfile), log) | |
| 448 | + | s.check(dev, "left handed", s.dev.SetLeftHanded(dev, c.LeftHanded), log) | |
| 449 | + | } | |
| 450 | + | ||
| 451 | + | // check logs a refused setting; the device name is only read then. | |
| 452 | + | func (s *inputState) check(dev uintptr, setting string, err error, log zerowrap.Logger) { | |
| 453 | + | if err != nil { | |
| 454 | + | log.Warn().Err(err).Str("device", s.dev.Name(dev)).Str("setting", setting).Msg("input device setting rejected") | |
| 364 | 455 | } | |
| 365 | 456 | } | |
| 366 | 457 | ||
| @@ -453,7 +544,7 @@ func translateEvent(ev uintptr, opts Options, p *pointer, in *inputState, state | |||
| 453 | 544 | case evDeviceAdded: | |
| 454 | 545 | dev := eventDevice(ev) | |
| 455 | 546 | log.Info().Str("device", deviceName(dev)).Msg("input device added") | |
| 456 | - | if configurable(dev) && !in.devices[dev] { | |
| 547 | + | if in.configurable(dev) && !in.devices[dev] { | |
| 457 | 548 | in.devices[deviceRef(dev)] = true | |
| 458 | 549 | in.configure(dev, log) | |
| 459 | 550 | } | |
| @@ -519,7 +610,7 @@ func translateEvent(ev uintptr, opts Options, p *pointer, in *inputState, state | |||
| 519 | 610 | s.Set, s.Value = true, scrollValue(pe, uint32(i)) | |
| 520 | 611 | if source == ports.AxisFinger { | |
| 521 | 612 | // Two-finger scroll is the touchpad's only scroll source. | |
| 522 | - | s.Value *= in.touchpad.ScrollFactor | |
| 613 | + | s.Value *= in.cfg.Touchpad.ScrollFactor | |
| 523 | 614 | } | |
| 524 | 615 | if source == ports.AxisWheel { | |
| 525 | 616 | s.V120 = int32(scrollV120(pe, uint32(i))) | |
| @@ -39,57 +39,59 @@ var ( | |||||
| 39 | loadOnce sync.Once | 39 | loadOnce sync.Once | ||
| 40 | loadErr error | 40 | loadErr error | ||
| 41 | 41 | ||||
| 42 | - | udevNew func() uintptr | 42 | + | udevNew func() uintptr |
| 43 | - | udevUnref func(uintptr) uintptr | 43 | + | udevUnref func(uintptr) uintptr |
| 44 | - | createContext func(iface unsafe.Pointer, data uintptr, udev uintptr) uintptr | 44 | + | createContext func(iface unsafe.Pointer, data uintptr, udev uintptr) uintptr |
| 45 | - | assignSeat func(li uintptr, seat string) int32 | 45 | + | assignSeat func(li uintptr, seat string) int32 |
| 46 | - | unref func(li uintptr) uintptr | 46 | + | unref func(li uintptr) uintptr |
| 47 | - | getFD func(li uintptr) int32 | 47 | + | getFD func(li uintptr) int32 |
| 48 | - | dispatch func(li uintptr) int32 | 48 | + | dispatch func(li uintptr) int32 |
| 49 | - | getEvent func(li uintptr) uintptr | 49 | + | getEvent func(li uintptr) uintptr |
| 50 | - | eventType func(ev uintptr) int32 | 50 | + | eventType func(ev uintptr) int32 |
| 51 | - | eventDestroy func(ev uintptr) | 51 | + | eventDestroy func(ev uintptr) |
| 52 | - | eventDevice func(ev uintptr) uintptr | 52 | + | eventDevice func(ev uintptr) uintptr |
| 53 | - | deviceName func(dev uintptr) string | 53 | + | deviceName func(dev uintptr) string |
| 54 | - | suspend func(li uintptr) | 54 | + | suspend func(li uintptr) |
| 55 | - | resume func(li uintptr) int32 | 55 | + | resume func(li uintptr) int32 |
| 56 | - | keyboardEvent func(ev uintptr) uintptr | 56 | + | keyboardEvent func(ev uintptr) uintptr |
| 57 | - | keyboardKey func(kev uintptr) uint32 | 57 | + | keyboardKey func(kev uintptr) uint32 |
| 58 | - | keyboardState func(kev uintptr) int32 | 58 | + | keyboardState func(kev uintptr) int32 |
| 59 | - | keyboardUsec func(kev uintptr) uint64 | 59 | + | keyboardUsec func(kev uintptr) uint64 |
| 60 | - | pointerEvent func(ev uintptr) uintptr | 60 | + | pointerEvent func(ev uintptr) uintptr |
| 61 | - | pointerDX func(pev uintptr) float64 | 61 | + | pointerDX func(pev uintptr) float64 |
| 62 | - | pointerDY func(pev uintptr) float64 | 62 | + | pointerDY func(pev uintptr) float64 |
| 63 | - | pointerRawDX func(pev uintptr) float64 | 63 | + | pointerRawDX func(pev uintptr) float64 |
| 64 | - | pointerRawDY func(pev uintptr) float64 | 64 | + | pointerRawDY func(pev uintptr) float64 |
| 65 | - | pointerUsec func(pev uintptr) uint64 | 65 | + | pointerUsec func(pev uintptr) uint64 |
| 66 | - | pointerAbsX func(pev uintptr, width uint32) float64 | 66 | + | pointerAbsX func(pev uintptr, width uint32) float64 |
| 67 | - | pointerAbsY func(pev uintptr, height uint32) float64 | 67 | + | pointerAbsY func(pev uintptr, height uint32) float64 |
| 68 | - | pointerButton func(pev uintptr) uint32 | 68 | + | pointerButton func(pev uintptr) uint32 |
| 69 | - | pointerBtnState func(pev uintptr) int32 | 69 | + | pointerBtnState func(pev uintptr) int32 |
| 70 | - | pointerHasAxis func(pev uintptr, axis uint32) int32 | 70 | + | pointerHasAxis func(pev uintptr, axis uint32) int32 |
| 71 | - | scrollValue func(pev uintptr, axis uint32) float64 | 71 | + | scrollValue func(pev uintptr, axis uint32) float64 |
| 72 | - | scrollV120 func(pev uintptr, axis uint32) float64 | 72 | + | scrollV120 func(pev uintptr, axis uint32) float64 |
| 73 | - | gestureEvent func(ev uintptr) uintptr | 73 | + | gestureEvent func(ev uintptr) uintptr |
| 74 | - | gestureFingers func(gev uintptr) int32 | 74 | + | gestureFingers func(gev uintptr) int32 |
| 75 | - | gestureCanceled func(gev uintptr) int32 | 75 | + | gestureCanceled func(gev uintptr) int32 |
| 76 | - | gestureDX func(gev uintptr) float64 | 76 | + | gestureDX func(gev uintptr) float64 |
| 77 | - | gestureDY func(gev uintptr) float64 | 77 | + | gestureDY func(gev uintptr) float64 |
| 78 | - | gestureUsec func(gev uintptr) uint64 | 78 | + | gestureUsec func(gev uintptr) uint64 |
| 79 | - | deviceRef func(dev uintptr) uintptr | 79 | + | deviceRef func(dev uintptr) uintptr |
| 80 | - | deviceUnref func(dev uintptr) uintptr | 80 | + | deviceUnref func(dev uintptr) uintptr |
| 81 | - | hasNatural func(dev uintptr) int32 | 81 | + | hasNatural func(dev uintptr) int32 |
| 82 | - | setNatural func(dev uintptr, on int32) int32 | 82 | + | setNatural func(dev uintptr, on int32) int32 |
| 83 | - | tapFingers func(dev uintptr) int32 | 83 | + | tapFingers func(dev uintptr) int32 |
| 84 | - | setTap func(dev uintptr, on int32) int32 | 84 | + | setTap func(dev uintptr, on int32) int32 |
| 85 | - | setTapMap func(dev uintptr, m int32) int32 | 85 | + | setTapMap func(dev uintptr, m int32) int32 |
| 86 | - | hasCapability func(dev uintptr, capability int32) int32 | 86 | + | hasCapability func(dev uintptr, capability int32) int32 |
| 87 | - | accelAvailable func(dev uintptr) int32 | 87 | + | accelAvailable func(dev uintptr) int32 |
| 88 | - | setAccelSpeed func(dev uintptr, speed float64) int32 | 88 | + | setAccelSpeed func(dev uintptr, speed float64) int32 |
| 89 | - | accelProfiles func(dev uintptr) uint32 | 89 | + | accelProfiles func(dev uintptr) uint32 |
| 90 | - | setAccelProfile func(dev uintptr, profile uint32) int32 | 90 | + | leftHandedAvailable func(dev uintptr) int32 |
| 91 | - | iface [2]uintptr | 91 | + | setLeftHanded func(dev uintptr, on int32) int32 |
| 92 | - | active ports.Seat | 92 | + | setAccelProfile func(dev uintptr, profile uint32) int32 |
| 93 | + | iface [2]uintptr | |||
| 94 | + | active ports.Seat | |||
| 93 | ) | 95 | ) | ||
| 94 | 96 | ||||
| 95 | func load() error { | 97 | func load() error { | ||
| @@ -159,6 +161,8 @@ func load() error { | |||||
| 159 | reg(&setAccelSpeed, "device_config_accel_set_speed") | 161 | reg(&setAccelSpeed, "device_config_accel_set_speed") | ||
| 160 | reg(&accelProfiles, "device_config_accel_get_profiles") | 162 | reg(&accelProfiles, "device_config_accel_get_profiles") | ||
| 161 | reg(&setAccelProfile, "device_config_accel_set_profile") | 163 | reg(&setAccelProfile, "device_config_accel_set_profile") | ||
| 164 | + | reg(&leftHandedAvailable, "device_config_left_handed_is_available") | |||
| 165 | + | reg(&setLeftHanded, "device_config_left_handed_set") | |||
| 162 | iface[0] = purego.NewCallback(func(path *byte, _, _ uintptr) uintptr { | 166 | iface[0] = purego.NewCallback(func(path *byte, _, _ uintptr) uintptr { | ||
| 163 | fd, err := active.OpenDevice(goString(path)) | 167 | fd, err := active.OpenDevice(goString(path)) | ||
| 164 | if err != nil { | 168 | if err != nil { | ||
| @@ -199,10 +203,12 @@ type Options struct { | |||||
| 199 | // Constraints holds the pointer lock or confinement of the focused | 203 | // Constraints holds the pointer lock or confinement of the focused | ||
| 200 | // window, in global logical coordinates. | 204 | // window, in global logical coordinates. | ||
| 201 | Constraints <-chan ports.PointerConstraint | 205 | Constraints <-chan ports.PointerConstraint | ||
| 202 | - | // Touchpad is the initial touchpad config; Touchpads replaces it live. | 206 | + | // DeviceConfig is the initial config: Touchpad for touchpads, Mouse for |
| 203 | - | Touchpad ports.TouchpadConfig | 207 | + | // the other pointers (mice, trackballs, trackpoints); other devices are |
| 204 | - | Touchpads <-chan ports.TouchpadConfig | 208 | + | // left alone. DeviceConfigs replaces it live, on the devices already added. |
| 205 | - | Active <-chan bool | 209 | + | DeviceConfig ports.InputDevicesConfig |
| 210 | + | DeviceConfigs <-chan ports.InputDevicesConfig | |||
| 211 | + | Active <-chan bool | |||
| 206 | // MoveCursor, when set, places the hardware cursor as soon as motion is | 212 | // MoveCursor, when set, places the hardware cursor as soon as motion is | ||
| 207 | // read, before core sees the event: the output under the pointer and | 213 | // read, before core sees the event: the output under the pointer and | ||
| 208 | // the physical position on it. motion is false when the pointer is only | 214 | // the physical position on it. motion is false when the pointer is only | ||
| @@ -246,7 +252,7 @@ func Run(ctx context.Context, opts Options, input chan<- ports.InputEvent) error | |||||
| 246 | } | 252 | } | ||
| 247 | p := newPointer(opts.Layout) | 253 | p := newPointer(opts.Layout) | ||
| 248 | p.moved(opts.MoveCursor, false) | 254 | p.moved(opts.MoveCursor, false) | ||
| 249 | - | in := &inputState{touchpad: opts.Touchpad, devices: map[uintptr]bool{}} | 255 | + | in := &inputState{cfg: opts.DeviceConfig, dev: libinputDevices{}, devices: map[uintptr]bool{}} |
| 250 | defer in.release() | 256 | defer in.release() | ||
| 251 | fd := getFD(li) | 257 | fd := getFD(li) | ||
| 252 | fwd := newForwarder(opts.Log) | 258 | fwd := newForwarder(opts.Log) | ||
| @@ -283,12 +289,14 @@ func Run(ctx context.Context, opts Options, input chan<- ports.InputEvent) error | |||||
| 283 | case c := <-opts.Constraints: | 289 | case c := <-opts.Constraints: | ||
| 284 | p.constrain(c) | 290 | p.constrain(c) | ||
| 285 | p.moved(opts.MoveCursor, false) | 291 | p.moved(opts.MoveCursor, false) | ||
| 286 | - | case t := <-opts.Touchpads: | 292 | + | case c := <-opts.DeviceConfigs: |
| 287 | - | in.touchpad = t | 293 | + | in.cfg = c |
| 288 | for dev := range in.devices { | 294 | for dev := range in.devices { | ||
| 289 | in.configure(dev, opts.Log) | 295 | in.configure(dev, opts.Log) | ||
| 290 | } | 296 | } | ||
| 291 | - | opts.Log.Info().Bool("natural_scroll", t.NaturalScroll).Bool("tap", t.Tap).Float64("accel_speed", t.AccelSpeed).Str("accel_profile", t.AccelProfile).Float64("scroll_factor", t.ScrollFactor).Msg("touchpad reloaded") | 297 | + | t, m := c.Touchpad, c.Mouse |
| 298 | + | opts.Log.Info().Bool("natural_scroll", t.NaturalScroll).Bool("tap", t.Tap).Float64("accel_speed", t.AccelSpeed).Str("accel_profile", t.AccelProfile).Bool("left_handed", t.LeftHanded).Float64("scroll_factor", t.ScrollFactor). | |||
| 299 | + | Bool("mouse_natural_scroll", m.NaturalScroll).Float64("mouse_accel_speed", m.AccelSpeed).Str("mouse_accel_profile", m.AccelProfile).Bool("mouse_left_handed", m.LeftHanded).Msg("input devices reloaded") | |||
| 292 | default: | 300 | default: | ||
| 293 | } | 301 | } | ||
| 294 | fds := []unix.PollFd{{Fd: fd, Events: unix.POLLIN}} | 302 | fds := []unix.PollFd{{Fd: fd, Events: unix.POLLIN}} | ||
| @@ -316,51 +324,134 @@ func Run(ctx context.Context, opts Options, input chan<- ports.InputEvent) error | |||||
| 316 | return nil | 324 | return nil | ||
| 317 | } | 325 | } | ||
| 318 | 326 | ||||
| 319 | - | // inputState is the device state Run owns: touchpad config, the devices | 327 | + | // inputState is the device state Run owns: the device config, the devices |
| 320 | // it configures (referenced until removed) and the swipe in progress on | 328 | // it configures (referenced until removed) and the swipe in progress on | ||
| 321 | - | // each touchpad. | 329 | + | // each touchpad. dev applies the config to libinput; it is a zero-size |
| 330 | + | // type, so storing it in the interface allocates nothing. | |||
| 322 | type inputState struct { | 331 | type inputState struct { | ||
| 323 | - | touchpad ports.TouchpadConfig | 332 | + | cfg ports.InputDevicesConfig |
| 324 | - | devices map[uintptr]bool | 333 | + | dev deviceConfig |
| 325 | - | swipes swipes | 334 | + | devices map[uintptr]bool |
| 335 | + | swipes swipes | |||
| 326 | } | 336 | } | ||
| 327 | 337 | ||||
| 328 | - | // configurable reports whether the touchpad config applies to dev: it has | 338 | + | // deviceConfig is libinput's device configuration side: what a device is |
| 329 | - | // natural scroll or tapping, or is a touchpad. | 339 | + | // and the settings it takes. libinputDevices is the real one; tests use its |
| 330 | - | func configurable(dev uintptr) bool { | 340 | + | // Mockery mock. It serves device-added and config reload only, never the |
| 331 | - | return hasNatural(dev) != 0 || tapFingers(dev) > 0 || touchpad(dev) | 341 | + | // event path. A setting the device lacks is skipped without error; one it |
| 342 | + | // refuses returns a sentinel error. | |||
| 343 | + | type deviceConfig interface { | |||
| 344 | + | // IsTouchpad reports the gesture capability libinput gives touchpads. | |||
| 345 | + | IsTouchpad(dev uintptr) bool | |||
| 346 | + | // IsPointer reports the pointer capability: mice, trackballs, | |||
| 347 | + | // trackpoints and touchpads. | |||
| 348 | + | IsPointer(dev uintptr) bool | |||
| 349 | + | // Name is the device name, for logs. | |||
| 350 | + | Name(dev uintptr) string | |||
| 351 | + | SetNaturalScroll(dev uintptr, on bool) error | |||
| 352 | + | // SetAccel sets the pointer speed and, when the device offers it, the | |||
| 353 | + | // profile (ports.AccelFlat or ports.AccelAdaptive). | |||
| 354 | + | SetAccel(dev uintptr, speed float64, profile string) error | |||
| 355 | + | SetLeftHanded(dev uintptr, on bool) error | |||
| 356 | + | // SetTap enables tap to click, with one finger left, two right, three | |||
| 357 | + | // middle. Devices that cannot tap skip it. | |||
| 358 | + | SetTap(dev uintptr, on bool) error | |||
| 332 | } | 359 | } | ||
| 333 | 360 | ||||
| 334 | - | // capGesture is LIBINPUT_DEVICE_CAP_GESTURE: libinput gives it to | 361 | + | // errRejected is a setting the device refuses (libinput status not success). |
| 335 | - | // touchpads, with or without tapping. | 362 | + | var errRejected = errors.New("rejected by libinput") |
| 336 | - | const capGesture = 5 | |||
| 337 | 363 | ||||
| 338 | - | func touchpad(dev uintptr) bool { return hasCapability(dev, capGesture) != 0 } | 364 | + | // libinputDevices implements deviceConfig over the libinput functions. |
| 365 | + | type libinputDevices struct{} | |||
| 339 | 366 | ||||
| 340 | - | // tapButtonMap is LIBINPUT_CONFIG_TAP_MAP_LRM: one finger taps left, two | 367 | + | const ( |
| 341 | - | // right, three middle. | 368 | + | // capPointer is LIBINPUT_DEVICE_CAP_POINTER. |
| 342 | - | const tapButtonMap = 0 | 369 | + | capPointer = 1 |
| 370 | + | // capGesture is LIBINPUT_DEVICE_CAP_GESTURE: libinput gives it to | |||
| 371 | + | // touchpads, with or without tapping. | |||
| 372 | + | capGesture = 5 | |||
| 373 | + | // tapButtonMap is LIBINPUT_CONFIG_TAP_MAP_LRM. | |||
| 374 | + | tapButtonMap = 0 | |||
| 375 | + | ) | |||
| 343 | 376 | ||||
| 344 | - | func (s *inputState) configure(dev uintptr, log zerowrap.Logger) { | 377 | + | func (libinputDevices) IsTouchpad(dev uintptr) bool { return hasCapability(dev, capGesture) != 0 } |
| 345 | - | if hasNatural(dev) != 0 && setNatural(dev, flag(s.touchpad.NaturalScroll)) != 0 { | 378 | + | func (libinputDevices) IsPointer(dev uintptr) bool { return hasCapability(dev, capPointer) != 0 } |
| 346 | - | log.Warn().Str("device", deviceName(dev)).Msg("natural scroll rejected") | 379 | + | func (libinputDevices) Name(dev uintptr) string { return deviceName(dev) } |
| 380 | + | ||||
| 381 | + | func (libinputDevices) SetNaturalScroll(dev uintptr, on bool) error { | |||
| 382 | + | if hasNatural(dev) != 0 && setNatural(dev, flag(on)) != 0 { | |||
| 383 | + | return errRejected | |||
| 347 | } | 384 | } | ||
| 348 | - | if tapFingers(dev) > 0 { | 385 | + | return nil |
| 349 | - | if setTap(dev, flag(s.touchpad.Tap)) != 0 { | 386 | + | } |
| 350 | - | log.Warn().Str("device", deviceName(dev)).Msg("tap to click rejected") | 387 | + | |
| 351 | - | } | 388 | + | func (libinputDevices) SetAccel(dev uintptr, speed float64, profile string) error { |
| 352 | - | if setTapMap(dev, tapButtonMap) != 0 { | 389 | + | if accelAvailable(dev) == 0 { |
| 353 | - | log.Warn().Str("device", deviceName(dev)).Msg("tap button map rejected") | 390 | + | return nil |
| 354 | - | } | |||
| 355 | } | 391 | } | ||
| 356 | - | // Pointer speed is a touchpad setting: mice keep libinput's own. | 392 | + | var err error |
| 357 | - | if touchpad(dev) && accelAvailable(dev) != 0 { | 393 | + | if setAccelSpeed(dev, speed) != 0 { |
| 358 | - | if setAccelSpeed(dev, s.touchpad.AccelSpeed) != 0 { | 394 | + | err = errRejected |
| 359 | - | log.Warn().Str("device", deviceName(dev)).Msg("accel speed rejected") | 395 | + | } |
| 360 | - | } | 396 | + | if p := accelProfile(profile); accelProfiles(dev)&p != 0 && setAccelProfile(dev, p) != 0 { |
| 361 | - | if p := accelProfile(s.touchpad.AccelProfile); accelProfiles(dev)&p != 0 && setAccelProfile(dev, p) != 0 { | 397 | + | err = errRejected |
| 362 | - | log.Warn().Str("device", deviceName(dev)).Msg("accel profile rejected") | 398 | + | } |
| 363 | - | } | 399 | + | return err |
| 400 | + | } | |||
| 401 | + | ||||
| 402 | + | func (libinputDevices) SetLeftHanded(dev uintptr, on bool) error { | |||
| 403 | + | if leftHandedAvailable(dev) != 0 && setLeftHanded(dev, flag(on)) != 0 { | |||
| 404 | + | return errRejected | |||
| 405 | + | } | |||
| 406 | + | return nil | |||
| 407 | + | } | |||
| 408 | + | ||||
| 409 | + | func (libinputDevices) SetTap(dev uintptr, on bool) error { | |||
| 410 | + | if tapFingers(dev) <= 0 { | |||
| 411 | + | return nil | |||
| 412 | + | } | |||
| 413 | + | var err error | |||
| 414 | + | if setTap(dev, flag(on)) != 0 { | |||
| 415 | + | err = errRejected | |||
| 416 | + | } | |||
| 417 | + | if setTapMap(dev, tapButtonMap) != 0 { | |||
| 418 | + | err = errRejected | |||
| 419 | + | } | |||
| 420 | + | return err | |||
| 421 | + | } | |||
| 422 | + | ||||
| 423 | + | // configurable reports whether the device config applies to dev: a | |||
| 424 | + | // touchpad or another pointer. Keyboards, switches and the like are left | |||
| 425 | + | // alone and not referenced. | |||
| 426 | + | func (s *inputState) configurable(dev uintptr) bool { | |||
| 427 | + | return s.dev.IsTouchpad(dev) || s.dev.IsPointer(dev) | |||
| 428 | + | } | |||
| 429 | + | ||||
| 430 | + | // configure applies the config to dev by type: touchpads take the touchpad | |||
| 431 | + | // settings and tapping, other pointers (mice, trackballs, trackpoints) the | |||
| 432 | + | // mouse settings, other devices nothing. A refused setting is logged and | |||
| 433 | + | // the rest still apply. The success path logs and allocates nothing. | |||
| 434 | + | func (s *inputState) configure(dev uintptr, log zerowrap.Logger) { | |||
| 435 | + | switch { | |||
| 436 | + | case s.dev.IsTouchpad(dev): | |||
| 437 | + | s.applyPointer(dev, s.cfg.Touchpad.PointerConfig, log) | |||
| 438 | + | s.check(dev, "tap to click", s.dev.SetTap(dev, s.cfg.Touchpad.Tap), log) | |||
| 439 | + | case s.dev.IsPointer(dev): | |||
| 440 | + | s.applyPointer(dev, s.cfg.Mouse, log) | |||
| 441 | + | } | |||
| 442 | + | } | |||
| 443 | + | ||||
| 444 | + | // applyPointer applies the settings touchpads and mice share. | |||
| 445 | + | func (s *inputState) applyPointer(dev uintptr, c ports.PointerConfig, log zerowrap.Logger) { | |||
| 446 | + | s.check(dev, "natural scroll", s.dev.SetNaturalScroll(dev, c.NaturalScroll), log) | |||
| 447 | + | s.check(dev, "accel", s.dev.SetAccel(dev, c.AccelSpeed, c.AccelProfile), log) | |||
| 448 | + | s.check(dev, "left handed", s.dev.SetLeftHanded(dev, c.LeftHanded), log) | |||
| 449 | + | } | |||
| 450 | + | ||||
| 451 | + | // check logs a refused setting; the device name is only read then. | |||
| 452 | + | func (s *inputState) check(dev uintptr, setting string, err error, log zerowrap.Logger) { | |||
| 453 | + | if err != nil { | |||
| 454 | + | log.Warn().Err(err).Str("device", s.dev.Name(dev)).Str("setting", setting).Msg("input device setting rejected") | |||
| 364 | } | 455 | } | ||
| 365 | } | 456 | } | ||
| 366 | 457 | ||||
| @@ -453,7 +544,7 @@ func translateEvent(ev uintptr, opts Options, p *pointer, in *inputState, state | |||||
| 453 | case evDeviceAdded: | 544 | case evDeviceAdded: | ||
| 454 | dev := eventDevice(ev) | 545 | dev := eventDevice(ev) | ||
| 455 | log.Info().Str("device", deviceName(dev)).Msg("input device added") | 546 | log.Info().Str("device", deviceName(dev)).Msg("input device added") | ||
| 456 | - | if configurable(dev) && !in.devices[dev] { | 547 | + | if in.configurable(dev) && !in.devices[dev] { |
| 457 | in.devices[deviceRef(dev)] = true | 548 | in.devices[deviceRef(dev)] = true | ||
| 458 | in.configure(dev, log) | 549 | in.configure(dev, log) | ||
| 459 | } | 550 | } | ||
| @@ -519,7 +610,7 @@ func translateEvent(ev uintptr, opts Options, p *pointer, in *inputState, state | |||||
| 519 | s.Set, s.Value = true, scrollValue(pe, uint32(i)) | 610 | s.Set, s.Value = true, scrollValue(pe, uint32(i)) | ||
| 520 | if source == ports.AxisFinger { | 611 | if source == ports.AxisFinger { | ||
| 521 | // Two-finger scroll is the touchpad's only scroll source. | 612 | // Two-finger scroll is the touchpad's only scroll source. | ||
| 522 | - | s.Value *= in.touchpad.ScrollFactor | 613 | + | s.Value *= in.cfg.Touchpad.ScrollFactor |
| 523 | } | 614 | } | ||
| 524 | if source == ports.AxisWheel { | 615 | if source == ports.AxisWheel { | ||
| 525 | s.V120 = int32(scrollV120(pe, uint32(i))) | 616 | s.V120 = int32(scrollV120(pe, uint32(i))) | ||
M internal/app/app.go +30 -26
| @@ -191,7 +191,7 @@ func run(ctx context.Context, opts Options, inject func(chan<- ports.InputEvent) | |||
| 191 | 191 | } | |
| 192 | 192 | watched := make(chan ports.ConfigChanged, 8) | |
| 193 | 193 | keymaps := make(chan *xkb.Keymap, 1) | |
| 194 | - | touchpads := make(chan ports.TouchpadConfig, 1) | |
| 194 | + | deviceConfigs := make(chan ports.InputDevicesConfig, 1) | |
| 195 | 195 | go func() { | |
| 196 | 196 | defer workers.Done() | |
| 197 | 197 | done <- config.Watch(ctx, path, watched, logging.For(ctx, "config")) | |
| @@ -209,7 +209,7 @@ func run(ctx context.Context, opts Options, inject func(chan<- ports.InputEvent) | |||
| 209 | 209 | workers.Add(1) | |
| 210 | 210 | go func() { | |
| 211 | 211 | defer workers.Done() | |
| 212 | - | relayConfig(ctx, opts.Config, watched, filtered, keymaps, touchpads, curs, commands, logging.For(ctx, "config")) | |
| 212 | + | relayConfig(ctx, opts.Config, watched, filtered, keymaps, deviceConfigs, curs, commands, logging.For(ctx, "config")) | |
| 213 | 213 | }() | |
| 214 | 214 | script := make(chan string) | |
| 215 | 215 | go func() { | |
| @@ -231,7 +231,7 @@ func run(ctx context.Context, opts Options, inject func(chan<- ports.InputEvent) | |||
| 231 | 231 | log.Warn().Str("component", "sched").Err(err).Msg("input scheduling") | |
| 232 | 232 | } | |
| 233 | 233 | } | |
| 234 | - | return libinput.Run(ctx, libinput.Options{Security: security, Seat: hw.seat, SeatName: hw.seat.Name(), Keymap: km, Keymaps: keymaps, Layout: layout, Layouts: layouts, Constraints: constraints, Touchpad: opts.Config.Touchpad, Touchpads: touchpads, Active: hw.seat.Subscribe(), MoveCursor: curs.move, Log: logging.For(ctx, "input"), LogMotion: logging.Enabled(ctx, "input-motion"), LogKeys: logging.Enabled(ctx, "input-keys")}, input) | |
| 234 | + | return libinput.Run(ctx, libinput.Options{Security: security, Seat: hw.seat, SeatName: hw.seat.Name(), Keymap: km, Keymaps: keymaps, Layout: layout, Layouts: layouts, Constraints: constraints, DeviceConfig: opts.Config.InputDevicesConfig, DeviceConfigs: deviceConfigs, Active: hw.seat.Subscribe(), MoveCursor: curs.move, Log: logging.For(ctx, "input"), LogMotion: logging.Enabled(ctx, "input-motion"), LogKeys: logging.Enabled(ctx, "input-keys")}, input) | |
| 235 | 235 | }) | |
| 236 | 236 | return | |
| 237 | 237 | } | |
| @@ -369,11 +369,12 @@ const screensaverRetry = 2 * time.Second | |||
| 369 | 369 | // relayConfig forwards reloads to core. A layout change builds a new keymap, hands it | |
| 370 | 370 | // to the input goroutine and sends it to clients; a repeat-only change just updates | |
| 371 | 371 | // clients. A keymap that fails to build keeps the previous layout. A touchpad | |
| 372 | - | // change goes to the input goroutine; only the newest one waits there. The | |
| 373 | - | // cursor idle delay goes straight to the cursor router. | |
| 374 | - | func relayConfig(ctx context.Context, cur ports.Config, in <-chan ports.ConfigChanged, out chan<- ports.ConfigChanged, keymaps chan *xkb.Keymap, touchpads chan ports.TouchpadConfig, curs *cursors, commands chan<- ports.ClientCommand, log zerowrap.Logger) { | |
| 372 | + | // or mouse change goes to the input goroutine as one InputDevicesConfig; only | |
| 373 | + | // the newest one waits there. The cursor idle delay goes straight to the | |
| 374 | + | // cursor router. | |
| 375 | + | func relayConfig(ctx context.Context, cur ports.Config, in <-chan ports.ConfigChanged, out chan<- ports.ConfigChanged, keymaps chan *xkb.Keymap, deviceConfigs chan ports.InputDevicesConfig, curs *cursors, commands chan<- ports.ClientCommand, log zerowrap.Logger) { | |
| 375 | 376 | kb := cur.Keyboard | |
| 376 | - | tp := cur.Touchpad | |
| 377 | + | devCfg := cur.InputDevicesConfig | |
| 377 | 378 | for { | |
| 378 | 379 | var ev ports.ConfigChanged | |
| 379 | 380 | select { | |
| @@ -408,13 +409,9 @@ func relayConfig(ctx context.Context, cur ports.Config, in <-chan ports.ConfigCh | |||
| 408 | 409 | } | |
| 409 | 410 | } | |
| 410 | 411 | curs.setHideAfter(ev.Config.Cursor.HideAfter) | |
| 411 | - | if ev.Config.Touchpad != tp { | |
| 412 | - | tp = ev.Config.Touchpad | |
| 413 | - | select { | |
| 414 | - | case <-touchpads: | |
| 415 | - | default: | |
| 416 | - | } | |
| 417 | - | touchpads <- tp // buffered and just drained: this goroutine is the only sender | |
| 412 | + | if ev.Config.InputDevicesConfig != devCfg { | |
| 413 | + | devCfg = ev.Config.InputDevicesConfig | |
| 414 | + | latest(deviceConfigs, devCfg) // this goroutine is the only sender | |
| 418 | 415 | } | |
| 419 | 416 | if layoutChanged || repeatChanged { | |
| 420 | 417 | select { | |
| @@ -475,18 +472,7 @@ func consumeScenes(ctx context.Context, scenes <-chan []ports.Scene, configError | |||
| 475 | 472 | default: | |
| 476 | 473 | } | |
| 477 | 474 | } | |
| 478 | - | select { | |
| 479 | - | case renderScenes <- set: | |
| 480 | - | default: | |
| 481 | - | select { | |
| 482 | - | case <-renderScenes: | |
| 483 | - | default: | |
| 484 | - | } | |
| 485 | - | select { | |
| 486 | - | case renderScenes <- set: | |
| 487 | - | default: | |
| 488 | - | } | |
| 489 | - | } | |
| 475 | + | latest(renderScenes, set) // this goroutine is the only sender | |
| 490 | 476 | for _, s := range set { | |
| 491 | 477 | ev := log.Debug().Str("output", s.Output).Uint64("seq", s.Seq).Int("out_w", s.OutputWidth).Int("out_h", s.OutputHeight).Float64("scale", s.Scale) | |
| 492 | 478 | rects := make([]string, 0, len(s.Windows)) | |
| @@ -532,3 +518,21 @@ func renderNode(dev uint64) string { | |||
| 532 | 518 | } | |
| 533 | 519 | return fmt.Sprintf("/dev/dri/renderD%d", unix.Minor(dev)) | |
| 534 | 520 | } | |
| 521 | + | ||
| 522 | + | // latest sends v without blocking and replaces a value the consumer has not | |
| 523 | + | // read yet: the newest wins. Only one goroutine may send on ch; consumers only | |
| 524 | + | // receive, so the retry after draining cannot find the buffer full again. | |
| 525 | + | func latest[T any](ch chan T, v T) { | |
| 526 | + | select { | |
| 527 | + | case ch <- v: | |
| 528 | + | default: | |
| 529 | + | select { | |
| 530 | + | case <-ch: | |
| 531 | + | default: | |
| 532 | + | } | |
| 533 | + | select { | |
| 534 | + | case ch <- v: | |
| 535 | + | default: | |
| 536 | + | } | |
| 537 | + | } | |
| 538 | + | } | |
| @@ -191,7 +191,7 @@ func run(ctx context.Context, opts Options, inject func(chan<- ports.InputEvent) | |||||
| 191 | } | 191 | } | ||
| 192 | watched := make(chan ports.ConfigChanged, 8) | 192 | watched := make(chan ports.ConfigChanged, 8) | ||
| 193 | keymaps := make(chan *xkb.Keymap, 1) | 193 | keymaps := make(chan *xkb.Keymap, 1) | ||
| 194 | - | touchpads := make(chan ports.TouchpadConfig, 1) | 194 | + | deviceConfigs := make(chan ports.InputDevicesConfig, 1) |
| 195 | go func() { | 195 | go func() { | ||
| 196 | defer workers.Done() | 196 | defer workers.Done() | ||
| 197 | done <- config.Watch(ctx, path, watched, logging.For(ctx, "config")) | 197 | done <- config.Watch(ctx, path, watched, logging.For(ctx, "config")) | ||
| @@ -209,7 +209,7 @@ func run(ctx context.Context, opts Options, inject func(chan<- ports.InputEvent) | |||||
| 209 | workers.Add(1) | 209 | workers.Add(1) | ||
| 210 | go func() { | 210 | go func() { | ||
| 211 | defer workers.Done() | 211 | defer workers.Done() | ||
| 212 | - | relayConfig(ctx, opts.Config, watched, filtered, keymaps, touchpads, curs, commands, logging.For(ctx, "config")) | 212 | + | relayConfig(ctx, opts.Config, watched, filtered, keymaps, deviceConfigs, curs, commands, logging.For(ctx, "config")) |
| 213 | }() | 213 | }() | ||
| 214 | script := make(chan string) | 214 | script := make(chan string) | ||
| 215 | go func() { | 215 | go func() { | ||
| @@ -231,7 +231,7 @@ func run(ctx context.Context, opts Options, inject func(chan<- ports.InputEvent) | |||||
| 231 | log.Warn().Str("component", "sched").Err(err).Msg("input scheduling") | 231 | log.Warn().Str("component", "sched").Err(err).Msg("input scheduling") | ||
| 232 | } | 232 | } | ||
| 233 | } | 233 | } | ||
| 234 | - | return libinput.Run(ctx, libinput.Options{Security: security, Seat: hw.seat, SeatName: hw.seat.Name(), Keymap: km, Keymaps: keymaps, Layout: layout, Layouts: layouts, Constraints: constraints, Touchpad: opts.Config.Touchpad, Touchpads: touchpads, Active: hw.seat.Subscribe(), MoveCursor: curs.move, Log: logging.For(ctx, "input"), LogMotion: logging.Enabled(ctx, "input-motion"), LogKeys: logging.Enabled(ctx, "input-keys")}, input) | 234 | + | return libinput.Run(ctx, libinput.Options{Security: security, Seat: hw.seat, SeatName: hw.seat.Name(), Keymap: km, Keymaps: keymaps, Layout: layout, Layouts: layouts, Constraints: constraints, DeviceConfig: opts.Config.InputDevicesConfig, DeviceConfigs: deviceConfigs, Active: hw.seat.Subscribe(), MoveCursor: curs.move, Log: logging.For(ctx, "input"), LogMotion: logging.Enabled(ctx, "input-motion"), LogKeys: logging.Enabled(ctx, "input-keys")}, input) |
| 235 | }) | 235 | }) | ||
| 236 | return | 236 | return | ||
| 237 | } | 237 | } | ||
| @@ -369,11 +369,12 @@ const screensaverRetry = 2 * time.Second | |||||
| 369 | // relayConfig forwards reloads to core. A layout change builds a new keymap, hands it | 369 | // relayConfig forwards reloads to core. A layout change builds a new keymap, hands it | ||
| 370 | // to the input goroutine and sends it to clients; a repeat-only change just updates | 370 | // to the input goroutine and sends it to clients; a repeat-only change just updates | ||
| 371 | // clients. A keymap that fails to build keeps the previous layout. A touchpad | 371 | // clients. A keymap that fails to build keeps the previous layout. A touchpad | ||
| 372 | - | // change goes to the input goroutine; only the newest one waits there. The | 372 | + | // or mouse change goes to the input goroutine as one InputDevicesConfig; only |
| 373 | - | // cursor idle delay goes straight to the cursor router. | 373 | + | // the newest one waits there. The cursor idle delay goes straight to the |
| 374 | - | func relayConfig(ctx context.Context, cur ports.Config, in <-chan ports.ConfigChanged, out chan<- ports.ConfigChanged, keymaps chan *xkb.Keymap, touchpads chan ports.TouchpadConfig, curs *cursors, commands chan<- ports.ClientCommand, log zerowrap.Logger) { | 374 | + | // cursor router. |
| 375 | + | func relayConfig(ctx context.Context, cur ports.Config, in <-chan ports.ConfigChanged, out chan<- ports.ConfigChanged, keymaps chan *xkb.Keymap, deviceConfigs chan ports.InputDevicesConfig, curs *cursors, commands chan<- ports.ClientCommand, log zerowrap.Logger) { | |||
| 375 | kb := cur.Keyboard | 376 | kb := cur.Keyboard | ||
| 376 | - | tp := cur.Touchpad | 377 | + | devCfg := cur.InputDevicesConfig |
| 377 | for { | 378 | for { | ||
| 378 | var ev ports.ConfigChanged | 379 | var ev ports.ConfigChanged | ||
| 379 | select { | 380 | select { | ||
| @@ -408,13 +409,9 @@ func relayConfig(ctx context.Context, cur ports.Config, in <-chan ports.ConfigCh | |||||
| 408 | } | 409 | } | ||
| 409 | } | 410 | } | ||
| 410 | curs.setHideAfter(ev.Config.Cursor.HideAfter) | 411 | curs.setHideAfter(ev.Config.Cursor.HideAfter) | ||
| 411 | - | if ev.Config.Touchpad != tp { | 412 | + | if ev.Config.InputDevicesConfig != devCfg { |
| 412 | - | tp = ev.Config.Touchpad | 413 | + | devCfg = ev.Config.InputDevicesConfig |
| 413 | - | select { | 414 | + | latest(deviceConfigs, devCfg) // this goroutine is the only sender |
| 414 | - | case <-touchpads: | |||
| 415 | - | default: | |||
| 416 | - | } | |||
| 417 | - | touchpads <- tp // buffered and just drained: this goroutine is the only sender | |||
| 418 | } | 415 | } | ||
| 419 | if layoutChanged || repeatChanged { | 416 | if layoutChanged || repeatChanged { | ||
| 420 | select { | 417 | select { | ||
| @@ -475,18 +472,7 @@ func consumeScenes(ctx context.Context, scenes <-chan []ports.Scene, configError | |||||
| 475 | default: | 472 | default: | ||
| 476 | } | 473 | } | ||
| 477 | } | 474 | } | ||
| 478 | - | select { | 475 | + | latest(renderScenes, set) // this goroutine is the only sender |
| 479 | - | case renderScenes <- set: | |||
| 480 | - | default: | |||
| 481 | - | select { | |||
| 482 | - | case <-renderScenes: | |||
| 483 | - | default: | |||
| 484 | - | } | |||
| 485 | - | select { | |||
| 486 | - | case renderScenes <- set: | |||
| 487 | - | default: | |||
| 488 | - | } | |||
| 489 | - | } | |||
| 490 | for _, s := range set { | 476 | for _, s := range set { | ||
| 491 | ev := log.Debug().Str("output", s.Output).Uint64("seq", s.Seq).Int("out_w", s.OutputWidth).Int("out_h", s.OutputHeight).Float64("scale", s.Scale) | 477 | ev := log.Debug().Str("output", s.Output).Uint64("seq", s.Seq).Int("out_w", s.OutputWidth).Int("out_h", s.OutputHeight).Float64("scale", s.Scale) | ||
| 492 | rects := make([]string, 0, len(s.Windows)) | 478 | rects := make([]string, 0, len(s.Windows)) | ||
| @@ -532,3 +518,21 @@ func renderNode(dev uint64) string { | |||||
| 532 | } | 518 | } | ||
| 533 | return fmt.Sprintf("/dev/dri/renderD%d", unix.Minor(dev)) | 519 | return fmt.Sprintf("/dev/dri/renderD%d", unix.Minor(dev)) | ||
| 534 | } | 520 | } | ||
| 521 | + | ||||
| 522 | + | // latest sends v without blocking and replaces a value the consumer has not | |||
| 523 | + | // read yet: the newest wins. Only one goroutine may send on ch; consumers only | |||
| 524 | + | // receive, so the retry after draining cannot find the buffer full again. | |||
| 525 | + | func latest[T any](ch chan T, v T) { | |||
| 526 | + | select { | |||
| 527 | + | case ch <- v: | |||
| 528 | + | default: | |||
| 529 | + | select { | |||
| 530 | + | case <-ch: | |||
| 531 | + | default: | |||
| 532 | + | } | |||
| 533 | + | select { | |||
| 534 | + | case ch <- v: | |||
| 535 | + | default: | |||
| 536 | + | } | |||
| 537 | + | } | |||
| 538 | + | } | |||
M internal/app/app_test.go +29 -5
| @@ -273,13 +273,13 @@ func TestRelayConfigKeyboard(t *testing.T) { | |||
| 273 | 273 | out := make(chan ports.ConfigChanged, 1) | |
| 274 | 274 | keymaps := make(chan *xkb.Keymap, 1) | |
| 275 | 275 | commands := make(chan ports.ClientCommand, 1) | |
| 276 | - | touchpads := make(chan ports.TouchpadConfig, 1) | |
| 277 | - | go relayConfig(ctx, cur, in, out, keymaps, touchpads, newCursors(clock.System{}, 0), commands, logging.For(ctx, "config")) | |
| 276 | + | devices := make(chan ports.InputDevicesConfig, 1) | |
| 277 | + | go relayConfig(ctx, cur, in, out, keymaps, devices, newCursors(clock.System{}, 0), commands, logging.For(ctx, "config")) | |
| 278 | 278 | ||
| 279 | 279 | next := config.Defaults() | |
| 280 | 280 | next.Background.Color = "#000000" | |
| 281 | 281 | in <- ports.ConfigChanged{Config: next} | |
| 282 | - | if got := <-out; got.Config.Background.Color != "#000000" || len(keymaps) != 0 || len(commands) != 0 || len(touchpads) != 0 { | |
| 282 | + | if got := <-out; got.Config.Background.Color != "#000000" || len(keymaps) != 0 || len(commands) != 0 || len(devices) != 0 { | |
| 283 | 283 | t.Fatalf("non-keyboard change rebuilt keymap: %+v", got) | |
| 284 | 284 | } | |
| 285 | 285 | ||
| @@ -290,8 +290,32 @@ func TestRelayConfigKeyboard(t *testing.T) { | |||
| 290 | 290 | next.Touchpad.NaturalScroll = false | |
| 291 | 291 | in <- ports.ConfigChanged{Config: next} | |
| 292 | 292 | <-out | |
| 293 | - | if tp := <-touchpads; tp.NaturalScroll || len(touchpads) != 0 { | |
| 294 | - | t.Fatalf("touchpad: %+v", tp) | |
| 293 | + | if d := <-devices; d.Touchpad.NaturalScroll || len(devices) != 0 { | |
| 294 | + | t.Fatalf("touchpad: %+v", d) | |
| 295 | + | } | |
| 296 | + | ||
| 297 | + | // Touchpad and mouse changes travel together. | |
| 298 | + | next.Touchpad.Tap = false | |
| 299 | + | next.Mouse.LeftHanded = true | |
| 300 | + | next.Mouse.AccelSpeed = 0.25 | |
| 301 | + | in <- ports.ConfigChanged{Config: next} | |
| 302 | + | <-out | |
| 303 | + | want := ports.InputDevicesConfig{Touchpad: next.Touchpad, Mouse: next.Mouse} | |
| 304 | + | if d := <-devices; d != want || len(devices) != 0 { | |
| 305 | + | t.Fatalf("devices: %+v, want %+v", d, want) | |
| 306 | + | } | |
| 307 | + | // A mouse-only change is relayed too, with the touchpad config beside it. | |
| 308 | + | // relayConfig sends to devices before out, so it is ready after <-out. | |
| 309 | + | next.Mouse.NaturalScroll = true | |
| 310 | + | in <- ports.ConfigChanged{Config: next} | |
| 311 | + | <-out | |
| 312 | + | select { | |
| 313 | + | case d := <-devices: | |
| 314 | + | if !d.Mouse.NaturalScroll || !d.Mouse.LeftHanded || d.Touchpad != want.Touchpad || len(devices) != 0 { | |
| 315 | + | t.Fatalf("mouse-only change: %+v", d) | |
| 316 | + | } | |
| 317 | + | default: | |
| 318 | + | t.Fatal("mouse-only change not relayed") | |
| 295 | 319 | } | |
| 296 | 320 | ||
| 297 | 321 | next.Keyboard.Layout, next.Keyboard.RepeatRate = "fr", 40 | |
| @@ -273,13 +273,13 @@ func TestRelayConfigKeyboard(t *testing.T) { | |||||
| 273 | out := make(chan ports.ConfigChanged, 1) | 273 | out := make(chan ports.ConfigChanged, 1) | ||
| 274 | keymaps := make(chan *xkb.Keymap, 1) | 274 | keymaps := make(chan *xkb.Keymap, 1) | ||
| 275 | commands := make(chan ports.ClientCommand, 1) | 275 | commands := make(chan ports.ClientCommand, 1) | ||
| 276 | - | touchpads := make(chan ports.TouchpadConfig, 1) | 276 | + | devices := make(chan ports.InputDevicesConfig, 1) |
| 277 | - | go relayConfig(ctx, cur, in, out, keymaps, touchpads, newCursors(clock.System{}, 0), commands, logging.For(ctx, "config")) | 277 | + | go relayConfig(ctx, cur, in, out, keymaps, devices, newCursors(clock.System{}, 0), commands, logging.For(ctx, "config")) |
| 278 | 278 | ||||
| 279 | next := config.Defaults() | 279 | next := config.Defaults() | ||
| 280 | next.Background.Color = "#000000" | 280 | next.Background.Color = "#000000" | ||
| 281 | in <- ports.ConfigChanged{Config: next} | 281 | in <- ports.ConfigChanged{Config: next} | ||
| 282 | - | if got := <-out; got.Config.Background.Color != "#000000" || len(keymaps) != 0 || len(commands) != 0 || len(touchpads) != 0 { | 282 | + | if got := <-out; got.Config.Background.Color != "#000000" || len(keymaps) != 0 || len(commands) != 0 || len(devices) != 0 { |
| 283 | t.Fatalf("non-keyboard change rebuilt keymap: %+v", got) | 283 | t.Fatalf("non-keyboard change rebuilt keymap: %+v", got) | ||
| 284 | } | 284 | } | ||
| 285 | 285 | ||||
| @@ -290,8 +290,32 @@ func TestRelayConfigKeyboard(t *testing.T) { | |||||
| 290 | next.Touchpad.NaturalScroll = false | 290 | next.Touchpad.NaturalScroll = false | ||
| 291 | in <- ports.ConfigChanged{Config: next} | 291 | in <- ports.ConfigChanged{Config: next} | ||
| 292 | <-out | 292 | <-out | ||
| 293 | - | if tp := <-touchpads; tp.NaturalScroll || len(touchpads) != 0 { | 293 | + | if d := <-devices; d.Touchpad.NaturalScroll || len(devices) != 0 { |
| 294 | - | t.Fatalf("touchpad: %+v", tp) | 294 | + | t.Fatalf("touchpad: %+v", d) |
| 295 | + | } | |||
| 296 | + | ||||
| 297 | + | // Touchpad and mouse changes travel together. | |||
| 298 | + | next.Touchpad.Tap = false | |||
| 299 | + | next.Mouse.LeftHanded = true | |||
| 300 | + | next.Mouse.AccelSpeed = 0.25 | |||
| 301 | + | in <- ports.ConfigChanged{Config: next} | |||
| 302 | + | <-out | |||
| 303 | + | want := ports.InputDevicesConfig{Touchpad: next.Touchpad, Mouse: next.Mouse} | |||
| 304 | + | if d := <-devices; d != want || len(devices) != 0 { | |||
| 305 | + | t.Fatalf("devices: %+v, want %+v", d, want) | |||
| 306 | + | } | |||
| 307 | + | // A mouse-only change is relayed too, with the touchpad config beside it. | |||
| 308 | + | // relayConfig sends to devices before out, so it is ready after <-out. | |||
| 309 | + | next.Mouse.NaturalScroll = true | |||
| 310 | + | in <- ports.ConfigChanged{Config: next} | |||
| 311 | + | <-out | |||
| 312 | + | select { | |||
| 313 | + | case d := <-devices: | |||
| 314 | + | if !d.Mouse.NaturalScroll || !d.Mouse.LeftHanded || d.Touchpad != want.Touchpad || len(devices) != 0 { | |||
| 315 | + | t.Fatalf("mouse-only change: %+v", d) | |||
| 316 | + | } | |||
| 317 | + | default: | |||
| 318 | + | t.Fatal("mouse-only change not relayed") | |||
| 295 | } | 319 | } | ||
| 296 | 320 | ||||
| 297 | next.Keyboard.Layout, next.Keyboard.RepeatRate = "fr", 40 | 321 | next.Keyboard.Layout, next.Keyboard.RepeatRate = "fr", 40 | ||
M internal/core/overview.go +3 -3
| @@ -911,9 +911,9 @@ const ( | |||
| 911 | 911 | // in a stack is easy to pick. | |
| 912 | 912 | // Continuous scrolling steps once per overviewScrollStep and a wheel once | |
| 913 | 913 | // per notch (high-resolution wheels add up their fractions of a notch). | |
| 914 | - | // Vertical steps select cards first, then rows; touchpad.natural-scroll | |
| 915 | - | // flips both axes as libinput reports. The end of a scroll resets it | |
| 916 | - | // (scrollStop). | |
| 914 | + | // Vertical steps select cards first, then rows; natural scroll | |
| 915 | + | // (touchpad.* or mouse.*) flips both axes as libinput reports. The end of | |
| 916 | + | // a scroll resets it (scrollStop). | |
| 917 | 917 | func (m *Monitor) overviewScroll(a ports.PointerAxis) (changed bool) { | |
| 918 | 918 | if a.Vertical.Stop || a.Horizontal.Stop { | |
| 919 | 919 | return false | |
| @@ -911,9 +911,9 @@ const ( | |||||
| 911 | // in a stack is easy to pick. | 911 | // in a stack is easy to pick. | ||
| 912 | // Continuous scrolling steps once per overviewScrollStep and a wheel once | 912 | // Continuous scrolling steps once per overviewScrollStep and a wheel once | ||
| 913 | // per notch (high-resolution wheels add up their fractions of a notch). | 913 | // per notch (high-resolution wheels add up their fractions of a notch). | ||
| 914 | - | // Vertical steps select cards first, then rows; touchpad.natural-scroll | 914 | + | // Vertical steps select cards first, then rows; natural scroll |
| 915 | - | // flips both axes as libinput reports. The end of a scroll resets it | 915 | + | // (touchpad.* or mouse.*) flips both axes as libinput reports. The end of |
| 916 | - | // (scrollStop). | 916 | + | // a scroll resets it (scrollStop). |
| 917 | func (m *Monitor) overviewScroll(a ports.PointerAxis) (changed bool) { | 917 | func (m *Monitor) overviewScroll(a ports.PointerAxis) (changed bool) { | ||
| 918 | if a.Vertical.Stop || a.Horizontal.Stop { | 918 | if a.Vertical.Stop || a.Horizontal.Stop { | ||
| 919 | return false | 919 | return false | ||
M internal/ports/config.go +26 -10
| @@ -110,7 +110,8 @@ type Config struct { | |||
| 110 | 110 | LayoutRules | |
| 111 | 111 | Outputs []OutputLayout | |
| 112 | 112 | } | |
| 113 | - | Touchpad TouchpadConfig | |
| 113 | + | // InputDevicesConfig holds Touchpad and Mouse, promoted. | |
| 114 | + | InputDevicesConfig | |
| 114 | 115 | // Cursor.HideAfter hides the pointer cursor after this long without | |
| 115 | 116 | // motion; the next motion shows it. 0 never hides it. | |
| 116 | 117 | Cursor struct{ HideAfter time.Duration } | |
| @@ -146,20 +147,35 @@ type Config struct { | |||
| 146 | 147 | } | |
| 147 | 148 | } | |
| 148 | 149 | ||
| 149 | - | // TouchpadConfig configures touchpads. NaturalScroll moves the content with | |
| 150 | - | // the fingers, for two-finger scroll and three-finger swipes. Tap clicks on | |
| 151 | - | // a tap: one finger left, two right, three middle. AccelSpeed (-1 to 1) and | |
| 152 | - | // AccelProfile (AccelAdaptive or AccelFlat) set the pointer speed; | |
| 153 | - | // ScrollFactor multiplies two-finger scroll. | |
| 154 | - | type TouchpadConfig struct { | |
| 150 | + | // PointerConfig holds the settings mice and touchpads share. NaturalScroll | |
| 151 | + | // moves the content with the fingers or the wheel instead of against them. | |
| 152 | + | // AccelSpeed (-1 to 1) and AccelProfile (AccelAdaptive or AccelFlat) set the | |
| 153 | + | // pointer speed. LeftHanded swaps the left and right buttons. | |
| 154 | + | type PointerConfig struct { | |
| 155 | 155 | NaturalScroll bool | |
| 156 | - | Tap bool | |
| 157 | 156 | AccelSpeed float64 | |
| 158 | 157 | AccelProfile string | |
| 159 | - | ScrollFactor float64 | |
| 158 | + | LeftHanded bool | |
| 159 | + | } | |
| 160 | + | ||
| 161 | + | // TouchpadConfig configures touchpads: the shared pointer settings | |
| 162 | + | // (NaturalScroll also applies to two-finger scroll and swipes), plus Tap, | |
| 163 | + | // which clicks on a tap (one finger left, two right, three middle), and | |
| 164 | + | // ScrollFactor, which multiplies two-finger scroll. | |
| 165 | + | type TouchpadConfig struct { | |
| 166 | + | PointerConfig | |
| 167 | + | Tap bool | |
| 168 | + | ScrollFactor float64 | |
| 169 | + | } | |
| 170 | + | ||
| 171 | + | // InputDevicesConfig is the live configuration of the input devices, sent to | |
| 172 | + | // the input adapter as one value. It is comparable. | |
| 173 | + | type InputDevicesConfig struct { | |
| 174 | + | Touchpad TouchpadConfig | |
| 175 | + | Mouse PointerConfig | |
| 160 | 176 | } | |
| 161 | 177 | ||
| 162 | - | // Touchpad pointer acceleration profiles. | |
| 178 | + | // Pointer acceleration profiles, for mice and touchpads. | |
| 163 | 179 | const ( | |
| 164 | 180 | AccelAdaptive = "adaptive" | |
| 165 | 181 | AccelFlat = "flat" | |
| @@ -110,7 +110,8 @@ type Config struct { | |||||
| 110 | LayoutRules | 110 | LayoutRules | ||
| 111 | Outputs []OutputLayout | 111 | Outputs []OutputLayout | ||
| 112 | } | 112 | } | ||
| 113 | - | Touchpad TouchpadConfig | 113 | + | // InputDevicesConfig holds Touchpad and Mouse, promoted. |
| 114 | + | InputDevicesConfig | |||
| 114 | // Cursor.HideAfter hides the pointer cursor after this long without | 115 | // Cursor.HideAfter hides the pointer cursor after this long without | ||
| 115 | // motion; the next motion shows it. 0 never hides it. | 116 | // motion; the next motion shows it. 0 never hides it. | ||
| 116 | Cursor struct{ HideAfter time.Duration } | 117 | Cursor struct{ HideAfter time.Duration } | ||
| @@ -146,20 +147,35 @@ type Config struct { | |||||
| 146 | } | 147 | } | ||
| 147 | } | 148 | } | ||
| 148 | 149 | ||||
| 149 | - | // TouchpadConfig configures touchpads. NaturalScroll moves the content with | 150 | + | // PointerConfig holds the settings mice and touchpads share. NaturalScroll |
| 150 | - | // the fingers, for two-finger scroll and three-finger swipes. Tap clicks on | 151 | + | // moves the content with the fingers or the wheel instead of against them. |
| 151 | - | // a tap: one finger left, two right, three middle. AccelSpeed (-1 to 1) and | 152 | + | // AccelSpeed (-1 to 1) and AccelProfile (AccelAdaptive or AccelFlat) set the |
| 152 | - | // AccelProfile (AccelAdaptive or AccelFlat) set the pointer speed; | 153 | + | // pointer speed. LeftHanded swaps the left and right buttons. |
| 153 | - | // ScrollFactor multiplies two-finger scroll. | 154 | + | type PointerConfig struct { |
| 154 | - | type TouchpadConfig struct { | |||
| 155 | NaturalScroll bool | 155 | NaturalScroll bool | ||
| 156 | - | Tap bool | |||
| 157 | AccelSpeed float64 | 156 | AccelSpeed float64 | ||
| 158 | AccelProfile string | 157 | AccelProfile string | ||
| 159 | - | ScrollFactor float64 | 158 | + | LeftHanded bool |
| 159 | + | } | |||
| 160 | + | ||||
| 161 | + | // TouchpadConfig configures touchpads: the shared pointer settings | |||
| 162 | + | // (NaturalScroll also applies to two-finger scroll and swipes), plus Tap, | |||
| 163 | + | // which clicks on a tap (one finger left, two right, three middle), and | |||
| 164 | + | // ScrollFactor, which multiplies two-finger scroll. | |||
| 165 | + | type TouchpadConfig struct { | |||
| 166 | + | PointerConfig | |||
| 167 | + | Tap bool | |||
| 168 | + | ScrollFactor float64 | |||
| 169 | + | } | |||
| 170 | + | ||||
| 171 | + | // InputDevicesConfig is the live configuration of the input devices, sent to | |||
| 172 | + | // the input adapter as one value. It is comparable. | |||
| 173 | + | type InputDevicesConfig struct { | |||
| 174 | + | Touchpad TouchpadConfig | |||
| 175 | + | Mouse PointerConfig | |||
| 160 | } | 176 | } | ||
| 161 | 177 | ||||
| 162 | - | // Touchpad pointer acceleration profiles. | 178 | + | // Pointer acceleration profiles, for mice and touchpads. |
| 163 | const ( | 179 | const ( | ||
| 164 | AccelAdaptive = "adaptive" | 180 | AccelAdaptive = "adaptive" | ||
| 165 | AccelFlat = "flat" | 181 | AccelFlat = "flat" | ||