【Mac対応】pynputでマウスのサイドボタンを入力する

:::note warn
イベント発火のみで、入力の検出はできていません
:::

# 結論

```python
from pynput.mouse import Button
from pynput.mouse._darwin import _button_value
from enum import Enum

class Buttons(Enum):
    left = Button.left.value
    right = Button.right.value
    middle = Button.middle.value
    x1 = _button_value("kCGEventOther", 3) # ここ重要
    x2 = _button_value("kCGEventOther", 4) # ここ重要
```

### 使い方

```python
from pynput.mouse import Controller

controller = Controller()

controller.click(Buttons.x1) # 第4ボタン
controller.click(Buttons.x2) # 第5ボタン
```

:::note info
システム設定からアクセシビリティにアプリを追加するのを忘れないようにしましょう。
:::


# はじめに
pynputを使ってマウスのサイドボタンを入力させる場合、Windowsでは以下のように書くことができます。

```python
from pynput.mouse import Button, Controller

controller = Controller()

controller.click(Button.x1) # 第4ボタン
controller.click(Button.x2) # 第5ボタン
```

しかしMacではAttributeErrorとなってしまいます。

```
AttributeError: type object 'Button' has no attribute 'x1'
```

これは、pynputがMacのサイドボタンに対応していないためです。

# 辿り着いた経緯

pynputの内部コードを覗いてみます。

```python:pynput/mouse/_darwin.py
...

def _button_value(base_name, mouse_button):
    ...
    return (
        tuple(
            getattr(Quartz, '%sMouse%s' % (base_name, name))
            for name in ('Down', 'Up', 'Dragged')),
        mouse_button)

        
class Button(enum.Enum):
    """The various buttons.
    """
    unknown = None
    left = _button_value('kCGEventLeft', 0)
    middle = _button_value('kCGEventOther', 2)
    right = _button_value('kCGEventRight', 1)

...
```

`kCGEvent`というキーワードが出てきたので調べてみると、[Apple Developer](https://developer.apple.com/documentation/coregraphics/cgeventtype)のサイトがヒットしました。

>case otherMouseDown
>　　Specifies a mouse down event with one of **buttons 2-31**.
>case otherMouseUp
>　　Specifies a mouse up event with one of **buttons 2-31**.
>case otherMouseDragged
>　　Specifies a mouse drag event with one of **buttons 2-31** down.

どうやらボタン2〜31は全て`kCGEventOther`として括られるようです。
ボタン0が左クリック、ボタン1が右クリックで、ボタン2がミドルクリック（マウスホイール）だとすると、サイドボタンは3と4である可能性が高そうです。

2〜31の`kCGEventOther`を実行して検証してみます。
```python
from pynput.mouse import Controller, Button
from pynput.mouse._darwin import _button_value
from time import sleep
from enum import Enum

addition = {}
for i in range(2, 32):
    button = _button_value("kCGEventOther", i)
    addition[f"button{i}"] = button
# Enumの拡張については下を参考
# https://zenn.dev/yuji38kwmt/articles/63cda54021ecf3
tmp = {e.name: e.value for e in Button} | addition
buttons = Enum("buttons", tmp)

sleep(5) # この間にブラウザなど、サイドボタンの動きがわかるアプリにフォーカスする

controller = Controller()
for i in range(2, 32):
    controller.click(buttons[f"button{i}"])
    print(f"Clicked button {i}")
    sleep(3)
```

検証の結果、button3の時に「戻る」、button4の時に「進む」が実行されました。

# 終わりに
### Windows / Mac両対応のコード

```python
from pynput.mouse import Button as bt
from enum import Enum
import platform

system = platform.system()

class Button(Enum):
    if system == "Windows":
        left = bt.left.value
        right = bt.right.value
        middle = bt.middle.value
        x1 = bt.x1.value
        x2 = bt.x2.value
    elif system == "Darwin":
        from pynput.mouse._darwin import _button_value
        left = bt.left.value
        right = bt.right.value
        middle = bt.middle.value
        x1 = _button_value("kCGEventOther", 3)
        x2 = _button_value("kCGEventOther", 4)
    elif system == "Linux":
        pass # 検証環境をサッと用意できなかったので割愛
```

または

```python
from pynput.mouse import Button
import sys

if sys.platform == "darwin":
    from pynput.mouse._darwin import _button_value
    from enum import Enum
    Button = Enum("Button", {e.name: e.value for e in Button} | {"x1": _button_value("kCGEventOther", 3), "x2": _button_value("kCGEventOther", 4)})
```

#### 検証

```python
from pynput.mouse import Controller
from time import sleep

controller = Controller()

sleep(5)

controller.click(Button.x1, 1)
sleep(3)
controller.click(Button.x2, 1)
```