diff --git a/bin/input-monitor.c b/bin/input-monitor.c
index 49f092b68072315d6ceb6401ee40fc7f95360834..036b1b13a3ed8acaf32d8707b471481744bb1dab 100644
--- a/bin/input-monitor.c
+++ b/bin/input-monitor.c
@@ -45,6 +45,41 @@ static gboolean opt_one_line = FALSE;
 static gboolean opt_seq = FALSE;
 static gboolean opt_verbose = FALSE;
 
+static void
+jsonify_flags (JsonBuilder *builder,
+               GType flags_type,
+               unsigned int values)
+{
+  GFlagsClass *class;
+  GFlagsValue *flags_value;
+
+  g_return_if_fail (G_TYPE_IS_FLAGS (flags_type));
+
+  class = g_type_class_ref (flags_type);
+
+  while (values != 0)
+    {
+      flags_value = g_flags_get_first_value (class, values);
+
+      if (flags_value == NULL)
+        break;
+
+      json_builder_add_string_value (builder, flags_value->value_nick);
+      values &= ~flags_value->value;
+    }
+
+  if (values)
+    {
+      gchar *rest = g_strdup_printf ("0x%x", values);
+
+      json_builder_add_string_value (builder, rest);
+
+      g_free (rest);
+    }
+
+  g_type_class_unref (class);
+}
+
 static void
 print_json (JsonBuilder *builder)
 {
@@ -124,6 +159,12 @@ added (SrtInputDeviceMonitor *monitor,
           g_auto(GStrv) udev_properties = NULL;
           const char *sys_path;
 
+          json_builder_set_member_name (builder, "interface_flags");
+          json_builder_begin_array (builder);
+          jsonify_flags (builder, SRT_TYPE_INPUT_DEVICE_INTERFACE_FLAGS,
+                         srt_input_device_get_interface_flags (dev));
+          json_builder_end_array (builder);
+
           json_builder_set_member_name (builder, "dev_node");
           json_builder_add_string_value (builder,
                                          srt_input_device_get_dev_node (dev));
diff --git a/steam-runtime-tools/direct-input-device.c b/steam-runtime-tools/direct-input-device.c
index 7cd31f930ecb81090827e5e98b00368afd97654e..b182082c7419e6c7e96c45c78b8d182b5316f7c8 100644
--- a/steam-runtime-tools/direct-input-device.c
+++ b/steam-runtime-tools/direct-input-device.c
@@ -170,6 +170,23 @@ struct _SrtDirectInputDevice
   {
     gchar *sys_path;
   } usb_device_ancestor;
+
+  struct
+  {
+    guint32 bus_type;
+    guint32 product_id;
+    guint32 vendor_id;
+    guint32 version;
+  } evdev;
+
+  struct
+  {
+    guint32 bus_type;
+    guint32 product_id;
+    guint32 vendor_id;
+  } hid;
+
+  SrtInputDeviceInterfaceFlags iface_flags;
 };
 
 struct _SrtDirectInputDeviceClass
@@ -241,6 +258,14 @@ _srt_direct_input_device_class_init (SrtDirectInputDeviceClass *cls)
   object_class->finalize = srt_direct_input_device_finalize;
 }
 
+static SrtInputDeviceInterfaceFlags
+srt_direct_input_device_get_interface_flags (SrtInputDevice *device)
+{
+  SrtDirectInputDevice *self = SRT_DIRECT_INPUT_DEVICE (device);
+
+  return self->iface_flags;
+}
+
 static const char *
 srt_direct_input_device_get_dev_node (SrtInputDevice *device)
 {
@@ -294,6 +319,7 @@ srt_direct_input_device_iface_init (SrtInputDeviceInterface *iface)
 {
 #define IMPLEMENT(x) iface->x = srt_direct_input_device_ ## x
 
+  IMPLEMENT (get_interface_flags);
   IMPLEMENT (get_dev_node);
   IMPLEMENT (get_sys_path);
   IMPLEMENT (get_subsystem);
@@ -441,6 +467,9 @@ add_device (SrtDirectInputDeviceMonitor *self,
       return;
     }
 
+  if (subsystem == quark_hidraw)
+    device->iface_flags |= SRT_INPUT_DEVICE_INTERFACE_FLAGS_RAW_HID;
+
   g_debug ("Trying to open %s", devnode);
 
   fd = open (devnode, O_RDONLY | O_NONBLOCK | ALWAYS_OPEN_FLAGS);
@@ -449,6 +478,36 @@ add_device (SrtDirectInputDeviceMonitor *self,
     {
       /* The permissions are already OK for use */
       g_debug ("Opened %s", devnode);
+      device->iface_flags |= SRT_INPUT_DEVICE_INTERFACE_FLAGS_READABLE;
+
+      if (_srt_get_identity_from_raw_hid (fd,
+                                          &device->hid.bus_type,
+                                          &device->hid.vendor_id,
+                                          &device->hid.product_id))
+        {
+          g_debug ("%s is raw HID: bus type 0x%04x, vendor 0x%04x, product 0x%04x",
+                   devnode,
+                   device->hid.bus_type,
+                   device->hid.vendor_id,
+                   device->hid.product_id);
+          device->iface_flags |= SRT_INPUT_DEVICE_INTERFACE_FLAGS_RAW_HID;
+        }
+
+      if (_srt_get_identity_from_evdev (fd,
+                                        &device->evdev.bus_type,
+                                        &device->evdev.vendor_id,
+                                        &device->evdev.product_id,
+                                        &device->evdev.version))
+        {
+          g_debug ("%s is evdev: bus type 0x%04x, vendor 0x%04x, product 0x%04x, version 0x%04x",
+                   devnode,
+                   device->evdev.bus_type,
+                   device->evdev.vendor_id,
+                   device->evdev.product_id,
+                   device->evdev.version);
+          device->iface_flags |= SRT_INPUT_DEVICE_INTERFACE_FLAGS_EVENT;
+        }
+
       close (fd);
     }
   else
@@ -459,6 +518,23 @@ add_device (SrtDirectInputDeviceMonitor *self,
       return;
     }
 
+  fd = open (devnode, O_RDWR | O_NONBLOCK | ALWAYS_OPEN_FLAGS);
+
+  if (fd >= 0)
+    {
+      g_debug ("Opened %s rw", devnode);
+      device->iface_flags |= SRT_INPUT_DEVICE_INTERFACE_FLAGS_READ_WRITE;
+      close (fd);
+    }
+
+  if ((device->iface_flags & (SRT_INPUT_DEVICE_INTERFACE_FLAGS_EVENT
+                              | SRT_INPUT_DEVICE_INTERFACE_FLAGS_RAW_HID)) == 0)
+    {
+      g_debug ("%s is neither evdev nor raw HID, ignoring", devnode);
+      g_object_unref (device);
+      return;
+    }
+
   device->hid_ancestor.sys_path = get_ancestor_with_subsystem_devtype (device->sys_path,
                                                               "hid", NULL,
                                                               NULL);
diff --git a/steam-runtime-tools/input-device-internal.h b/steam-runtime-tools/input-device-internal.h
index 4b5589ff17fa3a952e4f619a7b7aad3e9ba401e2..820f34fa2709a46f61e271029b7d4947184e7543 100644
--- a/steam-runtime-tools/input-device-internal.h
+++ b/steam-runtime-tools/input-device-internal.h
@@ -32,6 +32,8 @@ struct _SrtInputDeviceInterface
 {
   GTypeInterface parent;
 
+  SrtInputDeviceInterfaceFlags (*get_interface_flags) (SrtInputDevice *device);
+
   const char * (*get_dev_node) (SrtInputDevice *device);
   const char * (*get_sys_path) (SrtInputDevice *device);
   const char * (*get_subsystem) (SrtInputDevice *device);
@@ -73,6 +75,16 @@ void _srt_input_device_monitor_emit_removed (SrtInputDeviceMonitor *monitor,
                                              SrtInputDevice *device);
 void _srt_input_device_monitor_emit_all_for_now (SrtInputDeviceMonitor *monitor);
 
+
+gboolean _srt_get_identity_from_evdev (int fd,
+                                       guint32 *bus_type,
+                                       guint32 *vendor,
+                                       guint32 *product,
+                                       guint32 *version);
+gboolean _srt_get_identity_from_raw_hid (int fd,
+                                         guint32 *bus_type,
+                                         guint32 *vendor,
+                                         guint32 *product);
 gchar *_srt_input_device_uevent_field (const char *text,
                                        const char *key);
 gboolean _srt_input_device_uevent_field_equals (const char *text,
diff --git a/steam-runtime-tools/input-device.c b/steam-runtime-tools/input-device.c
index 3054cc2ffcc2435f2b24b8d6433b4fc528d849a4..753847648a85b8fada668524a29283ebc5658751 100644
--- a/steam-runtime-tools/input-device.c
+++ b/steam-runtime-tools/input-device.c
@@ -26,6 +26,9 @@
 #include "steam-runtime-tools/input-device.h"
 #include "steam-runtime-tools/input-device-internal.h"
 
+#include <linux/hidraw.h>
+#include <linux/input.h>
+
 #include <libglnx.h>
 
 #include "steam-runtime-tools/enums.h"
@@ -60,6 +63,30 @@ srt_input_device_default_get_null_strv (SrtInputDevice *device)
   return NULL;
 }
 
+/**
+ * srt_input_device_get_interface_flags:
+ * @device: An object implementing #SrtInputDeviceInterface
+ *
+ * Return flags describing how the input device can be used.
+ *
+ * Returns: Flags describing the input device.
+ */
+SrtInputDeviceInterfaceFlags
+srt_input_device_get_interface_flags (SrtInputDevice *device)
+{
+  SrtInputDeviceInterface *iface = SRT_INPUT_DEVICE_GET_INTERFACE (device);
+
+  g_return_val_if_fail (iface != NULL, SRT_INPUT_DEVICE_INTERFACE_FLAGS_NONE);
+
+  return iface->get_interface_flags (device);
+}
+
+static SrtInputDeviceInterfaceFlags
+srt_input_device_default_get_interface_flags (SrtInputDevice *device)
+{
+  return SRT_INPUT_DEVICE_INTERFACE_FLAGS_NONE;
+}
+
 /**
  * srt_input_device_get_dev_node:
  * @device: An object implementing #SrtInputDeviceInterface
@@ -373,6 +400,7 @@ srt_input_device_default_init (SrtInputDeviceInterface *iface)
 #define IMPLEMENT2(x, y) iface->x = srt_input_device_default_ ## y
 #define IMPLEMENT(x) IMPLEMENT2 (x, x)
 
+  IMPLEMENT (get_interface_flags);
   IMPLEMENT2 (get_dev_node, get_null_string);
   IMPLEMENT2 (get_sys_path, get_null_string);
   IMPLEMENT2 (get_subsystem, get_null_string);
@@ -759,6 +787,80 @@ _srt_input_device_monitor_emit_all_for_now (SrtInputDeviceMonitor *monitor)
   g_signal_emit (monitor, monitor_signal_all_for_now, 0);
 }
 
+gboolean
+_srt_get_identity_from_evdev (int fd,
+                              guint32 *bus_type,
+                              guint32 *vendor,
+                              guint32 *product,
+                              guint32 *version)
+{
+  struct input_id iid = {};
+
+  if (bus_type != NULL)
+    *bus_type = 0;
+
+  if (vendor != NULL)
+    *vendor = 0;
+
+  if (product != NULL)
+    *product = 0;
+
+  if (ioctl (fd, EVIOCGID, &iid) < 0)
+    {
+      g_debug ("EVIOCGID: %s", g_strerror (errno));
+      return FALSE;
+    }
+
+  if (bus_type != NULL)
+    *bus_type = iid.bustype;
+
+  if (vendor != NULL)
+    *vendor = iid.vendor;
+
+  if (product != NULL)
+    *product = iid.product;
+
+  if (version != NULL)
+    *version = iid.version;
+
+  return TRUE;
+}
+
+gboolean
+_srt_get_identity_from_raw_hid (int fd,
+                                guint32 *bus_type,
+                                guint32 *vendor,
+                                guint32 *product)
+{
+  struct hidraw_devinfo devinfo = {};
+
+  if (bus_type != NULL)
+    *bus_type = 0;
+
+  if (vendor != NULL)
+    *vendor = 0;
+
+  if (product != NULL)
+    *product = 0;
+
+  if (ioctl (fd, HIDIOCGRAWINFO, &devinfo) < 0)
+    {
+      g_debug ("HIDIOCGRAWINFO: %s", g_strerror (errno));
+      return FALSE;
+    }
+
+  if (bus_type != NULL)
+    *bus_type = devinfo.bustype;
+
+  if (vendor != NULL)
+    *vendor = devinfo.vendor;
+
+  if (product != NULL)
+    *product = devinfo.product;
+
+  return TRUE;
+}
+
 /*
  * Returns: The field @key from @text, or %NULL if not found.
  */
diff --git a/steam-runtime-tools/input-device.h b/steam-runtime-tools/input-device.h
index 037e4c992be9a65bbf11c5dc523504733a035a79..0f1b13493f7523d44d5a68fdfe6ab5d45df3a3a3 100644
--- a/steam-runtime-tools/input-device.h
+++ b/steam-runtime-tools/input-device.h
@@ -33,6 +33,28 @@
 
 #include "steam-runtime-tools/macros.h"
 
+/**
+ * SrtInputDeviceInterfaceFlags:
+ * @SRT_INPUT_DEVICE_INTERFACE_FLAGS_EVENT: evdev event device nodes,
+ *  typically /dev/input/event*
+ * @SRT_INPUT_DEVICE_INTERFACE_FLAGS_RAW_HID: Raw USB or Bluetooth HID
+ *  device nodes, typically /dev/hidraw*
+ * @SRT_INPUT_DEVICE_INTERFACE_FLAGS_READABLE: Only report device nodes that appear to be
+ *  openable in read-only mode
+ * @SRT_INPUT_DEVICE_INTERFACE_FLAGS_READ_WRITE: Only report device nodes that appear to be
+ *  openable in read/write mode
+ *
+ * Flags describing the interface offered by an input device.
+ */
+typedef enum
+{
+  SRT_INPUT_DEVICE_INTERFACE_FLAGS_EVENT = (1 << 0),
+  SRT_INPUT_DEVICE_INTERFACE_FLAGS_RAW_HID = (1 << 1),
+  SRT_INPUT_DEVICE_INTERFACE_FLAGS_READABLE = (1 << 2),
+  SRT_INPUT_DEVICE_INTERFACE_FLAGS_READ_WRITE = (1 << 3),
+  SRT_INPUT_DEVICE_INTERFACE_FLAGS_NONE = 0
+} SrtInputDeviceInterfaceFlags;
+
 #define SRT_TYPE_INPUT_DEVICE (srt_input_device_get_type ())
 #define SRT_INPUT_DEVICE(x) (G_TYPE_CHECK_INSTANCE_CAST ((x), SRT_TYPE_INPUT_DEVICE, SrtInputDevice))
 #define SRT_IS_INPUT_DEVICE(x) (G_TYPE_CHECK_INSTANCE_TYPE ((x), SRT_TYPE_INPUT_DEVICE))
@@ -44,6 +66,8 @@ typedef struct _SrtInputDeviceInterface SrtInputDeviceInterface;
 _SRT_PUBLIC
 GType srt_input_device_get_type (void);
 
+_SRT_PUBLIC
+SrtInputDeviceInterfaceFlags srt_input_device_get_interface_flags (SrtInputDevice *device);
 _SRT_PUBLIC
 const char *srt_input_device_get_dev_node (SrtInputDevice *device);
 _SRT_PUBLIC
diff --git a/steam-runtime-tools/udev-input-device.c b/steam-runtime-tools/udev-input-device.c
index ca3594cb81cc88f9704a7d130f09737b675b1995..04bb4824487ba79ae835e41075a36e6ad0fe8486 100644
--- a/steam-runtime-tools/udev-input-device.c
+++ b/steam-runtime-tools/udev-input-device.c
@@ -36,6 +36,8 @@
 #include "steam-runtime-tools/input-device-internal.h"
 #include "steam-runtime-tools/utils-internal.h"
 
+#define ALWAYS_OPEN_FLAGS (O_CLOEXEC | O_NOCTTY)
+
 static void srt_udev_input_device_iface_init (SrtInputDeviceInterface *iface);
 static void srt_udev_input_device_monitor_iface_init (SrtInputDeviceMonitorInterface *iface);
 static void srt_udev_input_device_monitor_initable_iface_init (GInitableIface *iface);
@@ -123,6 +125,8 @@ struct _SrtUdevInputDevice
   {
     struct udev_device *dev;                  /* borrowed from child dev */
   } usb_device_ancestor;
+
+  SrtInputDeviceInterfaceFlags iface_flags;
 };
 
 struct _SrtUdevInputDeviceClass
@@ -193,6 +197,14 @@ _srt_udev_input_device_class_init (SrtUdevInputDeviceClass *cls)
   object_class->finalize = srt_udev_input_device_finalize;
 }
 
+static SrtInputDeviceInterfaceFlags
+srt_udev_input_device_get_interface_flags (SrtInputDevice *device)
+{
+  SrtUdevInputDevice *self = SRT_UDEV_INPUT_DEVICE (device);
+
+  return self->iface_flags;
+}
+
 static const char *
 srt_udev_input_device_get_dev_node (SrtInputDevice *device)
 {
@@ -320,6 +332,7 @@ srt_udev_input_device_iface_init (SrtInputDeviceInterface *iface)
 {
 #define IMPLEMENT(x) iface->x = srt_udev_input_device_ ## x
 
+  IMPLEMENT (get_interface_flags);
   IMPLEMENT (get_dev_node);
   IMPLEMENT (get_sys_path);
   IMPLEMENT (get_subsystem);
@@ -535,6 +548,7 @@ add_device (SrtUdevInputDeviceMonitor *self,
   gboolean is_evdev = FALSE;
   gboolean is_hidraw = FALSE;
   const char *slash;
+  int fd;
 
   syspath = symbols.udev_device_get_syspath (dev);
   devnode = symbols.udev_device_get_devnode (dev);
@@ -587,6 +601,28 @@ add_device (SrtUdevInputDeviceMonitor *self,
   device = g_object_new (SRT_TYPE_UDEV_INPUT_DEVICE, NULL);
   device->dev = symbols.udev_device_ref (dev);
 
+  if (is_evdev)
+    device->iface_flags |= SRT_INPUT_DEVICE_INTERFACE_FLAGS_EVENT;
+
+  if (is_hidraw)
+    device->iface_flags |= SRT_INPUT_DEVICE_INTERFACE_FLAGS_RAW_HID;
+
+  fd = open (devnode, O_RDONLY | O_NONBLOCK | ALWAYS_OPEN_FLAGS);
+
+  if (fd >= 0)
+    {
+      device->iface_flags |= SRT_INPUT_DEVICE_INTERFACE_FLAGS_READABLE;
+      close (fd);
+    }
+
+  fd = open (devnode, O_RDWR | O_NONBLOCK | ALWAYS_OPEN_FLAGS);
+
+  if (fd >= 0)
+    {
+      device->iface_flags |= SRT_INPUT_DEVICE_INTERFACE_FLAGS_READ_WRITE;
+      close (fd);
+    }
+
   device->hid_ancestor.dev = symbols.udev_device_get_parent_with_subsystem_devtype (device->dev,
                                                                                     "hid",
                                                                                     NULL);
diff --git a/tests/input-device.c b/tests/input-device.c
index af84938582f1ece392a01560b2086486af465c64..c1389154e17aed7549285b40b70c4320f0f1d937 100644
--- a/tests/input-device.c
+++ b/tests/input-device.c
@@ -108,6 +108,8 @@ test_input_device_usb (Fixture *f,
   g_autofree gchar *input_uevent = NULL;
   g_autofree gchar *usb_uevent = NULL;
 
+  mock_device->iface_flags = (SRT_INPUT_DEVICE_INTERFACE_FLAGS_EVENT
+                              | SRT_INPUT_DEVICE_INTERFACE_FLAGS_READABLE);
   mock_device->dev_node = g_strdup ("/dev/input/event0");
   mock_device->sys_path = g_strdup ("/sys/devices/mock/usb/hid/input/input0/event0");
   mock_device->subsystem = g_strdup ("input");
@@ -128,6 +130,9 @@ test_input_device_usb (Fixture *f,
   /* TODO: Fill in somewhat realistic details for a USB-attached
    * Steam Controller */
 
+  g_assert_cmpuint (srt_input_device_get_interface_flags (device), ==,
+                    SRT_INPUT_DEVICE_INTERFACE_FLAGS_EVENT
+                    | SRT_INPUT_DEVICE_INTERFACE_FLAGS_READABLE);
   g_assert_cmpstr (srt_input_device_get_dev_node (device), ==,
                    "/dev/input/event0");
   g_assert_cmpstr (srt_input_device_get_sys_path (device), ==,
@@ -196,6 +201,11 @@ device_added_cb (SrtInputDeviceMonitor *monitor,
       g_autofree gchar *usb_uevent = NULL;
       g_auto(GStrv) udev_properties = NULL;
 
+      g_assert_cmpuint (srt_input_device_get_interface_flags (device), ==,
+                        SRT_INPUT_DEVICE_INTERFACE_FLAGS_EVENT
+                        | SRT_INPUT_DEVICE_INTERFACE_FLAGS_READABLE
+                        | SRT_INPUT_DEVICE_INTERFACE_FLAGS_READ_WRITE);
+
       uevent = srt_input_device_dup_uevent (device);
       g_assert_cmpstr (uevent, ==, "ONE=1\nTWO=2\n");
 
diff --git a/tests/mock-input-device.c b/tests/mock-input-device.c
index 3a2bda45d618db18b1c6b561787dc50c67a9591d..31e08abf1cf035f4b3c7aa720c174d77339620af 100644
--- a/tests/mock-input-device.c
+++ b/tests/mock-input-device.c
@@ -86,6 +86,14 @@ mock_input_device_class_init (MockInputDeviceClass *cls)
   object_class->finalize = mock_input_device_finalize;
 }
 
+static SrtInputDeviceInterfaceFlags
+mock_input_device_get_interface_flags (SrtInputDevice *device)
+{
+  MockInputDevice *self = MOCK_INPUT_DEVICE (device);
+
+  return self->iface_flags;
+}
+
 static const char *
 mock_input_device_get_dev_node (SrtInputDevice *device)
 {
@@ -179,6 +187,7 @@ mock_input_device_iface_init (SrtInputDeviceInterface *iface)
 {
 #define IMPLEMENT(x) iface->x = mock_input_device_ ## x
 
+  IMPLEMENT (get_interface_flags);
   IMPLEMENT (get_dev_node);
   IMPLEMENT (get_sys_path);
   IMPLEMENT (get_subsystem);
@@ -341,6 +350,9 @@ add_steam_controller (MockInputDeviceMonitor *self,
   g_autoptr(MockInputDevice) device = mock_input_device_new ();
   g_autoptr(GPtrArray) arr = g_ptr_array_new_full (1, g_free);
 
+  device->iface_flags = (SRT_INPUT_DEVICE_INTERFACE_FLAGS_EVENT
+                         | SRT_INPUT_DEVICE_INTERFACE_FLAGS_READABLE
+                         | SRT_INPUT_DEVICE_INTERFACE_FLAGS_READ_WRITE);
   device->dev_node = g_strdup_printf ("/dev/input/event%s", tail);
   device->sys_path = g_strdup_printf ("/sys/devices/mock/usb/hid/input/input0/event%s",
                                       tail);
diff --git a/tests/mock-input-device.h b/tests/mock-input-device.h
index b3255af3760896ac2b407ae916e9f5af6538940b..11c3e32dd06f4706c9baa6ff55f5e65c2dfc0e8b 100644
--- a/tests/mock-input-device.h
+++ b/tests/mock-input-device.h
@@ -72,6 +72,8 @@ struct _MockInputDevice
     gchar *sys_path;
     gchar *uevent;
   } usb_device_ancestor;
+
+  SrtInputDeviceInterfaceFlags iface_flags;
 };
 
 struct _MockInputDeviceClass