This commit is contained in:
Ray Strode 2023-09-29 16:59:56 -04:00
parent 98b1900d2a
commit 16e1ec43ff
4 changed files with 1891 additions and 0 deletions

View File

@ -0,0 +1,724 @@
From 574f2cf39e37ac6ecdb6d9b8b38626b4d6502822 Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
Date: Mon, 25 Sep 2023 22:22:08 +0200
Subject: [PATCH 1/3] util: Add way to print backtraces
Now that mutter can use a realtime thread its very important that
it doesn't stall for too long, since that can result in the kernel
killing it. Ironically, a main reason mutter could stall is
kernel bugs.
When a stall happens, we need a way to see why. This commit adds
a new function, "meta_print_backtrace" to print backtrace of the
current process and the kernel (if possible).
A future commit will use this new function.
---
meson.build | 8 ++++
meson_options.txt | 6 +++
src/50-mutter.rules | 9 ++++
src/core/util-private.h | 2 +
src/core/util.c | 57 ++++++++++++++++++++++++
src/meson.build | 27 +++++++++++
src/mutter-backtrace | 17 +++++++
src/org.gnome.mutter.backtrace.policy.in | 17 +++++++
8 files changed, 143 insertions(+)
create mode 100644 src/50-mutter.rules
create mode 100755 src/mutter-backtrace
create mode 100644 src/org.gnome.mutter.backtrace.policy.in
diff --git a/meson.build b/meson.build
index d08401b3c..5482dfb9e 100644
--- a/meson.build
+++ b/meson.build
@@ -270,60 +270,67 @@ if have_wayland or have_native_backend
libdrm_dep = dependency('libdrm')
endif
have_egl_device = get_option('egl_device')
have_wayland_eglstream = get_option('wayland_eglstream')
if have_wayland_eglstream
wayland_eglstream_protocols_dep = dependency('wayland-eglstream-protocols')
dl_dep = cc.find_library('dl', required: true)
if not have_wayland
error('Wayland EGLStream support requires Wayland to be enabled')
endif
endif
have_sm = get_option('sm')
if have_sm
sm_dep = dependency('sm')
endif
have_libwacom = get_option('libwacom')
if have_libwacom
libwacom_dep = dependency('libwacom', version: libwacom_req)
endif
have_pango_ft2 = get_option('pango_ft2')
if have_pango_ft2
pangoft2_dep = dependency('pangoft2')
endif
+have_polkit = get_option('polkit')
+if have_polkit
+ polkit_dep = dependency('polkit-gobject-1')
+ polkit_policy_dir = polkit_dep.get_variable('policydir')
+ polkit_action_dir = polkit_dep.get_variable('actiondir')
+endif
+
have_startup_notification = get_option('startup_notification')
if have_startup_notification
if have_x11_client
libstartup_notification_dep = dependency('libstartup-notification-1.0',
version: libstartup_notification_req)
else
error('startup_notification requires X11 or Xwayland to be enabled')
endif
endif
have_remote_desktop = get_option('remote_desktop')
if have_remote_desktop
libpipewire_dep = dependency('libpipewire-0.3', version: libpipewire_req)
endif
have_introspection = get_option('introspection')
if have_introspection
gobject_introspection_dep = dependency('gobject-introspection-1.0')
introspection_args = [
'--quiet',
'-U_GNU_SOURCE',
]
endif
have_documentation = get_option('docs')
if have_documentation
gidocgen_dep = dependency('gi-docgen', version: '>= 2021.1',
fallback: ['gi-docgen', 'dummy_dep'])
endif
@@ -671,50 +678,51 @@ if have_documentation
subdir('doc/reference')
endif
gnome.post_install(
glib_compile_schemas: true,
)
meson.add_dist_script('meson/check-version.py', meson.project_version(), 'NEWS')
summary('prefix', prefix, section: 'Directories')
summary('libexecdir', libexecdir, section: 'Directories')
summary('pkgdatadir', pkgdatadir, section: 'Directories')
summary('buildtype', get_option('buildtype'), section: 'Build Configuration')
summary('debug', get_option('debug'), section: 'Build Configuration')
summary('OpenGL', have_gl, section: 'Rendering APIs')
summary('GLES2', have_gles2, section: 'Rendering APIs')
summary('EGL', have_egl, section: 'Rendering APIs')
summary('GLX', have_glx, section: 'Rendering APIs')
summary('Wayland', have_wayland, section: 'Options')
summary('Wayland EGLStream', have_wayland_eglstream, section: 'Options')
summary('X11', have_x11, section: 'Options')
summary('XWayland', have_xwayland, section: 'Options')
summary('Native Backend', have_native_backend, section: 'Options')
summary('EGL Device', have_egl_device, section: 'Options')
summary('Remote desktop', have_remote_desktop, section: 'Options')
summary('libgnome-desktop', have_gnome_desktop, section: 'Options')
summary('libdisplay-info', have_libdisplay_info, section: 'Options')
+summary('Polkit enhanced backtrace support', have_polkit, section: 'Options')
summary('Sound player', have_sound_player, section: 'Options')
summary('gudev', have_libgudev, section: 'Options')
summary('Wacom', have_libwacom, section: 'Options')
summary('SM', have_sm, section: 'Options')
summary('Startup notification', have_startup_notification, section: 'Options')
summary('Introspection', have_introspection, section: 'Options')
summary('Documentation', have_documentation, section: 'Options')
summary('Profiler', have_profiler, section: 'Options')
summary('Xwayland initfd', have_xwayland_initfd, section: 'Options')
summary('Xwayland listenfd', have_xwayland_listenfd, section: 'Options')
summary('Xwayland terminate delay', have_xwayland_terminate_delay, section: 'Options')
summary('Xwayland byte-swapped clients', have_xwayland_byte_swapped_clients, section: 'Options')
summary('Enabled', have_tests, section: 'Tests')
summary('Core tests', have_core_tests, section: 'Tests')
summary('Cogl tests', have_cogl_tests, section: 'Tests')
summary('Clutter tests', have_clutter_tests, section: 'Tests')
summary('KVM tests', get_option('kvm_tests'), section: 'Tests')
summary('Installed tests', have_installed_tests, section: 'Tests')
summary('Coverage', get_option('b_coverage'), section: 'Tests')
diff --git a/meson_options.txt b/meson_options.txt
index b5d215b24..b6b18ebdf 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -84,60 +84,66 @@ option('wayland_eglstream',
option('udev',
type: 'boolean',
value: true,
description: 'Enable udev support when using the X11 backend'
)
option('udev_dir',
type: 'string',
value: '',
description: 'Absolute path of the udev base directory'
)
option('libwacom',
type: 'boolean',
value: true,
description: 'Enable libwacom support'
)
option('sound_player',
type: 'boolean',
value: true,
description: 'Enable sound player support using libcanberra',
)
option('pango_ft2',
type: 'boolean',
value: true,
description: 'Enable PangoFt2 support'
)
+option('polkit',
+ type: 'boolean',
+ value: true,
+ description: 'Enable Polkit enhanced backtrace support'
+)
+
option('startup_notification',
type: 'boolean',
value: true,
description: 'Enable startup notification support'
)
option('sm',
type: 'boolean',
value: true,
description: 'Enable X11 session management support'
)
option('introspection',
type: 'boolean',
value: true,
description: 'Enable GObject introspection'
)
option('docs',
type: 'boolean',
value: false,
description: 'Enable gi-docgen documentation'
)
option('cogl_tests',
type: 'boolean',
value: true,
description: 'Enable cogl tests'
)
diff --git a/src/50-mutter.rules b/src/50-mutter.rules
new file mode 100644
index 000000000..d14735fe6
--- /dev/null
+++ b/src/50-mutter.rules
@@ -0,0 +1,9 @@
+polkit.addRule(function(action, subject) {
+ if (subject.isInGroup("wheel") &&
+ subject.active &&
+ subject.local &&
+ action.id == "org.gnome.mutter.backtrace") {
+ return polkit.Result.YES;
+ }
+});
+
diff --git a/src/core/util-private.h b/src/core/util-private.h
index 18ae68de7..32798ca4e 100644
--- a/src/core/util-private.h
+++ b/src/core/util-private.h
@@ -13,46 +13,48 @@
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <glib/gi18n-lib.h>
#include <sys/time.h>
#include "meta/util.h"
#include "meta/common.h"
/* META_EXPORT_TEST should be used to export symbols that are exported only
* for testability purposes */
#define META_EXPORT_TEST META_EXPORT
void meta_set_verbose (gboolean setting);
void meta_set_debugging (gboolean setting);
void meta_set_is_wayland_compositor (gboolean setting);
char * meta_generate_random_id (GRand *rand,
int length);
+void meta_print_backtrace (void);
+
void meta_init_debug_utils (void);
static inline int64_t
meta_timeval_to_microseconds (const struct timeval *tv)
{
return ((int64_t) tv->tv_sec) * G_USEC_PER_SEC + tv->tv_usec;
}
#define META_POINT_IN_RECT(xcoord, ycoord, rect) \
((xcoord) >= (rect).x && \
(xcoord) < ((rect).x + (rect).width) && \
(ycoord) >= (rect).y && \
(ycoord) < ((rect).y + (rect).height))
#define META_CONTAINER_OF(ptr, type, member) \
(type *) ((uint8_t *) (ptr) - G_STRUCT_OFFSET (type, member))
diff --git a/src/core/util.c b/src/core/util.c
index 05a0dea39..dc547713c 100644
--- a/src/core/util.c
+++ b/src/core/util.c
@@ -1,107 +1,110 @@
/*
* Copyright (C) 2001 Havoc Pennington
* Copyright (C) 2005 Elijah Newren
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#define _POSIX_C_SOURCE 200112L /* for fdopen() */
#include "config.h"
#include "core/display-private.h"
#include "core/util-private.h"
+#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
+#include <sys/wait.h>
#ifdef HAVE_SYS_PRCTL
#include <sys/prctl.h>
#endif
#include "clutter/clutter-mutter.h"
#include "cogl/cogl.h"
#include "meta/common.h"
#include "meta/main.h"
static const GDebugKey meta_debug_keys[] = {
{ "focus", META_DEBUG_FOCUS },
{ "workarea", META_DEBUG_WORKAREA },
{ "stack", META_DEBUG_STACK },
{ "sm", META_DEBUG_SM },
{ "events", META_DEBUG_EVENTS },
{ "window-state", META_DEBUG_WINDOW_STATE },
{ "window-ops", META_DEBUG_WINDOW_OPS },
{ "geometry", META_DEBUG_GEOMETRY },
{ "placement", META_DEBUG_PLACEMENT },
{ "ping", META_DEBUG_PING },
{ "keybindings", META_DEBUG_KEYBINDINGS },
{ "sync", META_DEBUG_SYNC },
{ "startup", META_DEBUG_STARTUP },
{ "prefs", META_DEBUG_PREFS },
{ "groups", META_DEBUG_GROUPS },
{ "resizing", META_DEBUG_RESIZING },
{ "shapes", META_DEBUG_SHAPES },
{ "edge-resistance", META_DEBUG_EDGE_RESISTANCE },
{ "dbus", META_DEBUG_DBUS },
{ "input", META_DEBUG_INPUT },
{ "wayland", META_DEBUG_WAYLAND },
{ "kms", META_DEBUG_KMS },
{ "screen-cast", META_DEBUG_SCREEN_CAST },
{ "remote-desktop", META_DEBUG_REMOTE_DESKTOP },
{ "backend", META_DEBUG_BACKEND },
{ "render", META_DEBUG_RENDER },
{ "color", META_DEBUG_COLOR },
{ "input-events", META_DEBUG_INPUT_EVENTS },
{ "eis", META_DEBUG_EIS },
};
static gint verbose_topics = 0;
static gboolean is_wayland_compositor = FALSE;
static int debug_paint_flags = 0;
static GLogLevelFlags mutter_log_level = G_LOG_LEVEL_MESSAGE;
+static char *backtrace_command = NULL;
#ifdef WITH_VERBOSE_MODE
static FILE* logfile = NULL;
static void
ensure_logfile (void)
{
if (logfile == NULL && g_getenv ("MUTTER_USE_LOGFILE"))
{
char *filename = NULL;
char *tmpl;
int fd;
GError *err;
tmpl = g_strdup_printf ("mutter-%d-debug-log-XXXXXX",
(int) getpid ());
err = NULL;
fd = g_file_open_tmp (tmpl,
&filename,
&err);
g_free (tmpl);
if (err != NULL)
{
meta_warning ("Failed to open debug log: %s",
err->message);
g_error_free (err);
return;
@@ -186,60 +189,70 @@ meta_remove_verbose_topic (MetaDebugTopic topic)
verbose_topics = 0;
else
verbose_topics &= ~topic;
}
void
meta_init_debug_utils (void)
{
const char *debug_env;
#ifdef HAVE_SYS_PRCTL
prctl (PR_SET_DUMPABLE, 1);
#endif
if (g_getenv ("MUTTER_VERBOSE"))
meta_set_verbose (TRUE);
debug_env = g_getenv ("MUTTER_DEBUG");
if (debug_env)
{
MetaDebugTopic topics;
topics = g_parse_debug_string (debug_env,
meta_debug_keys,
G_N_ELEMENTS (meta_debug_keys));
meta_add_verbose_topic (topics);
}
if (g_test_initialized ())
mutter_log_level = G_LOG_LEVEL_DEBUG;
+
+ /* If pkexec works we'll get a kernel backtrace too,
+ * but it may not work if the user isn't in the wheel group.
+ * If it fails, we fall back to running unprivileged to at
+ * least get a backtrace of the process.
+ */
+ if (backtrace_command == NULL)
+ backtrace_command = g_strdup_printf ("pkexec %1$s %2$d || %1$s %2$d",
+ MUTTER_LIBEXECDIR "/mutter-backtrace",
+ (int) getpid ());
}
gboolean
meta_is_wayland_compositor (void)
{
return is_wayland_compositor;
}
void
meta_set_is_wayland_compositor (gboolean value)
{
is_wayland_compositor = value;
}
char *
meta_g_utf8_strndup (const gchar *src,
gsize n)
{
const gchar *s = src;
while (n && *s)
{
s = g_utf8_next_char (s);
n--;
}
return g_strndup (src, s - src);
}
static int
utf8_fputs (const char *str,
@@ -513,60 +526,104 @@ MetaLocaleDirection
meta_get_locale_direction (void)
{
switch (clutter_get_text_direction ())
{
case CLUTTER_TEXT_DIRECTION_LTR:
return META_LOCALE_DIRECTION_LTR;
case CLUTTER_TEXT_DIRECTION_RTL:
return META_LOCALE_DIRECTION_RTL;
default:
g_assert_not_reached ();
return 0;
}
}
char *
meta_generate_random_id (GRand *rand,
int length)
{
char *id;
int i;
/* Generate a random string of printable ASCII characters. */
id = g_new0 (char, length + 1);
for (i = 0; i < length; i++)
id[i] = (char) g_rand_int_range (rand, 32, 127);
return id;
}
+static gboolean
+unix_signal_safe_run_command (const char *command)
+{
+ pid_t pid;
+ int status;
+ int ret;
+
+ pid = fork ();
+ if (pid == -1)
+ return FALSE;
+
+ if (pid == 0)
+ {
+ int stdin_fd;
+
+ const char *argv[] = {
+ "/bin/sh",
+ "-c",
+ command,
+ NULL
+ };
+
+ stdin_fd = open ("/dev/null", O_RDWR);
+ dup2 (stdin_fd, STDIN_FILENO);
+
+ execve (argv[0], (char **) argv, environ);
+
+ _exit (1);
+ }
+
+ do
+ {
+ ret = waitpid (pid, &status, 0);
+ }
+ while (ret != 0 && errno == EINTR);
+
+ return WIFEXITED (status) && WEXITSTATUS (status) == 0;
+}
+
+void
+meta_print_backtrace (void)
+{
+ unix_signal_safe_run_command (backtrace_command);
+}
void
meta_add_clutter_debug_flags (ClutterDebugFlag debug_flags,
ClutterDrawDebugFlag draw_flags,
ClutterPickDebugFlag pick_flags)
{
clutter_add_debug_flags (debug_flags, draw_flags, pick_flags);
}
void
meta_remove_clutter_debug_flags (ClutterDebugFlag debug_flags,
ClutterDrawDebugFlag draw_flags,
ClutterPickDebugFlag pick_flags)
{
clutter_remove_debug_flags (debug_flags, draw_flags, pick_flags);
}
/**
* meta_get_clutter_debug_flags:
* @debug_flags: (out) (optional): return location for debug flags
* @draw_flags: (out) (optional): return location for draw debug flags
* @pick_flags: (out) (optional): return location for pick debug flags
*/
void
meta_get_clutter_debug_flags (ClutterDebugFlag *debug_flags,
ClutterDrawDebugFlag *draw_flags,
ClutterPickDebugFlag *pick_flags)
{
clutter_get_debug_flags (debug_flags, draw_flags, pick_flags);
}
diff --git a/src/meson.build b/src/meson.build
index ca2ef166c..4aabb6a14 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -1197,60 +1197,87 @@ libmutter = shared_library(libmutter_name,
],
install_rpath: pkglibdir,
install_dir: libdir,
install: true,
)
libmutter_dep = declare_dependency(
link_with: libmutter,
include_directories: mutter_includes,
sources: mutter_built_headers,
dependencies: [
libmutter_cogl_dep,
libmutter_clutter_dep,
mutter_deps,
],
)
mutter = executable('mutter',
sources: [
files('core/mutter.c'),
],
include_directories: mutter_includes,
c_args: [
mutter_c_args,
'-DG_LOG_DOMAIN="mutter"',
],
dependencies: [libmutter_dep],
install_dir: bindir,
install: true,
)
+
+install_data(
+ 'mutter-backtrace',
+ install_dir : libexecdir,
+ install_mode : 'rwxr-xr-x'
+)
+
+if have_polkit
+ backtrace_polkit_policy = configure_file(
+ input : 'org.gnome.mutter.backtrace.policy.in',
+ output : 'org.gnome.mutter.backtrace.policy',
+ configuration : {'LIBEXECDIR': libexecdir}
+ )
+
+ install_data(
+ backtrace_polkit_policy,
+ install_dir : polkit_policy_dir,
+ install_mode : 'rw-r--r--'
+ )
+
+ install_data(
+ '50-mutter.rules',
+ install_dir : polkit_action_dir,
+ install_mode : 'rw-r--r--'
+ )
+endif
+
if have_x11
executable('mutter-restart-helper',
sources: [
files('core/restart-helper.c'),
],
include_directories: [
top_includepath,
],
c_args: [
mutter_c_args,
'-DG_LOG_DOMAIN="mutter-restart-helper"',
],
dependencies: [
x11_dep,
xcomposite_dep,
],
install_dir: libexecdir,
install: true,
)
endif
if have_introspection
mutter_introspected_sources = []
foreach source : mutter_sources
if source.endswith('.c')
mutter_introspected_sources += source
endif
endforeach
libmutter_gir = gnome.generate_gir(libmutter,
diff --git a/src/mutter-backtrace b/src/mutter-backtrace
new file mode 100755
index 000000000..20c9d4e75
--- /dev/null
+++ b/src/mutter-backtrace
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+[ $# -ne 1 ] && exit 1
+
+gdb_command=(gdb -p "$1" -ex 'thread apply all bt full' -batch)
+
+if [ -n $PKEXEC_UID -a $(id -u) -eq 0 ]; then
+ cd /proc/$1/task
+ for thread in *; do
+ echo "Thread ${thread}:"
+ cat $thread/stack
+ done
+ pkexec --user $(id -u -n $PKEXEC_UID) "${gdb_command[@]}"
+ exit 0
+fi
+
+${gdb_command[@]}
diff --git a/src/org.gnome.mutter.backtrace.policy.in b/src/org.gnome.mutter.backtrace.policy.in
new file mode 100644
index 000000000..39985acb9
--- /dev/null
+++ b/src/org.gnome.mutter.backtrace.policy.in
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE policyconfig PUBLIC
+ "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
+<policyconfig>
+ <action id="org.gnome.mutter.backtrace">
+ <message>Authentication is required to run mutter-backtrace</message>
+ <icon_name>dialog-password</icon_name>
+ <defaults>
+ <allow_any>auth_admin</allow_any>
+ <allow_inactive>auth_admin</allow_inactive>
+ <allow_active>auth_admin</allow_active>
+ </defaults>
+ <annotate key="org.freedesktop.policykit.exec.path">@LIBEXECDIR@/mutter-backtrace</annotate>
+ </action>
+</policyconfig>
+
--
2.41.0

View File

@ -0,0 +1,89 @@
From 2c78762dfe3dc05febe6be6408e4018be048159e Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
Date: Fri, 29 Sep 2023 10:53:50 -0400
Subject: [PATCH 2/3] clutter: Add ms2ns helper
Some system apis we're going to need in the future use
nanoseconds, but we're never going to need more timing
precision that milliseconds.
In preparate for that, this commit adds a new helper ns2ms
to help avoid bugs in unit conversion.
---
clutter/clutter/clutter-private.h | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/clutter/clutter/clutter-private.h b/clutter/clutter/clutter-private.h
index 9d5e68c8c..007e15eba 100644
--- a/clutter/clutter/clutter-private.h
+++ b/clutter/clutter/clutter-private.h
@@ -219,60 +219,66 @@ gboolean _clutter_run_progress_function (GType gtype,
const GValue *final,
gdouble progress,
GValue *retval);
void clutter_timeline_cancel_delay (ClutterTimeline *timeline);
static inline void
clutter_round_to_256ths (float *f)
{
*f = roundf ((*f) * 256) / 256;
}
static inline uint64_t
ns (uint64_t ns)
{
return ns;
}
static inline int64_t
us (int64_t us)
{
return us;
}
static inline int64_t
ms (int64_t ms)
{
return ms;
}
+static inline int64_t
+ms2ns (int64_t ms)
+{
+ return ns (us (ms * 1000) * 1000);
+}
+
static inline int64_t
ms2us (int64_t ms)
{
return us (ms * 1000);
}
static inline int64_t
us2ns (int64_t us)
{
return ns (us * 1000);
}
static inline int64_t
us2ms (int64_t us)
{
return (int64_t) (us / 1000);
}
static inline int64_t
ns2us (int64_t ns)
{
return us (ns / 1000);
}
static inline int64_t
s2us (int64_t s)
{
return s * G_USEC_PER_SEC;
}
--
2.41.0

File diff suppressed because it is too large Load Diff

View File

@ -32,6 +32,10 @@ Patch2: 0001-place-Always-center-initial-setup-fedora-welcome.patch
Patch4: 0001-gschema-Enable-scale-monitor-framebuffer-experimenta.patch Patch4: 0001-gschema-Enable-scale-monitor-framebuffer-experimenta.patch
Patch5: 0001-util-Add-way-to-print-backtraces.patch
Patch6: 0002-clutter-Add-ms2ns-helper.patch
Patch7: 0003-native-Stop-using-real-time-thread-if-it-stalls.patch
BuildRequires: pkgconfig(gobject-introspection-1.0) >= 1.41.0 BuildRequires: pkgconfig(gobject-introspection-1.0) >= 1.41.0
BuildRequires: pkgconfig(sm) BuildRequires: pkgconfig(sm)
BuildRequires: pkgconfig(libwacom) BuildRequires: pkgconfig(libwacom)