diff --git a/clients/calibrator.c b/clients/calibrator.c
index b98a676..41a309b 100644
--- a/clients/calibrator.c
+++ b/clients/calibrator.c
@@ -34,7 +34,7 @@
 #include <getopt.h>
 #include <errno.h>
 
-#include <linux/input.h>
+#include "../shared/input-event-codes.h"
 #include <wayland-client.h>
 
 #include "window.h"
diff --git a/clients/clickdot.c b/clients/clickdot.c
index 4a946df..29839e0 100644
--- a/clients/clickdot.c
+++ b/clients/clickdot.c
@@ -36,7 +36,7 @@
 #include <time.h>
 #include <errno.h>
 
-#include <linux/input.h>
+#include "../shared/input-event-codes.h"
 #include <wayland-client.h>
 
 #include "window.h"
diff --git a/clients/cliptest.c b/clients/cliptest.c
index bdff3db..f4abd3b 100644
--- a/clients/cliptest.c
+++ b/clients/cliptest.c
@@ -51,7 +51,7 @@
 #include <assert.h>
 #include <errno.h>
 
-#include <linux/input.h>
+#include "../shared/input-event-codes.h"
 #include <wayland-client.h>
 
 #include "libweston/matrix.h"
diff --git a/clients/constraints.c b/clients/constraints.c
index e53389f..d98c53a 100644
--- a/clients/constraints.c
+++ b/clients/constraints.c
@@ -27,7 +27,7 @@
 #include <stdbool.h>
 #include <getopt.h>
 #include <sys/mman.h>
-#include <linux/input.h>
+#include "../shared/input-event-codes.h"
 #include <wayland-cursor.h>
 
 #include "shared/helpers.h"
diff --git a/clients/content_protection.c b/clients/content_protection.c
index 6a5c63c..8ff4351 100644
--- a/clients/content_protection.c
+++ b/clients/content_protection.c
@@ -32,7 +32,7 @@
 #include <cairo.h>
 #include <sys/time.h>
 
-#include <linux/input.h>
+#include "../shared/input-event-codes.h"
 #include <wayland-client.h>
 #include "weston-content-protection-client-protocol.h"
 #include "window.h"
diff --git a/clients/desktop-shell.c b/clients/desktop-shell.c
index 0722e95..d64c7b2 100644
--- a/clients/desktop-shell.c
+++ b/clients/desktop-shell.c
@@ -34,7 +34,7 @@
 #include <math.h>
 #include <cairo.h>
 #include <sys/wait.h>
-#include <linux/input.h>
+#include "../shared/input-event-codes.h"
 #include <libgen.h>
 #include <ctype.h>
 #include <time.h>
diff --git a/clients/editor.c b/clients/editor.c
index ed67fd1..7f360fd 100644
--- a/clients/editor.c
+++ b/clients/editor.c
@@ -33,7 +33,7 @@
 #include <stdbool.h>
 #include <unistd.h>
 
-#include <linux/input.h>
+#include "../shared/input-event-codes.h"
 #include <cairo.h>
 
 #include <pango/pangocairo.h>
diff --git a/clients/flower.c b/clients/flower.c
index 3676c40..dcbc414 100644
--- a/clients/flower.c
+++ b/clients/flower.c
@@ -33,7 +33,7 @@
 #include <errno.h>
 #include <sys/time.h>
 
-#include <linux/input.h>
+#include "../shared/input-event-codes.h"
 #include <wayland-client.h>
 #include "window.h"
 
diff --git a/clients/fullscreen.c b/clients/fullscreen.c
index 5b3c906..ba0b1f3 100644
--- a/clients/fullscreen.c
+++ b/clients/fullscreen.c
@@ -33,7 +33,7 @@
 #include <errno.h>
 #include <cairo.h>
 
-#include <linux/input.h>
+#include "../shared/input-event-codes.h"
 #include <wayland-client.h>
 #include "window.h"
 #include <libweston/zalloc.h>
diff --git a/clients/image.c b/clients/image.c
index b48d7f4..830f398 100644
--- a/clients/image.c
+++ b/clients/image.c
@@ -37,7 +37,7 @@
 #include <cairo.h>
 #include <assert.h>
 #include <errno.h>
-#include <linux/input.h>
+#include "../shared/input-event-codes.h"
 
 #include <wayland-client.h>
 
diff --git a/clients/ivi-shell-user-interface.c b/clients/ivi-shell-user-interface.c
index 2bf164e..b4c873a 100644
--- a/clients/ivi-shell-user-interface.c
+++ b/clients/ivi-shell-user-interface.c
@@ -29,7 +29,7 @@
 #include <stdint.h>
 #include <stdio.h>
 #include <string.h>
-#include <linux/input.h>
+#include "../shared/input-event-codes.h"
 #include <assert.h>
 #include <fcntl.h>
 #include <signal.h>
diff --git a/clients/keyboard.c b/clients/keyboard.c
index 49b94ba..6689a71 100644
--- a/clients/keyboard.c
+++ b/clients/keyboard.c
@@ -31,7 +31,7 @@
 #include <string.h>
 #include <errno.h>
 
-#include <linux/input.h>
+#include "../shared/input-event-codes.h"
 #include <cairo.h>
 
 #include "window.h"
diff --git a/clients/meson.build b/clients/meson.build
index 7cf5906..79ec40f 100644
--- a/clients/meson.build
+++ b/clients/meson.build
@@ -23,14 +23,25 @@ srcs_toytoolkit = [
 	viewporter_client_protocol_h,
 	viewporter_protocol_c,
 ]
+# libutil may not exist on all platforms (e.g., macOS)
+dep_util = cc.find_library('util', required: false)
+
+# libepoll-shim is needed on BSD/macOS for epoll compatibility
+dep_epoll_shim = dependency('epoll-shim', required: false)
+
 deps_toytoolkit = [
 	dep_wayland_client,
 	dep_lib_cairo_shared,
 	dep_matrix_c,
 	dep_xkbcommon,
 	dependency('wayland-cursor'),
-	cc.find_library('util'),
 ]
+if dep_util.found()
+	deps_toytoolkit += dep_util
+endif
+if dep_epoll_shim.found()
+	deps_toytoolkit += dep_epoll_shim
+endif
 lib_toytoolkit = static_library(
 	'toytoolkit',
 	srcs_toytoolkit,
@@ -79,7 +90,7 @@ simple_clients = [
 			dep_libshared,
 			dep_pixman,
 			dep_libdrm,
-			dependency('libudev', version: '>= 136'),
+			dependency('libudev', version: '>= 136', required: simple_build_all or simple_clients_enabled.contains('dmabuf-feedback')),
 			# gbm_bo_get_fd_for_plane() from 21.1.0
 			dependency('gbm', version: '>= 21.1.1',
 				required: simple_build_all or simple_clients_enabled.contains('dmabuf-feedback'),
@@ -452,14 +463,34 @@ if get_option('demo-clients')
 	foreach t : demo_clients
 		t_name = 'weston-' + t.get('basename')
 		t_srcs = [ t.get('basename') + '.c' ] + t.get('add_sources', [])
-		t_deps = [ dep_toytoolkit, t.get('dep_objs', []) ]
+
+		# Check if all dep_objs are available (skip if any stub dependency)
+		skip_client = false
+		t_dep_objs = t.get('dep_objs', [])
+		foreach dep_obj : t_dep_objs
+			if not dep_obj.found()
+				message('Skipping @0@ - missing internal dependency'.format(t_name))
+				skip_client = true
+				break
+			endif
+		endforeach
+		if skip_client
+			continue
+		endif
+
+		t_deps = [ dep_toytoolkit ] + t_dep_objs
 		foreach depname : t.get('deps', [])
 			dep = dependency(depname, required: false)
 			if not dep.found()
-				error('@0@ requires \'@1@\' which was not found. If you rather not build this, set \'-Ddemo-clients=false\'.'.format(t_name, depname))
+				message('Skipping @0@ - missing @1@'.format(t_name, depname))
+				skip_client = true
+				break
 			endif
 			t_deps += dep
 		endforeach
+		if skip_client
+			continue
+		endif
 
 		executable(
 			t_name, t_srcs,
diff --git a/clients/resizor.c b/clients/resizor.c
index 3caad55..6809f51 100644
--- a/clients/resizor.c
+++ b/clients/resizor.c
@@ -33,7 +33,7 @@
 #include <assert.h>
 #include <errno.h>
 
-#include <linux/input.h>
+#include "../shared/input-event-codes.h"
 #include <wayland-client.h>
 
 #include "window.h"
diff --git a/clients/scaler.c b/clients/scaler.c
index 3948fa2..0250a71 100644
--- a/clients/scaler.c
+++ b/clients/scaler.c
@@ -29,9 +29,10 @@
 #include <string.h>
 #include <assert.h>
 #include <errno.h>
+#include <sys/time.h>
 #include <cairo.h>
 
-#include <linux/input.h>
+#include "../shared/input-event-codes.h"
 
 #include "window.h"
 #include "viewporter-client-protocol.h"
diff --git a/clients/simple-dmabuf-v4l.c b/clients/simple-dmabuf-v4l.c
index 8ed3a0b..202c468 100644
--- a/clients/simple-dmabuf-v4l.c
+++ b/clients/simple-dmabuf-v4l.c
@@ -42,7 +42,7 @@
 #include <fcntl.h>
 #include <errno.h>
 #include <linux/videodev2.h>
-#include <linux/input.h>
+#include "../shared/input-event-codes.h"
 
 #include <wayland-client.h>
 #include <wayland-cursor.h>
diff --git a/clients/simple-egl.c b/clients/simple-egl.c
index 7cc98ea..4a7cf3a 100644
--- a/clients/simple-egl.c
+++ b/clients/simple-egl.c
@@ -32,7 +32,7 @@
 #include <assert.h>
 #include <signal.h>
 
-#include <linux/input.h>
+#include "../shared/input-event-codes.h"
 
 #include <wayland-client.h>
 #include <wayland-egl.h>
diff --git a/clients/simple-im.c b/clients/simple-im.c
index a9ed27f..ba11a27 100644
--- a/clients/simple-im.c
+++ b/clients/simple-im.c
@@ -32,7 +32,7 @@
 #include <sys/mman.h>
 #include <signal.h>
 
-#include <linux/input.h>
+#include "../shared/input-event-codes.h"
 
 #include "window.h"
 #include "input-method-unstable-v1-client-protocol.h"
diff --git a/clients/simple-shm.c b/clients/simple-shm.c
index 534acfa..d83e370 100644
--- a/clients/simple-shm.c
+++ b/clients/simple-shm.c
@@ -36,7 +36,7 @@
 #include <errno.h>
 #include <assert.h>
 
-#include <linux/input.h>
+#include "../shared/input-event-codes.h"
 
 #include <wayland-client.h>
 #include "shared/os-compatibility.h"
diff --git a/clients/simple-timing.c b/clients/simple-timing.c
index ed5b6ea..4f12b84 100644
--- a/clients/simple-timing.c
+++ b/clients/simple-timing.c
@@ -37,7 +37,7 @@
 #include <errno.h>
 #include <assert.h>
 
-#include <linux/input.h>
+#include "../shared/input-event-codes.h"
 
 #include <wayland-client.h>
 #include "shared/os-compatibility.h"
diff --git a/clients/simple-vulkan.c b/clients/simple-vulkan.c
index f795ec4..4dfc1dc 100644
--- a/clients/simple-vulkan.c
+++ b/clients/simple-vulkan.c
@@ -38,7 +38,7 @@
 #include <assert.h>
 #include <signal.h>
 
-#include <linux/input.h>
+#include "../shared/input-event-codes.h"
 
 #include <wayland-client.h>
 #include <wayland-cursor.h>
diff --git a/clients/stacking.c b/clients/stacking.c
index 6802559..71894ed 100644
--- a/clients/stacking.c
+++ b/clients/stacking.c
@@ -31,7 +31,7 @@
 #include <stdbool.h>
 #include <errno.h>
 
-#include <linux/input.h>
+#include "../shared/input-event-codes.h"
 #include <cairo.h>
 #include <wayland-util.h>
 
diff --git a/clients/subsurfaces.c b/clients/subsurfaces.c
index 530ee19..b27c371 100644
--- a/clients/subsurfaces.c
+++ b/clients/subsurfaces.c
@@ -34,7 +34,7 @@
 #include <assert.h>
 #include <errno.h>
 
-#include <linux/input.h>
+#include "../shared/input-event-codes.h"
 #include <wayland-client.h>
 
 #include <wayland-egl.h>
diff --git a/clients/tablet.c b/clients/tablet.c
index 112fc4b..a902f98 100644
--- a/clients/tablet.c
+++ b/clients/tablet.c
@@ -30,7 +30,7 @@
 #include <assert.h>
 #include <stdbool.h>
 
-#include <linux/input.h>
+#include "../shared/input-event-codes.h"
 #include <wayland-client.h>
 
 #include "window.h"
diff --git a/clients/terminal.c b/clients/terminal.c
index beeea4b..265695a 100644
--- a/clients/terminal.c
+++ b/clients/terminal.c
@@ -33,15 +33,24 @@
 #include <unistd.h>
 #include <math.h>
 #include <time.h>
-#include <pty.h>
 #include <ctype.h>
+#include <sys/ioctl.h>
+
+/* PTY header varies by platform */
+#if defined(__APPLE__)
+#include <util.h>
+#elif defined(__FreeBSD__) || defined(__DragonFly__)
+#include <libutil.h>
+#else
+#include <pty.h>
+#endif
 #include <cairo.h>
 #include <sys/epoll.h>
 #include <wchar.h>
 #include <locale.h>
 #include <errno.h>
 
-#include <linux/input.h>
+#include "../shared/input-event-codes.h"
 
 #include <wayland-client.h>
 
@@ -3004,10 +3013,8 @@ terminal_create(struct display *display)
 	/* Compute the average ascii glyph width */
 	cairo_text_extents(cr, TERMINAL_DRAW_SINGLE_WIDE_CHARACTERS,
 			   &text_extents);
-	terminal->average_width = howmany
-		(text_extents.width,
-		 strlen(TERMINAL_DRAW_SINGLE_WIDE_CHARACTERS));
-	terminal->average_width = ceil(terminal->average_width);
+	terminal->average_width = ceil(text_extents.width /
+		(double)strlen(TERMINAL_DRAW_SINGLE_WIDE_CHARACTERS));
 
 	cairo_destroy(cr);
 	cairo_surface_destroy(surface);
diff --git a/clients/touch-calibrator.c b/clients/touch-calibrator.c
index 374ae49..906aca0 100644
--- a/clients/touch-calibrator.c
+++ b/clients/touch-calibrator.c
@@ -38,6 +38,12 @@
 
 #include <wayland-client.h>
 
+/* program_invocation_short_name is a GNU extension */
+#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__)
+#include <stdlib.h>
+#define program_invocation_short_name getprogname()
+#endif
+
 #include "clients/window.h"
 #include "shared/helpers.h"
 #include <libweston/matrix.h>
diff --git a/clients/transformed.c b/clients/transformed.c
index 1c1b9e9..4f0e0a0 100644
--- a/clients/transformed.c
+++ b/clients/transformed.c
@@ -32,7 +32,7 @@
 #include <errno.h>
 #include <cairo.h>
 
-#include <linux/input.h>
+#include "../shared/input-event-codes.h"
 #include <wayland-client.h>
 #include "window.h"
 
diff --git a/clients/window.c b/clients/window.c
index 90440c1..d0ff05e 100644
--- a/clients/window.c
+++ b/clients/window.c
@@ -39,15 +39,16 @@
 #include <cairo.h>
 #include <sys/mman.h>
 #include <sys/epoll.h>
-#include <sys/timerfd.h>
 #include <stdbool.h>
 
+#include "../shared/platform-compat.h"
+
 
 #include <xkbcommon/xkbcommon.h>
 #include <xkbcommon/xkbcommon-compose.h>
 #include <wayland-cursor.h>
 
-#include <linux/input.h>
+#include "../shared/input-event-codes.h"
 #include <wayland-client.h>
 #include "shared/cairo-util.h"
 #include "shared/helpers.h"
diff --git a/clients/window.h b/clients/window.h
index 8b3d9d1..8b7b48a 100644
--- a/clients/window.h
+++ b/clients/window.h
@@ -28,6 +28,7 @@
 
 #include <stdint.h>
 #include <time.h>
+#include "../shared/platform-compat.h"
 #include <xkbcommon/xkbcommon.h>
 #include <wayland-client.h>
 #include <cairo.h>
diff --git a/meson.build b/meson.build
index 1d8b62d..a80d329 100644
--- a/meson.build
+++ b/meson.build
@@ -87,7 +87,7 @@
 
 optional_libc_funcs = [
 	'mkostemp', 'strchrnul', 'initgroups', 'posix_fallocate',
-	'memfd_create', 'unreachable',
+	'memfd_create', 'unreachable', 'pipe2', 'timerfd_create',
 ]
 foreach func : optional_libc_funcs
 	if cc.has_function(func)
@@ -140,36 +140,55 @@
 config_h.set_quoted('MODULEDIR', dir_module_weston)
 config_h.set_quoted('LIBWESTON_MODULEDIR', dir_module_libweston)
 
+# Determine if we're building any compositor backends
+building_any_backend = get_option('backend-drm') or get_option('backend-headless') or get_option('backend-pipewire') or get_option('backend-rdp') or get_option('backend-vnc') or get_option('backend-wayland') or get_option('backend-x11')
+
 backend_default = get_option('backend-default')
-if backend_default == 'auto'
+if backend_default == 'auto' and building_any_backend
 	foreach b : [ 'headless', 'x11', 'wayland', 'drm' ]
 		if get_option('backend-' + b)
 			backend_default = b
 		endif
 	endforeach
 endif
-config_h.set_quoted('WESTON_NATIVE_BACKEND', backend_default)
-message('The default backend is ' + backend_default)
-if not get_option('backend-' + backend_default)
-	error('Backend @0@ was chosen as native but is not being built.'.format(backend_default))
+
+if building_any_backend
+	config_h.set_quoted('WESTON_NATIVE_BACKEND', backend_default)
+	message('The default backend is ' + backend_default)
+	if backend_default not in ['auto', 'none'] and not get_option('backend-' + backend_default)
+		error('Backend @0@ was chosen as native but is not being built.'.format(backend_default))
+	endif
+else
+	config_h.set_quoted('WESTON_NATIVE_BACKEND', '')
+	message('No backends are being built (clients-only build)')
 endif
 
 dep_wayland_server = dependency('wayland-server', version: '>= 1.22.0')
 dep_wayland_client = dependency('wayland-client', version: '>= 1.22.0')
 dep_pixman = dependency('pixman-1', version: '>= 0.25.2')
 dep_xkbcommon = dependency('xkbcommon', version: '>= 0.5.0')
-dep_libinput = dependency('libinput', version: '>= 1.2.0')
-if dep_xkbcommon.version().version_compare('>= 1.8.0')
-	if dep_libinput.version().version_compare('>= 1.26.0')
-		config_h.set('HAVE_COMPOSE_AND_KANA', '1')
+
+# Linux-specific dependencies - only required when building backends
+if building_any_backend
+	dep_libinput = dependency('libinput', version: '>= 1.2.0')
+	if dep_xkbcommon.version().version_compare('>= 1.8.0')
+		if dep_libinput.version().version_compare('>= 1.26.0')
+			config_h.set('HAVE_COMPOSE_AND_KANA', '1')
+		endif
 	endif
+	dep_libevdev = dependency('libevdev')
+	dep_libdrm = dependency('libdrm', version: '>= 2.4.108')
+	dep_libdrm_headers = dep_libdrm.partial_dependency(compile_args: true)
+else
+	# Provide stub dependencies for clients-only build
+	dep_libinput = dependency('', required: false)
+	dep_libevdev = dependency('', required: false)
+	dep_libdrm = dependency('', required: false)
+	dep_libdrm_headers = dependency('', required: false)
 endif
 
-dep_libevdev = dependency('libevdev')
 dep_libm = cc.find_library('m')
-dep_libdl = cc.find_library('dl')
-dep_libdrm = dependency('libdrm', version: '>= 2.4.108')
-dep_libdrm_headers = dep_libdrm.partial_dependency(compile_args: true)
+dep_libdl = cc.find_library('dl', required: false)
 dep_threads = dependency('threads')
 
 dep_lcms2 = dependency('lcms2', version: '>= 2.9', required: false)
@@ -177,15 +196,19 @@
 	config_h.set('HAVE_LCMS', '1')
 endif
 
-dep_libdisplay_info = dependency(
-	'libdisplay-info',
-	version: ['>= 0.2.0', '< 0.4.0'],
-	fallback: ['display-info', 'di_dep'],
-	default_options: [
-		'werror=false',
-	],
-	required: true,
-)
+if building_any_backend
+	dep_libdisplay_info = dependency(
+		'libdisplay-info',
+		version: ['>= 0.2.0', '< 0.4.0'],
+		fallback: ['display-info', 'di_dep'],
+		default_options: [
+			'werror=false',
+		],
+		required: true,
+	)
+else
+	dep_libdisplay_info = dependency('', required: false)
+endif
 
 prog_python = import('python').find_installation('python3')
 files_xxd_py = files('tools/xxd.py')
@@ -228,15 +251,24 @@
 subdir('include')
 subdir('protocol')
 subdir('shared')
-subdir('libweston')
-subdir('xwayland')
-subdir('frontend')
-subdir('desktop-shell')
-subdir('ivi-shell')
-subdir('kiosk-shell')
-subdir('lua-shell')
-subdir('remoting')
-subdir('pipewire')
+
+# Compositor components - only build when backends are enabled
+if building_any_backend
+	subdir('libweston')
+	subdir('xwayland')
+	subdir('frontend')
+	subdir('desktop-shell')
+	subdir('ivi-shell')
+	subdir('kiosk-shell')
+	subdir('lua-shell')
+	subdir('remoting')
+	subdir('pipewire')
+else
+	# Stub dependencies for clients that optionally use libweston components
+	dep_libweston_private = dependency('', required: false)
+	dep_vertex_clipping = dependency('', required: false)
+endif
+
 subdir('clients')
 if get_option('tests')
 	subdir('tests')
diff --git a/meson_options.txt b/meson_options.txt
index 0ebaee5..fd44274 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -47,9 +47,9 @@ option(
 option(
 	'backend-default',
 	type: 'combo',
-	choices: [ 'auto', 'drm', 'wayland', 'x11', 'headless', 'rdp' ],
-	value: 'drm',
-	description: 'Default backend when no parent display server detected'
+	choices: [ 'auto', 'drm', 'wayland', 'x11', 'headless', 'rdp', 'none' ],
+	value: 'auto',
+	description: 'Default backend when no parent display server detected (use none for clients-only build)'
 )
 
 option(
diff --git a/shared/config-parser.c b/shared/config-parser.c
index cd93e5f..1a4e636 100644
--- a/shared/config-parser.c
+++ b/shared/config-parser.c
@@ -44,6 +44,7 @@
 #include <libweston/libweston.h>
 #include "helpers.h"
 #include "string-helpers.h"
+#include "os-compatibility.h"
 
 /**
  * \defgroup weston-config Weston configuration
diff --git a/shared/frame.c b/shared/frame.c
index e8d4cde..9248eac 100644
--- a/shared/frame.c
+++ b/shared/frame.c
@@ -31,7 +31,8 @@
 #include <stdlib.h>
 #include <string.h>
 #include <wayland-util.h>
-#include <linux/input.h>
+
+#include "input-event-codes.h"
 
 #include "cairo-util.h"
 #include "shared/file-util.h"
diff --git a/shared/input-event-codes.h b/shared/input-event-codes.h
new file mode 100644
index 0000000..8ffa188
--- /dev/null
+++ b/shared/input-event-codes.h
@@ -0,0 +1,148 @@
+/*
+ * Input event codes compatibility header for non-Linux systems
+ *
+ * On Linux, these come from <linux/input.h> or <linux/input-event-codes.h>
+ * On other systems, we provide the subset needed by Weston clients.
+ */
+
+#ifndef INPUT_EVENT_CODES_H
+#define INPUT_EVENT_CODES_H
+
+#ifdef __linux__
+#include <linux/input.h>
+#else
+
+/* Event types */
+#define EV_SYN       0x00
+#define EV_KEY       0x01
+#define EV_REL       0x02
+#define EV_ABS       0x03
+
+/* Mouse buttons */
+#define BTN_MISC     0x100
+#define BTN_LEFT     0x110
+#define BTN_RIGHT    0x111
+#define BTN_MIDDLE   0x112
+#define BTN_SIDE     0x113
+#define BTN_EXTRA    0x114
+
+/* Keyboard keys - common subset */
+#define KEY_RESERVED         0
+#define KEY_ESC              1
+#define KEY_1                2
+#define KEY_2                3
+#define KEY_3                4
+#define KEY_4                5
+#define KEY_5                6
+#define KEY_6                7
+#define KEY_7                8
+#define KEY_8                9
+#define KEY_9                10
+#define KEY_0                11
+#define KEY_MINUS            12
+#define KEY_EQUAL            13
+#define KEY_BACKSPACE        14
+#define KEY_TAB              15
+#define KEY_Q                16
+#define KEY_W                17
+#define KEY_E                18
+#define KEY_R                19
+#define KEY_T                20
+#define KEY_Y                21
+#define KEY_U                22
+#define KEY_I                23
+#define KEY_O                24
+#define KEY_P                25
+#define KEY_LEFTBRACE        26
+#define KEY_RIGHTBRACE       27
+#define KEY_ENTER            28
+#define KEY_LEFTCTRL         29
+#define KEY_A                30
+#define KEY_S                31
+#define KEY_D                32
+#define KEY_F                33
+#define KEY_G                34
+#define KEY_H                35
+#define KEY_J                36
+#define KEY_K                37
+#define KEY_L                38
+#define KEY_SEMICOLON        39
+#define KEY_APOSTROPHE       40
+#define KEY_GRAVE            41
+#define KEY_LEFTSHIFT        42
+#define KEY_BACKSLASH        43
+#define KEY_Z                44
+#define KEY_X                45
+#define KEY_C                46
+#define KEY_V                47
+#define KEY_B                48
+#define KEY_N                49
+#define KEY_M                50
+#define KEY_COMMA            51
+#define KEY_DOT              52
+#define KEY_SLASH            53
+#define KEY_RIGHTSHIFT       54
+#define KEY_KPASTERISK       55
+#define KEY_LEFTALT          56
+#define KEY_SPACE            57
+#define KEY_CAPSLOCK         58
+#define KEY_F1               59
+#define KEY_F2               60
+#define KEY_F3               61
+#define KEY_F4               62
+#define KEY_F5               63
+#define KEY_F6               64
+#define KEY_F7               65
+#define KEY_F8               66
+#define KEY_F9               67
+#define KEY_F10              68
+#define KEY_NUMLOCK          69
+#define KEY_SCROLLLOCK       70
+#define KEY_KP7              71
+#define KEY_KP8              72
+#define KEY_KP9              73
+#define KEY_KPMINUS          74
+#define KEY_KP4              75
+#define KEY_KP5              76
+#define KEY_KP6              77
+#define KEY_KPPLUS           78
+#define KEY_KP1              79
+#define KEY_KP2              80
+#define KEY_KP3              81
+#define KEY_KP0              82
+#define KEY_KPDOT            83
+#define KEY_F11              87
+#define KEY_F12              88
+#define KEY_KPENTER          96
+#define KEY_RIGHTCTRL        97
+#define KEY_KPSLASH          98
+#define KEY_RIGHTALT         100
+#define KEY_HOME             102
+#define KEY_UP               103
+#define KEY_PAGEUP           104
+#define KEY_LEFT             105
+#define KEY_RIGHT            106
+#define KEY_END              107
+#define KEY_DOWN             108
+#define KEY_PAGEDOWN         109
+#define KEY_INSERT           110
+#define KEY_DELETE           111
+#define KEY_KPEQUAL          117
+#define KEY_PAUSE            119
+#define KEY_LEFTMETA         125
+#define KEY_RIGHTMETA        126
+#define KEY_COMPOSE          127
+
+/* Relative axes */
+#define REL_X        0x00
+#define REL_Y        0x01
+#define REL_WHEEL    0x08
+
+/* Absolute axes */
+#define ABS_X        0x00
+#define ABS_Y        0x01
+#define ABS_PRESSURE 0x18
+
+#endif /* __linux__ */
+
+#endif /* INPUT_EVENT_CODES_H */
diff --git a/shared/meson.build b/shared/meson.build
index eb2f30d..c6c5ee6 100644
--- a/shared/meson.build
+++ b/shared/meson.build
@@ -6,7 +6,13 @@ srcs_libshared = [
         'process-util.c',
 	'hash.c',
 ]
+
+# libepoll-shim is needed on BSD/macOS for epoll compatibility in os-compatibility.c
+dep_epoll_shim_shared = dependency('epoll-shim', required: false)
 deps_libshared = [dep_wayland_client, dep_pixman, deps_for_libweston_users, dep_egl, dep_vulkan]
+if dep_epoll_shim_shared.found()
+	deps_libshared += dep_epoll_shim_shared
+endif
 
 lib_libshared = static_library(
 	'shared',
@@ -105,12 +111,17 @@ dep_matrix_c = declare_dependency(
 	dependencies: dep_libm
 )
 
-dep_client_buffer = declare_dependency(
-	sources: [
-		'client-buffer-util.c',
-		linux_dmabuf_unstable_v1_client_protocol_h,
-		linux_dmabuf_unstable_v1_protocol_c,
-	],
-	include_directories: public_inc,
-	dependencies: [ dep_libm, dep_libdrm_headers ]
-)
+# dep_client_buffer requires libdrm headers which may not be available
+if dep_libdrm_headers.found()
+	dep_client_buffer = declare_dependency(
+		sources: [
+			'client-buffer-util.c',
+			linux_dmabuf_unstable_v1_client_protocol_h,
+			linux_dmabuf_unstable_v1_protocol_c,
+		],
+		include_directories: public_inc,
+		dependencies: [ dep_libm, dep_libdrm_headers ]
+	)
+else
+	dep_client_buffer = dependency('', required: false)
+endif
diff --git a/shared/os-compatibility.c b/shared/os-compatibility.c
index d19d0a4..2a4825c 100644
--- a/shared/os-compatibility.c
+++ b/shared/os-compatibility.c
@@ -30,8 +30,20 @@
 #include <unistd.h>
 #include <fcntl.h>
 #include <errno.h>
-#include <sys/epoll.h>
 #include <string.h>
+
+/*
+ * On macOS/BSD with libepoll-shim installed, we use the epoll API
+ * provided by libepoll-shim (which internally uses kqueue).
+ * Only fall back to raw kqueue if sys/epoll.h is not available.
+ */
+#if __has_include(<sys/epoll.h>)
+#include <sys/epoll.h>
+#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__)
+#include <sys/event.h>
+#include <sys/time.h>
+#define USE_KQUEUE 1
+#endif
 #include <stdlib.h>
 #include <libweston/zalloc.h>
 #include <sys/mman.h>
@@ -119,6 +131,11 @@ os_epoll_create_cloexec(void)
 {
 	int fd;
 
+#if defined(USE_KQUEUE)
+	/* kqueue-based implementation for BSD/macOS */
+	fd = kqueue();
+	return set_cloexec_or_close(fd);
+#else
 #ifdef EPOLL_CLOEXEC
 	fd = epoll_create1(EPOLL_CLOEXEC);
 	if (fd >= 0)
@@ -129,6 +146,7 @@ os_epoll_create_cloexec(void)
 
 	fd = epoll_create(1);
 	return set_cloexec_or_close(fd);
+#endif
 }
 
 static int
diff --git a/shared/platform-compat.h b/shared/platform-compat.h
new file mode 100644
index 0000000..34c320d
--- /dev/null
+++ b/shared/platform-compat.h
@@ -0,0 +1,56 @@
+/*
+ * Platform compatibility header for non-Linux systems
+ */
+
+#ifndef PLATFORM_COMPAT_H
+#define PLATFORM_COMPAT_H
+
+#include "config.h"
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <time.h>
+
+#ifdef __linux__
+#include <sys/timerfd.h>
+#else
+/*
+ * timerfd support for BSD/macOS via libepoll-shim.
+ * We need to define itimerspec before including timerfd.h
+ */
+
+/* itimerspec might not be defined on all systems */
+#if !defined(_STRUCT_ITIMERSPEC) && !defined(EPOLL_SHIM_ITIMERSPEC)
+#define _STRUCT_ITIMERSPEC
+struct itimerspec {
+	struct timespec it_interval;
+	struct timespec it_value;
+};
+#endif
+
+/* Now include libepoll-shim's timerfd.h for the functions */
+#include <sys/timerfd.h>
+
+#endif /* __linux__ */
+
+/* pipe2() compatibility */
+#ifndef HAVE_PIPE2
+static inline int pipe2(int pipefd[2], int flags)
+{
+	int ret = pipe(pipefd);
+	if (ret == -1)
+		return -1;
+
+	if (flags & O_CLOEXEC) {
+		fcntl(pipefd[0], F_SETFD, FD_CLOEXEC);
+		fcntl(pipefd[1], F_SETFD, FD_CLOEXEC);
+	}
+	if (flags & O_NONBLOCK) {
+		fcntl(pipefd[0], F_SETFL, O_NONBLOCK);
+		fcntl(pipefd[1], F_SETFL, O_NONBLOCK);
+	}
+	return 0;
+}
+#endif /* HAVE_PIPE2 */
+
+#endif /* PLATFORM_COMPAT_H */
diff --git a/shared/xalloc.h b/shared/xalloc.h
index a2747f9..aa670dd 100644
--- a/shared/xalloc.h
+++ b/shared/xalloc.h
@@ -36,6 +36,12 @@ extern "C" {
 #include <unistd.h>
 #include <string.h>
 
+/* program_invocation_short_name is a GNU extension */
+#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__)
+#include <stdlib.h>
+#define program_invocation_short_name getprogname()
+#endif
+
 static inline void *
 abort_oom_if_null(void *p)
 {
