From d6744fbc4eb5b3a8d7dd8f4a3cc4348f576d6878 Mon Sep 17 00:00:00 2001
From: Josh Bendavid <Josh.Bendavid@cern.ch>
Date: Thu, 26 Dec 2019 13:06:57 -0500
Subject: [PATCH] Fix handling of symlinked device descriptors in
 keyboard_remote and move remaining sync io to executor thread pool (#30206)

* fix handling of symlinked device decriptors

* make check for symlinked paths more efficient

* make variable names pylint compliant

* move sync io during setup and device connect/disconnect to executor thread pool

* move remaining sync io during setup to executor thread pool

* remove unnecessary lambda functions
---
 .../components/keyboard_remote/__init__.py    | 24 +++++++++++++++----
 1 file changed, 19 insertions(+), 5 deletions(-)

diff --git a/homeassistant/components/keyboard_remote/__init__.py b/homeassistant/components/keyboard_remote/__init__.py
index 24889a3f820..310bd0189bd 100644
--- a/homeassistant/components/keyboard_remote/__init__.py
+++ b/homeassistant/components/keyboard_remote/__init__.py
@@ -2,6 +2,7 @@
 # pylint: disable=import-error
 import asyncio
 import logging
+import os
 
 import aionotify
 from evdev import InputDevice, categorize, ecodes, list_devices
@@ -119,9 +120,11 @@ class KeyboardRemote:
         # add initial devices (do this AFTER starting watcher in order to
         # avoid race conditions leading to missing device connections)
         initial_start_monitoring = set()
-        descriptors = list_devices(DEVINPUT)
+        descriptors = await self.hass.async_add_executor_job(list_devices, DEVINPUT)
         for descriptor in descriptors:
-            dev, handler = self.get_device_handler(descriptor)
+            dev, handler = await self.hass.async_add_executor_job(
+                self.get_device_handler, descriptor
+            )
 
             if handler is None:
                 continue
@@ -165,6 +168,15 @@ class KeyboardRemote:
             handler = self.handlers_by_descriptor[descriptor]
         elif dev.name in self.handlers_by_name:
             handler = self.handlers_by_name[dev.name]
+        else:
+            # check for symlinked paths matching descriptor
+            for test_descriptor, test_handler in self.handlers_by_descriptor.items():
+                if test_handler.dev is not None:
+                    fullpath = test_handler.dev.path
+                else:
+                    fullpath = os.path.realpath(test_descriptor)
+                if fullpath == descriptor:
+                    handler = test_handler
 
         return (dev, handler)
 
@@ -186,7 +198,9 @@ class KeyboardRemote:
                     (event.flags & aionotify.Flags.CREATE)
                     or (event.flags & aionotify.Flags.ATTRIB)
                 ) and not descriptor_active:
-                    dev, handler = self.get_device_handler(descriptor)
+                    dev, handler = await self.hass.async_add_executor_job(
+                        self.get_device_handler, descriptor
+                    )
                     if handler is None:
                         continue
                     self.active_handlers_by_descriptor[descriptor] = handler
@@ -242,7 +256,7 @@ class KeyboardRemote:
             """Stop event monitoring task and issue event."""
             if self.monitor_task is not None:
                 try:
-                    self.dev.ungrab()
+                    await self.hass.async_add_executor_job(self.dev.ungrab)
                 except OSError:
                     pass
                 # monitoring of the device form the event loop and closing of the
@@ -272,7 +286,7 @@ class KeyboardRemote:
 
             try:
                 _LOGGER.debug("Start device monitoring")
-                dev.grab()
+                await self.hass.async_add_executor_job(dev.grab)
                 async for event in dev.async_read_loop():
                     if event.type is ecodes.EV_KEY:
                         if event.value in self.key_values:
-- 
GitLab