# -*- python-mode -*-
# -*- coding: UTF-8 -*-

## Copyright (C) 2012-2013  Daniel Pavel
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License along
## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

from __future__ import absolute_import, division, print_function, unicode_literals

from time import time as _timestamp

from logitech_receiver import base as _base
from logitech_receiver import hidpp10 as _hidpp10
from logitech_receiver import notifications as _notifications
from logitech_receiver import status as _status


def run(receivers, args, find_receiver, _ignore):
    assert receivers

    if args.receiver:
        receiver_name = args.receiver.lower()
        receiver = find_receiver(receiver_name)
        if not receiver:
            raise Exception("no receiver found matching '%s'" % receiver_name)
    else:
        receiver = receivers[0]

    assert receiver
    receiver.status = _status.ReceiverStatus(receiver, lambda *args, **kwargs: None)

    # check if it's necessary to set the notification flags
    old_notification_flags = _hidpp10.get_notification_flags(receiver) or 0
    if not (old_notification_flags & _hidpp10.NOTIFICATION_FLAG.wireless):
        _hidpp10.set_notification_flags(receiver, old_notification_flags | _hidpp10.NOTIFICATION_FLAG.wireless)

    # get all current devices
    known_devices = [dev.number for dev in receiver]

    class _HandleWithNotificationHook(int):
        def notifications_hook(self, n):
            nonlocal known_devices
            assert n
            if n.devnumber == 0xFF:
                _notifications.process(receiver, n)
            elif n.sub_id == 0x41 and len(n.data) == _base._SHORT_MESSAGE_SIZE - 4:
                kd, known_devices = known_devices, None  # only process one connection notification
                if kd is not None:
                    if n.devnumber not in kd:
                        receiver.status.new_device = receiver.register_new_device(n.devnumber, n)
                    elif receiver.re_pairs:
                        del receiver[n.devnumber]  # get rid of information on device re-paired away
                        receiver.status.new_device = receiver.register_new_device(n.devnumber, n)

    timeout = 20  # seconds
    receiver.handle = _HandleWithNotificationHook(receiver.handle)

    receiver.set_lock(False, timeout=timeout)
    print('Pairing: turn your new device on (timing out in', timeout, 'seconds).')

    # the lock-open notification may come slightly later, wait for it a bit
    pairing_start = _timestamp()
    patience = 5  # seconds

    while receiver.status.lock_open or _timestamp() - pairing_start < patience:
        n = _base.read(receiver.handle)
        if n:
            n = _base.make_notification(*n)
            if n:
                receiver.handle.notifications_hook(n)

    if not (old_notification_flags & _hidpp10.NOTIFICATION_FLAG.wireless):
        # only clear the flags if they weren't set before, otherwise a
        # concurrently running Solaar app might stop working properly
        _hidpp10.set_notification_flags(receiver, old_notification_flags)

    if receiver.status.new_device:
        dev = receiver.status.new_device
        print('Paired device %d: %s (%s) [%s:%s]' % (dev.number, dev.name, dev.codename, dev.wpid, dev.serial))
    else:
        error = receiver.status.get(_status.KEYS.ERROR)
        if error:
            raise Exception('pairing failed: %s' % error)
        else:
            print('Paired a device')  # this is better than an error

lib/solaar/cli/pair.py
# -*- python-mode -*-
# -*- coding: UTF-8 -*-

## Copyright (C) 2012-2013  Daniel Pavel
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License along
## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

from __future__ import absolute_import, division, print_function, unicode_literals

from time import time as _timestamp

from logitech_receiver import base as _base
from logitech_receiver import hidpp10 as _hidpp10
from logitech_receiver import notifications as _notifications
from logitech_receiver import status as _status


def run(receivers, args, find_receiver, _ignore):
    assert receivers

    if args.receiver:
        receiver_name = args.receiver.lower()
        receiver = find_receiver(receivers, receiver_name)
        if not receiver:
            raise Exception("no receiver found matching '%s'" % receiver_name)
    else:
        receiver = receivers[0]

    assert receiver
    receiver.status = _status.ReceiverStatus(receiver, lambda *args, **kwargs: None)

    # check if it's necessary to set the notification flags
    old_notification_flags = _hidpp10.get_notification_flags(receiver) or 0
    if not (old_notification_flags & _hidpp10.NOTIFICATION_FLAG.wireless):
        _hidpp10.set_notification_flags(receiver, old_notification_flags | _hidpp10.NOTIFICATION_FLAG.wireless)

    # get all current devices
    known_devices = [dev.number for dev in receiver]

    class _HandleWithNotificationHook(int):
        def notifications_hook(self, n):
            nonlocal known_devices
            assert n
            if n.devnumber == 0xFF:
                _notifications.process(receiver, n)
            elif n.sub_id == 0x41 and len(n.data) == _base._SHORT_MESSAGE_SIZE - 4:
                kd, known_devices = known_devices, None  # only process one connection notification
                if kd is not None:
                    if n.devnumber not in kd:
                        receiver.status.new_device = receiver.register_new_device(n.devnumber, n)
                    elif receiver.re_pairs:
                        del receiver[n.devnumber]  # get rid of information on device re-paired away
                        receiver.status.new_device = receiver.register_new_device(n.devnumber, n)

    timeout = 20  # seconds
    receiver.handle = _HandleWithNotificationHook(receiver.handle)

    receiver.set_lock(False, timeout=timeout)
    print('Pairing: turn your new device on (timing out in', timeout, 'seconds).')

    # the lock-open notification may come slightly later, wait for it a bit
    pairing_start = _timestamp()
    patience = 5  # seconds

    while receiver.status.lock_open or _timestamp() - pairing_start < patience:
        n = _base.read(receiver.handle)
        if n:
            n = _base.make_notification(*n)
            if n:
                receiver.handle.notifications_hook(n)

    if not (old_notification_flags & _hidpp10.NOTIFICATION_FLAG.wireless):
        # only clear the flags if they weren't set before, otherwise a
        # concurrently running Solaar app might stop working properly
        _hidpp10.set_notification_flags(receiver, old_notification_flags)

    if receiver.status.new_device:
        dev = receiver.status.new_device
        print('Paired device %d: %s (%s) [%s:%s]' % (dev.number, dev.name, dev.codename, dev.wpid, dev.serial))
    else:
        error = receiver.status.get(_status.KEYS.ERROR)
        if error:
            raise Exception('pairing failed: %s' % error)
        else:
            print('Paired a device')  # this is better than an error

lib/solaar/cli/pair.py
diff --git a/lib/solaar/cli/pair.py b/lib/solaar/cli/pair.py
index c3c9bcb47b..34af861976 100644
--- a/lib/solaar/cli/pair.py
+++ b/lib/solaar/cli/pair.py
@@ -32,7 +32,7 @@ def run(receivers, args, find_receiver, _ignore):
 
     if args.receiver:
         receiver_name = args.receiver.lower()
-        receiver = find_receiver(receiver_name)
+        receiver = find_receiver(receivers, receiver_name)
         if not receiver:
             raise Exception("no receiver found matching '%s'" % receiver_name)
     else:

Either documentation or behavior with a second Unifying Receiver - How to specify?
Arch Linux
solaar-git 1.0.5rc2.r0.g8d01e28-1
2021 Feb 20

Suppose you have, for instance, a second Unifying Receiver that you want to pair with a particular device, perhaps before moving the second receiver and the paired device to another machine.

To specify and pair that particular device with a specific Unifying Receiver, when there is more than one receiver, and consulting the embedded documentation, compare:
```
$ solaar pair --help
usage: solaar pair [-h] [receiver]

positional arguments:
  receiver    select a certain receiver when more than one is present

optional arguments:
  -h, --help  show this help message and exit

The Logitech Unifying Receiver supports up to 6 paired devices at the same time.1 Feb 20
```
versus:
```
$ solaar --help
usage: solaar [-h] [-d] [-D PATH] [--restart-on-wake-up] [-w {show,hide,only}] [-b {regular,symbolic,solaar}] [-V] [--help-actions] ...

positional arguments:
  {show,probe,config,pair,unpair}
                        optional actions to perform

optional arguments:
  -h, --help            show this help message and exit
  -d, --debug           print logging messages, for debugging purposes (may be repeated for extra verbosity)
  -D PATH, --hidraw PATH
                        unifying receiver to use; the first detected receiver if unspecified. Example: /dev/hidraw2
  --restart-on-wake-up  restart Solaar on sleep wake-up (experimental)
  -w {show,hide,only}, --window {show,hide,only}
                        start with window showing / hidden / only (no tray icon)
  -b {regular,symbolic,solaar}, --battery-icons {regular,symbolic,solaar}
                        prefer regular battery / symbolic battery / solaar icons
  -V, --version         show program's version number and exit
  --help-actions        print help for the optional actions
```

Note that, currently, the first approach fails - assuming that the receiver is selected using its Serial number, found with `solaar show` - giving:
```
$ solaar pair xxxxxxxx
solaar: error: Traceback (most recent call last):
  File "/usr/lib/python3.9/site-packages/solaar/cli/__init__.py", line 202, in run
    m.run(c, args, _find_receiver, _find_device)
  File "/usr/lib/python3.9/site-packages/solaar/cli/pair.py", line 35, in run
    receiver = find_receiver(receiver_name)
TypeError: _find_receiver() missing 1 required positional argument: 'name'
```
Well, no, the "1 required positional argument" was not "missing", it was just not recognized.  Using "Device path" as the name, instead of "Serial", produces the same failed result.

Instead, the second approach does work:
```
$ solaar -D /dev/hidraw0 pair
Pairing: turn your new device on (timing out in 20 seconds).
Paired device 1: Wireless Mouse M525 (M525) [4013:xxxxxxxx]
```

So, what is the preferred behavior?  If the device path switch approach is preferred, then the `solaar pair --help` usage message should be revised.  If, instead, the original `solaar pair [-h] [receiver]` approach is preferred, then that would suggest that the device path switch approach should be reverted.  I expect that there was a good reason to implement the device path switch approach, which maybe implies that the original `solaar pair [-h] [receiver]` approach is now deprecated.

It just seems that `/usr/lib/python3.9/site-packages/solaar/cli/pair.py` is still expecting `find_receiver(receiver_name)`, so, a little confusing.  I haven't tried to grok the code, but I do notice there is still:
```
/usr/lib/python3.9/site-packages/solaar/cli/__init__.py
def _find_receiver(receivers, name):
    assert receivers
    assert name

    for r in receivers:
        if name in r.name.lower() or (r.serial is not None and name == r.serial.lower()):
            return r
```

Maybe someone can take a look at all this and see what is going on?
