Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Button mappings: Act as mouse buttons #319

Merged
merged 2 commits into from
Jan 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
32 changes: 31 additions & 1 deletion Documentation/Configuration.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,12 @@ declare namespace Scheme {
| MouseWheelScrollUp
| MouseWheelScrollDown
| MouseWheelScrollLeft
| MouseWheelScrollRight;
| MouseWheelScrollRight
| MouseButtonLeft
| MouseButtonMiddle
| MouseButtonRight
| MouseButtonBack
| MouseButtonForward;

/**
* @description Do not modify the button behavior.
Expand Down Expand Up @@ -472,6 +477,31 @@ declare namespace Scheme {
*/
type MouseWheelScrollRight = "mouse.wheel.scrollRight";

/**
* @description Mouse: Button: Act as left button.
*/
type MouseButtonLeft = "mouse.button.left";

/**
* @description Mouse: Button: Act as middle button.
*/
type MouseButtonMiddle = "mouse.button.middle";

/**
* @description Mouse: Button: Act as right button.
*/
type MouseButtonRight = "mouse.button.right";

/**
* @description Mouse: Button: Act as back button.
*/
type MouseButtonBack = "mouse.button.back";

/**
* @description Mouse: Button: Act as forward button.
*/
type MouseButtonForward = "mouse.button.forward";

type Run = {
/**
* @description Run a specific command. For example, `"open -a 'Mission Control'"`.
Expand Down
40 changes: 40 additions & 0 deletions Documentation/Configuration.json
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,31 @@
"description": "Mission Control: Move right a space.",
"type": "string"
},
"Scheme.Buttons.Mapping.MouseButtonBack": {
"const": "mouse.button.back",
"description": "Mouse: Button: Act as back button.",
"type": "string"
},
"Scheme.Buttons.Mapping.MouseButtonForward": {
"const": "mouse.button.forward",
"description": "Mouse: Button: Act as forward button.",
"type": "string"
},
"Scheme.Buttons.Mapping.MouseButtonLeft": {
"const": "mouse.button.left",
"description": "Mouse: Button: Act as left button.",
"type": "string"
},
"Scheme.Buttons.Mapping.MouseButtonMiddle": {
"const": "mouse.button.middle",
"description": "Mouse: Button: Act as middle button.",
"type": "string"
},
"Scheme.Buttons.Mapping.MouseButtonRight": {
"const": "mouse.button.right",
"description": "Mouse: Button: Act as right button.",
"type": "string"
},
"Scheme.Buttons.Mapping.MouseWheelScrollDown": {
"const": "mouse.wheel.scrollDown",
"description": "Mouse: Wheel: Scroll down.",
Expand Down Expand Up @@ -501,6 +526,21 @@
},
{
"$ref": "#/definitions/Scheme.Buttons.Mapping.MouseWheelScrollRight"
},
{
"$ref": "#/definitions/Scheme.Buttons.Mapping.MouseButtonLeft"
},
{
"$ref": "#/definitions/Scheme.Buttons.Mapping.MouseButtonMiddle"
},
{
"$ref": "#/definitions/Scheme.Buttons.Mapping.MouseButtonRight"
},
{
"$ref": "#/definitions/Scheme.Buttons.Mapping.MouseButtonBack"
},
{
"$ref": "#/definitions/Scheme.Buttons.Mapping.MouseButtonForward"
}
]
},
Expand Down
87 changes: 61 additions & 26 deletions Documentation/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,36 @@ If you hold <kbd>option + back</kbd>, the volume will continue to decrease.
}
```

### Swap back and forward buttons

```json
{
"schemes": [
{
"if": [
{
"device": {
"category": "mouse"
}
}
],
"buttons": {
"mappings": [
{
"button": 3,
"action": "mouse.button.forward"
},
{
"button": 4,
"action": "mouse.button.back"
}
]
}
}
]
}
```

### Action sheet

#### Simple actions
Expand All @@ -508,32 +538,37 @@ A simple action is an action without any parameters.

`<action>` could be one of:

| Action | Description |
| --------------------------- | ------------------------------------ |
| `auto` | Do not modify the button behavior. |
| `none` | Prevent the button events. |
| `missionControl` | Mission Control. |
| `missionControl.spaceLeft` | Mission Control: Move left a space. |
| `missionControl.spaceRight` | Mission Control: Move right a space. |
| `appExpose` | App Exposé. |
| `launchpad` | Launchpad. |
| `showDesktop` | Show desktop. |
| `display.brightnessUp` | Display: Brightness up. |
| `display.brightnessDown` | Display: Brightness down. |
| `media.volumeUp` | Media: Volume up. |
| `media.volumeDown` | Media: Volume down. |
| `media.mute` | Media: Toggle mute. |
| `media.playPause` | Media: Play / pause. |
| `media.next` | Media: Next. |
| `media.previous` | Media: Previous. |
| `media.fastForward` | Media: Fast forward. |
| `media.rewind` | Media: Rewind. |
| `keyboard.brightnessUp` | Keyboard: Brightness up. |
| `keyboard.brightnessDown` | Keyboard: Brightness down. |
| `mouse.wheel.scrollUp` | Mouse: Wheel: Scroll up. |
| `mouse.wheel.scrollDown` | Mouse: Wheel: Scroll down. |
| `mouse.wheel.scrollLeft` | Mouse: Wheel: Scroll left. |
| `mouse.wheel.scrollRight` | Mouse: Wheel: Scroll right. |
| Action | Description |
| --------------------------- | ------------------------------------- |
| `auto` | Do not modify the button behavior. |
| `none` | Prevent the button events. |
| `missionControl` | Mission Control. |
| `missionControl.spaceLeft` | Mission Control: Move left a space. |
| `missionControl.spaceRight` | Mission Control: Move right a space. |
| `appExpose` | App Exposé. |
| `launchpad` | Launchpad. |
| `showDesktop` | Show desktop. |
| `display.brightnessUp` | Display: Brightness up. |
| `display.brightnessDown` | Display: Brightness down. |
| `media.volumeUp` | Media: Volume up. |
| `media.volumeDown` | Media: Volume down. |
| `media.mute` | Media: Toggle mute. |
| `media.playPause` | Media: Play / pause. |
| `media.next` | Media: Next. |
| `media.previous` | Media: Previous. |
| `media.fastForward` | Media: Fast forward. |
| `media.rewind` | Media: Rewind. |
| `keyboard.brightnessUp` | Keyboard: Brightness up. |
| `keyboard.brightnessDown` | Keyboard: Brightness down. |
| `mouse.wheel.scrollUp` | Mouse: Wheel: Scroll up. |
| `mouse.wheel.scrollDown` | Mouse: Wheel: Scroll down. |
| `mouse.wheel.scrollLeft` | Mouse: Wheel: Scroll left. |
| `mouse.wheel.scrollRight` | Mouse: Wheel: Scroll right. |
| `mouse.button.left` | Mouse: Button: Act as left button. |
| `mouse.button.middle` | Mouse: Button: Act as middle button. |
| `mouse.button.right` | Mouse: Button: Act as right button. |
| `mouse.button.back` | Mouse: Button: Act as back button. |
| `mouse.button.forward` | Mouse: Button: Act as forward button. |

#### Run shell commands

Expand Down
4 changes: 4 additions & 0 deletions LinearMouse/Device/Device.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ extension Device {
device.serialNumber
}

var buttonCount: Int? {
device.buttonCount
}

enum Category {
case mouse, trackpad
}
Expand Down
13 changes: 8 additions & 5 deletions LinearMouse/EventTap.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,17 @@ class EventTap {
}

init() {
let eventsOfInterest: CGEventMask =
var eventsOfInterest: CGEventMask =
1 << CGEventType.scrollWheel.rawValue
| 1 << CGEventType.leftMouseDown.rawValue
| 1 << CGEventType.leftMouseUp.rawValue
| 1 << CGEventType.rightMouseDown.rawValue
| 1 << CGEventType.rightMouseUp.rawValue
| 1 << CGEventType.otherMouseDown.rawValue
| 1 << CGEventType.otherMouseUp.rawValue
| 1 << CGEventType.leftMouseDragged.rawValue
eventsOfInterest |= 1 << CGEventType.rightMouseDown.rawValue
| 1 << CGEventType.rightMouseUp.rawValue
| 1 << CGEventType.rightMouseDragged.rawValue
eventsOfInterest |= 1 << CGEventType.otherMouseDown.rawValue
| 1 << CGEventType.otherMouseUp.rawValue
| 1 << CGEventType.otherMouseDragged.rawValue
eventTap = CGEvent.tapCreate(
tap: .cghidEventTap,
place: .headInsertEventTap,
Expand Down
87 changes: 85 additions & 2 deletions LinearMouse/EventTransformer/ButtonActions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,21 @@ extension ButtonActions: EventTransformer {
[.leftMouseUp, .rightMouseUp, .otherMouseUp]
}

var mouseDraggedEventTypes: [CGEventType] {
[.leftMouseDragged, .rightMouseDragged, .otherMouseDragged]
}

var scrollWheelsEventTypes: [CGEventType] {
[.scrollWheel]
}

var allEventTypesOfInterest: [CGEventType] {
[mouseDownEventTypes, mouseUpEventTypes, mouseDraggedEventTypes, scrollWheelsEventTypes]
.flatMap { $0 }
}

func transform(_ event: CGEvent) -> CGEvent? {
guard mouseDownEventTypes.contains(event.type) || mouseUpEventTypes.contains(event.type) || event
.type == .scrollWheel else {
guard allEventTypesOfInterest.contains(event.type) else {
return event
}

Expand Down Expand Up @@ -62,6 +74,10 @@ extension ButtonActions: EventTransformer {
let keyRepeatInterval = mapping.repeat == true ? NSEvent.keyRepeatInterval : 0
let keyRepeatEnabled = keyRepeatDelay > 0 && keyRepeatInterval > 0

if !keyRepeatEnabled, handleSimpleButtonMappings(event: event, action: action) {
return event
}

// Actions are executed when button is down if key repeat is enabled; otherwise, actions are
// executed when button is up.
eventsOfInterest = keyRepeatEnabled ? mouseDownEventTypes : mouseUpEventTypes
Expand Down Expand Up @@ -207,6 +223,21 @@ extension ButtonActions: EventTransformer {
case .simpleAction(.mouseWheelScrollRight):
postScrollEvent(horizontal: -3, vertical: 0)

case .simpleAction(.mouseButtonLeft):
postClickEvent(mouseButton: .left)

case .simpleAction(.mouseButtonMiddle):
postClickEvent(mouseButton: .center)

case .simpleAction(.mouseButtonRight):
postClickEvent(mouseButton: .right)

case .simpleAction(.mouseButtonBack):
postClickEvent(mouseButton: .back)

case .simpleAction(.mouseButtonForward):
postClickEvent(mouseButton: .forward)

case let .run(command):
let task = Process()
task.launchPath = "/bin/bash"
Expand Down Expand Up @@ -300,4 +331,56 @@ extension ButtonActions: EventTransformer {
}
}
}

private func handleSimpleButtonMappings(event: CGEvent, action: Scheme.Buttons.Mapping.Action) -> Bool {
guard [mouseDownEventTypes, mouseUpEventTypes, mouseDraggedEventTypes].flatMap({ $0 }).contains(event.type)
else {
return false
}

let mouseEventView = MouseEventView(event)

switch action {
case .simpleAction(.mouseButtonLeft):
mouseEventView.mouseButton = .left
case .simpleAction(.mouseButtonMiddle):
mouseEventView.mouseButton = .center
case .simpleAction(.mouseButtonRight):
mouseEventView.mouseButton = .right
case .simpleAction(.mouseButtonBack):
mouseEventView.mouseButton = .back
case .simpleAction(.mouseButtonForward):
mouseEventView.mouseButton = .forward
default:
return false
}

return true
}

private func postClickEvent(mouseButton: CGMouseButton) {
guard let location = CGEvent(source: nil)?.location else {
return
}

guard let mouseDownEvent = CGEvent(
mouseEventSource: nil,
mouseType: mouseButton.fixedCGEventType(of: .leftMouseDown),
mouseCursorPosition: location,
mouseButton: mouseButton
) else {
return
}
guard let mouseUpEvent = CGEvent(
mouseEventSource: nil,
mouseType: mouseButton.fixedCGEventType(of: .leftMouseUp),
mouseCursorPosition: location,
mouseButton: mouseButton
) else {
return
}

mouseDownEvent.post(tap: .cgSessionEventTap)
mouseUpEvent.post(tap: .cgSessionEventTap)
}
}
5 changes: 0 additions & 5 deletions LinearMouse/EventTransformer/UniversalBackForward.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,6 @@ import Foundation
import GestureKit
import os.log

extension CGMouseButton {
static let back = CGMouseButton(rawValue: 3)!
static let forward = CGMouseButton(rawValue: 4)!
}

class UniversalBackForward: EventTransformer {
private static let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "UniversalBackForward")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ extension Scheme.Buttons.Mapping.Action: Codable {
case mouseWheelScrollDown = "mouse.wheel.scrollDown"
case mouseWheelScrollLeft = "mouse.wheel.scrollLeft"
case mouseWheelScrollRight = "mouse.wheel.scrollRight"

case mouseButtonLeft = "mouse.button.left"
case mouseButtonMiddle = "mouse.button.middle"
case mouseButtonRight = "mouse.button.right"
case mouseButtonBack = "mouse.button.back"
case mouseButtonForward = "mouse.button.forward"
}

enum ValueError: Error {
Expand Down
17 changes: 14 additions & 3 deletions LinearMouse/MouseEventView/MouseEventView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,21 @@ class MouseEventView {
}

var mouseButton: CGMouseButton? {
guard let mouseButtonNumber = UInt32(exactly: event.getIntegerValueField(.mouseEventButtonNumber)) else {
return nil
get {
guard let mouseButtonNumber = UInt32(exactly: event.getIntegerValueField(.mouseEventButtonNumber)) else {
return nil
}
return CGMouseButton(rawValue: mouseButtonNumber)!
}

set {
guard let newValue = newValue else {
return
}

event.type = newValue.fixedCGEventType(of: event.type)
event.setIntegerValueField(.mouseEventButtonNumber, value: Int64(newValue.rawValue))
}
return CGMouseButton(rawValue: mouseButtonNumber)!
}

var modifierFlags: CGEventFlags {
Expand Down