From 98e90eaec449a67e0c4216d0aac38ee9896a6a8b Mon Sep 17 00:00:00 2001
From: Michael Forney <mforney@mforney.org>
Date: Fri, 3 Jan 2020 17:18:10 -0800
Subject: [PATCH] Use forward-declarations of udev structures in headers

This way, it is possible for the libinput API to be implemented without
requiring udev.
---
 meson.build                   | 7 ++-----
 src/evdev-mt-touchpad.c       | 1 +
 src/evdev-tablet-pad-leds.c   | 1 +
 src/evdev.c                   | 1 +
 src/libinput.c                | 1 +
 src/libinput.h                | 4 +++-
 src/quirks.h                  | 4 ++--
 src/udev-seat.c               | 1 +
 src/udev-seat.h               | 1 -
 test/litest.h                 | 1 +
 tools/libinput-debug-gui.c    | 1 +
 tools/libinput-debug-tablet.c | 1 +
 tools/libinput-quirks.c       | 1 +
 13 files changed, 16 insertions(+), 9 deletions(-)

diff --git a/meson.build b/meson.build
index af4c87e8..e65ac51e 100644
--- a/meson.build
+++ b/meson.build
@@ -278,7 +278,7 @@ src_libinput_util = [
 ]
 libinput_util = static_library('libinput-util',
 			       src_libinput_util,
-			       dependencies : [dep_udev, dep_libevdev, dep_libwacom],
+			       dependencies : [dep_libevdev, dep_libwacom],
 			       include_directories : includes_include)
 dep_libinput_util = declare_dependency(link_with : libinput_util)
 
@@ -296,7 +296,7 @@ src_libfilter = [
 		'src/filter-private.h'
 ]
 libfilter = static_library('filter', src_libfilter,
-			   dependencies : [dep_udev, dep_libwacom],
+			   dependencies : [dep_libwacom],
 			   include_directories : includes_include)
 dep_libfilter = declare_dependency(link_with : libfilter)
 
@@ -726,14 +726,12 @@ test('symbols-leak-test',
 # build-test only
 executable('test-build-pedantic',
 	   'test/build-pedantic.c',
-	   dependencies : [dep_udev],
 	   include_directories : [includes_src, includes_include],
 	   c_args : ['-std=c99', '-pedantic', '-Werror'],
 	   install : false)
 # build-test only
 executable('test-build-std-gnuc90',
 	   'test/build-pedantic.c',
-	   dependencies : [dep_udev],
 	   include_directories : [includes_src, includes_include],
 	   c_args : ['-std=gnu89', '-Werror'],
 	   install : false)
@@ -747,7 +745,6 @@ executable('test-build-linker',
 if add_languages('cpp', required: false)
 	executable('test-build-cxx',
 		   'test/build-cxx.cc',
-		   dependencies : [dep_udev],
 		   include_directories : [includes_src, includes_include],
 		   install : false)
 endif
diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c
index 4ffc4a39..29faf59e 100644
--- a/src/evdev-mt-touchpad.c
+++ b/src/evdev-mt-touchpad.c
@@ -27,6 +27,7 @@
 #include <math.h>
 #include <stdbool.h>
 #include <limits.h>
+#include <libudev.h>
 
 #if HAVE_LIBWACOM
 #include <libwacom/libwacom.h>
diff --git a/src/evdev-tablet-pad-leds.c b/src/evdev-tablet-pad-leds.c
index ff21878f..70df1d06 100644
--- a/src/evdev-tablet-pad-leds.c
+++ b/src/evdev-tablet-pad-leds.c
@@ -23,6 +23,7 @@
 
 #include "config.h"
 
+#include <libudev.h>
 #include <limits.h>
 #include <fcntl.h>
 
diff --git a/src/evdev.c b/src/evdev.c
index bf85aa24..ba889b6a 100644
--- a/src/evdev.c
+++ b/src/evdev.c
@@ -37,6 +37,7 @@
 #include <mtdev-plumbing.h>
 #include <assert.h>
 #include <math.h>
+#include <libudev.h>
 
 #include "libinput.h"
 #include "evdev.h"
diff --git a/src/libinput.c b/src/libinput.c
index e764375b..b532f1e3 100644
--- a/src/libinput.c
+++ b/src/libinput.c
@@ -33,6 +33,7 @@
 #include <sys/epoll.h>
 #include <unistd.h>
 #include <assert.h>
+#include <libudev.h>
 
 #include "libinput.h"
 #include "libinput-private.h"
diff --git a/src/libinput.h b/src/libinput.h
index 5a19f79d..f5ae835d 100644
--- a/src/libinput.h
+++ b/src/libinput.h
@@ -32,12 +32,14 @@ extern "C" {
 #include <stdlib.h>
 #include <stdint.h>
 #include <stdarg.h>
-#include <libudev.h>
 
 #define LIBINPUT_ATTRIBUTE_PRINTF(_format, _args) \
 	__attribute__ ((format (printf, _format, _args)))
 #define LIBINPUT_ATTRIBUTE_DEPRECATED __attribute__ ((deprecated))
 
+struct udev;
+struct udev_device;
+
 /**
  * @ingroup base
  * @struct libinput
diff --git a/src/quirks.h b/src/quirks.h
index 88159b59..526177c0 100644
--- a/src/quirks.h
+++ b/src/quirks.h
@@ -28,10 +28,10 @@
 #include <stdbool.h>
 #include <stdint.h>
 
-#include <libudev.h>
-
 #include "libinput.h"
 
+struct udev_device;
+
 /**
  * Handle to the quirks context.
  */
diff --git a/src/udev-seat.c b/src/udev-seat.c
index ce96ece3..3af01606 100644
--- a/src/udev-seat.c
+++ b/src/udev-seat.c
@@ -24,6 +24,7 @@
 
 #include "config.h"
 
+#include <libudev.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
diff --git a/src/udev-seat.h b/src/udev-seat.h
index ee54b422..196561db 100644
--- a/src/udev-seat.h
+++ b/src/udev-seat.h
@@ -26,7 +26,6 @@
 
 #include "config.h"
 
-#include <libudev.h>
 #include "libinput-private.h"
 
 struct udev_seat {
diff --git a/test/litest.h b/test/litest.h
index ab66ff9e..9d6598be 100644
--- a/test/litest.h
+++ b/test/litest.h
@@ -33,6 +33,7 @@
 #include <libevdev/libevdev.h>
 #include <libevdev/libevdev-uinput.h>
 #include <libinput.h>
+#include <libudev.h>
 #include <math.h>
 
 #include "check-double-macros.h"
diff --git a/tools/libinput-debug-gui.c b/tools/libinput-debug-gui.c
index d68f1ea1..ae9364e6 100644
--- a/tools/libinput-debug-gui.c
+++ b/tools/libinput-debug-gui.c
@@ -29,6 +29,7 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <getopt.h>
+#include <libudev.h>
 #include <math.h>
 #include <stdio.h>
 #include <stdlib.h>
diff --git a/tools/libinput-debug-tablet.c b/tools/libinput-debug-tablet.c
index b2406d68..a5959fa7 100644
--- a/tools/libinput-debug-tablet.c
+++ b/tools/libinput-debug-tablet.c
@@ -28,6 +28,7 @@
 #include <fcntl.h>
 #include <inttypes.h>
 #include <getopt.h>
+#include <libudev.h>
 #include <poll.h>
 #include <stdio.h>
 #include <string.h>
diff --git a/tools/libinput-quirks.c b/tools/libinput-quirks.c
index 1a80f367..6aac4b1e 100644
--- a/tools/libinput-quirks.c
+++ b/tools/libinput-quirks.c
@@ -27,6 +27,7 @@
 #include <stdlib.h>
 #include <errno.h>
 #include <getopt.h>
+#include <libudev.h>
 #include <sys/stat.h>
 
 #include "quirks.h"

From a6f21673e13efc52fff100d315316ba14c6db848 Mon Sep 17 00:00:00 2001
From: Michael Forney <mforney@mforney.org>
Date: Fri, 3 Jan 2020 21:51:55 -0800
Subject: [PATCH] Make udev optional

---
 meson.build                 | 568 +++++++++++++++++++-----------------
 meson_options.txt           |   8 +
 src/evdev-mt-touchpad.c     |   9 +-
 src/evdev-tablet-pad-leds.c |  15 +-
 src/evdev.c                 | 117 +++++++-
 src/evdev.h                 |   5 +-
 src/libinput.c              |   5 +
 src/path-seat.c             | 115 +++++---
 src/quirks.c                |  46 +--
 src/udev-seat.c             |  25 +-
 10 files changed, 564 insertions(+), 349 deletions(-)

diff --git a/meson.build b/meson.build
index e65ac51e..75b393f4 100644
--- a/meson.build
+++ b/meson.build
@@ -122,7 +122,6 @@ endif
 
 # Dependencies
 pkgconfig = import('pkgconfig')
-dep_udev = dependency('libudev')
 dep_mtdev = dependency('mtdev', version : '>= 1.1.0')
 dep_libevdev = dependency('libevdev')
 dep_lm = cc.find_library('m', required : false)
@@ -152,46 +151,54 @@ endif
 
 ############ udev bits ############
 
-executable('libinput-device-group',
-	   'udev/libinput-device-group.c',
-	   dependencies : [dep_udev, dep_libwacom],
-	   include_directories : [includes_src, includes_include],
-	   install : true,
-	   install_dir : dir_udev_callouts)
-executable('libinput-fuzz-extract',
-	   'udev/libinput-fuzz-extract.c',
-	   'src/util-strings.c',
-	   'src/util-prop-parsers.c',
-	   dependencies : [dep_udev, dep_libevdev, dep_lm],
-	   include_directories : [includes_src, includes_include],
-	   install : true,
-	   install_dir : dir_udev_callouts)
-executable('libinput-fuzz-to-zero',
-	   'udev/libinput-fuzz-to-zero.c',
-	   dependencies : [dep_udev, dep_libevdev],
-	   include_directories : [includes_src, includes_include],
-	   install : true,
-	   install_dir : dir_udev_callouts)
-
-udev_rules_config = configuration_data()
-udev_rules_config.set('UDEV_TEST_PATH', '')
-configure_file(input : 'udev/80-libinput-device-groups.rules.in',
-	       output : '80-libinput-device-groups.rules',
-	       install_dir : dir_udev_rules,
-	       configuration : udev_rules_config)
-configure_file(input : 'udev/90-libinput-fuzz-override.rules.in',
-	       output : '90-libinput-fuzz-override.rules',
-	       install_dir : dir_udev_rules,
-	       configuration : udev_rules_config)
-
-litest_udev_rules_config = configuration_data()
-litest_udev_rules_config.set('UDEV_TEST_PATH', meson.current_build_dir() + '/')
-litest_groups_rules_file = configure_file(input : 'udev/80-libinput-device-groups.rules.in',
-	       output : '80-libinput-device-groups-litest.rules',
-	       configuration : litest_udev_rules_config)
-litest_fuzz_override_file = configure_file(input : 'udev/90-libinput-fuzz-override.rules.in',
-					   output : '90-libinput-fuzz-override-litest.rules',
-					   configuration : litest_udev_rules_config)
+have_udev = get_option('udev')
+config_h.set10('HAVE_UDEV', have_udev)
+if have_udev
+	dep_udev = dependency('libudev')
+
+	executable('libinput-device-group',
+		   'udev/libinput-device-group.c',
+		   dependencies : [dep_udev, dep_libwacom],
+		   include_directories : [includes_src, includes_include],
+		   install : true,
+		   install_dir : dir_udev_callouts)
+	executable('libinput-fuzz-extract',
+		   'udev/libinput-fuzz-extract.c',
+		   'src/util-strings.c',
+		   'src/util-prop-parsers.c',
+		   dependencies : [dep_udev, dep_libevdev, dep_lm],
+		   include_directories : [includes_src, includes_include],
+		   install : true,
+		   install_dir : dir_udev_callouts)
+	executable('libinput-fuzz-to-zero',
+		   'udev/libinput-fuzz-to-zero.c',
+		   dependencies : [dep_udev, dep_libevdev],
+		   include_directories : [includes_src, includes_include],
+		   install : true,
+		   install_dir : dir_udev_callouts)
+
+	udev_rules_config = configuration_data()
+	udev_rules_config.set('UDEV_TEST_PATH', '')
+	configure_file(input : 'udev/80-libinput-device-groups.rules.in',
+		       output : '80-libinput-device-groups.rules',
+		       install_dir : dir_udev_rules,
+		       configuration : udev_rules_config)
+	configure_file(input : 'udev/90-libinput-fuzz-override.rules.in',
+		       output : '90-libinput-fuzz-override.rules',
+		       install_dir : dir_udev_rules,
+		       configuration : udev_rules_config)
+
+	litest_udev_rules_config = configuration_data()
+	litest_udev_rules_config.set('UDEV_TEST_PATH', meson.current_build_dir() + '/')
+	litest_groups_rules_file = configure_file(input : 'udev/80-libinput-device-groups.rules.in',
+		       output : '80-libinput-device-groups-litest.rules',
+		       configuration : litest_udev_rules_config)
+	litest_fuzz_override_file = configure_file(input : 'udev/90-libinput-fuzz-override.rules.in',
+						   output : '90-libinput-fuzz-override-litest.rules',
+						   configuration : litest_udev_rules_config)
+else
+	dep_udev = declare_dependency()
+endif
 
 ############ Check for leftover udev rules ########
 
@@ -465,256 +472,267 @@ endif
 subdir('completion/zsh')
 
 ############ tools ############
-libinput_tool_path = dir_libexec
-config_h.set_quoted('LIBINPUT_TOOL_PATH', libinput_tool_path)
-tools_shared_sources = [ 'tools/shared.c',
-			 'tools/shared.h',
-			 'src/builddir.h' ]
-deps_tools_shared = [ dep_libinput, dep_libevdev ]
-lib_tools_shared = static_library('tools_shared',
-				  tools_shared_sources,
-				  include_directories : [includes_src, includes_include],
-				  dependencies : deps_tools_shared)
-dep_tools_shared = declare_dependency(link_with : lib_tools_shared,
-				      dependencies : deps_tools_shared)
-
-man_config = configuration_data()
-man_config.set('LIBINPUT_VERSION', meson.project_version())
-man_config.set('LIBINPUT_DATA_DIR', dir_data)
-
-deps_tools = [ dep_tools_shared, dep_libinput ]
-libinput_debug_events_sources = [
-	'tools/libinput-debug-events.c',
-	libinput_version_h,
-]
-executable('libinput-debug-events',
-	   libinput_debug_events_sources,
-	   dependencies : deps_tools,
-	   include_directories : [includes_src, includes_include],
-	   install_dir : libinput_tool_path,
-	   install : true
-	   )
-configure_file(input : 'tools/libinput-debug-events.man',
-	       output : 'libinput-debug-events.1',
-	       configuration : man_config,
-	       install_dir : dir_man1,
-	       )
-
-libinput_debug_tablet_sources = [ 'tools/libinput-debug-tablet.c' ]
-executable('libinput-debug-tablet',
-	   libinput_debug_tablet_sources,
-	   dependencies : deps_tools,
-	   include_directories : [includes_src, includes_include],
-	   install_dir : libinput_tool_path,
-	   install : true)
-
-configure_file(input : 'tools/libinput-debug-tablet.man',
-	       output : 'libinput-debug-tablet.1',
-	       configuration : man_config,
-	       install_dir : dir_man1,
-	       )
-
-libinput_quirks_sources = [ 'tools/libinput-quirks.c' ]
-libinput_quirks = executable('libinput-quirks',
-			     libinput_quirks_sources,
-			     dependencies : [dep_libquirks, dep_tools_shared, dep_libinput],
-			     include_directories : [includes_src, includes_include],
-			     install_dir : libinput_tool_path,
-			     install : true
-			    )
-test('validate-quirks',
-     libinput_quirks,
-     args: ['validate', '--data-dir=@0@'.format(dir_src_quirks)],
-     suite : ['all']
-     )
 
-configure_file(input : 'tools/libinput-quirks.man',
-	       output : 'libinput-quirks.1',
-	       configuration : man_config,
-	       install_dir : dir_man1,
-	       )
-# Same man page for the subtools to stay consistent with the other tools
-configure_file(input : 'tools/libinput-quirks.man',
-	       output : 'libinput-quirks-list.1',
-	       configuration : man_config,
-	       install_dir : dir_man1,
-	       )
-configure_file(input : 'tools/libinput-quirks.man',
-	       output : 'libinput-quirks-validate.1',
-	       configuration : man_config,
-	       install_dir : dir_man1,
-	       )
-
-libinput_list_devices_sources = [ 'tools/libinput-list-devices.c' ]
-libinput_list_devices = executable('libinput-list-devices',
-				   libinput_list_devices_sources,
-				   dependencies : deps_tools,
-				   include_directories : [includes_src, includes_include],
-				   install_dir : libinput_tool_path,
-				   install : true,
-				  )
-test('list-devices',
-     libinput_list_devices,
-     suite : ['all', 'root', 'hardware'])
-
-configure_file(input : 'tools/libinput-list-devices.man',
-	       output : 'libinput-list-devices.1',
-	       configuration : man_config,
-	       install_dir : dir_man1,
-	       )
-
-libinput_measure_sources = [ 'tools/libinput-measure.c' ]
-executable('libinput-measure',
-	   libinput_measure_sources,
-	   dependencies : deps_tools,
-	   include_directories : [includes_src, includes_include],
-	   install_dir : libinput_tool_path,
-	   install : true,
-	   )
-configure_file(input : 'tools/libinput-measure.man',
-	       output : 'libinput-measure.1',
-	       configuration : man_config,
-	       install_dir : dir_man1,
-	       )
-
-src_python_tools = files(
-	      'tools/libinput-measure-fuzz.py',
-	      'tools/libinput-measure-touchpad-tap.py',
-	      'tools/libinput-measure-touchpad-pressure.py',
-	      'tools/libinput-measure-touch-size.py',
-)
+if get_option('tools')
+	if not have_udev
+		error('tools require -Dudev=true')
+	endif
 
-config_noop = configuration_data()
-# Set a dummy replacement to silence meson warnings:
-# meson.build:487: WARNING: Got an empty configuration_data() object and
-# 		   found no substitutions in the input file 'foo'. If you
-# 		   want to copy a file to the build dir, use the 'copy:'
-# 		   keyword argument added in 0.47.0
-config_noop.set('dummy', 'dummy')
-foreach t : src_python_tools
-	configure_file(input: t,
-		       output: '@BASENAME@',
-		       configuration : config_noop,
-		       install_dir : libinput_tool_path
-		      )
-endforeach
+	libinput_tool_path = dir_libexec
+	config_h.set_quoted('LIBINPUT_TOOL_PATH', libinput_tool_path)
+	tools_shared_sources = [ 'tools/shared.c',
+				 'tools/shared.h',
+				 'src/builddir.h' ]
+	deps_tools_shared = [ dep_libinput, dep_libevdev ]
+	lib_tools_shared = static_library('tools_shared',
+					  tools_shared_sources,
+					  include_directories : [includes_src, includes_include],
+					  dependencies : deps_tools_shared)
+	dep_tools_shared = declare_dependency(link_with : lib_tools_shared,
+					      dependencies : deps_tools_shared)
+
+	man_config = configuration_data()
+	man_config.set('LIBINPUT_VERSION', meson.project_version())
+	man_config.set('LIBINPUT_DATA_DIR', dir_data)
+
+	deps_tools = [ dep_tools_shared, dep_libinput ]
+	libinput_debug_events_sources = [
+		'tools/libinput-debug-events.c',
+		libinput_version_h,
+	]
+	executable('libinput-debug-events',
+		   libinput_debug_events_sources,
+		   dependencies : deps_tools,
+		   include_directories : [includes_src, includes_include],
+		   install_dir : libinput_tool_path,
+		   install : true
+		   )
+	configure_file(input : 'tools/libinput-debug-events.man',
+		       output : 'libinput-debug-events.1',
+		       configuration : man_config,
+		       install_dir : dir_man1,
+		       )
 
-src_man = files(
-	      'tools/libinput-measure-fuzz.man',
-	      'tools/libinput-measure-touchpad-tap.man',
-	      'tools/libinput-measure-touchpad-pressure.man',
-	      'tools/libinput-measure-touch-size.man',
-)
+	libinput_debug_tablet_sources = [ 'tools/libinput-debug-tablet.c' ]
+	executable('libinput-debug-tablet',
+		   libinput_debug_tablet_sources,
+		   dependencies : deps_tools,
+		   include_directories : [includes_src, includes_include],
+		   install_dir : libinput_tool_path,
+		   install : true)
 
-foreach m : src_man
-	configure_file(input : m,
-		       output : '@BASENAME@.1',
+	configure_file(input : 'tools/libinput-debug-tablet.man',
+		       output : 'libinput-debug-tablet.1',
 		       configuration : man_config,
-		       install_dir : dir_man1)
-endforeach
+		       install_dir : dir_man1,
+		       )
 
-libinput_record_sources = [ 'tools/libinput-record.c', git_version_h ]
-executable('libinput-record',
-	   libinput_record_sources,
-	   dependencies : deps_tools + [dep_udev],
-	   include_directories : [includes_src, includes_include],
-	   install_dir : libinput_tool_path,
-	   install : true,
-	   )
-configure_file(input : 'tools/libinput-record.man',
-	       output : 'libinput-record.1',
-	       configuration : man_config,
-	       install_dir : dir_man1,
-	       )
-
-install_data('tools/libinput-replay',
-	     install_dir : libinput_tool_path)
-configure_file(input : 'tools/libinput-replay.man',
-	       output : 'libinput-replay.1',
-	       configuration : man_config,
-	       install_dir : dir_man1,
-	       )
-
-if get_option('debug-gui')
-	dep_gtk = dependency('gtk+-3.0', version : '>= 3.20')
-	dep_cairo = dependency('cairo')
-	dep_glib = dependency('glib-2.0')
-
-	debug_gui_sources = [ 'tools/libinput-debug-gui.c' ]
-	deps_debug_gui = [
-			dep_gtk,
-			dep_cairo,
-			dep_glib,
-			] + deps_tools
-	executable('libinput-debug-gui',
-		   debug_gui_sources,
-		   dependencies : deps_debug_gui,
+	libinput_quirks_sources = [ 'tools/libinput-quirks.c' ]
+	libinput_quirks = executable('libinput-quirks',
+				     libinput_quirks_sources,
+				     dependencies : [dep_libquirks, dep_tools_shared, dep_libinput],
+				     include_directories : [includes_src, includes_include],
+				     install_dir : libinput_tool_path,
+				     install : true
+				    )
+	test('validate-quirks',
+	     libinput_quirks,
+	     args: ['validate', '--data-dir=@0@'.format(dir_src_quirks)],
+	     suite : ['all']
+	     )
+
+	configure_file(input : 'tools/libinput-quirks.man',
+		       output : 'libinput-quirks.1',
+		       configuration : man_config,
+		       install_dir : dir_man1,
+		       )
+	# Same man page for the subtools to stay consistent with the other tools
+	configure_file(input : 'tools/libinput-quirks.man',
+		       output : 'libinput-quirks-list.1',
+		       configuration : man_config,
+		       install_dir : dir_man1,
+		       )
+	configure_file(input : 'tools/libinput-quirks.man',
+		       output : 'libinput-quirks-validate.1',
+		       configuration : man_config,
+		       install_dir : dir_man1,
+		       )
+
+	libinput_list_devices_sources = [ 'tools/libinput-list-devices.c' ]
+	libinput_list_devices = executable('libinput-list-devices',
+					   libinput_list_devices_sources,
+					   dependencies : deps_tools,
+					   include_directories : [includes_src, includes_include],
+					   install_dir : libinput_tool_path,
+					   install : true,
+					  )
+	test('list-devices',
+	     libinput_list_devices,
+	     suite : ['all', 'root', 'hardware'])
+
+	configure_file(input : 'tools/libinput-list-devices.man',
+		       output : 'libinput-list-devices.1',
+		       configuration : man_config,
+		       install_dir : dir_man1,
+		       )
+
+	libinput_measure_sources = [ 'tools/libinput-measure.c' ]
+	executable('libinput-measure',
+		   libinput_measure_sources,
+		   dependencies : deps_tools,
 		   include_directories : [includes_src, includes_include],
 		   install_dir : libinput_tool_path,
-		   install : true
+		   install : true,
 		   )
-	configure_file(input : 'tools/libinput-debug-gui.man',
-		       output : 'libinput-debug-gui.1',
+	configure_file(input : 'tools/libinput-measure.man',
+		       output : 'libinput-measure.1',
+		       configuration : man_config,
+		       install_dir : dir_man1,
+		       )
+
+	src_python_tools = files(
+		      'tools/libinput-measure-fuzz.py',
+		      'tools/libinput-measure-touchpad-tap.py',
+		      'tools/libinput-measure-touchpad-pressure.py',
+		      'tools/libinput-measure-touch-size.py',
+	)
+
+	config_noop = configuration_data()
+	# Set a dummy replacement to silence meson warnings:
+	# meson.build:487: WARNING: Got an empty configuration_data() object and
+	# 		   found no substitutions in the input file 'foo'. If you
+	# 		   want to copy a file to the build dir, use the 'copy:'
+	# 		   keyword argument added in 0.47.0
+	config_noop.set('dummy', 'dummy')
+	foreach t : src_python_tools
+		configure_file(input: t,
+			       output: '@BASENAME@',
+			       configuration : config_noop,
+			       install_dir : libinput_tool_path
+			      )
+	endforeach
+
+	src_man = files(
+		      'tools/libinput-measure-fuzz.man',
+		      'tools/libinput-measure-touchpad-tap.man',
+		      'tools/libinput-measure-touchpad-pressure.man',
+		      'tools/libinput-measure-touch-size.man',
+	)
+
+	foreach m : src_man
+		configure_file(input : m,
+			       output : '@BASENAME@.1',
+			       configuration : man_config,
+			       install_dir : dir_man1)
+	endforeach
+
+	libinput_record_sources = [ 'tools/libinput-record.c', git_version_h ]
+	executable('libinput-record',
+		   libinput_record_sources,
+		   dependencies : deps_tools + [dep_udev],
+		   include_directories : [includes_src, includes_include],
+		   install_dir : libinput_tool_path,
+		   install : true,
+		   )
+	configure_file(input : 'tools/libinput-record.man',
+		       output : 'libinput-record.1',
 		       configuration : man_config,
 		       install_dir : dir_man1,
 		       )
-endif
 
-libinput_sources = [ 'tools/libinput-tool.c' ]
+	install_data('tools/libinput-replay',
+		     install_dir : libinput_tool_path)
+	configure_file(input : 'tools/libinput-replay.man',
+		       output : 'libinput-replay.1',
+		       configuration : man_config,
+		       install_dir : dir_man1,
+		       )
 
-libinput_tool = executable('libinput',
-			   libinput_sources,
-			   dependencies : deps_tools,
+	if get_option('debug-gui')
+		if not have_udev
+			error('debug-gui requires -Dudev=true')
+		endif
+
+		dep_gtk = dependency('gtk+-3.0', version : '>= 3.20')
+		dep_cairo = dependency('cairo')
+		dep_glib = dependency('glib-2.0')
+
+		debug_gui_sources = [ 'tools/libinput-debug-gui.c' ]
+		deps_debug_gui = [
+				dep_gtk,
+				dep_cairo,
+				dep_glib,
+				] + deps_tools
+		executable('libinput-debug-gui',
+			   debug_gui_sources,
+			   dependencies : deps_debug_gui,
 			   include_directories : [includes_src, includes_include],
+			   install_dir : libinput_tool_path,
 			   install : true
-			  )
-configure_file(input : 'tools/libinput.man',
-	       output : 'libinput.1',
-	       configuration : man_config,
-	       install_dir : dir_man1,
-	       )
-
-ptraccel_debug_sources = [ 'tools/ptraccel-debug.c' ]
-executable('ptraccel-debug',
-	   ptraccel_debug_sources,
-	   dependencies : [ dep_libfilter, dep_libinput ],
-	   include_directories : [includes_src, includes_include],
-	   install : false
-	   )
+			   )
+		configure_file(input : 'tools/libinput-debug-gui.man',
+			       output : 'libinput-debug-gui.1',
+			       configuration : man_config,
+			       install_dir : dir_man1,
+			       )
+	endif
 
-# Don't run the test during a release build because we rely on the magic
-# subtool lookup
-if get_option('buildtype') == 'debug' or get_option('buildtype') == 'debugoptimized'
-	config_tool_option_test = configuration_data()
-	config_tool_option_test.set('MESON_ENABLED_DEBUG_GUI', get_option('debug-gui'))
-	tool_option_test = configure_file(input: 'tools/test-tool-option-parsing.py',
-					  output: '@BASENAME@',
-					  configuration : config_tool_option_test)
-	test('tool-option-parsing',
-	     tool_option_test,
-	     args : ['--tool-path', libinput_tool.full_path()],
-	     suite : ['all', 'root'],
-	     timeout : 240)
-endif
+	libinput_sources = [ 'tools/libinput-tool.c' ]
 
-# the libinput tools check whether we execute from the builddir, this is
-# the test to verify that lookup. We test twice, once as normal test
-# run from the builddir, once after copying to /tmp
-test_builddir_lookup = executable('test-builddir-lookup',
-				  'test/test-builddir-lookup.c',
-				  dependencies : [ dep_tools_shared],
-				  include_directories : [includes_src, includes_include],
-				  install : false)
-test('tools-builddir-lookup',
-     test_builddir_lookup,
-     args : ['--builddir-is-set'],
-     suite : ['all'])
-test('tools-builddir-lookup-installed',
-     find_program('test/helper-copy-and-exec-from-tmp.sh'),
-     args : [test_builddir_lookup.full_path(), '--builddir-is-null'],
-     env : ['LD_LIBRARY_PATH=@0@'.format(meson.current_build_dir())],
-     suite : ['all'],
-     workdir : '/tmp')
+	libinput_tool = executable('libinput',
+				   libinput_sources,
+				   dependencies : deps_tools,
+				   include_directories : [includes_src, includes_include],
+				   install : true
+				  )
+	configure_file(input : 'tools/libinput.man',
+		       output : 'libinput.1',
+		       configuration : man_config,
+		       install_dir : dir_man1,
+		       )
+
+	ptraccel_debug_sources = [ 'tools/ptraccel-debug.c' ]
+	executable('ptraccel-debug',
+		   ptraccel_debug_sources,
+		   dependencies : [ dep_libfilter, dep_libinput ],
+		   include_directories : [includes_src, includes_include],
+		   install : false
+		   )
+
+	# Don't run the test during a release build because we rely on the magic
+	# subtool lookup
+	if get_option('buildtype') == 'debug' or get_option('buildtype') == 'debugoptimized'
+		config_tool_option_test = configuration_data()
+		config_tool_option_test.set('MESON_ENABLED_DEBUG_GUI', get_option('debug-gui'))
+		tool_option_test = configure_file(input: 'tools/test-tool-option-parsing.py',
+						  output: '@BASENAME@',
+						  configuration : config_tool_option_test)
+		test('tool-option-parsing',
+		     tool_option_test,
+		     args : ['--tool-path', libinput_tool.full_path()],
+		     suite : ['all', 'root'],
+		     timeout : 240)
+	endif
+
+	# the libinput tools check whether we execute from the builddir, this is
+	# the test to verify that lookup. We test twice, once as normal test
+	# run from the builddir, once after copying to /tmp
+	test_builddir_lookup = executable('test-builddir-lookup',
+					  'test/test-builddir-lookup.c',
+					  dependencies : [ dep_tools_shared],
+					  include_directories : [includes_src, includes_include],
+					  install : false)
+	test('tools-builddir-lookup',
+	     test_builddir_lookup,
+	     args : ['--builddir-is-set'],
+	     suite : ['all'])
+	test('tools-builddir-lookup-installed',
+	     find_program('test/helper-copy-and-exec-from-tmp.sh'),
+	     args : [test_builddir_lookup.full_path(), '--builddir-is-null'],
+	     env : ['LD_LIBRARY_PATH=@0@'.format(meson.current_build_dir())],
+	     suite : ['all'],
+	     workdir : '/tmp')
+endif
 
 ############ tests ############
 
@@ -752,6 +770,10 @@ endif
 # This is the test suite runner, we allow disabling that one because of
 # dependencies
 if get_option('tests')
+	if not have_udev
+		error('tests require -Dudev=true')
+	endif
+
 	dep_check = dependency('check', version : '>= 0.9.10')
 
 	gstack = find_program('gstack', required : false)
diff --git a/meson_options.txt b/meson_options.txt
index 7819449c..c1cf43a6 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -10,6 +10,10 @@ option('libwacom',
        type: 'boolean',
        value: true,
        description: 'Use libwacom for tablet identification (default=true)')
+option('udev',
+       type: 'boolean',
+       value: true,
+       description: 'Use libudev for device detection (default=true)')
 option('debug-gui',
        type: 'boolean',
        value: true,
@@ -18,6 +22,10 @@ option('tests',
        type: 'boolean',
        value: true,
        description: 'Build the tests [default=true]')
+option('tools',
+       type: 'boolean',
+       value: true,
+       description: 'Build the tools [default=true]')
 option('install-tests',
        type: 'boolean',
        value: false,
diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c
index 29faf59e..3f605c73 100644
--- a/src/evdev-mt-touchpad.c
+++ b/src/evdev-mt-touchpad.c
@@ -27,12 +27,15 @@
 #include <math.h>
 #include <stdbool.h>
 #include <limits.h>
-#include <libudev.h>
 
 #if HAVE_LIBWACOM
 #include <libwacom/libwacom.h>
 #endif
 
+#if HAVE_UDEV
+#include <libudev.h>
+#endif
+
 #include "quirks.h"
 #include "evdev-mt-touchpad.h"
 
@@ -2609,8 +2612,12 @@ evdev_tag_touchpad(struct evdev_device *device,
 	int bustype, vendor;
 	const char *prop;
 
+#if HAVE_UDEV
 	prop = udev_device_get_property_value(udev_device,
 					      "ID_INPUT_TOUCHPAD_INTEGRATION");
+#else
+	prop = NULL;
+#endif
 	if (prop) {
 		if (streq(prop, "internal")) {
 			evdev_tag_touchpad_internal(device);
diff --git a/src/evdev-tablet-pad-leds.c b/src/evdev-tablet-pad-leds.c
index 70df1d06..2cdda68c 100644
--- a/src/evdev-tablet-pad-leds.c
+++ b/src/evdev-tablet-pad-leds.c
@@ -23,7 +23,6 @@
 
 #include "config.h"
 
-#include <libudev.h>
 #include <limits.h>
 #include <fcntl.h>
 
@@ -33,6 +32,10 @@
 #include <libwacom/libwacom.h>
 #endif
 
+#if HAVE_UDEV
+#include <libudev.h>
+#endif
+
 struct pad_led_group {
 	struct libinput_tablet_pad_mode_group base;
 	struct list led_list;
@@ -187,8 +190,12 @@ pad_group_new_basic(struct pad_dispatch *pad,
 static inline bool
 is_litest_device(struct evdev_device *device)
 {
+#if HAVE_UDEV
 	return !!udev_device_get_property_value(device->udev_device,
 						"LIBINPUT_TEST_DEVICE");
+#else
+	return false;
+#endif
 }
 
 static inline struct pad_led_group *
@@ -240,6 +247,7 @@ pad_led_get_sysfs_base_path(struct evdev_device *device,
 			    char *path_out,
 			    size_t path_out_sz)
 {
+#if HAVE_UDEV
 	struct udev_device *parent, *udev_device;
 	const char *test_path;
 	int rc;
@@ -268,6 +276,9 @@ pad_led_get_sysfs_base_path(struct evdev_device *device,
 		      udev_device_get_sysname(parent));
 
 	return rc != -1;
+#else
+	return false;
+#endif
 }
 
 #if HAVE_LIBWACOM
@@ -499,7 +510,7 @@ pad_init_leds_from_libwacom(struct pad_dispatch *pad,
 		goto out;
 
 	wacom = libwacom_new_from_path(db,
-				       udev_device_get_devnode(device->udev_device),
+				       device->devnode,
 				       WFALLBACK_NONE,
 				       NULL);
 	if (!wacom)
diff --git a/src/evdev.c b/src/evdev.c
index ba889b6a..34a9d67d 100644
--- a/src/evdev.c
+++ b/src/evdev.c
@@ -31,13 +31,16 @@
 #include <stdlib.h>
 #include <string.h>
 #include <sys/stat.h>
+#include <sys/types.h>
+#ifndef major
+#include <sys/sysmacros.h>
+#endif
 #include "linux/input.h"
 #include <unistd.h>
 #include <fcntl.h>
 #include <mtdev-plumbing.h>
 #include <assert.h>
 #include <math.h>
-#include <libudev.h>
 
 #include "libinput.h"
 #include "evdev.h"
@@ -50,6 +53,12 @@
 #include <libwacom/libwacom.h>
 #endif
 
+#if HAVE_UDEV
+#include <libudev.h>
+#endif
+
+#define INPUT_MAJOR 13
+
 #define DEFAULT_WHEEL_CLICK_ANGLE 15
 #define DEFAULT_BUTTON_SCROLL_TIMEOUT ms2us(200)
 
@@ -73,6 +82,7 @@ struct evdev_udev_tag_match {
 	enum evdev_device_udev_tags tag;
 };
 
+#if HAVE_UDEV
 static const struct evdev_udev_tag_match evdev_udev_tag_matches[] = {
 	{"ID_INPUT",			EVDEV_UDEV_TAG_INPUT},
 	{"ID_INPUT_KEYBOARD",		EVDEV_UDEV_TAG_KEYBOARD},
@@ -88,6 +98,7 @@ static const struct evdev_udev_tag_match evdev_udev_tag_matches[] = {
 	{"ID_INPUT_TRACKBALL",		EVDEV_UDEV_TAG_TRACKBALL},
 	{"ID_INPUT_SWITCH",		EVDEV_UDEV_TAG_SWITCH},
 };
+#endif
 
 static inline bool
 parse_udev_flag(struct evdev_device *device,
@@ -96,7 +107,11 @@ parse_udev_flag(struct evdev_device *device,
 {
 	const char *val;
 
+#if HAVE_UDEV
 	val = udev_device_get_property_value(udev_device, property);
+#else
+	val = NULL;
+#endif
 	if (!val)
 		return false;
 
@@ -1207,7 +1222,11 @@ evdev_read_wheel_click_prop(struct evdev_device *device,
 	int val;
 
 	*angle = DEFAULT_WHEEL_CLICK_ANGLE;
+#if HAVE_UDEV
 	prop = udev_device_get_property_value(device->udev_device, prop);
+#else
+	prop = NULL;
+#endif
 	if (!prop)
 		return false;
 
@@ -1232,7 +1251,11 @@ evdev_read_wheel_click_count_prop(struct evdev_device *device,
 {
 	int val;
 
+#if HAVE_UDEV
 	prop = udev_device_get_property_value(device->udev_device, prop);
+#else
+	prop = NULL;
+#endif
 	if (!prop)
 		return false;
 
@@ -1356,8 +1379,12 @@ evdev_read_dpi_prop(struct evdev_device *device)
 	if (device->tags & EVDEV_TAG_TRACKPOINT)
 		return DEFAULT_MOUSE_DPI;
 
+#if HAVE_UDEV
 	mouse_dpi = udev_device_get_property_value(device->udev_device,
 						   "MOUSE_DPI");
+#else
+	mouse_dpi = NULL;
+#endif
 	if (mouse_dpi) {
 		dpi = parse_mouse_dpi_property(mouse_dpi);
 		if (!dpi) {
@@ -1558,9 +1585,9 @@ evdev_device_get_udev_tags(struct evdev_device *device,
 			   struct udev_device *udev_device)
 {
 	enum evdev_device_udev_tags tags = 0;
-	int i;
 
-	for (i = 0; i < 2 && udev_device; i++) {
+#if HAVE_UDEV
+	for (int i = 0; i < 2 && udev_device; i++) {
 		unsigned j;
 		for (j = 0; j < ARRAY_LENGTH(evdev_udev_tag_matches); j++) {
 			const struct evdev_udev_tag_match match = evdev_udev_tag_matches[j];
@@ -1571,6 +1598,29 @@ evdev_device_get_udev_tags(struct evdev_device *device,
 		}
 		udev_device = udev_device_get_parent(udev_device);
 	}
+#else
+	struct libevdev *evdev = device->evdev;
+	struct stat st;
+	if (fstat(device->fd, &st) < 0)
+		return 0;
+	if (major(st.st_rdev) == INPUT_MAJOR)
+		tags |= EVDEV_UDEV_TAG_INPUT;
+	if (libevdev_has_event_code(evdev, EV_KEY, KEY_ENTER))
+		tags |= EVDEV_UDEV_TAG_KEYBOARD;
+	if (libevdev_has_event_code(evdev, EV_REL, REL_X) &&
+	    libevdev_has_event_code(evdev, EV_REL, REL_Y) &&
+	    libevdev_has_event_code(evdev, EV_KEY, BTN_MOUSE))
+		tags |= EVDEV_UDEV_TAG_MOUSE;
+	if (libevdev_has_event_code(evdev, EV_ABS, ABS_X) &&
+	    libevdev_has_event_code(evdev, EV_ABS, ABS_Y)) {
+		if (libevdev_has_event_code(evdev, EV_KEY, BTN_TOOL_FINGER) &&
+		    !libevdev_has_event_code(evdev, EV_KEY, BTN_TOOL_PEN)) {
+			tags |= EVDEV_UDEV_TAG_TOUCHPAD;
+		} else if (libevdev_has_event_code(evdev, EV_KEY, BTN_MOUSE)) {
+			tags |= EVDEV_UDEV_TAG_MOUSE;
+		}
+	}
+#endif
 
 	return tags;
 }
@@ -1969,6 +2019,7 @@ evdev_notify_added_device(struct evdev_device *device)
 static bool
 evdev_device_have_same_syspath(struct udev_device *udev_device, int fd)
 {
+#if HAVE_UDEV
 	struct udev *udev = udev_device_get_udev(udev_device);
 	struct udev_device *udev_device_new = NULL;
 	struct stat st;
@@ -1987,6 +2038,9 @@ evdev_device_have_same_syspath(struct udev_device *udev_device, int fd)
 	if (udev_device_new)
 		udev_device_unref(udev_device_new);
 	return rc;
+#else
+	return true;
+#endif
 }
 
 static bool
@@ -1997,8 +2051,12 @@ evdev_set_device_group(struct evdev_device *device,
 	struct libinput_device_group *group = NULL;
 	const char *udev_group;
 
+#if HAVE_UDEV
 	udev_group = udev_device_get_property_value(udev_device,
 						    "LIBINPUT_DEVICE_GROUP");
+#else
+	udev_group = NULL;
+#endif
 	if (udev_group)
 		group = libinput_device_group_find_group(libinput, udev_group);
 
@@ -2105,7 +2163,7 @@ libevdev_log_func(const struct libevdev *evdev,
 	struct libinput *libinput = data;
 	enum libinput_log_priority pri = LIBINPUT_LOG_PRIORITY_ERROR;
 	const char prefix[] = "libevdev: ";
-	char fmt[strlen(format) + strlen(prefix) + 1];
+	char fmt[1024];
 
 	switch (priority) {
 	case LIBEVDEV_LOG_ERROR:
@@ -2129,23 +2187,33 @@ udev_device_should_be_ignored(struct udev_device *udev_device)
 {
 	const char *value;
 
+#if HAVE_UDEV
 	value = udev_device_get_property_value(udev_device,
 					       "LIBINPUT_IGNORE_DEVICE");
+#else
+	value = NULL;
+#endif
 
 	return value && !streq(value, "0");
 }
 
 struct evdev_device *
 evdev_device_create(struct libinput_seat *seat,
-		    struct udev_device *udev_device)
+		    struct udev_device *udev_device,
+		    const char *devnode, const char *sysname)
 {
 	struct libinput *libinput = seat->libinput;
 	struct evdev_device *device = NULL;
 	int rc;
 	int fd;
 	int unhandled_device = 0;
-	const char *devnode = udev_device_get_devnode(udev_device);
-	const char *sysname = udev_device_get_sysname(udev_device);
+
+#if HAVE_UDEV
+	if (udev_device) {
+		devnode = udev_device_get_devnode(udev_device);
+		sysname = udev_device_get_sysname(udev_device);
+	}
+#endif
 
 	if (!devnode) {
 		log_info(libinput, "%s: no device node associated\n", sysname);
@@ -2193,10 +2261,16 @@ evdev_device_create(struct libinput_seat *seat,
 	device->seat_caps = 0;
 	device->is_mt = 0;
 	device->mtdev = NULL;
+#if HAVE_UDEV
 	device->udev_device = udev_device_ref(udev_device);
+#else
+	device->udev_device = NULL;
+#endif
 	device->dispatch = NULL;
 	device->fd = fd;
+	device->devnode = devnode;
 	device->devname = libevdev_get_name(device->evdev);
+	device->sysname = sysname;
 	device->scroll.threshold = 5.0; /* Default may be overridden */
 	device->scroll.direction_lock_threshold = 5.0; /* Default may be overridden */
 	device->scroll.direction = 0;
@@ -2255,7 +2329,11 @@ evdev_device_get_output(struct evdev_device *device)
 const char *
 evdev_device_get_sysname(struct evdev_device *device)
 {
+#if HAVE_UDEV
 	return udev_device_get_sysname(device->udev_device);
+#else
+	return device->sysname;
+#endif
 }
 
 const char *
@@ -2279,7 +2357,11 @@ evdev_device_get_id_vendor(struct evdev_device *device)
 struct udev_device *
 evdev_device_get_udev_device(struct evdev_device *device)
 {
+#if HAVE_UDEV
 	return udev_device_ref(device->udev_device);
+#else
+	return NULL;
+#endif
 }
 
 void
@@ -2360,8 +2442,12 @@ evdev_read_calibration_prop(struct evdev_device *device)
 	const char *prop;
 	float calibration[6];
 
+#if HAVE_UDEV
 	prop = udev_device_get_property_value(device->udev_device,
 					      "LIBINPUT_CALIBRATION_MATRIX");
+#else
+	prop = NULL;
+#endif
 
 	if (prop == NULL)
 		return;
@@ -2396,7 +2482,11 @@ evdev_read_fuzz_prop(struct evdev_device *device, unsigned int code)
 	if (rc == -1)
 		return 0;
 
+#if HAVE_UDEV
 	prop = udev_device_get_property_value(device->udev_device, name);
+#else
+	prop = NULL;
+#endif
 	if (prop && (safe_atoi(prop, &fuzz) == false || fuzz < 0)) {
 		evdev_log_bug_libinput(device,
 				       "invalid LIBINPUT_FUZZ property value: %s\n",
@@ -2726,7 +2816,6 @@ evdev_device_resume(struct evdev_device *device)
 {
 	struct libinput *libinput = evdev_libinput_context(device);
 	int fd;
-	const char *devnode;
 	struct input_event ev;
 	enum libevdev_read_status status;
 
@@ -2736,11 +2825,7 @@ evdev_device_resume(struct evdev_device *device)
 	if (device->was_removed)
 		return -ENODEV;
 
-	devnode = udev_device_get_devnode(device->udev_device);
-	if (!devnode)
-		return -ENODEV;
-
-	fd = open_restricted(libinput, devnode,
+	fd = open_restricted(libinput, device->devnode,
 			     O_RDWR | O_NONBLOCK | O_CLOEXEC);
 
 	if (fd < 0)
@@ -2839,7 +2924,9 @@ evdev_device_destroy(struct evdev_device *device)
 	libinput_timer_destroy(&device->middlebutton.timer);
 	libinput_seat_unref(device->base.seat);
 	libevdev_free(device->evdev);
+#if HAVE_UDEV
 	udev_device_unref(device->udev_device);
+#endif
 	free(device);
 }
 
@@ -2852,17 +2939,15 @@ evdev_tablet_has_left_handed(struct evdev_device *device)
 	WacomDeviceDatabase *db = NULL;
 	WacomDevice *d = NULL;
 	WacomError *error;
-	const char *devnode;
 
 	db = libinput_libwacom_ref(li);
 	if (!db)
 		goto out;
 
 	error = libwacom_error_new();
-	devnode = udev_device_get_devnode(device->udev_device);
 
 	d = libwacom_new_from_path(db,
-				   devnode,
+				   device->devnode,
 				   WFALLBACK_NONE,
 				   error);
 
diff --git a/src/evdev.h b/src/evdev.h
index e95f7e60..490d542e 100644
--- a/src/evdev.h
+++ b/src/evdev.h
@@ -167,7 +167,9 @@ struct evdev_device {
 	struct libevdev *evdev;
 	struct udev_device *udev_device;
 	char *output_name;
+	const char *devnode;
 	const char *devname;
+	const char *sysname;
 	bool was_removed;
 	int fd;
 	enum evdev_device_seat_capability seat_caps;
@@ -375,7 +377,8 @@ evdev_verify_dispatch_type(struct evdev_dispatch *dispatch,
 
 struct evdev_device *
 evdev_device_create(struct libinput_seat *seat,
-		    struct udev_device *device);
+		    struct udev_device *device,
+		    const char *devnode, const char *sysname);
 
 static inline struct libinput *
 evdev_libinput_context(const struct evdev_device *device)
diff --git a/src/libinput.c b/src/libinput.c
index b532f1e3..76c41d6f 100644
--- a/src/libinput.c
+++ b/src/libinput.c
@@ -33,7 +33,10 @@
 #include <sys/epoll.h>
 #include <unistd.h>
 #include <assert.h>
+
+#if HAVE_UDEV
 #include <libudev.h>
+#endif
 
 #include "libinput.h"
 #include "libinput-private.h"
@@ -1974,9 +1977,11 @@ close_restricted(struct libinput *libinput, int fd)
 bool
 ignore_litest_test_suite_device(struct udev_device *device)
 {
+#if HAVE_UDEV
 	if (!getenv("LIBINPUT_RUNNING_TEST_SUITE") &&
 	    udev_device_get_property_value(device, "LIBINPUT_TEST_DEVICE"))
 		return true;
+#endif
 
 	return false;
 }
diff --git a/src/path-seat.c b/src/path-seat.c
index 99b089a4..22bf06c9 100644
--- a/src/path-seat.c
+++ b/src/path-seat.c
@@ -25,7 +25,9 @@
 
 #include <string.h>
 #include <sys/stat.h>
+#if HAVE_UDEV
 #include <libudev.h>
+#endif
 
 #include "evdev.h"
 
@@ -38,6 +40,8 @@ struct path_input {
 struct path_device {
 	struct list link;
 	struct udev_device *udev_device;
+	const char *devnode;
+	const char *sysname;
 };
 
 struct path_seat {
@@ -121,33 +125,36 @@ path_seat_get_named(struct path_input *input,
 
 static struct path_seat *
 path_seat_get_for_device(struct path_input *input,
-			 struct udev_device *udev_device,
+			 struct path_device *dev,
 			 const char *seat_logical_name_override)
 {
 	struct path_seat *seat = NULL;
 	char *seat_name = NULL, *seat_logical_name = NULL;
 	const char *seat_prop;
 
-	const char *devnode, *sysname;
-
-	devnode = udev_device_get_devnode(udev_device);
-	sysname = udev_device_get_sysname(udev_device);
-
-	seat_prop = udev_device_get_property_value(udev_device, "ID_SEAT");
+#if HAVE_UDEV
+	seat_prop = udev_device_get_property_value(dev->udev_device, "ID_SEAT");
+#else
+	seat_prop = NULL;
+#endif
 	seat_name = safe_strdup(seat_prop ? seat_prop : default_seat);
 
 	if (seat_logical_name_override) {
 		seat_logical_name = safe_strdup(seat_logical_name_override);
 	} else {
-		seat_prop = udev_device_get_property_value(udev_device, "WL_SEAT");
+#if HAVE_UDEV
+		seat_prop = udev_device_get_property_value(dev->udev_device, "WL_SEAT");
+#else
+		seat_prop = NULL;
+#endif
 		seat_logical_name = strdup(seat_prop ? seat_prop : default_seat_name);
 	}
 
 	if (!seat_logical_name) {
 		log_error(&input->base,
 			  "%s: failed to create seat name for device '%s'.\n",
-			  sysname,
-			  devnode);
+			  dev->sysname,
+			  dev->devnode);
 		goto out;
 	}
 
@@ -158,8 +165,8 @@ path_seat_get_for_device(struct path_input *input,
 	if (!seat) {
 		log_info(&input->base,
 			 "%s: failed to create seat for device '%s'.\n",
-			 sysname,
-			 devnode);
+			 dev->sysname,
+			 dev->devnode);
 		goto out;
 	}
 
@@ -173,41 +180,41 @@ path_seat_get_for_device(struct path_input *input,
 
 static struct libinput_device *
 path_device_enable(struct path_input *input,
-		   struct udev_device *udev_device,
+		   struct path_device *dev,
 		   const char *seat_logical_name_override)
 {
 	struct path_seat *seat;
 	struct evdev_device *device = NULL;
 	const char *output_name;
-	const char *devnode, *sysname;
-
-	devnode = udev_device_get_devnode(udev_device);
-	sysname = udev_device_get_sysname(udev_device);
 
-	seat = path_seat_get_for_device(input, udev_device, seat_logical_name_override);
+	seat = path_seat_get_for_device(input, dev, seat_logical_name_override);
 	if (!seat)
 		goto out;
 
-	device = evdev_device_create(&seat->base, udev_device);
+	device = evdev_device_create(&seat->base, dev->udev_device, dev->devnode, dev->sysname);
 	libinput_seat_unref(&seat->base);
 
 	if (device == EVDEV_UNHANDLED_DEVICE) {
 		device = NULL;
 		log_info(&input->base,
 			 "%-7s - not using input device '%s'.\n",
-			 sysname,
-			 devnode);
+			 dev->sysname,
+			 dev->devnode);
 		goto out;
 	} else if (device == NULL) {
 		log_info(&input->base,
 			 "%-7s - failed to create input device '%s'.\n",
-			 sysname,
-			 devnode);
+			 dev->sysname,
+			 dev->devnode);
 		goto out;
 	}
 
 	evdev_read_calibration_prop(device);
-	output_name = udev_device_get_property_value(udev_device, "WL_OUTPUT");
+#if HAVE_UDEV
+	output_name = udev_device_get_property_value(dev->udev_device, "WL_OUTPUT");
+#else
+	output_name = NULL;
+#endif
 	device->output_name = safe_strdup(output_name);
 
 out:
@@ -221,7 +228,7 @@ path_input_enable(struct libinput *libinput)
 	struct path_device *dev;
 
 	list_for_each(dev, &input->path_list, link) {
-		if (path_device_enable(input, dev->udev_device, NULL) == NULL) {
+		if (path_device_enable(input, dev, NULL) == NULL) {
 			path_input_disable(libinput);
 			return -1;
 		}
@@ -234,7 +241,11 @@ static void
 path_device_destroy(struct path_device *dev)
 {
 	list_remove(&dev->link);
+#if HAVE_UDEV
 	udev_device_unref(dev->udev_device);
+#else
+	free((char *)dev->devnode);
+#endif
 	free(dev);
 }
 
@@ -244,16 +255,18 @@ path_input_destroy(struct libinput *input)
 	struct path_input *path_input = (struct path_input*)input;
 	struct path_device *dev, *tmp;
 
+#if HAVE_UDEV
 	udev_unref(path_input->udev);
+#endif
 
 	list_for_each_safe(dev, tmp, &path_input->path_list, link)
 		path_device_destroy(dev);
-
 }
 
 static struct libinput_device *
 path_create_device(struct libinput *libinput,
 		   struct udev_device *udev_device,
+		   const char *devnode,
 		   const char *seat_name)
 {
 	struct path_input *input = (struct path_input*)libinput;
@@ -261,11 +274,23 @@ path_create_device(struct libinput *libinput,
 	struct libinput_device *device;
 
 	dev = zalloc(sizeof *dev);
+#if HAVE_UDEV
 	dev->udev_device = udev_device_ref(udev_device);
+	dev->devnode = udev_device_get_devnode(udev_device);
+	dev->sysname = udev_device_get_sysname(udev_device);
+#else
+	dev->udev_device = NULL;
+	dev->devnode = safe_strdup(devnode);
+	dev->sysname = strrchr(devnode, '/');
+	if (dev->sysname)
+		++dev->sysname;
+	else
+		dev->sysname = "";
+#endif
 
 	list_insert(&input->path_list, &dev->link);
 
-	device = path_device_enable(input, udev_device, seat_name);
+	device = path_device_enable(input, dev, seat_name);
 
 	if (!device)
 		path_device_destroy(dev);
@@ -280,15 +305,24 @@ path_device_change_seat(struct libinput_device *device,
 	struct libinput *libinput = device->seat->libinput;
 	struct evdev_device *evdev = evdev_device(device);
 	struct udev_device *udev_device = NULL;
+	char *devnode = NULL;
 	int rc = -1;
 
+#if HAVE_UDEV
 	udev_device = evdev->udev_device;
 	udev_device_ref(udev_device);
+#else
+	devnode = strdup(evdev->devnode);
+	if (!devnode)
+		return -1;
+#endif
 	libinput_path_remove_device(device);
 
-	if (path_create_device(libinput, udev_device, seat_name) != NULL)
+	if (path_create_device(libinput, udev_device, devnode, seat_name) != NULL)
 		rc = 0;
+#if HAVE_UDEV
 	udev_device_unref(udev_device);
+#endif
 	return rc;
 }
 
@@ -309,14 +343,20 @@ libinput_path_create_context(const struct libinput_interface *interface,
 	if (!interface)
 		return NULL;
 
+#if HAVE_UDEV
 	udev = udev_new();
 	if (!udev)
 		return NULL;
+#else
+	udev = NULL;
+#endif
 
 	input = zalloc(sizeof *input);
 	if (libinput_init(&input->base, interface,
 			  &interface_backend, user_data) != 0) {
+#if HAVE_UDEV
 		udev_unref(udev);
+#endif
 		free(input);
 		return NULL;
 	}
@@ -327,6 +367,7 @@ libinput_path_create_context(const struct libinput_interface *interface,
 	return &input->base;
 }
 
+#if HAVE_UDEV
 static inline struct udev_device *
 udev_device_from_devnode(struct libinput *libinput,
 			 struct udev *udev,
@@ -356,13 +397,12 @@ udev_device_from_devnode(struct libinput *libinput,
 
 	return dev;
 }
+#endif
 
 LIBINPUT_EXPORT struct libinput_device *
 libinput_path_add_device(struct libinput *libinput,
 			 const char *path)
 {
-	struct path_input *input = (struct path_input *)libinput;
-	struct udev *udev = input->udev;
 	struct udev_device *udev_device;
 	struct libinput_device *device;
 
@@ -378,7 +418,10 @@ libinput_path_add_device(struct libinput *libinput,
 		return NULL;
 	}
 
-	udev_device = udev_device_from_devnode(libinput, udev, path);
+#if HAVE_UDEV
+	struct path_input *input = (struct path_input *)libinput;
+
+	udev_device = udev_device_from_devnode(libinput, input->udev, path);
 	if (!udev_device) {
 		log_bug_client(libinput, "Invalid path %s\n", path);
 		return NULL;
@@ -388,6 +431,9 @@ libinput_path_add_device(struct libinput *libinput,
 		udev_device_unref(udev_device);
 		return NULL;
 	}
+#else
+	udev_device = NULL;
+#endif
 
 	/* We cannot do this during path_create_context because the log
 	 * handler isn't set up there but we really want to log to the right
@@ -396,8 +442,10 @@ libinput_path_add_device(struct libinput *libinput,
 	 */
 	libinput_init_quirks(libinput);
 
-	device = path_create_device(libinput, udev_device, NULL);
+	device = path_create_device(libinput, udev_device, path, NULL);
+#if HAVE_UDEV
 	udev_device_unref(udev_device);
+#endif
 	return device;
 }
 
@@ -416,7 +464,8 @@ libinput_path_remove_device(struct libinput_device *device)
 	}
 
 	list_for_each(dev, &input->path_list, link) {
-		if (dev->udev_device == evdev->udev_device) {
+		if (dev->udev_device == evdev->udev_device &&
+		    dev->devnode == evdev->devnode) {
 			path_device_destroy(dev);
 			break;
 		}
diff --git a/src/quirks.c b/src/quirks.c
index 8c0bb96e..ea13f9a7 100644
--- a/src/quirks.c
+++ b/src/quirks.c
@@ -31,11 +31,14 @@
 #undef NDEBUG /* You don't get to disable asserts here */
 #include <assert.h>
 #include <stdlib.h>
-#include <libudev.h>
 #include <dirent.h>
 #include <fnmatch.h>
 #include <libgen.h>
 
+#if HAVE_UDEV
+#include <libudev.h>
+#endif
+
 #include "libinput-versionsort.h"
 #include "libinput-util.h"
 
@@ -348,34 +351,22 @@ property_cleanup(struct property *p)
 static inline char *
 init_dmi(void)
 {
-	struct udev *udev;
-	struct udev_device *udev_device;
-	const char *modalias = NULL;
+	char modalias[1024];
 	char *copy = NULL;
-	const char *syspath = "/sys/devices/virtual/dmi/id";
+	const char *syspath = "/sys/devices/virtual/dmi/id/modalias";
+	FILE *fp;
 
 	if (getenv("LIBINPUT_RUNNING_TEST_SUITE"))
 		return safe_strdup("dmi:");
 
-	udev = udev_new();
-	if (!udev)
+	fp = fopen(syspath, "r");
+	if (!fp)
 		return NULL;
 
-	udev_device = udev_device_new_from_syspath(udev, syspath);
-	if (udev_device)
-		modalias = udev_device_get_property_value(udev_device,
-							  "MODALIAS");
-
-	/* Not sure whether this could ever really fail, if so we should
-	 * open the sysfs file directly. But then udev wouldn't have failed,
-	 * so... */
-	if (!modalias)
-		modalias = "dmi:*";
+	if (fgets(modalias, sizeof(modalias), fp))
+		copy = safe_strdup(modalias);
 
-	copy = safe_strdup(modalias);
-
-	udev_device_unref(udev_device);
-	udev_unref(udev);
+	fclose(fp);
 
 	return copy;
 }
@@ -1100,6 +1091,7 @@ quirks_context_unref(struct quirks_context *ctx)
 	return NULL;
 }
 
+#if HAVE_UDEV
 static struct quirks *
 quirks_new(void)
 {
@@ -1112,6 +1104,7 @@ quirks_new(void)
 
 	return q;
 }
+#endif
 
 struct quirks *
 quirks_unref(struct quirks *q)
@@ -1142,13 +1135,16 @@ quirks_unref(struct quirks *q)
 static const char *
 udev_prop(struct udev_device *device, const char *prop)
 {
-	struct udev_device *d = device;
 	const char *value = NULL;
 
+#if HAVE_UDEV
+	struct udev_device *d = device;
+
 	do {
 		value = udev_device_get_property_value(d, prop);
 		d = udev_device_get_parent(d);
 	} while (value == NULL && d != NULL);
+#endif
 
 	return value;
 }
@@ -1262,6 +1258,7 @@ match_fill_dmi_dt(struct match *m, char *dmi, char *dt)
 	}
 }
 
+#if HAVE_UDEV
 static struct match *
 match_new(struct udev_device *device,
 	  char *dmi, char *dt)
@@ -1387,11 +1384,13 @@ quirk_match_section(struct quirks_context *ctx,
 
 	return true;
 }
+#endif
 
 struct quirks *
 quirks_fetch_for_device(struct quirks_context *ctx,
 			struct udev_device *udev_device)
 {
+#if HAVE_UDEV
 	struct quirks *q = NULL;
 	struct section *s;
 	struct match *m;
@@ -1420,6 +1419,9 @@ quirks_fetch_for_device(struct quirks_context *ctx,
 	list_insert(&ctx->quirks, &q->link);
 
 	return q;
+#else
+	return NULL;
+#endif
 }
 
 
diff --git a/src/udev-seat.c b/src/udev-seat.c
index 3af01606..df280d9a 100644
--- a/src/udev-seat.c
+++ b/src/udev-seat.c
@@ -24,6 +24,8 @@
 
 #include "config.h"
 
+#if HAVE_UDEV
+
 #include <libudev.h>
 #include <stdlib.h>
 #include <stdio.h>
@@ -81,7 +83,7 @@ device_added(struct udev_device *udev_device,
 			return -1;
 	}
 
-	device = evdev_device_create(&seat->base, udev_device);
+	device = evdev_device_create(&seat->base, udev_device, NULL, NULL);
 	libinput_seat_unref(&seat->base);
 
 	if (device == EVDEV_UNHANDLED_DEVICE) {
@@ -412,3 +414,24 @@ libinput_udev_assign_seat(struct libinput *libinput,
 
 	return 0;
 }
+
+#else
+
+#include "udev-seat.h"
+
+LIBINPUT_EXPORT struct libinput *
+libinput_udev_create_context(const struct libinput_interface *interface,
+			     void *user_data,
+			     struct udev *udev)
+{
+	return NULL;
+}
+
+LIBINPUT_EXPORT int
+libinput_udev_assign_seat(struct libinput *libinput,
+			  const char *seat_id)
+{
+	return -1;
+}
+
+#endif /* HAVE_UDEV */

From 597b16f5134b8d095039efb3a27a4d0b643718c3 Mon Sep 17 00:00:00 2001
From: Michael Forney <mforney@mforney.org>
Date: Fri, 3 Jan 2020 22:08:41 -0800
Subject: [PATCH] Add netlink seat

---
 meson.build        |   2 +
 src/libinput.h     |  42 +++++
 src/netlink-seat.c | 373 +++++++++++++++++++++++++++++++++++++++++++++
 src/netlink-seat.h |  42 +++++
 4 files changed, 459 insertions(+)
 create mode 100644 src/netlink-seat.c
 create mode 100644 src/netlink-seat.h

diff --git a/meson.build b/meson.build
index 75b393f4..7d66eba2 100644
--- a/meson.build
+++ b/meson.build
@@ -392,6 +392,8 @@ src_libinput = src_libfilter + [
 	'src/evdev-tablet-pad.c',
 	'src/evdev-tablet-pad.h',
 	'src/evdev-tablet-pad-leds.c',
+	'src/netlink-seat.c',
+	'src/netlink-seat.h',
 	'src/path-seat.c',
 	'src/udev-seat.c',
 	'src/udev-seat.h',
diff --git a/src/libinput.h b/src/libinput.h
index f5ae835d..b90cb4ab 100644
--- a/src/libinput.h
+++ b/src/libinput.h
@@ -3304,6 +3304,7 @@ libinput_event_switch_get_time_usec(struct libinput_event_switch *event);
  *
  * @see libinput_udev_create_context
  * @see libinput_path_create_context
+ * @see libinput_netlink_create_context
  */
 struct libinput_interface {
 	/**
@@ -3439,6 +3440,47 @@ libinput_path_add_device(struct libinput *libinput,
 void
 libinput_path_remove_device(struct libinput_device *device);
 
+/**
+ * @ingroup base
+ *
+ * Create a new libinput context from netlink. This context is inactive until
+ * assigned a seat ID with libinput_netlink_assign_seat().
+ *
+ * @param interface The callback interface
+ * @param user_data Caller-specific data passed to the various callback
+ * interfaces.
+ *
+ * @return An initialized, but inactive libinput context or NULL on error
+ */
+struct libinput *
+libinput_netlink_create_context(const struct libinput_interface *interface,
+				void *user_data);
+
+/**
+ * @ingroup base
+ *
+ * Assign a seat to this libinput context. New devices or the removal of
+ * existing devices will appear as events during libinput_dispatch().
+ *
+ * libinput_netlink_assign_seat() succeeds even if no input devices are currently
+ * available on this seat, or if devices are available but fail to open in
+ * @ref libinput_interface::open_restricted. Devices that do not have the
+ * minimum capabilities to be recognized as pointer, keyboard or touch
+ * device are ignored. Such devices and those that failed to open
+ * ignored until the next call to libinput_resume().
+ *
+ * This function may only be called once per context.
+ *
+ * @param libinput A libinput context initialized with
+ * libinput_udev_create_context()
+ * @param seat_id A seat identifier. This string must not be NULL.
+ *
+ * @return 0 on success or -1 on failure.
+ */
+int
+libinput_netlink_assign_seat(struct libinput *libinput,
+			     const char *seat_id);
+
 /**
  * @ingroup base
  *
diff --git a/src/netlink-seat.c b/src/netlink-seat.c
new file mode 100644
index 00000000..d22a2821
--- /dev/null
+++ b/src/netlink-seat.c
@@ -0,0 +1,373 @@
+/*
+ * Copyright © 2013 Intel Corporation
+ * Copyright © 2013-2015 Red Hat, Inc.
+ * Copyright © 2017 Michael Forney
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+
+#include <sys/socket.h>
+#include <linux/netlink.h>
+
+#include "evdev.h"
+#include "netlink-seat.h"
+
+#define INPUT_MAJOR 13
+
+static const char default_seat[] = "seat0";
+static const char default_seat_name[] = "default";
+
+static struct netlink_seat *
+netlink_seat_create(struct netlink_input *input,
+		    const char *device_seat,
+		    const char *seat_name);
+static struct netlink_seat *
+netlink_seat_get_named(struct netlink_input *input, const char *seat_name);
+
+static int
+device_added(struct netlink_input *input,
+	     const char *devnode)
+{
+	struct evdev_device *device;
+	const char *device_seat, *seat_name, *sysname;
+	struct netlink_seat *seat;
+
+	device_seat = default_seat;
+	seat_name = default_seat_name;
+	seat = netlink_seat_get_named(input, seat_name);
+
+	if (seat)
+		libinput_seat_ref(&seat->base);
+	else {
+		seat = netlink_seat_create(input, device_seat, seat_name);
+		if (!seat)
+			return -1;
+	}
+
+	sysname = strrchr(devnode, '/');
+	if (sysname)
+		++sysname;
+	else
+		sysname = "";
+
+	device = evdev_device_create(&seat->base, NULL, devnode, sysname);
+	libinput_seat_unref(&seat->base);
+
+	if (device == EVDEV_UNHANDLED_DEVICE) {
+		log_info(&input->base,
+			 "%-7s - not using input device '%s'\n",
+			 sysname,
+			 devnode);
+		return 0;
+	} else if (device == NULL) {
+		log_info(&input->base,
+			 "%-7s - failed to create input device '%s'\n",
+			 sysname,
+			 devnode);
+		return 0;
+	}
+
+	evdev_read_calibration_prop(device);
+
+	return 0;
+}
+
+static void
+device_removed(struct netlink_input *input, const char *devnode)
+{
+	struct evdev_device *device, *next;
+	struct netlink_seat *seat;
+
+	list_for_each(seat, &input->base.seat_list, base.link) {
+		list_for_each_safe(device, next,
+				   &seat->base.devices_list, base.link) {
+			if (streq(devnode, device->devnode)) {
+				evdev_device_remove(device);
+				break;
+			}
+		}
+	}
+}
+
+static int
+select_device(const struct dirent *entry)
+{
+	const char *p;
+
+	if (strncmp(entry->d_name, "event", 5) != 0)
+		return 0;
+	for (p = entry->d_name + 5; '0' <= *p && *p <= '9'; ++p)
+		;
+	return *p == '\0';
+}
+
+static int
+netlink_input_add_devices(struct netlink_input *input)
+{
+	struct dirent **devices;
+	char path[PATH_MAX];
+	int i, n, len;
+
+	n = scandir("/dev/input", &devices, &select_device, &alphasort);
+	if (n == -1)
+		return -1;
+	for (i = 0; i < n; ++i) {
+		len = snprintf(path, sizeof(path), "/dev/input/%s", devices[i]->d_name);
+		free(devices[i]);
+		if (len < 0 || (size_t)len >= sizeof(path)) {
+			free(devices);
+			return -1;
+		}
+		device_added(input, path);
+	}
+	free(devices);
+
+	return 0;
+}
+
+static void
+netlink_input_remove_devices(struct netlink_input *input)
+{
+	struct evdev_device *device, *next;
+	struct netlink_seat *seat, *tmp;
+
+	list_for_each_safe(seat, tmp, &input->base.seat_list, base.link) {
+		libinput_seat_ref(&seat->base);
+		list_for_each_safe(device, next,
+				   &seat->base.devices_list, base.link) {
+			evdev_device_remove(device);
+		}
+		libinput_seat_unref(&seat->base);
+	}
+}
+
+static void
+netlink_input_disable(struct libinput *libinput)
+{
+	struct netlink_input *input = (struct netlink_input*)libinput;
+
+	if (input->sock == -1)
+		return;
+
+	close(input->sock);
+	input->sock = -1;
+	libinput_remove_source(&input->base, input->source);
+	input->source = NULL;
+
+	netlink_input_remove_devices(input);
+}
+
+static void
+netlink_handler(void *data)
+{
+	struct netlink_input *input = data;
+	char buf[BUFSIZ], *key, *val;
+	ssize_t n;
+	size_t len;
+	char *action = NULL, *devname = NULL, *devnode, *sysname;
+
+	n = read(input->sock, buf, sizeof(buf));
+	if (n <= 0)
+		return;
+	for (key = buf; key < buf + n; key += len + 1) {
+		len = strlen(key);
+		val = strchr(key, '=');
+		if (!val)
+			continue;
+		*val++ = '\0';
+		if (strcmp(key, "ACTION") == 0) {
+			action = val;
+		} else if (strcmp(key, "SUBSYSTEM") == 0) {
+			if (strcmp(val, "input") != 0)
+				return;
+		} else if (strcmp(key, "DEVNAME") == 0) {
+			devname = val;
+		}
+	}
+	if (!action || !devname)
+		return;
+	sysname = strrchr(devname, '/');
+	if (sysname)
+		++sysname;
+	else
+		sysname = devname;
+	if (strncmp(sysname, "event", 5) != 0)
+		return;
+	devnode = devname - 5;
+	memcpy(devnode, "/dev/", 5);
+	if (strcmp(action, "add") == 0)
+		device_added(input, devnode);
+	else if (strcmp(action, "remove") == 0)
+		device_removed(input, devnode);
+}
+
+static int
+netlink_input_enable(struct libinput *libinput)
+{
+	int s;
+	struct sockaddr_nl addr = {
+		.nl_family = AF_NETLINK,
+		.nl_groups = 1,
+	};
+	struct netlink_input *input = (struct netlink_input*)libinput;
+
+	if (input->sock != -1)
+		return 0;
+	s = socket(AF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_KOBJECT_UEVENT);
+	if (s == -1)
+		return -1;
+	if (bind(s, (void *)&addr, sizeof(addr)) < 0) {
+		close(s);
+		return -1;
+	}
+	input->source = libinput_add_fd(&input->base, s, netlink_handler, input);
+	if (!input->source) {
+		close(s);
+		return -1;
+	}
+	input->sock = s;
+	if (netlink_input_add_devices(input) < 0) {
+		netlink_input_disable(libinput);
+		return -1;
+	}
+
+	return 0;
+}
+
+static void
+netlink_input_destroy(struct libinput *input)
+{
+}
+
+static void
+netlink_seat_destroy(struct libinput_seat *seat)
+{
+	struct netlink_seat *nseat = (struct netlink_seat*)seat;
+	free(nseat);
+}
+
+static struct netlink_seat *
+netlink_seat_create(struct netlink_input *input,
+		    const char *device_seat,
+		    const char *seat_name)
+{
+	struct netlink_seat *seat;
+
+	seat = zalloc(sizeof(*seat));
+
+	libinput_seat_init(&seat->base, &input->base,
+			   device_seat, seat_name,
+			   netlink_seat_destroy);
+
+	return seat;
+}
+
+static struct netlink_seat *
+netlink_seat_get_named(struct netlink_input *input, const char *seat_name)
+{
+	struct netlink_seat *seat;
+
+	list_for_each(seat, &input->base.seat_list, base.link) {
+		if (streq(seat->base.logical_name, seat_name))
+			return seat;
+	}
+
+	return NULL;
+}
+
+static int
+netlink_device_change_seat(struct libinput_device *device,
+			   const char *seat_name)
+{
+	struct libinput *libinput = device->seat->libinput;
+	struct netlink_input *input = (struct netlink_input *)libinput;
+	struct evdev_device *evdev = evdev_device(device);
+	char *devnode;
+	int rc;
+
+	devnode = safe_strdup(evdev->devnode);
+	device_removed(input, devnode);
+	rc = device_added(input, devnode);
+	free(devnode);
+
+	return rc;
+}
+
+static const struct libinput_interface_backend interface_backend = {
+	.resume = netlink_input_enable,
+	.suspend = netlink_input_disable,
+	.destroy = netlink_input_destroy,
+	.device_change_seat = netlink_device_change_seat,
+};
+
+LIBINPUT_EXPORT struct libinput *
+libinput_netlink_create_context(const struct libinput_interface *interface,
+				void *user_data)
+{
+	struct netlink_input *input;
+
+	if (!interface)
+		return NULL;
+
+	input = zalloc(sizeof(*input));
+	input->sock = -1;
+
+	if (libinput_init(&input->base, interface,
+			  &interface_backend, user_data) != 0) {
+		libinput_unref(&input->base);
+		free(input);
+		return NULL;
+	}
+
+	return &input->base;
+}
+
+LIBINPUT_EXPORT int
+libinput_netlink_assign_seat(struct libinput *libinput,
+			     const char *seat_id)
+{
+	struct netlink_input *input = (struct netlink_input*)libinput;
+
+	if (libinput->interface_backend != &interface_backend) {
+		log_bug_client(libinput, "Mismatching backends.\n");
+		return -1;
+	}
+
+	/* We cannot do this during netlink_create_context because the log
+	 * handler isn't set up there but we really want to log to the right
+	 * place if the quirks run into parser errors. So we have to do it
+	 * here since we can expect the log handler to be set up by now.
+	 */
+	libinput_init_quirks(libinput);
+
+	if (netlink_input_enable(&input->base) < 0)
+		return -1;
+
+	return 0;
+}
diff --git a/src/netlink-seat.h b/src/netlink-seat.h
new file mode 100644
index 00000000..cd8d3ef2
--- /dev/null
+++ b/src/netlink-seat.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright © 2013 Intel Corporation
+ * Copyright © 2017 Michael Forney
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef _NETLINK_SEAT_H_
+#define _NETLINK_SEAT_H_
+
+#include "config.h"
+
+#include "libinput-private.h"
+
+struct netlink_seat {
+	struct libinput_seat base;
+};
+
+struct netlink_input {
+	struct libinput base;
+	struct libinput_source *source;
+	int sock;
+};
+
+#endif