Repository: pwr-Solaar/Solaar

Issue:
Device status missing when device charging
**Information**
<!-- Do not bother opening an issue for version 0.9.2 or older, it is extremely outdated. Please update to the latest one and see if your issue persists. -->
- Solaar version (`git describe --tags` if cloned from Solaar repository):  1.0.2-rc1
- Distribution:  Ubuntu 18.04 LTS
- Kernel version (ex. `uname -srmo`): Linux 4.15.0-96-generic x86_64 GNU/Linux
- Output of `solaar show`:
```
Unifying Receiver
  Device path  : /dev/hidraw4
  USB id       : 046d:c52b
  Serial       : C07291C4
    Firmware   : 12.11.B0032
    Bootloader : 04.16
    Other      : AA.AA
  Has 2 paired device(s) out of a maximum of 6.
  Notifications: wireless (0x000100)
  Device activity counters: 1=146, 2=81

  1: Wireless Mouse MX Master 3
     Codename     : MX Master 3000
     Kind         : mouse
     Wireless PID : 4082
     Protocol     : HID++ 4.5
     Polling rate : 8 ms (125Hz)
     Serial number: 12E60703
        Bootloader: BOT 95.00.B0013
          Firmware: MPM 19.00.B0013
             Other:
     The power switch is located on the base.
     Supports 34 HID++ 2.0 features:
         0: ROOT                   {0000}
         1: FEATURE SET            {0001}
         2: DEVICE FW VERSION      {0003}
         3: DEVICE NAME            {0005}
         4: WIRELESS DEVICE STATUS {1D4B}
         5: RESET                  {0020}
         6: CRYPTO ID              {0021}
         7: BATTERY STATUS         {1000}
         8: REPROG CONTROLS V4     {1B04}
         9: CHANGE HOST            {1814}
        10: unknown:2250           {2250}
        11: ADJUSTABLE DPI         {2201}
        12: SMART SHIFT            {2110}
        13: HIRES WHEEL            {2121}
            Multiplier: 15
            Has invert
              Normal wheel motion
            Has ratchet switch
              Normal wheel mode
            Low resolution mode
            HID notification
        14: THUMB WHEEL            {2150}
        15: unknown:2251           {2251}
        16: DFUCONTROL SIGNED      {00C2}
        17: DEVICE RESET           {1802}   internal, hidden
        18: unknown:1803           {1803}   internal, hidden
        19: CONFIG DEVICE PROPS    {1806}   internal, hidden
        20: unknown:1813           {1813}   internal, hidden
        21: OOBSTATE               {1805}   internal, hidden
        22: unknown:1830           {1830}   internal, hidden
        23: unknown:1890           {1890}   internal, hidden
        24: unknown:1891           {1891}   internal, hidden
        25: unknown:18A1           {18A1}   internal, hidden
        26: unknown:1DF3           {1DF3}   internal, hidden
        27: unknown:1E00           {1E00}   hidden
        28: unknown:1EB0           {1EB0}   internal, hidden
        29: unknown:1861           {1861}   internal, hidden
        30: unknown:9001           {9001}   internal, hidden
        31: unknown:9203           {9203}   internal, hidden
        32: unknown:9205           {9205}   internal, hidden
        33: unknown:9300           {9300}   internal, hidden
     Has 8 reprogrammable keys:
         0: LEFT CLICK                , default: LeftClick                   => LEFT CLICK
             mse, pos:0, group:1, gmask:1
         1: RIGHT CLICK               , default: RightClick                  => RIGHT CLICK
             mse, pos:0, group:1, gmask:1
         2: MIDDLE BUTTON             , default: MiddleMouseButton           => MIDDLE BUTTON
             mse, reprogrammable, divertable, pos:0, group:3, gmask:7
         3: BACK AS BUTTON 4          , default: BackEx                      => BACK AS BUTTON 4
             mse, reprogrammable, divertable, pos:0, group:2, gmask:3
         4: FORWARD AS BUTTON 5       , default: BrowserForwardEx            => FORWARD AS BUTTON 5
             mse, reprogrammable, divertable, pos:0, group:2, gmask:3
         5: unknown:00C3              , default: unknown:00A9                => unknown:00C3
             mse, reprogrammable, divertable, pos:0, group:3, gmask:7
         6: unknown:00C4              , default: unknown:009D                => unknown:00C4
             mse, reprogrammable, divertable, pos:0, group:3, gmask:7
         7: unknown:00D7              , default: unknown:00B4                => unknown:00D7
             divertable, virtual, pos:0, group:4, gmask:0
     Battery: 100%, discharging.

  2: MX Keys Wireless Keyboard
     Codename     : MX Keys
     Kind         : keyboard
     Wireless PID : 408A
     Protocol     : HID++ 4.5
     Polling rate : 20 ms (50Hz)
     Serial number: 5F6A4FEC
        Bootloader: BL1 08.00.B0011
          Firmware: MPK 12.00.B0011
             Other:
     The power switch is located on the edge of top right corner.
     Supports 33 HID++ 2.0 features:
         0: ROOT                   {0000}
         1: FEATURE SET            {0001}
         2: DEVICE FW VERSION      {0003}
         3: DEVICE NAME            {0005}
         4: WIRELESS DEVICE STATUS {1D4B}
         5: RESET                  {0020}
         6: DEVICE FRIENDLY NAME   {0007}
         7: BATTERY STATUS         {1000}
         8: REPROG CONTROLS V4     {1B04}
         9: CHANGE HOST            {1814}
        10: HOSTS INFO             {1815}
        11: BACKLIGHT2             {1982}
        12: K375S FN INVERSION     {40A3}
        13: ENCRYPTION             {4100}
        14: LOCK KEY STATE         {4220}
        15: KEYBOARD DISABLE       {4521}
        16: MULTIPLATFORM          {4531}
        17: DFUCONTROL SIGNED      {00C2}
        18: DEVICE RESET           {1802}   internal, hidden
        19: unknown:1803           {1803}   internal, hidden
        21: CONFIG DEVICE PROPS    {1806}   internal, hidden
        21: unknown:1813           {1813}   internal, hidden
        22: OOBSTATE               {1805}   internal, hidden
        23: unknown:1830           {1830}   internal, hidden
        24: unknown:1890           {1890}   internal, hidden
        25: unknown:1891           {1891}   internal, hidden
        26: unknown:18A1           {18A1}   internal, hidden
        27: unknown:1DF3           {1DF3}   internal, hidden
        28: unknown:1E00           {1E00}   hidden
        29: unknown:1EB0           {1EB0}   internal, hidden
        30: unknown:1861           {1861}   internal, hidden
        31: unknown:1A20           {1A20}   internal, hidden
        32: unknown:18B0           {18B0}   internal, hidden
     Has 24 reprogrammable keys:
         0: unknown:00D1              , default: unknown:00AE                => unknown:00D1
             nonstandard, divertable, persistently divertable, pos:0, group:0, gmask:0
         1: unknown:00D2              , default: unknown:00AF                => unknown:00D2
             nonstandard, divertable, persistently divertable, pos:0, group:0, gmask:0
         2: unknown:00D3              , default: unknown:00B0                => unknown:00D3
             nonstandard, divertable, persistently divertable, pos:0, group:0, gmask:0
         3: unknown:00C7              , default: unknown:00A3                => unknown:00C7
             is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:1, group:0, gmask:0
         4: unknown:00C8              , default: unknown:00A4                => unknown:00C8
             is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:2, group:0, gmask:0
         5: unknown:00E0              , default: unknown:00BF                => unknown:00E0
             is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:3, group:0, gmask:0
         6: unknown:00E1              , default: unknown:00C0                => unknown:00E1
             is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:4, group:0, gmask:0
         7: SHOW DESKTOP              , default: ShowDesktop                 => SHOW DESKTOP
             is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:5, group:0, gmask:0
         8: unknown:00E2              , default: unknown:00C1                => unknown:00E2
             is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:6, group:0, gmask:0
         9: unknown:00E3              , default: unknown:00C2                => unknown:00E3
             is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:7, group:0, gmask:0
        10: unknown:00E4              , default: Previous                    => unknown:00E4
             is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:8, group:0, gmask:0
        11: unknown:00E5              , default: Play/Pause                  => unknown:00E5
             is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:9, group:0, gmask:0
        12: unknown:00E6              , default: Next                        => unknown:00E6
             is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:10, group:0, gmask:0
        13: unknown:00E7              , default: Mute                        => unknown:00E7
             is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:11, group:0, gmask:0
        14: unknown:00E8              , default: Volume Down                 => unknown:00E8
             is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:12, group:0, gmask:0
        15: unknown:00E9              , default: Volume Up                   => unknown:00E9
             nonstandard, reprogrammable, divertable, persistently divertable, pos:0, group:0, gmask:0
        16: Calculator                , default: Calculator                  => Calculator
             nonstandard, reprogrammable, divertable, persistently divertable, pos:0, group:0, gmask:0
        17: unknown:00BF              , default: unknown:009B                => unknown:00BF
             nonstandard, reprogrammable, divertable, persistently divertable, pos:0, group:0, gmask:0
        18: unknown:00EA              , default: unknown:00C3                => unknown:00EA
             nonstandard, reprogrammable, divertable, persistently divertable, pos:0, group:0, gmask:0
        19: Lock PC                   , default: WindowsLock                 => Lock PC
             nonstandard, reprogrammable, divertable, persistently divertable, pos:0, group:0, gmask:0
        20: unknown:00EC              , default: unknown:00B8                => unknown:00EC
             nonstandard, divertable, persistently divertable, pos:0, group:0, gmask:0
        21: unknown:00EB              , default: unknown:00B6                => unknown:00EB
             nonstandard, divertable, persistently divertable, pos:0, group:0, gmask:0
        22: unknown:00DE              , default: Do Nothing One              => unknown:00DE
            is FN, pos:0, group:0, gmask:0
        23: unknown:0034              , default: Do Nothing One              => unknown:0034
             nonstandard, pos:0, group:0, gmask:0
     Battery: N/A, full.
```

**Describe the bug*
When switching between devices and MX Keyboard is physically plugged in with the USB wire, the Keyboard shows in the GUI, but charging state does not.

**To Reproduce**
Steps to reproduce the behavior:
1. MX Keyboard is paired and switched to device 2 and ON (also using a Unifying receiver)
2. USB cable is plugged in to USB computer port and the MX Keyboard
3. Use the keyboard button to switch to device 1
4. Display sees keyboard but not device status

5. If I unplug the keyboard at the keyboard, it will show the status.  Then if I plug the keyboard back in, the charging status stays on screen.

**Screenshots**
![Screenshot from 2020-04-23 18-50-05](https://user-images.githubusercontent.com/11669881/80157096-76eaaf80-8593-11ea-84fe-23382facf894.png)


**Additional context**
Add any other context about the problem here.



Discussion:
I don't have access to an MX Keys, but my guess is that it can be connected by either wired or wireless and the problem is that when it is wired Solaar does not report battery status.  Is this the problem?

If so, then my guess is that when connected wired the keyboard does not respond to the messages that Solaar sends through the Logitech wireless receiver.  I don't think that Solaar can do much about this without a significant change to also use the wired connection.

PS:  I see you are using a dark theme.  What do the Solaar icons in the system tray look like?
Connecting the MX Keys with usb cable is only to charge the battery. All the other things are default: communication is still with the unifying receiver/bluetooth.

I have here MX Keys as well, and the moment just charged.
@pfps, is there additional debug info possible to look at, if I'm in situation of charging as well?
OK, then the issue is that when the cable is plugged in the mouse is reporting the battery discharge level as 0, which means unknown.   To check please run `solaar -dd show`

It may be that in this situation Solaar should be reporting the battery status, which as you can guess is full.  Is it the case that your battery is indeed fully charged?

@FFY00 Is it reasonable to report a full battery if the battery status is full but the battery discharge status is 0?


I just updated my PC which included a solaar update.  I used to be able to reproduce this issue with consistency every single time.  I am now no longer able to recreate this issue after the update.  

@pfps No, it was likely at 95% since my workaround still showed it charging.  I think this was due to the way the battery status is reported in increments (100,50,20). The keyboard was almost fully charged.   I also believe it is reasonable to expect the Status indicator to report a status of the battery even if fully charged.  The intent is to inform the user of the current state of the device.    

I do not think it will report 95%, so I was also missing the "charging" state indicator.  So if the *battery* is not actually at 100%, the status shows 100% but also Charging. Confirm?   Once the battery is actually full the keyboard shows 100% and Not Charging.

(Dark mode screen added)
![dark-mode-solaar](https://user-images.githubusercontent.com/11669881/80274736-97595d80-86aa-11ea-8514-7973953d5333.png)

@kauffman214 What distribution and window manager are you running?  Others are reporting dark-on-dark icons.
The battery state is shown better in `solaar show`, but the system tray and the main window use the same icon code.  I expect that when completely charged the mouse reports differently than when almost completely charged.
> @kauffman214 What distribution and window manager are you running? Others are reporting dark-on-dark icons.

The distro is in the bug filing.  Ubuntu 18.04 LTS.    I'm using the ARC-DARK theme using TWEAK, since 18.04 did not have a dark mode.  That said, it looks like if I switch back to Adwaita-dark and Ubuntu-mono-dark icons,  I get a different but appropriate icon in almost white.  I wonder if the OS is not changing the ICON sets correctly when people change to dark mode.

![Screenshot from 2020-04-26 08-16-26](https://user-images.githubusercontent.com/11669881/80307321-46bf2e80-8796-11ea-9f2b-5bac4405f1b2.png)

ALSO... the bug I filed came back.  I have attached two debug files so you can compare.   One is the solaar -dd show  output while the problem is present.  The other is after I unplug and plug the keyboard back which resolves the status reporting issue.

As you can see in the attachment, the keyboard is 100% but charging - so the battery is not full, but status is reporting 100%.

[debug-solaar-20200426-error.txt](https://github.com/pwr-Solaar/Solaar/files/4535283/debug-solaar-20200426-error.txt)
[debug-solaar-20200426-replugged.txt](https://github.com/pwr-Solaar/Solaar/files/4535289/debug-solaar-20200426-replugged.txt)
![Screenshot from 2020-04-26 08-23-31](https://user-images.githubusercontent.com/11669881/80307493-6a36a900-8797-11ea-9e43-ad769eeb3cc2.png)



I've looked at the icon code in icons.py.    The two solaar show outputs demonstrate that the keyboard is reporting a charging status, but no battery level status.  No battery level status is supposed to be unknown.  But the keyboard is also reporting  a charging status of recharging or full.  I put in a question to @FFY00 whether that should override the unknown battery level.

The image that you provide shows 100% and charging for the keyboard, which indicates a different report from the keyboard.
@kauffman214 The latest image that you report shows a light-on-dark system tray icon for Solaar, or so it appears.  Is that the case?  If you switch to a light theme does this icon changes to dark-on-light?  Other people, including myself, never see light icons, even when starting with a dark theme, so I'm puzzled as to how you have achieved this.

Resolving patch:
diff --git a/lib/logitech_receiver/status.py b/lib/logitech_receiver/status.py
--- a/lib/logitech_receiver/status.py
+++ b/lib/logitech_receiver/status.py
@@ -176,8 +176,16 @@ def set_battery_info(self, level, status, timestamp=None):
 
 		if level is None:
 			# Some notifications may come with no battery level info, just
-			# charging state info, so assume the level is unchanged.
-			level = self.get(KEYS.BATTERY_LEVEL)
+			# charging state info, so do our best to infer a level (even if it is just the last level)
+			# It is not always possible to do this well
+			if status == _hidpp20.BATTERY_STATUS.full:
+				level = _hidpp10.BATTERY_APPOX.full
+			elif status in (_hidpp20.BATTERY_STATUS.almost_full, _hidpp20.BATTERY_STATUS.recharging):
+				level = _hidpp10.BATTERY_APPOX.good
+			elif status == _hidpp20.BATTERY_STATUS.slow_recharge:
+				level = _hidpp10.BATTERY_APPOX.low
+			else:
+				level = self.get(KEYS.BATTERY_LEVEL)
 		else:
 			assert isinstance(level, int)
 
@@ -185,7 +193,8 @@ def set_battery_info(self, level, status, timestamp=None):
 		old_level, self[KEYS.BATTERY_LEVEL] = self.get(KEYS.BATTERY_LEVEL), level
 		old_status, self[KEYS.BATTERY_STATUS] = self.get(KEYS.BATTERY_STATUS), status
 
-		charging = status in (_hidpp20.BATTERY_STATUS.recharging, _hidpp20.BATTERY_STATUS.slow_recharge)
+		charging = status in (_hidpp20.BATTERY_STATUS.recharging, _hidpp20.BATTERY_STATUS.almost_full,
+				      _hidpp20.BATTERY_STATUS.full, _hidpp20.BATTERY_STATUS.slow_recharge)
 		old_charging, self[KEYS.BATTERY_CHARGING] = self.get(KEYS.BATTERY_CHARGING), charging
 
 		changed = old_level != level or old_status != status or old_charging != charging


Test patch: