aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLexi Winter <ivy@FreeBSD.org>2025-09-05 06:11:43 +0100
committerLexi Winter <ivy@FreeBSD.org>2025-09-05 06:11:43 +0100
commit176112fffc40fb650751b279ce9583dc0cb72de1 (patch)
tree867f9fe7534d4de50a460a117f0a5471cb0a09a8
parent7efead58e7ccc06b64b17da5d34bf4ef1f0a8eb4 (diff)
parent1aad58b919d3d22f86be01b8e26a203cd020eaae (diff)
Merge remote-tracking branch 'freebsd/stable/15' into lf/stable/15lf/stable/15
-rw-r--r--UPDATING18
-rw-r--r--lib/libjail/jail.c64
-rw-r--r--lib/libsys/Symbol.sys.map2
-rw-r--r--lib/libsys/_libsys.h4
-rw-r--r--lib/libsys/jail.2267
-rw-r--r--lib/libsys/kqueue.258
-rw-r--r--lib/libsys/syscalls.map4
-rw-r--r--libexec/rc/rc.conf10
-rw-r--r--libexec/rc/rc.d/Makefile1
-rwxr-xr-xlibexec/rc/rc.d/msconvd61
-rw-r--r--release/Makefile3
-rw-r--r--release/release.conf.sample2
-rw-r--r--share/man/man4/tcp.410
-rw-r--r--share/man/man5/src.conf.512
-rw-r--r--share/mk/src.opts.mk6
-rw-r--r--share/sendmail/Makefile4
-rw-r--r--sys/amd64/conf/GENERIC2
-rw-r--r--sys/amd64/conf/GENERIC-NODEBUG31
-rw-r--r--sys/amd64/conf/MINIMAL2
-rw-r--r--sys/amd64/conf/MINIMAL-NODEBUG11
-rw-r--r--sys/arm/conf/GENERIC-NODEBUG31
-rw-r--r--sys/arm/conf/std.armv73
-rw-r--r--sys/arm64/arm64/machdep.c2
-rw-r--r--sys/arm64/arm64/pmap.c96
-rw-r--r--sys/arm64/conf/GENERIC-MMCCAM-NODEBUG14
-rw-r--r--sys/arm64/conf/GENERIC-NODEBUG31
-rw-r--r--sys/arm64/conf/std.arm642
-rw-r--r--sys/arm64/include/cpu.h3
-rw-r--r--sys/arm64/include/pmap.h2
-rw-r--r--sys/compat/freebsd32/freebsd32_syscall.h4
-rw-r--r--sys/compat/freebsd32/freebsd32_syscalls.c2
-rw-r--r--sys/compat/freebsd32/freebsd32_sysent.c2
-rw-r--r--sys/compat/freebsd32/freebsd32_systrace_args.c44
-rw-r--r--sys/conf/files1
-rw-r--r--sys/dev/cyapa/cyapa.c95
-rw-r--r--sys/dev/sound/pci/hda/hdac.c10
-rw-r--r--sys/dev/virtio/network/if_vtnet.c11
-rw-r--r--sys/i386/conf/GENERIC2
-rw-r--r--sys/i386/conf/GENERIC-NODEBUG33
-rw-r--r--sys/i386/conf/MINIMAL2
-rw-r--r--sys/i386/conf/MINIMAL-NODEBUG11
-rw-r--r--sys/kern/init_sysent.c2
-rw-r--r--sys/kern/kern_descrip.c2
-rw-r--r--sys/kern/kern_event.c139
-rw-r--r--sys/kern/kern_jail.c460
-rw-r--r--sys/kern/kern_jaildesc.c337
-rw-r--r--sys/kern/syscalls.c2
-rw-r--r--sys/kern/syscalls.master10
-rw-r--r--sys/kern/systrace_args.c44
-rw-r--r--sys/modules/sound/driver/hda/Makefile4
-rw-r--r--sys/netinet/tcp_sack.c21
-rw-r--r--sys/powerpc/conf/GENERIC2
-rw-r--r--sys/powerpc/conf/GENERIC-NODEBUG31
-rw-r--r--sys/powerpc/conf/GENERIC642
-rw-r--r--sys/powerpc/conf/GENERIC64-NODEBUG31
-rw-r--r--sys/powerpc/conf/GENERIC64LE2
-rw-r--r--sys/powerpc/conf/GENERIC64LE-NODEBUG31
-rw-r--r--sys/riscv/conf/GENERIC2
-rw-r--r--sys/riscv/conf/GENERIC-NODEBUG31
-rw-r--r--sys/sys/event.h19
-rw-r--r--sys/sys/file.h1
-rw-r--r--sys/sys/jail.h20
-rw-r--r--sys/sys/jaildesc.h85
-rw-r--r--sys/sys/param.h2
-rw-r--r--sys/sys/syscall.h4
-rw-r--r--sys/sys/syscall.mk4
-rw-r--r--sys/sys/sysproto.h10
-rw-r--r--sys/sys/user.h4
-rw-r--r--tools/build/mk/OptionalObsoleteFiles.inc7
-rw-r--r--usr.sbin/moused/Makefile12
-rw-r--r--usr.sbin/moused/Makefile.depend17
-rw-r--r--usr.sbin/moused/moused/Makefile28
-rw-r--r--usr.sbin/moused/moused/event-names.h1656
-rw-r--r--usr.sbin/moused/moused/moused.8538
-rw-r--r--usr.sbin/moused/moused/moused.c3205
-rw-r--r--usr.sbin/moused/moused/moused.conf43
-rw-r--r--usr.sbin/moused/moused/moused.conf.5422
-rw-r--r--usr.sbin/moused/moused/quirks.c2033
-rw-r--r--usr.sbin/moused/moused/quirks.h369
-rw-r--r--usr.sbin/moused/moused/quirks/5-generic-touchpad.quirks9
-rw-r--r--usr.sbin/moused/moused/util-evdev.c173
-rw-r--r--usr.sbin/moused/moused/util-evdev.h35
-rw-r--r--usr.sbin/moused/moused/util-list.c86
-rw-r--r--usr.sbin/moused/moused/util-list.h194
-rw-r--r--usr.sbin/moused/moused/util.c423
-rw-r--r--usr.sbin/moused/moused/util.h413
-rw-r--r--usr.sbin/moused/msconvd/Makefile8
-rw-r--r--usr.sbin/moused/msconvd/msconvd.8 (renamed from usr.sbin/moused/moused.8)395
-rw-r--r--usr.sbin/moused/msconvd/msconvd.c (renamed from usr.sbin/moused/moused.c)1212
-rw-r--r--usr.sbin/vidcontrol/vidcontrol.115
90 files changed, 11751 insertions, 1817 deletions
diff --git a/UPDATING b/UPDATING
index 21c6e92d6454..2837b5aec0a8 100644
--- a/UPDATING
+++ b/UPDATING
@@ -1,4 +1,4 @@
-Updating Information for users of FreeBSD-CURRENT.
+Updating Information for users of FreeBSD stable/15.
This file is maintained and copyrighted by M. Warner Losh <imp@freebsd.org>.
See end of file for further details. For commonly done items, please see the
@@ -12,20 +12,8 @@ Items affecting the ports and packages system can be found in
/usr/ports/UPDATING. Please read that file before updating system packages
and/or ports.
-NOTE TO PEOPLE WHO THINK THAT FreeBSD 15.x IS SLOW:
- FreeBSD 15.x has many debugging features turned on, in both the kernel
- and userland. These features attempt to detect incorrect use of
- system primitives, and encourage loud failure through extra sanity
- checking and fail stop semantics. They also substantially impact
- system performance. If you want to do performance measurement,
- benchmarking, and optimization, you'll want to turn them off. This
- includes various WITNESS- related kernel options, INVARIANTS, malloc
- debugging flags in userland, and various verbose features in the
- kernel. Many developers choose to disable these features on build
- machines to maximize performance. (To completely disable malloc
- debugging, define WITH_MALLOC_PRODUCTION in /etc/src.conf and rebuild
- world, or to merely disable the most expensive debugging functionality
- at runtime, run "ln -s 'abort:false,junk:false' /etc/malloc.conf".)
+20250905:
+ __FreeBSD_version 1500064 after branching stable/15 from main.
20250903:
The BLOAT_KERNEL_WITH_EXTERR kernel config option has been renamed to
diff --git a/lib/libjail/jail.c b/lib/libjail/jail.c
index 30282e67866c..931391055919 100644
--- a/lib/libjail/jail.c
+++ b/lib/libjail/jail.c
@@ -75,8 +75,9 @@ int
jail_setv(int flags, ...)
{
va_list ap, tap;
- struct jailparam *jp;
- const char *name, *value;
+ struct jailparam *jp, *jp_desc;
+ const char *name;
+ char *value, *desc_value;
int njp, jid;
/* Create the parameter list and import the parameters. */
@@ -86,15 +87,24 @@ jail_setv(int flags, ...)
(void)va_arg(tap, char *);
va_end(tap);
jp = alloca(njp * sizeof(struct jailparam));
- for (njp = 0; (name = va_arg(ap, char *)) != NULL;) {
+ jp_desc = NULL;
+ desc_value = NULL;
+ for (njp = 0; (name = va_arg(ap, char *)) != NULL; njp++) {
value = va_arg(ap, char *);
if (jailparam_init(jp + njp, name) < 0)
goto error;
- if (jailparam_import(jp + njp++, value) < 0)
+ if (jailparam_import(jp + njp, value) < 0)
goto error;
+ if (!strcmp(name, "desc")
+ && (flags & (JAIL_GET_DESC | JAIL_OWN_DESC))) {
+ jp_desc = jp + njp;
+ desc_value = value;
+ }
}
va_end(ap);
jid = jailparam_set(jp, njp, flags);
+ if (jid > 0 && jp_desc != NULL)
+ sprintf(desc_value, "%d", *(int *)jp_desc->jp_value);
jailparam_free(jp, njp);
return (jid);
@@ -112,9 +122,10 @@ int
jail_getv(int flags, ...)
{
va_list ap, tap;
- struct jailparam *jp, *jp_lastjid, *jp_jid, *jp_name, *jp_key;
+ struct jailparam *jp, *jp_desc, *jp_lastjid, *jp_jid, *jp_name, *jp_key;
char *valarg, *value;
- const char *name, *key_value, *lastjid_value, *jid_value, *name_value;
+ const char *name, *key_value, *desc_value, *lastjid_value, *jid_value;
+ const char *name_value;
int njp, i, jid;
/* Create the parameter list and find the key. */
@@ -126,15 +137,19 @@ jail_getv(int flags, ...)
jp = alloca(njp * sizeof(struct jailparam));
va_copy(tap, ap);
- jp_lastjid = jp_jid = jp_name = NULL;
- lastjid_value = jid_value = name_value = NULL;
+ jp_desc = jp_lastjid = jp_jid = jp_name = NULL;
+ desc_value = lastjid_value = jid_value = name_value = NULL;
for (njp = 0; (name = va_arg(tap, char *)) != NULL; njp++) {
value = va_arg(tap, char *);
if (jailparam_init(jp + njp, name) < 0) {
va_end(tap);
goto error;
}
- if (!strcmp(jp[njp].jp_name, "lastjid")) {
+ if (!strcmp(jp[njp].jp_name, "desc")
+ && (flags & (JAIL_USE_DESC | JAIL_AT_DESC))) {
+ jp_desc = jp + njp;
+ desc_value = value;
+ } else if (!strcmp(jp[njp].jp_name, "lastjid")) {
jp_lastjid = jp + njp;
lastjid_value = value;
} else if (!strcmp(jp[njp].jp_name, "jid")) {
@@ -147,7 +162,10 @@ jail_getv(int flags, ...)
}
va_end(tap);
/* Import the key parameter. */
- if (jp_lastjid != NULL) {
+ if (jp_desc != NULL && (flags & JAIL_USE_DESC)) {
+ jp_key = jp_desc;
+ key_value = desc_value;
+ } else if (jp_lastjid != NULL) {
jp_key = jp_lastjid;
key_value = lastjid_value;
} else if (jp_jid != NULL && strtol(jid_value, NULL, 10) != 0) {
@@ -163,6 +181,9 @@ jail_getv(int flags, ...)
}
if (jailparam_import(jp_key, key_value) < 0)
goto error;
+ if (jp_desc != NULL && jp_desc != jp_key
+ && jailparam_import(jp_desc, desc_value) < 0)
+ goto error;
/* Get the jail and export the parameters. */
jid = jailparam_get(jp, njp, flags);
if (jid < 0)
@@ -571,7 +592,7 @@ int
jailparam_get(struct jailparam *jp, unsigned njp, int flags)
{
struct iovec *jiov;
- struct jailparam *jp_lastjid, *jp_jid, *jp_name, *jp_key;
+ struct jailparam *jp_desc, *jp_lastjid, *jp_jid, *jp_name, *jp_key;
int i, ai, ki, jid, arrays, sanity;
unsigned j;
@@ -580,10 +601,13 @@ jailparam_get(struct jailparam *jp, unsigned njp, int flags)
* Find the key and any array parameters.
*/
jiov = alloca(sizeof(struct iovec) * 2 * (njp + 1));
- jp_lastjid = jp_jid = jp_name = NULL;
+ jp_desc = jp_lastjid = jp_jid = jp_name = NULL;
arrays = 0;
for (ai = j = 0; j < njp; j++) {
- if (!strcmp(jp[j].jp_name, "lastjid"))
+ if (!strcmp(jp[j].jp_name, "desc")
+ && (flags & (JAIL_USE_DESC | JAIL_AT_DESC)))
+ jp_desc = jp + j;
+ else if (!strcmp(jp[j].jp_name, "lastjid"))
jp_lastjid = jp + j;
else if (!strcmp(jp[j].jp_name, "jid"))
jp_jid = jp + j;
@@ -599,7 +623,9 @@ jailparam_get(struct jailparam *jp, unsigned njp, int flags)
ai++;
}
}
- jp_key = jp_lastjid ? jp_lastjid :
+ jp_key = jp_desc && jp_desc->jp_valuelen == sizeof(int) &&
+ jp_desc->jp_value && (flags & JAIL_USE_DESC) ? jp_desc :
+ jp_lastjid ? jp_lastjid :
jp_jid && jp_jid->jp_valuelen == sizeof(int) &&
jp_jid->jp_value && *(int *)jp_jid->jp_value ? jp_jid : jp_name;
if (jp_key == NULL || jp_key->jp_value == NULL) {
@@ -622,6 +648,14 @@ jailparam_get(struct jailparam *jp, unsigned njp, int flags)
jiov[ki].iov_len = JAIL_ERRMSGLEN;
ki++;
jail_errmsg[0] = 0;
+ if (jp_desc != NULL && jp_desc != jp_key) {
+ jiov[ki].iov_base = jp_desc->jp_name;
+ jiov[ki].iov_len = strlen(jp_desc->jp_name) + 1;
+ ki++;
+ jiov[ki].iov_base = jp_desc->jp_value;
+ jiov[ki].iov_len = jp_desc->jp_valuelen;
+ ki++;
+ }
if (arrays && jail_get(jiov, ki, flags) < 0) {
if (!jail_errmsg[0])
snprintf(jail_errmsg, sizeof(jail_errmsg),
@@ -649,7 +683,7 @@ jailparam_get(struct jailparam *jp, unsigned njp, int flags)
jiov[ai].iov_base = jp[j].jp_value;
memset(jiov[ai].iov_base, 0, jiov[ai].iov_len);
ai++;
- } else if (jp + j != jp_key) {
+ } else if (jp + j != jp_key && jp + j != jp_desc) {
jiov[i].iov_base = jp[j].jp_name;
jiov[i].iov_len = strlen(jp[j].jp_name) + 1;
i++;
diff --git a/lib/libsys/Symbol.sys.map b/lib/libsys/Symbol.sys.map
index 1a297f9df581..e3fd8ac10621 100644
--- a/lib/libsys/Symbol.sys.map
+++ b/lib/libsys/Symbol.sys.map
@@ -382,6 +382,8 @@ FBSD_1.8 {
getrlimitusage;
inotify_add_watch_at;
inotify_rm_watch;
+ jail_attach_jd;
+ jail_remove_jd;
kcmp;
setcred;
setgroups;
diff --git a/lib/libsys/_libsys.h b/lib/libsys/_libsys.h
index 34eebc1aa67a..6bd768708a78 100644
--- a/lib/libsys/_libsys.h
+++ b/lib/libsys/_libsys.h
@@ -468,6 +468,8 @@ typedef int (__sys_inotify_add_watch_at_t)(int, int, const char *, uint32_t);
typedef int (__sys_inotify_rm_watch_t)(int, int);
typedef int (__sys_getgroups_t)(int, gid_t *);
typedef int (__sys_setgroups_t)(int, const gid_t *);
+typedef int (__sys_jail_attach_jd_t)(int);
+typedef int (__sys_jail_remove_jd_t)(int);
_Noreturn void __sys__exit(int rval);
int __sys_fork(void);
@@ -872,6 +874,8 @@ int __sys_inotify_add_watch_at(int fd, int dfd, const char * path, uint32_t mask
int __sys_inotify_rm_watch(int fd, int wd);
int __sys_getgroups(int gidsetsize, gid_t * gidset);
int __sys_setgroups(int gidsetsize, const gid_t * gidset);
+int __sys_jail_attach_jd(int fd);
+int __sys_jail_remove_jd(int fd);
__END_DECLS
#endif /* __LIBSYS_H_ */
diff --git a/lib/libsys/jail.2 b/lib/libsys/jail.2
index 8f8b9925c712..a0f47cc61cb3 100644
--- a/lib/libsys/jail.2
+++ b/lib/libsys/jail.2
@@ -23,7 +23,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd November 29, 2023
+.Dd September 4, 2025
.Dt JAIL 2
.Os
.Sh NAME
@@ -31,7 +31,9 @@
.Nm jail_get ,
.Nm jail_set ,
.Nm jail_remove ,
-.Nm jail_attach
+.Nm jail_attach ,
+.Nm jail_remove_jd ,
+.Nm jail_attach_jd
.Nd create and manage system jails
.Sh LIBRARY
.Lb libc
@@ -44,6 +46,10 @@
.Fn jail_attach "int jid"
.Ft int
.Fn jail_remove "int jid"
+.Ft int
+.Fn jail_attach_jd "int fd"
+.Ft int
+.Fn jail_remove_jd "int fd"
.In sys/uio.h
.Ft int
.Fn jail_get "struct iovec *iov" "u_int niov" "int flags"
@@ -188,6 +194,29 @@ system call.
This is deprecated in
.Fn jail_set
and has no effect.
+.It Dv JAIL_USE_DESC
+Identify the jail by a descriptor in the
+.Va desc
+parameter.
+.It Dv JAIL_AT_DESC
+Operate in the context of the jail described by the
+.Va desc
+parameter, instead of the current jail.
+Only one of
+.Dv JAIL_USE_DESC
+or
+.Dv JAIL_AT_DESC
+may be specified.
+.It Dv JAIL_GET_DESC
+Return a new jail descriptor for the jail in the
+.Va desc
+parameter.
+.It Dv JAIL_OWN_DESC
+Return an
+.Dq owning
+jail descriptor in the
+.Va desc
+parameter.
.El
.Pp
The
@@ -221,6 +250,9 @@ arguments consists of one or more following flags:
.Bl -tag -width indent
.It Dv JAIL_DYING
Allow getting a jail that is in the process of being removed.
+.It Dv JAIL_USE_DESC , Dv JAIL_AT_DESC , Dv JAIL_GET_DESC , Dv JAIL_OWN_DESC
+These have the same meaning as they do in
+.Fn jail_set .
.El
.Pp
The
@@ -238,6 +270,101 @@ system call removes the jail identified by
.Fa jid .
It will kill all processes belonging to the jail, and remove any children
of that jail.
+.Pp
+The
+.Fn jail_attach_fd
+and
+.Fn jail_remove_fd
+system calls work the same as
+.Fn jail_attach
+and
+.Fn jail_remove ,
+except that they operate on the jail identified by jail descriptor
+.Fa fd .
+.Ss Jail Descriptors
+In addition to the jail ID,
+jails can be referred to using a jail descriptor,
+a type of file descriptor tied to a particular jail.
+Jail descriptors are created by calling
+.Fn jail_set
+or
+.Fn jail_get
+with the special parameter
+.Va desc ,
+and either the
+.Dv JAIL_GET_DESC
+or
+.Dv JAIL_OWN_DESC
+flags set.
+The difference between the two flags is that descriptors created with
+.Dv JAIL_OWN_DESC
+.Po
+called
+.Dq owning
+descriptors
+.Pc
+will automatically remove the jail when the descriptor is closed.
+.Pp
+Jail descriptors can be passed back to
+.Fn jail_set
+or
+.Fm jail_get
+with the
+.Va desc
+parameter,
+and either the
+.Dv JAIL_USE_DESC
+or
+.Dv JAIL_AT_DESC
+flags set.
+With
+.Dv JAIL_USE_DESC ,
+the descriptor identifies the jail to operate on,
+instead of the
+.Va jid
+or
+.Va name
+parameter.
+With
+.Dv JAIL_AT_DESC ,
+the descriptor is used in place of the current jail,
+allowing accessing or creating jails that are children of the
+descriptor jail.
+.Pp
+The system calls
+.Fn jail_attach_jd
+and
+.Fn jail_aremove_jd
+work the same as
+.Fn jail_attach
+and
+.Fn jail_remove ,
+except that they operate on the jail referred to by the passed descriptor.
+.Pp
+Jail operations via descriptors can be done by processes that do not
+normally have permission to see or affect the jail,
+as long as they are allowed by the file permissions of the jail
+descriptor itself.
+These permissions can be changed by the descriptor owner via
+.Xr fchmod 2
+and
+.Xr fchown 2 .
+.Fn jail_get
+requires read permission,
+.Fn jail_set
+and
+.Fn jail_remove
+require write permission,
+and
+.Fn jail_attach
+requires execute permission.
+Also, use of a descriptor with the
+.Dv JAIL_AT_DESC
+flag requires execute permission.
+An owning descriptor is identified by the
+.Em sticky bit ,
+which may also be changed via
+.Xr fchmod 2 .
.Sh RETURN VALUES
If successful,
.Fn jail ,
@@ -249,7 +376,7 @@ They return \-1 on failure, and set
.Va errno
to indicate the error.
.Pp
-.Rv -std jail_attach jail_remove
+.Rv -std jail_attach jail_remove jail_attach_jd jail_remove_jd
.Sh ERRORS
The
.Fn jail
@@ -275,12 +402,44 @@ The
system call
will fail if:
.Bl -tag -width Er
+.It Bq Er EBADF
+The
+.Va desc
+parameter does not refer to a valid jail descriptor,
+and either the
+.Dv JAIL_USE_DESC
+or
+.Dv JAIL_AT_DESC
+flag was set.
+.It Bq Er EACCES
+Write permission is denied on the jail descriptor in the
+.Va desc
+parameter,
+and the
+.Dv JAIL_USE_DESC
+flag was set.
+.It Bq Er EACCES
+Execute permission is denied on the jail descriptor in the
+.Va desc
+parameter,
+and either the
+.Dv JAIL_AT_DESC
+or
+.Dv JAIL_ATTACH
+flag was set.
.It Bq Er EPERM
This process is not allowed to create a jail, either because it is not
the super-user, or because it would exceed the jail's
.Va children.max
limit.
.It Bq Er EPERM
+The jail descriptor in the
+.Va desc
+parameter was created by a user other than the super-user,
+and the
+.Dv JAIL_USE_DESC
+flag was set.
+.It Bq Er EPERM
A jail parameter was set to a less restrictive value then the current
environment.
.It Bq Er EFAULT
@@ -298,8 +457,12 @@ flag is not set.
.It Bq Er ENOENT
The jail referred to by a
.Va jid
-is not accessible by the process, because the process is in a different
-jail.
+parameter is not accessible by the process, because the process is in a
+different jail.
+.It Bq Er ENOENT
+The jail referred to by a
+.Va desc
+parameter has been removed.
.It Bq Er EEXIST
The jail referred to by a
.Va jid
@@ -326,6 +489,24 @@ flags is not set.
A supplied string parameter is longer than allowed.
.It Bq Er EAGAIN
There are no jail IDs left.
+.It Bq Er EMFILE
+A jail descriptor could not be created for the
+.Va desc
+parameter with either the
+.Dv JAIL_GET_DESC
+or
+.Dv JAIL_OWN_DESC
+flag set,
+because the process has already reached its limit for open file descriptors.
+.It Bq Er ENFILE
+A jail descriptor could not be created for the
+.Va desc
+parameter with either the
+.Dv JAIL_GET_DESC
+or
+.Dv JAIL_OWN_DESC
+flag set,
+because the system file table is full.
.El
.Pp
The
@@ -333,6 +514,29 @@ The
system call
will fail if:
.Bl -tag -width Er
+.It Bq Er EBADF
+The
+.Va desc
+parameter does not refer to a valid jail descriptor,
+and either the
+.Dv JAIL_USE_DESC
+or
+.Dv JAIL_AT_DESC
+flag was set.
+.It Bq Er EACCES
+Read permission is denied on the jail descriptor in the
+.Va desc
+parameter,
+and the
+.Dv JAIL_USE_DESC
+flag was set.
+.It Bq Er EACCES
+Execute permission is denied on the jail descriptor in the
+.Va desc
+parameter,
+and the
+.Dv JAIL_AT_DESC
+flag was set.
.It Bq Er EFAULT
.Fa Iov ,
or one of the addresses contained within it,
@@ -352,10 +556,33 @@ jail.
The
.Va lastjid
parameter is greater than the highest current jail ID.
+.It Bq Er ENOENT
+The jail referred to by a
+.Va desc
+parameter has been removed
+.Pq even if the Dv JAIL_CREATE flag has been set .
.It Bq Er EINVAL
A supplied parameter is the wrong size.
.It Bq Er EINVAL
A supplied parameter name does not match any known parameters.
+.It Bq Er EMFILE
+A jail descriptor could not be created for the
+.Va desc
+parameter with either the
+.Dv JAIL_GET_DESC
+or
+.Dv JAIL_OWN_DESC
+flag set,
+because the process has already reached its limit for open file descriptors.
+.It Bq Er ENFILE
+A jail descriptor could not be created for the
+.Va desc
+parameter with either the
+.Dv JAIL_GET_DESC
+or
+.Dv JAIL_OWN_DESC
+flag set,
+because the system file table is full.
.El
.Pp
The
@@ -373,11 +600,39 @@ The jail specified by
does not exist.
.El
.Pp
+The
+.Fn jail_attach_jd
+and
+.Fn jail_remove_jd
+system calls
+will fail if:
+.Bl -tag -width Er
+.It Bq Er EBADF
+The
+.Fa fd
+argument is not a valid jail descriptor.
+.It Bq Er EACCES
+Permission is denied on the jail descriptor
+.Po
+execute permission for
+.Fn jail_attach_fd ,
+or write permission for
+.Fn jail_remove_fd
+.Pc .
+.It Bq Er EPERM
+The jail descriptor was created by a user other than the super-user.
+.It Bq Er EINVAL
+The jail specified by
+.Fa jid
+has been removed.
+.El
+.Pp
Further
.Fn jail ,
.Fn jail_set ,
+.Fn jail_attach ,
and
-.Fn jail_attach
+.Fn jail_attach_jd
call
.Xr chroot 2
internally, so they can fail for all the same reasons.
diff --git a/lib/libsys/kqueue.2 b/lib/libsys/kqueue.2
index d6e949baa24c..e413f7d4fbca 100644
--- a/lib/libsys/kqueue.2
+++ b/lib/libsys/kqueue.2
@@ -22,7 +22,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd March 26, 2023
+.Dd September 4, 2025
.Dt KQUEUE 2
.Os
.Sh NAME
@@ -593,6 +593,62 @@ returns the number of times the signal has occurred since the last call to
This filter automatically sets the
.Dv EV_CLEAR
flag internally.
+.It Dv EVFILT_JAIL
+Takes the jail ID to monitor as the identifier and the events to watch for
+in
+.Va fflags ,
+and returns when the jail performs one or more of the requested events.
+If a process can normally see a jail, it can attach an event to it.
+An identifier of zero will watch the process's own jail.
+The events to monitor are:
+.Bl -tag -width "Dv NOTE_JAIL_ATTACH"
+.It Dv NOTE_JAIL_SET
+The jail has been changed via
+.Xr jail_set 2 .
+.It Dv NOTE_JAIL_ATTACH
+A process has attached to the jail via
+.Xr jail_attach 2
+or a similar call.
+The process ID will be stored in
+.Va data .
+If more than one process has attached since the last call to
+.Fn kevent ,
+.Va data
+will contain the most recently attached process ID,
+with
+.Dv NOTE_JAIL_ATTACH_MULTI
+set in
+.Va fflags .
+.It Dv NOTE_JAIL_REMOVE
+The jail has been removed.
+.It Dv NOTE_JAIL_CHILD
+A child of the watched jail has been created.
+.It Dv NOTE_TRACK
+Follow child jails created under this jail.
+Register a new kevent to monitor the child jail using the same
+.Va fflags
+as the original event.
+The child jail will signal an event with
+.Dv NOTE_CHILD
+set in
+.Va fflags
+and the parent JID in
+.Va data .
+.Pp
+If registering a new kevent fails
+.Pq usually due to resource limitations ,
+it will signal an event with
+.Dv NOTE_TRACKERR
+set in
+.Va fflags ,
+and the child jail will not signal a
+.Dv NOTE_CHILD
+event.
+.El
+.Pp
+On return,
+.Va fflags
+contains the events which triggered the filter.
.It Dv EVFILT_TIMER
Establishes an arbitrary timer identified by
.Va ident .
diff --git a/lib/libsys/syscalls.map b/lib/libsys/syscalls.map
index 4cf80a2ffc69..b5400b9849b3 100644
--- a/lib/libsys/syscalls.map
+++ b/lib/libsys/syscalls.map
@@ -813,4 +813,8 @@ FBSDprivate_1.0 {
__sys_getgroups;
_setgroups;
__sys_setgroups;
+ _jail_attach_jd;
+ __sys_jail_attach_jd;
+ _jail_remove_jd;
+ __sys_jail_remove_jd;
};
diff --git a/libexec/rc/rc.conf b/libexec/rc/rc.conf
index bfa46bd343a6..82b6efa6ca62 100644
--- a/libexec/rc/rc.conf
+++ b/libexec/rc/rc.conf
@@ -586,15 +586,19 @@ font8x14="NO" # font 8x14 from /usr/share/{syscons,vt}/fonts/* (or NO).
font8x8="NO" # font 8x8 from /usr/share/{syscons,vt}/fonts/* (or NO).
blanktime="300" # blank time (in seconds) or "NO" to turn it off.
saver="NO" # screen saver: Uses /boot/kernel/${saver}_saver.ko
-moused_nondefault_enable="YES" # Treat non-default mice as enabled unless
+moused_nondefault_enable="NO" # Treat non-default mice as enabled unless
# specifically overridden in rc.conf(5).
moused_enable="NO" # Run the mouse daemon.
moused_type="auto" # See man page for rc.conf(5) for available settings.
-moused_port="/dev/psm0" # Set to your mouse port.
+moused_port="auto" # Set to your mouse port.
moused_flags="" # Any additional flags to moused.
mousechar_start="NO" # if 0xd0-0xd3 default range is occupied in your
# language code table, specify alternative range
# start like mousechar_start=3, see vidcontrol(1)
+msconvd_enable="NO" # Run the mouse protocol conversion daemon.
+msconvd_type="auto" # See rc.conf(5) man page for available moused_type-s.
+msconvd_ports="" # List of msconvd ports.
+msconvd_flags="" # Any additional flags to msconvd.
allscreens_flags="" # Set this vidcontrol mode for all virtual screens
allscreens_kbdflags="" # Set this kbdcontrol mode for all virtual screens
@@ -644,7 +648,7 @@ lpd_flags="" # Flags to lpd (if enabled).
nscd_enable="NO" # Run the nsswitch caching daemon.
chkprintcap_enable="NO" # Run chkprintcap(8) before running lpd.
chkprintcap_flags="-d" # Create missing directories by default.
-dumpdev="AUTO" # Device to crashdump to (device name, AUTO, or NO);
+dumpdev="NO" # Device to crashdump to (device name, AUTO, or NO);
# this should be commented out here
# for stable branches to respect kenv.
dumpon_flags="" # Options to pass to dumpon(8), followed by dumpdev.
diff --git a/libexec/rc/rc.d/Makefile b/libexec/rc/rc.d/Makefile
index 7c1f50b027a9..e5ee34e62185 100644
--- a/libexec/rc/rc.d/Makefile
+++ b/libexec/rc/rc.d/Makefile
@@ -273,6 +273,7 @@ JAILPACKAGE= jail
.if ${MK_LEGACY_CONSOLE} != "no"
CONFGROUPS+= CONSOLE
CONSOLE+= moused
+CONSOLE+= msconvd
CONSOLE+= syscons
CONSOLEPACKAGE= console-tools
.endif
diff --git a/libexec/rc/rc.d/msconvd b/libexec/rc/rc.d/msconvd
new file mode 100755
index 000000000000..c2a96bf2eb68
--- /dev/null
+++ b/libexec/rc/rc.d/msconvd
@@ -0,0 +1,61 @@
+#!/bin/sh
+#
+#
+
+# PROVIDE: msconvd
+# REQUIRE: DAEMON FILESYSTEMS
+# KEYWORD: nojail shutdown
+
+. /etc/rc.subr
+
+name="msconvd"
+desc="Mouse protocol conversion daemon"
+command="/usr/sbin/${name}"
+start_cmd="msconvd_start"
+pidprefix="/var/run/msconvd"
+load_rc_config $name
+
+: ${msconvd_enable="NO"}
+: ${msconvd_type="auto"}
+
+# doesn't make sense to run in a svcj: nojail keyword
+# XXX: How does msconvd communiacte with the kernel?
+# XXX: Does the kernel prevent this communcation in jails?
+msconvd_svcj="NO"
+
+# Set the pid file and variable name. The second argument, if it exists, is
+# expected to be the mouse device.
+#
+if [ -n "$2" ]; then
+ eval msconvd_$2_enable=\${msconvd_$2_enable-${msconvd_enable}}
+ rcvar="msconvd_$2_enable"
+ pidfile="${pidprefix}.$2.pid"
+else
+ for ms in ${msconvd_ports}; do
+ /etc/rc.d/msconvd $1 ${ms}
+ done
+ exit 0
+fi
+
+msconvd_start()
+{
+ local ms myflags myport mytype
+
+ # Set the mouse device and get any related variables. If
+ # a msconvd device has been specified on the commandline, then
+ # rc.conf(5) variables defined for that device take precedence
+ # over the generic msconvd_* variables. The only exception is
+ # the msconvd_port variable, which if not defined sets it to
+ # the passed in device name.
+ #
+ ms=$1
+ eval myflags=\${msconvd_${ms}_flags-$msconvd_flags}
+ eval myport=\${msconvd_${ms}_port-/dev/${ms}}
+ eval mytype=\${msconvd_${ms}_type-$msconvd_type}
+
+ startmsg -n "Starting ${ms} ${name}"
+ ${command} ${myflags} -p ${myport} -t ${mytype} -I ${pidfile}
+ startmsg '.'
+}
+
+run_rc_command $*
diff --git a/release/Makefile b/release/Makefile
index b6a9aa42c2e2..6fd3b2f61325 100644
--- a/release/Makefile
+++ b/release/Makefile
@@ -244,7 +244,6 @@ disc1: ${PKGBASE_REPO}
ln -fs /tmp/bsdinstall_etc/resolv.conf ${.TARGET}/etc/resolv.conf
echo sendmail_enable=\"NONE\" > ${.TARGET}/etc/rc.conf
echo hostid_enable=\"NO\" >> ${.TARGET}/etc/rc.conf
- echo debug.witness.trace=0 >> ${.TARGET}/etc/sysctl.conf
echo vfs.mountroot.timeout=\"10\" >> ${.TARGET}/boot/loader.conf
echo kernels_autodetect=\"NO\" >> ${.TARGET}/boot/loader.conf
echo loader_brand=\"install\" >> ${.TARGET}/boot/loader.conf
@@ -285,7 +284,6 @@ bootonly:
ln -fs /tmp/bsdinstall_etc/resolv.conf ${.TARGET}/etc/resolv.conf
echo sendmail_enable=\"NONE\" > ${.TARGET}/etc/rc.conf
echo hostid_enable=\"NO\" >> ${.TARGET}/etc/rc.conf
- echo debug.witness.trace=0 >> ${.TARGET}/etc/sysctl.conf
echo vfs.mountroot.timeout=\"10\" >> ${.TARGET}/boot/loader.conf
echo kernels_autodetect=\"NO\" >> ${.TARGET}/boot/loader.conf
echo loader_brand=\"install\" >> ${.TARGET}/boot/loader.conf
@@ -341,7 +339,6 @@ dvd: ${PKGBASE_REPO}
ln -fs /tmp/bsdinstall_etc/resolv.conf ${.TARGET}/etc/resolv.conf
echo sendmail_enable=\"NONE\" > ${.TARGET}/etc/rc.conf
echo hostid_enable=\"NO\" >> ${.TARGET}/etc/rc.conf
- echo debug.witness.trace=0 >> ${.TARGET}/etc/sysctl.conf
echo vfs.mountroot.timeout=\"10\" >> ${.TARGET}/boot/loader.conf
echo kernels_autodetect=\"NO\" >> ${.TARGET}/boot/loader.conf
echo loader_brand=\"install\" >> ${.TARGET}/boot/loader.conf
diff --git a/release/release.conf.sample b/release/release.conf.sample
index e583e49828d4..337019db6fa6 100644
--- a/release/release.conf.sample
+++ b/release/release.conf.sample
@@ -18,7 +18,7 @@ GITSRC="src.git"
GITPORTS="ports.git"
## Set the src/, ports/, and doc/ branches or tags.
-SRCBRANCH="main"
+SRCBRANCH="stable/15"
PORTBRANCH="main"
## Sample configuration for using git from ports.
diff --git a/share/man/man4/tcp.4 b/share/man/man4/tcp.4
index fcfda42908d8..3c9f4ff83f3d 100644
--- a/share/man/man4/tcp.4
+++ b/share/man/man4/tcp.4
@@ -31,7 +31,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd June 27, 2025
+.Dd September 5, 2025
.Dt TCP 4
.Os
.Sh NAME
@@ -940,9 +940,6 @@ maximum segment size.
This helps throughput in general, but
particularly affects short transfers and high-bandwidth large
propagation-delay connections.
-.It Va rfc6675_pipe
-Deprecated and superseded by
-.Va sack.revised
.It Va sack.enable
Enable support for RFC 2018, TCP Selective Acknowledgment option,
which allows the receiver to inform the sender about all successfully
@@ -974,6 +971,11 @@ recovery, the trailing segment is immediately resent, rather than waiting
for a Retransmission timeout.
Finally, SACK loss recovery is also engaged, once two segments plus one byte are
SACKed - even if no traditional duplicate ACKs were observed.
+.Va sack.revised
+is deprecated and will be removed in
+.Fx 16 .
+.Va sack.enable
+will always follow RFC6675.
.It Va sendbuf_auto
Enable automatic send buffer sizing.
.It Va sendbuf_auto_lowat
diff --git a/share/man/man5/src.conf.5 b/share/man/man5/src.conf.5
index 2895c0cf4746..fa3ab3bc63b0 100644
--- a/share/man/man5/src.conf.5
+++ b/share/man/man5/src.conf.5
@@ -1,5 +1,5 @@
.\" DO NOT EDIT-- this file is @generated by tools/build/options/makeman.
-.Dd August 20, 2025
+.Dd September 4, 2025
.Dt SRC.CONF 5
.Os
.Sh NAME
@@ -935,8 +935,8 @@ amd64/amd64, arm64/aarch64, i386/i386, powerpc/powerpc64 and powerpc/powerpc64le
Do not build the LLD linker during the bootstrap phase of
the build.
To be able to build the system an alternate linker must be provided via XLD.
-.It Va WITHOUT_LLVM_ASSERTIONS
-Disable debugging assertions in LLVM.
+.It Va WITH_LLVM_ASSERTIONS
+Enable debugging assertions in LLVM.
.It Va WITHOUT_LLVM_BINUTILS
Install ELF Tool Chain's binary utilities instead of LLVM's.
This includes
@@ -1190,15 +1190,15 @@ if executed as an unprivileged user.
See
.Xr tests 7
for more details.
-.It Va WITH_MALLOC_PRODUCTION
-Disable assertions and statistics gathering in
+.It Va WITHOUT_MALLOC_PRODUCTION
+Enable assertions and statistics gathering in
.Xr malloc 3 .
The run-time options
.Dv opt.abort ,
.Dv opt.abort_conf ,
and
.Dv opt.junk
-also default to false.
+also default to true.
.It Va WITHOUT_MAN
Do not build manual pages.
When set, these options are also in effect:
diff --git a/share/mk/src.opts.mk b/share/mk/src.opts.mk
index 85a003eb4eaf..60fe42372faa 100644
--- a/share/mk/src.opts.mk
+++ b/share/mk/src.opts.mk
@@ -123,7 +123,6 @@ __DEFAULT_YES_OPTIONS = \
LEGACY_CONSOLE \
LLD \
LLD_BOOTSTRAP \
- LLVM_ASSERTIONS \
LLVM_BINUTILS \
LLVM_COV \
LLVM_CXXFILT \
@@ -143,6 +142,7 @@ __DEFAULT_YES_OPTIONS = \
MAIL \
MAILWRAPPER \
MAKE \
+ MALLOC_PRODUCTION \
MITKRB5 \
MLX5TOOL \
NETCAT \
@@ -164,6 +164,7 @@ __DEFAULT_YES_OPTIONS = \
QUOTAS \
RADIUS_SUPPORT \
RBOOTD \
+ REPRODUCIBLE_BUILD \
RESCUE \
ROUTED \
SENDMAIL \
@@ -210,11 +211,10 @@ __DEFAULT_NO_OPTIONS = \
HESIOD \
LOADER_VERBOSE \
LOADER_VERIEXEC_PASS_MANIFEST \
+ LLVM_ASSERTIONS \
LLVM_FULL_DEBUGINFO \
- MALLOC_PRODUCTION \
OFED_EXTRA \
OPENLDAP \
- REPRODUCIBLE_BUILD \
RPCBIND_WARMSTART_SUPPORT \
SORT_THREADS \
ZONEINFO_LEAPSECONDS_SUPPORT \
diff --git a/share/sendmail/Makefile b/share/sendmail/Makefile
index b6b4d2ec726c..db868b7b33b9 100644
--- a/share/sendmail/Makefile
+++ b/share/sendmail/Makefile
@@ -6,8 +6,8 @@ FILESGROUPS+= SM
SENDMAIL_DIR= ${SRCTOP}/contrib/sendmail
CFDIR= cf
CFPACKAGE= sendmail
-CFDIRS!= (cd ${SENDMAIL_DIR}; find -L ${CFDIR} \( \( -name CVS -o -name .svn \) -prune \) -o -type d -print)
-CFFILES!= (cd ${SENDMAIL_DIR}; find -L ${CFDIR} \( \( -name CVS -o -name .svn \) -prune \) -o -type f -print)
+CFDIRS!= (cd ${SENDMAIL_DIR}; find -s -L ${CFDIR} \( \( -name CVS -o -name .svn \) -prune \) -o -type d -print)
+CFFILES!= (cd ${SENDMAIL_DIR}; find -s -L ${CFDIR} \( \( -name CVS -o -name .svn \) -prune \) -o -type f -print)
SMFILES= ${CFFILES}
SMFILESDIR= ${DDIR}
diff --git a/sys/amd64/conf/GENERIC b/sys/amd64/conf/GENERIC
index 786edc4125c9..c98cfce8613a 100644
--- a/sys/amd64/conf/GENERIC
+++ b/sys/amd64/conf/GENERIC
@@ -95,8 +95,6 @@ options RCTL # Resource limits
# Debugging support. Always need this:
options KDB # Enable kernel debugger support.
options KDB_TRACE # Print a stack trace for a panic.
-# For full debugger support use (turn off in stable branch):
-include "std.debug"
# Kernel dump features.
options EKCD # Support for encrypted kernel dumps
diff --git a/sys/amd64/conf/GENERIC-NODEBUG b/sys/amd64/conf/GENERIC-NODEBUG
deleted file mode 100644
index 1939b0efd352..000000000000
--- a/sys/amd64/conf/GENERIC-NODEBUG
+++ /dev/null
@@ -1,31 +0,0 @@
-#
-# GENERIC-NODEBUG -- WITNESS and INVARIANTS free kernel configuration file
-# for FreeBSD/amd64
-#
-# This configuration file removes several debugging options, including
-# WITNESS and INVARIANTS checking, which are known to have significant
-# performance impact on running systems. When benchmarking new features
-# this kernel should be used instead of the standard GENERIC.
-# This kernel configuration should never appear outside of the HEAD
-# of the FreeBSD tree.
-#
-# For more information on this file, please read the config(5) manual page,
-# and/or the handbook section on Kernel Configuration Files:
-#
-# https://docs.freebsd.org/en/books/handbook/kernelconfig/#kernelconfig-config
-#
-# The handbook is also available locally in /usr/share/doc/handbook
-# if you've installed the doc distribution, otherwise always see the
-# FreeBSD World Wide Web server (https://www.FreeBSD.org/) for the
-# latest information.
-#
-# An exhaustive list of options and more detailed explanations of the
-# device lines is also present in the ../../conf/NOTES and NOTES files.
-# If you are in doubt as to the purpose or necessity of a line, check first
-# in NOTES.
-#
-
-include GENERIC
-include "std.nodebug"
-
-ident GENERIC-NODEBUG
diff --git a/sys/amd64/conf/MINIMAL b/sys/amd64/conf/MINIMAL
index 0baf6d6431de..ec5ab2fcaee3 100644
--- a/sys/amd64/conf/MINIMAL
+++ b/sys/amd64/conf/MINIMAL
@@ -73,8 +73,6 @@ options INCLUDE_CONFIG_FILE # Include this file in kernel
# Debugging support. Always need this:
options KDB # Enable kernel debugger support.
options KDB_TRACE # Print a stack trace for a panic.
-# For full debugger support use (turn off in stable branch):
-include "std.debug"
# Make an SMP-capable kernel by default
options SMP # Symmetric MultiProcessor Kernel
diff --git a/sys/amd64/conf/MINIMAL-NODEBUG b/sys/amd64/conf/MINIMAL-NODEBUG
deleted file mode 100644
index 7b7c22bbcaf6..000000000000
--- a/sys/amd64/conf/MINIMAL-NODEBUG
+++ /dev/null
@@ -1,11 +0,0 @@
-#
-# MINIMAL-NODEBUG -- Non-debug MINIMAL kernel.
-#
-# This is the MINIMAL equivalent to GENERIC-NODEBUG.
-
-#NO_UNIVERSE
-
-include MINIMAL
-include "std.nodebug"
-
-ident MINIMAL-NODEBUG
diff --git a/sys/arm/conf/GENERIC-NODEBUG b/sys/arm/conf/GENERIC-NODEBUG
deleted file mode 100644
index 0b3199245187..000000000000
--- a/sys/arm/conf/GENERIC-NODEBUG
+++ /dev/null
@@ -1,31 +0,0 @@
-#
-# GENERIC-NODEBUG -- WITNESS and INVARIANTS free kernel configuration file
-# for FreeBSD/arm
-#
-# This configuration file removes several debugging options, including
-# WITNESS and INVARIANTS checking, which are known to have significant
-# performance impact on running systems. When benchmarking new features
-# this kernel should be used instead of the standard GENERIC.
-# This kernel configuration should never appear outside of the HEAD
-# of the FreeBSD tree.
-#
-# For more information on this file, please read the config(5) manual page,
-# and/or the handbook section on Kernel Configuration Files:
-#
-# https://docs.freebsd.org/en/books/handbook/kernelconfig/#kernelconfig-config
-#
-# The handbook is also available locally in /usr/share/doc/handbook
-# if you've installed the doc distribution, otherwise always see the
-# FreeBSD World Wide Web server (https://www.FreeBSD.org/) for the
-# latest information.
-#
-# An exhaustive list of options and more detailed explanations of the
-# device lines is also present in the ../../conf/NOTES and NOTES files.
-# If you are in doubt as to the purpose or necessity of a line, check first
-# in NOTES.
-#
-
-include GENERIC
-include "std.nodebug"
-
-ident GENERIC-NODEBUG
diff --git a/sys/arm/conf/std.armv7 b/sys/arm/conf/std.armv7
index 4ef60c331212..15d8304ae5f1 100644
--- a/sys/arm/conf/std.armv7
+++ b/sys/arm/conf/std.armv7
@@ -65,9 +65,6 @@ options KDB_TRACE # Print a stack trace for a panic.
options USB_DEBUG # Enable usb debug support code
-# For full debugger support use (turn off in stable branch):
-include "std.debug"
-
# Optional extras, never enabled by default:
#options BOOTVERBOSE
#options DEBUG # May result in extreme spewage
diff --git a/sys/arm64/arm64/machdep.c b/sys/arm64/arm64/machdep.c
index 627b02e82d34..47c701e8588c 100644
--- a/sys/arm64/arm64/machdep.c
+++ b/sys/arm64/arm64/machdep.c
@@ -858,7 +858,7 @@ initarm(struct arm64_bootparams *abp)
cninit();
set_ttbr0(abp->kern_ttbr0);
- cpu_tlb_flushID();
+ pmap_s1_invalidate_all_kernel();
if (!valid)
panic("Invalid bus configuration: %s",
diff --git a/sys/arm64/arm64/pmap.c b/sys/arm64/arm64/pmap.c
index 48b62442e68f..8a4395aa1c89 100644
--- a/sys/arm64/arm64/pmap.c
+++ b/sys/arm64/arm64/pmap.c
@@ -190,6 +190,8 @@ pt_entry_t __read_mostly pmap_gp_attr;
#define PMAP_SAN_PTE_BITS (ATTR_AF | ATTR_S1_XN | pmap_sh_attr | \
ATTR_KERN_GP | ATTR_S1_IDX(VM_MEMATTR_WRITE_BACK) | ATTR_S1_AP(ATTR_S1_AP_RW))
+static bool __read_mostly pmap_multiple_tlbi = false;
+
struct pmap_large_md_page {
struct rwlock pv_lock;
struct md_page pv_page;
@@ -1297,7 +1299,7 @@ pmap_bootstrap_dmap(vm_size_t kernlen)
}
}
- cpu_tlb_flushID();
+ pmap_s1_invalidate_all_kernel();
bs_state.dmap_valid = true;
@@ -1399,7 +1401,7 @@ pmap_bootstrap(void)
/* And the l3 tables for the early devmap */
pmap_bootstrap_l3(VM_MAX_KERNEL_ADDRESS - (PMAP_MAPDEV_EARLY_SIZE));
- cpu_tlb_flushID();
+ pmap_s1_invalidate_all_kernel();
#define alloc_pages(var, np) \
(var) = bs_state.freemempos; \
@@ -1723,6 +1725,51 @@ CPU_FEAT(feat_hafdbs, "Hardware management of the Access flag and dirty state",
pmap_dbm_check, pmap_dbm_has_errata, pmap_dbm_enable,
CPU_FEAT_AFTER_DEV | CPU_FEAT_PER_CPU);
+static cpu_feat_en
+pmap_multiple_tlbi_check(const struct cpu_feat *feat __unused, u_int midr)
+{
+ /*
+ * Cortex-A55 erratum 2441007 (Cat B rare)
+ * Present in all revisions
+ */
+ if (CPU_IMPL(midr) == CPU_IMPL_ARM &&
+ CPU_PART(midr) == CPU_PART_CORTEX_A55)
+ return (FEAT_DEFAULT_DISABLE);
+
+ /*
+ * Cortex-A76 erratum 1286807 (Cat B rare)
+ * Present in r0p0 - r3p0
+ * Fixed in r3p1
+ */
+ if (midr_check_var_part_range(midr, CPU_IMPL_ARM, CPU_PART_CORTEX_A76,
+ 0, 0, 3, 0))
+ return (FEAT_DEFAULT_DISABLE);
+
+ /*
+ * Cortex-A510 erratum 2441009 (Cat B rare)
+ * Present in r0p0 - r1p1
+ * Fixed in r1p2
+ */
+ if (midr_check_var_part_range(midr, CPU_IMPL_ARM, CPU_PART_CORTEX_A510,
+ 0, 0, 1, 1))
+ return (FEAT_DEFAULT_DISABLE);
+
+ return (FEAT_ALWAYS_DISABLE);
+}
+
+static bool
+pmap_multiple_tlbi_enable(const struct cpu_feat *feat __unused,
+ cpu_feat_errata errata_status, u_int *errata_list __unused,
+ u_int errata_count __unused)
+{
+ pmap_multiple_tlbi = true;
+ return (true);
+}
+
+CPU_FEAT(errata_multi_tlbi, "Multiple TLBI errata",
+ pmap_multiple_tlbi_check, NULL, pmap_multiple_tlbi_enable,
+ CPU_FEAT_EARLY_BOOT | CPU_FEAT_PER_CPU);
+
/*
* Initialize the pmap module.
*
@@ -1876,9 +1923,17 @@ pmap_s1_invalidate_page(pmap_t pmap, vm_offset_t va, bool final_only)
r = TLBI_VA(va);
if (pmap == kernel_pmap) {
pmap_s1_invalidate_kernel(r, final_only);
+ if (pmap_multiple_tlbi) {
+ dsb(ish);
+ pmap_s1_invalidate_kernel(r, final_only);
+ }
} else {
r |= ASID_TO_OPERAND(COOKIE_TO_ASID(pmap->pm_cookie));
pmap_s1_invalidate_user(r, final_only);
+ if (pmap_multiple_tlbi) {
+ dsb(ish);
+ pmap_s1_invalidate_user(r, final_only);
+ }
}
dsb(ish);
isb();
@@ -1920,12 +1975,24 @@ pmap_s1_invalidate_strided(pmap_t pmap, vm_offset_t sva, vm_offset_t eva,
end = TLBI_VA(eva);
for (r = start; r < end; r += TLBI_VA(stride))
pmap_s1_invalidate_kernel(r, final_only);
+
+ if (pmap_multiple_tlbi) {
+ dsb(ish);
+ for (r = start; r < end; r += TLBI_VA(stride))
+ pmap_s1_invalidate_kernel(r, final_only);
+ }
} else {
start = end = ASID_TO_OPERAND(COOKIE_TO_ASID(pmap->pm_cookie));
start |= TLBI_VA(sva);
end |= TLBI_VA(eva);
for (r = start; r < end; r += TLBI_VA(stride))
pmap_s1_invalidate_user(r, final_only);
+
+ if (pmap_multiple_tlbi) {
+ dsb(ish);
+ for (r = start; r < end; r += TLBI_VA(stride))
+ pmap_s1_invalidate_user(r, final_only);
+ }
}
dsb(ish);
isb();
@@ -1961,6 +2028,19 @@ pmap_invalidate_range(pmap_t pmap, vm_offset_t sva, vm_offset_t eva,
pmap_s2_invalidate_range(pmap, sva, eva, final_only);
}
+void
+pmap_s1_invalidate_all_kernel(void)
+{
+ dsb(ishst);
+ __asm __volatile("tlbi vmalle1is");
+ dsb(ish);
+ if (pmap_multiple_tlbi) {
+ __asm __volatile("tlbi vmalle1is");
+ dsb(ish);
+ }
+ isb();
+}
+
/*
* Invalidates all cached intermediate- and final-level TLB entries for the
* given virtual address space.
@@ -1975,9 +2055,17 @@ pmap_s1_invalidate_all(pmap_t pmap)
dsb(ishst);
if (pmap == kernel_pmap) {
__asm __volatile("tlbi vmalle1is");
+ if (pmap_multiple_tlbi) {
+ dsb(ish);
+ __asm __volatile("tlbi vmalle1is");
+ }
} else {
r = ASID_TO_OPERAND(COOKIE_TO_ASID(pmap->pm_cookie));
__asm __volatile("tlbi aside1is, %0" : : "r" (r));
+ if (pmap_multiple_tlbi) {
+ dsb(ish);
+ __asm __volatile("tlbi aside1is, %0" : : "r" (r));
+ }
}
dsb(ish);
isb();
@@ -7965,7 +8053,7 @@ pmap_mapbios(vm_paddr_t pa, vm_size_t size)
pa += L2_SIZE;
}
if ((old_l2e & ATTR_DESCR_VALID) != 0)
- pmap_s1_invalidate_all(kernel_pmap);
+ pmap_s1_invalidate_all_kernel();
else {
/*
* Because the old entries were invalid and the new
@@ -8056,7 +8144,7 @@ pmap_unmapbios(void *p, vm_size_t size)
}
}
if (preinit_map) {
- pmap_s1_invalidate_all(kernel_pmap);
+ pmap_s1_invalidate_all_kernel();
return;
}
diff --git a/sys/arm64/conf/GENERIC-MMCCAM-NODEBUG b/sys/arm64/conf/GENERIC-MMCCAM-NODEBUG
deleted file mode 100644
index b2e865129012..000000000000
--- a/sys/arm64/conf/GENERIC-MMCCAM-NODEBUG
+++ /dev/null
@@ -1,14 +0,0 @@
-#
-# GENERIC-MMCCAM-NODEBUG
-#
-# Custom kernel for arm64 plus MMCCAM as opposed to the prior MMC stack. It is
-# present to keep it building in tree since it wouldn't work in LINT. This
-# version without debugging features.
-#
-
-#NO_UNIVERSE
-
-include GENERIC-MMCCAM
-include "std.nodebug"
-
-ident GENERIC-MMCCAM-NODEBUG
diff --git a/sys/arm64/conf/GENERIC-NODEBUG b/sys/arm64/conf/GENERIC-NODEBUG
deleted file mode 100644
index 086942dfaab1..000000000000
--- a/sys/arm64/conf/GENERIC-NODEBUG
+++ /dev/null
@@ -1,31 +0,0 @@
-#
-# GENERIC-NODEBUG -- WITNESS and INVARIANTS free kernel configuration file
-# for FreeBSD/arm64
-#
-# This configuration file removes several debugging options, including
-# WITNESS and INVARIANTS checking, which are known to have significant
-# performance impact on running systems. When benchmarking new features
-# this kernel should be used instead of the standard GENERIC.
-# This kernel configuration should never appear outside of the HEAD
-# of the FreeBSD tree.
-#
-# For more information on this file, please read the config(5) manual page,
-# and/or the handbook section on Kernel Configuration Files:
-#
-# https://docs.freebsd.org/en/books/handbook/kernelconfig/#kernelconfig-config
-#
-# The handbook is also available locally in /usr/share/doc/handbook
-# if you've installed the doc distribution, otherwise always see the
-# FreeBSD World Wide Web server (https://www.FreeBSD.org/) for the
-# latest information.
-#
-# An exhaustive list of options and more detailed explanations of the
-# device lines is also present in the ../../conf/NOTES and NOTES files.
-# If you are in doubt as to the purpose or necessity of a line, check first
-# in NOTES.
-#
-
-include GENERIC
-include "std.nodebug"
-
-ident GENERIC-NODEBUG
diff --git a/sys/arm64/conf/std.arm64 b/sys/arm64/conf/std.arm64
index a0568466cfaf..58f3748e2700 100644
--- a/sys/arm64/conf/std.arm64
+++ b/sys/arm64/conf/std.arm64
@@ -74,8 +74,6 @@ options PERTHREAD_SSP # Per-thread SSP canary
# Debugging support. Always need this:
options KDB # Enable kernel debugger support.
options KDB_TRACE # Print a stack trace for a panic.
-# For full debugger support use (turn off in stable branch):
-include "std.debug"
# Kernel Sanitizers
#options COVERAGE # Generic kernel coverage. Used by KCOV
diff --git a/sys/arm64/include/cpu.h b/sys/arm64/include/cpu.h
index f07b67d18abf..07a783138f42 100644
--- a/sys/arm64/include/cpu.h
+++ b/sys/arm64/include/cpu.h
@@ -193,9 +193,6 @@
(((mask) & PCPU_GET(midr)) == \
((mask) & CPU_ID_RAW((impl), (part), (var), (rev))))
-#define CPU_MATCH_RAW(mask, devid) \
- (((mask) & PCPU_GET(midr)) == ((mask) & (devid)))
-
#if !defined(__ASSEMBLER__)
static inline bool
midr_check_var_part_range(u_int midr, u_int impl, u_int part, u_int var_low,
diff --git a/sys/arm64/include/pmap.h b/sys/arm64/include/pmap.h
index 357c1a0d8232..406b6e2c5e0a 100644
--- a/sys/arm64/include/pmap.h
+++ b/sys/arm64/include/pmap.h
@@ -175,6 +175,8 @@ int pmap_fault(pmap_t, uint64_t, uint64_t);
struct pcb *pmap_switch(struct thread *);
+void pmap_s1_invalidate_all_kernel(void);
+
extern void (*pmap_clean_stage2_tlbi)(void);
extern void (*pmap_stage2_invalidate_range)(uint64_t, vm_offset_t, vm_offset_t,
bool);
diff --git a/sys/compat/freebsd32/freebsd32_syscall.h b/sys/compat/freebsd32/freebsd32_syscall.h
index 90cd21a80923..54063150eef9 100644
--- a/sys/compat/freebsd32/freebsd32_syscall.h
+++ b/sys/compat/freebsd32/freebsd32_syscall.h
@@ -515,4 +515,6 @@
#define FREEBSD32_SYS_inotify_rm_watch 594
#define FREEBSD32_SYS_getgroups 595
#define FREEBSD32_SYS_setgroups 596
-#define FREEBSD32_SYS_MAXSYSCALL 597
+#define FREEBSD32_SYS_jail_attach_jd 597
+#define FREEBSD32_SYS_jail_remove_jd 598
+#define FREEBSD32_SYS_MAXSYSCALL 599
diff --git a/sys/compat/freebsd32/freebsd32_syscalls.c b/sys/compat/freebsd32/freebsd32_syscalls.c
index f0f8d26554b5..f7cc4c284e4d 100644
--- a/sys/compat/freebsd32/freebsd32_syscalls.c
+++ b/sys/compat/freebsd32/freebsd32_syscalls.c
@@ -602,4 +602,6 @@ const char *freebsd32_syscallnames[] = {
"inotify_rm_watch", /* 594 = inotify_rm_watch */
"getgroups", /* 595 = getgroups */
"setgroups", /* 596 = setgroups */
+ "jail_attach_jd", /* 597 = jail_attach_jd */
+ "jail_remove_jd", /* 598 = jail_remove_jd */
};
diff --git a/sys/compat/freebsd32/freebsd32_sysent.c b/sys/compat/freebsd32/freebsd32_sysent.c
index 12f1a346c3e9..18f809ef04e3 100644
--- a/sys/compat/freebsd32/freebsd32_sysent.c
+++ b/sys/compat/freebsd32/freebsd32_sysent.c
@@ -664,4 +664,6 @@ struct sysent freebsd32_sysent[] = {
{ .sy_narg = AS(inotify_rm_watch_args), .sy_call = (sy_call_t *)sys_inotify_rm_watch, .sy_auevent = AUE_INOTIFY, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 594 = inotify_rm_watch */
{ .sy_narg = AS(getgroups_args), .sy_call = (sy_call_t *)sys_getgroups, .sy_auevent = AUE_GETGROUPS, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 595 = getgroups */
{ .sy_narg = AS(setgroups_args), .sy_call = (sy_call_t *)sys_setgroups, .sy_auevent = AUE_SETGROUPS, .sy_flags = 0, .sy_thrcnt = SY_THR_STATIC }, /* 596 = setgroups */
+ { .sy_narg = AS(jail_attach_jd_args), .sy_call = (sy_call_t *)sys_jail_attach_jd, .sy_auevent = AUE_JAIL_ATTACH, .sy_flags = 0, .sy_thrcnt = SY_THR_STATIC }, /* 597 = jail_attach_jd */
+ { .sy_narg = AS(jail_remove_jd_args), .sy_call = (sy_call_t *)sys_jail_remove_jd, .sy_auevent = AUE_JAIL_REMOVE, .sy_flags = 0, .sy_thrcnt = SY_THR_STATIC }, /* 598 = jail_remove_jd */
};
diff --git a/sys/compat/freebsd32/freebsd32_systrace_args.c b/sys/compat/freebsd32/freebsd32_systrace_args.c
index e471c5148021..29a5497e9efa 100644
--- a/sys/compat/freebsd32/freebsd32_systrace_args.c
+++ b/sys/compat/freebsd32/freebsd32_systrace_args.c
@@ -3413,6 +3413,20 @@ systrace_args(int sysnum, void *params, uint64_t *uarg, int *n_args)
*n_args = 2;
break;
}
+ /* jail_attach_jd */
+ case 597: {
+ struct jail_attach_jd_args *p = params;
+ iarg[a++] = p->fd; /* int */
+ *n_args = 1;
+ break;
+ }
+ /* jail_remove_jd */
+ case 598: {
+ struct jail_remove_jd_args *p = params;
+ iarg[a++] = p->fd; /* int */
+ *n_args = 1;
+ break;
+ }
default:
*n_args = 0;
break;
@@ -9222,6 +9236,26 @@ systrace_entry_setargdesc(int sysnum, int ndx, char *desc, size_t descsz)
break;
};
break;
+ /* jail_attach_jd */
+ case 597:
+ switch (ndx) {
+ case 0:
+ p = "int";
+ break;
+ default:
+ break;
+ };
+ break;
+ /* jail_remove_jd */
+ case 598:
+ switch (ndx) {
+ case 0:
+ p = "int";
+ break;
+ default:
+ break;
+ };
+ break;
default:
break;
};
@@ -11130,6 +11164,16 @@ systrace_return_setargdesc(int sysnum, int ndx, char *desc, size_t descsz)
if (ndx == 0 || ndx == 1)
p = "int";
break;
+ /* jail_attach_jd */
+ case 597:
+ if (ndx == 0 || ndx == 1)
+ p = "int";
+ break;
+ /* jail_remove_jd */
+ case 598:
+ if (ndx == 0 || ndx == 1)
+ p = "int";
+ break;
default:
break;
};
diff --git a/sys/conf/files b/sys/conf/files
index d89813c70355..9661bafea8f9 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -3808,6 +3808,7 @@ kern/kern_hhook.c standard
kern/kern_idle.c standard
kern/kern_intr.c standard
kern/kern_jail.c standard
+kern/kern_jaildesc.c standard
kern/kern_jailmeta.c standard
kern/kern_kcov.c optional kcov \
compile-with "${NOSAN_C} ${MSAN_CFLAGS}"
diff --git a/sys/dev/cyapa/cyapa.c b/sys/dev/cyapa/cyapa.c
index 50fa4faa560a..ed755f992949 100644
--- a/sys/dev/cyapa/cyapa.c
+++ b/sys/dev/cyapa/cyapa.c
@@ -761,42 +761,60 @@ again:
/*
* Generate report
*/
- c0 = 0;
- if (delta_x < 0)
- c0 |= 0x10;
- if (delta_y < 0)
- c0 |= 0x20;
- c0 |= 0x08;
- if (but & CYAPA_FNGR_LEFT)
- c0 |= 0x01;
- if (but & CYAPA_FNGR_MIDDLE)
- c0 |= 0x04;
- if (but & CYAPA_FNGR_RIGHT)
- c0 |= 0x02;
-
- fifo_write_char(sc, &sc->rfifo, c0);
- fifo_write_char(sc, &sc->rfifo, (uint8_t)delta_x);
- fifo_write_char(sc, &sc->rfifo, (uint8_t)delta_y);
- switch(sc->zenabled) {
- case 1:
- /* Z axis all 8 bits */
- fifo_write_char(sc, &sc->rfifo, (uint8_t)delta_z);
- break;
- case 2:
- /*
- * Z axis low 4 bits + 4th button and 5th button
- * (high 2 bits must be left 0). Auto-scale
- * delta_z to fit to avoid a wrong-direction
- * overflow (don't try to retain the remainder).
- */
- while (delta_z > 7 || delta_z < -8)
- delta_z >>= 1;
- c0 = (uint8_t)delta_z & 0x0F;
+ if (sc->mode.level == 1) {
+ c0 = MOUSE_SYS_SYNC;
+ if (but & CYAPA_FNGR_LEFT)
+ c0 |= MOUSE_SYS_BUTTON1UP;
+ if (but & CYAPA_FNGR_MIDDLE)
+ c0 |= MOUSE_SYS_BUTTON2UP;
+ if (but & CYAPA_FNGR_RIGHT)
+ c0 |= MOUSE_SYS_BUTTON3UP;
fifo_write_char(sc, &sc->rfifo, c0);
- break;
- default:
- /* basic PS/2 */
- break;
+ fifo_write_char(sc, &sc->rfifo, delta_x >> 1);
+ fifo_write_char(sc, &sc->rfifo, delta_y >> 1);
+ fifo_write_char(sc, &sc->rfifo, delta_x - (delta_x >> 1));
+ fifo_write_char(sc, &sc->rfifo, delta_y - (delta_y >> 1));
+ fifo_write_char(sc, &sc->rfifo, delta_z >> 1);
+ fifo_write_char(sc, &sc->rfifo, delta_z - (delta_z >> 1));
+ fifo_write_char(sc, &sc->rfifo, MOUSE_SYS_EXTBUTTONS);
+ } else {
+ c0 = 0;
+ if (delta_x < 0)
+ c0 |= 0x10;
+ if (delta_y < 0)
+ c0 |= 0x20;
+ c0 |= 0x08;
+ if (but & CYAPA_FNGR_LEFT)
+ c0 |= 0x01;
+ if (but & CYAPA_FNGR_MIDDLE)
+ c0 |= 0x04;
+ if (but & CYAPA_FNGR_RIGHT)
+ c0 |= 0x02;
+
+ fifo_write_char(sc, &sc->rfifo, c0);
+ fifo_write_char(sc, &sc->rfifo, (uint8_t)delta_x);
+ fifo_write_char(sc, &sc->rfifo, (uint8_t)delta_y);
+ switch(sc->zenabled) {
+ case 1:
+ /* Z axis all 8 bits */
+ fifo_write_char(sc, &sc->rfifo, (uint8_t)delta_z);
+ break;
+ case 2:
+ /*
+ * Z axis low 4 bits + 4th button and 5th button
+ * (high 2 bits must be left 0). Auto-scale
+ * delta_z to fit to avoid a wrong-direction
+ * overflow (don't try to retain the remainder).
+ */
+ while (delta_z > 7 || delta_z < -8)
+ delta_z >>= 1;
+ c0 = (uint8_t)delta_z & 0x0F;
+ fifo_write_char(sc, &sc->rfifo, c0);
+ break;
+ default:
+ /* basic PS/2 */
+ break;
+ }
}
cyapa_notify(sc);
}
@@ -1205,6 +1223,11 @@ cyapaioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread
((mousemode_t *)data)->packetsize =
MOUSE_PS2_PACKETSIZE;
break;
+ case 1:
+ ((mousemode_t *)data)->protocol = MOUSE_PROTO_SYSMOUSE;
+ ((mousemode_t *)data)->packetsize =
+ MOUSE_SYS_PACKETSIZE;
+ break;
case 2:
((mousemode_t *)data)->protocol = MOUSE_PROTO_PS2;
((mousemode_t *)data)->packetsize =
@@ -1223,7 +1246,7 @@ cyapaioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread
error = EINVAL;
break;
}
- sc->mode.level = *(int *)data ? 2 : 0;
+ sc->mode.level = *(int *)data;
sc->zenabled = sc->mode.level ? 1 : 0;
break;
diff --git a/sys/dev/sound/pci/hda/hdac.c b/sys/dev/sound/pci/hda/hdac.c
index 900578b73de4..90cd74d28b3d 100644
--- a/sys/dev/sound/pci/hda/hdac.c
+++ b/sys/dev/sound/pci/hda/hdac.c
@@ -1773,17 +1773,17 @@ hdac_detach(device_t dev)
struct hdac_softc *sc = device_get_softc(dev);
int i, error;
+ callout_drain(&sc->poll_callout);
+ hdac_irq_free(sc);
+ taskqueue_drain(taskqueue_thread, &sc->unsolq_task);
+
error = bus_generic_detach(dev);
if (error != 0)
return (error);
hdac_lock(sc);
- callout_stop(&sc->poll_callout);
hdac_reset(sc, false);
hdac_unlock(sc);
- callout_drain(&sc->poll_callout);
- taskqueue_drain(taskqueue_thread, &sc->unsolq_task);
- hdac_irq_free(sc);
for (i = 0; i < sc->num_ss; i++)
hdac_dma_free(sc, &sc->streams[i].bdl);
@@ -2206,4 +2206,4 @@ static driver_t hdac_driver = {
sizeof(struct hdac_softc),
};
-DRIVER_MODULE(snd_hda, pci, hdac_driver, NULL, NULL);
+DRIVER_MODULE_ORDERED(snd_hda, pci, hdac_driver, NULL, NULL, SI_ORDER_ANY);
diff --git a/sys/dev/virtio/network/if_vtnet.c b/sys/dev/virtio/network/if_vtnet.c
index 4f19af6281a3..73f27ac147ff 100644
--- a/sys/dev/virtio/network/if_vtnet.c
+++ b/sys/dev/virtio/network/if_vtnet.c
@@ -1178,6 +1178,7 @@ vtnet_setup_interface(struct vtnet_softc *sc)
if (sc->vtnet_max_mtu >= ETHERMTU_JUMBO)
if_setcapabilitiesbit(ifp, IFCAP_JUMBO_MTU, 0);
if_setcapabilitiesbit(ifp, IFCAP_VLAN_MTU, 0);
+ if_setcapabilitiesbit(ifp, IFCAP_HWSTATS, 0);
/*
* Capabilities after here are not enabled by default.
@@ -3036,16 +3037,14 @@ vtnet_get_counter(if_t ifp, ift_counter cnt)
return (rxaccum.vrxs_iqdrops);
case IFCOUNTER_IERRORS:
return (rxaccum.vrxs_ierrors);
+ case IFCOUNTER_IBYTES:
+ return (rxaccum.vrxs_ibytes);
case IFCOUNTER_OPACKETS:
return (txaccum.vtxs_opackets);
case IFCOUNTER_OBYTES:
- if (!VTNET_ALTQ_ENABLED)
- return (txaccum.vtxs_obytes);
- /* FALLTHROUGH */
+ return (txaccum.vtxs_obytes);
case IFCOUNTER_OMCASTS:
- if (!VTNET_ALTQ_ENABLED)
- return (txaccum.vtxs_omcasts);
- /* FALLTHROUGH */
+ return (txaccum.vtxs_omcasts);
default:
return (if_get_counter_default(ifp, cnt));
}
diff --git a/sys/i386/conf/GENERIC b/sys/i386/conf/GENERIC
index 88b8967cd693..f426c3d11874 100644
--- a/sys/i386/conf/GENERIC
+++ b/sys/i386/conf/GENERIC
@@ -89,8 +89,6 @@ options RCTL # Resource limits
# Debugging support. Always need this:
options KDB # Enable kernel debugger support.
options KDB_TRACE # Print a stack trace for a panic.
-# For full debugger support use (turn off in stable branch):
-include "std.debug"
# Kernel dump features.
options EKCD # Support for encrypted kernel dumps
diff --git a/sys/i386/conf/GENERIC-NODEBUG b/sys/i386/conf/GENERIC-NODEBUG
deleted file mode 100644
index a93304481b5f..000000000000
--- a/sys/i386/conf/GENERIC-NODEBUG
+++ /dev/null
@@ -1,33 +0,0 @@
-#
-# GENERIC-NODEBUG -- WITNESS and INVARIANTS free kernel configuration file
-# for FreeBSD/i386
-#
-# This configuration file removes several debugging options, including
-# WITNESS and INVARIANTS checking, which are known to have significant
-# performance impact on running systems. When benchmarking new features
-# this kernel should be used instead of the standard GENERIC.
-# This kernel configuration should never appear outside of the HEAD
-# of the FreeBSD tree.
-#
-# For more information on this file, please read the config(5) manual page,
-# and/or the handbook section on Kernel Configuration Files:
-#
-# https://docs.freebsd.org/en/books/handbook/kernelconfig/#kernelconfig-config
-#
-# The handbook is also available locally in /usr/share/doc/handbook
-# if you've installed the doc distribution, otherwise always see the
-# FreeBSD World Wide Web server (https://www.FreeBSD.org/) for the
-# latest information.
-#
-# An exhaustive list of options and more detailed explanations of the
-# device lines is also present in the ../../conf/NOTES and NOTES files.
-# If you are in doubt as to the purpose or necessity of a line, check first
-# in NOTES.
-#
-
-#NO_UNIVERSE
-
-include GENERIC
-include "std.nodebug"
-
-ident GENERIC-NODEBUG
diff --git a/sys/i386/conf/MINIMAL b/sys/i386/conf/MINIMAL
index 8019617ca4d4..6b70c4e59825 100644
--- a/sys/i386/conf/MINIMAL
+++ b/sys/i386/conf/MINIMAL
@@ -83,8 +83,6 @@ options INCLUDE_CONFIG_FILE # Include this file in kernel
# Debugging support. Always need this:
options KDB # Enable kernel debugger support.
options KDB_TRACE # Print a stack trace for a panic.
-# For full debugger support use (turn off in stable branch):
-include "std.debug"
# Make an SMP-capable kernel by default
options SMP # Symmetric MultiProcessor Kernel
diff --git a/sys/i386/conf/MINIMAL-NODEBUG b/sys/i386/conf/MINIMAL-NODEBUG
deleted file mode 100644
index 7b7c22bbcaf6..000000000000
--- a/sys/i386/conf/MINIMAL-NODEBUG
+++ /dev/null
@@ -1,11 +0,0 @@
-#
-# MINIMAL-NODEBUG -- Non-debug MINIMAL kernel.
-#
-# This is the MINIMAL equivalent to GENERIC-NODEBUG.
-
-#NO_UNIVERSE
-
-include MINIMAL
-include "std.nodebug"
-
-ident MINIMAL-NODEBUG
diff --git a/sys/kern/init_sysent.c b/sys/kern/init_sysent.c
index fcd232cde21e..e42e7dcf8b44 100644
--- a/sys/kern/init_sysent.c
+++ b/sys/kern/init_sysent.c
@@ -663,4 +663,6 @@ struct sysent sysent[] = {
{ .sy_narg = AS(inotify_rm_watch_args), .sy_call = (sy_call_t *)sys_inotify_rm_watch, .sy_auevent = AUE_INOTIFY, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 594 = inotify_rm_watch */
{ .sy_narg = AS(getgroups_args), .sy_call = (sy_call_t *)sys_getgroups, .sy_auevent = AUE_GETGROUPS, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 595 = getgroups */
{ .sy_narg = AS(setgroups_args), .sy_call = (sy_call_t *)sys_setgroups, .sy_auevent = AUE_SETGROUPS, .sy_flags = 0, .sy_thrcnt = SY_THR_STATIC }, /* 596 = setgroups */
+ { .sy_narg = AS(jail_attach_jd_args), .sy_call = (sy_call_t *)sys_jail_attach_jd, .sy_auevent = AUE_JAIL_ATTACH, .sy_flags = 0, .sy_thrcnt = SY_THR_STATIC }, /* 597 = jail_attach_jd */
+ { .sy_narg = AS(jail_remove_jd_args), .sy_call = (sy_call_t *)sys_jail_remove_jd, .sy_auevent = AUE_JAIL_REMOVE, .sy_flags = 0, .sy_thrcnt = SY_THR_STATIC }, /* 598 = jail_remove_jd */
};
diff --git a/sys/kern/kern_descrip.c b/sys/kern/kern_descrip.c
index a27ab33b34da..057235574eb5 100644
--- a/sys/kern/kern_descrip.c
+++ b/sys/kern/kern_descrip.c
@@ -5250,6 +5250,8 @@ file_type_to_name(short type)
return ("eventfd");
case DTYPE_TIMERFD:
return ("timerfd");
+ case DTYPE_JAILDESC:
+ return ("jail");
default:
return ("unkn");
}
diff --git a/sys/kern/kern_event.c b/sys/kern/kern_event.c
index eb77a5064113..501adc151d44 100644
--- a/sys/kern/kern_event.c
+++ b/sys/kern/kern_event.c
@@ -50,6 +50,7 @@
#include <sys/filedesc.h>
#include <sys/filio.h>
#include <sys/fcntl.h>
+#include <sys/jail.h>
#include <sys/kthread.h>
#include <sys/selinfo.h>
#include <sys/queue.h>
@@ -163,6 +164,9 @@ static int filt_kqueue(struct knote *kn, long hint);
static int filt_procattach(struct knote *kn);
static void filt_procdetach(struct knote *kn);
static int filt_proc(struct knote *kn, long hint);
+static int filt_jailattach(struct knote *kn);
+static void filt_jaildetach(struct knote *kn);
+static int filt_jail(struct knote *kn, long hint);
static int filt_fileattach(struct knote *kn);
static void filt_timerexpire(void *knx);
static void filt_timerexpire_l(struct knote *kn, bool proc_locked);
@@ -195,6 +199,12 @@ static const struct filterops proc_filtops = {
.f_detach = filt_procdetach,
.f_event = filt_proc,
};
+static const struct filterops jail_filtops = {
+ .f_isfd = 0,
+ .f_attach = filt_jailattach,
+ .f_detach = filt_jaildetach,
+ .f_event = filt_jail,
+};
static const struct filterops timer_filtops = {
.f_isfd = 0,
.f_attach = filt_timerattach,
@@ -365,6 +375,7 @@ static struct {
[~EVFILT_USER] = { &user_filtops, 1 },
[~EVFILT_SENDFILE] = { &null_filtops },
[~EVFILT_EMPTY] = { &file_filtops, 1 },
+ [~EVFILT_JAIL] = { &jail_filtops, 1 },
};
/*
@@ -528,7 +539,8 @@ filt_proc(struct knote *kn, long hint)
* process forked. Additionally, for each knote attached to the
* parent, check whether user wants to track the new process. If so
* attach a new knote to it, and immediately report an event with the
- * child's pid.
+ * child's pid. This is also called on jail creation, which is treated
+ * the same way by jail events.
*/
void
knote_fork(struct knlist *list, int pid)
@@ -555,6 +567,8 @@ knote_fork(struct knlist *list, int pid)
/*
* The same as knote(), activate the event.
*/
+ _Static_assert(NOTE_JAIL_CHILD == NOTE_FORK,
+ "NOTE_JAIL_CHILD should be the same as NOTE_FORK");
if ((kn->kn_sfflags & NOTE_TRACK) == 0) {
if (kn->kn_fop->f_event(kn, NOTE_FORK))
KNOTE_ACTIVATE(kn, 1);
@@ -614,6 +628,124 @@ knote_fork(struct knlist *list, int pid)
}
}
+int
+filt_jailattach(struct knote *kn)
+{
+ struct prison *pr;
+ bool immediate;
+
+ immediate = false;
+ if (kn->kn_id == 0) {
+ /* Let jid=0 watch the current prison (including prison0). */
+ pr = curthread->td_ucred->cr_prison;
+ mtx_lock(&pr->pr_mtx);
+ } else if (kn->kn_flags & (EV_FLAG1 | EV_FLAG2)) {
+ /*
+ * The kernel registers prisons before they are valid,
+ * so prison_find_child will fail.
+ */
+ TAILQ_FOREACH(pr, &allprison, pr_list) {
+ if (pr->pr_id < kn->kn_id)
+ continue;
+ if (pr->pr_id > kn->kn_id) {
+ pr = NULL;
+ break;
+ }
+ mtx_lock(&pr->pr_mtx);
+ break;
+ }
+ if (pr == NULL)
+ return (ENOENT);
+ } else {
+ sx_slock(&allprison_lock);
+ pr = prison_find_child(curthread->td_ucred->cr_prison,
+ kn->kn_id);
+ sx_sunlock(&allprison_lock);
+ if (pr == NULL)
+ return (ENOENT);
+ if (!prison_isalive(pr)) {
+ mtx_unlock(&pr->pr_mtx);
+ return (ENOENT);
+ }
+ }
+ kn->kn_ptr.p_prison = pr;
+ kn->kn_flags |= EV_CLEAR;
+
+ /*
+ * Internal flag indicating registration done by kernel for the
+ * purposes of getting a NOTE_CHILD notification.
+ */
+ if (kn->kn_flags & EV_FLAG2) {
+ kn->kn_flags &= ~EV_FLAG2;
+ kn->kn_data = kn->kn_sdata; /* parent id */
+ kn->kn_fflags = NOTE_CHILD;
+ kn->kn_sfflags &= ~NOTE_JAIL_CTRLMASK;
+ immediate = true; /* Force immediate activation of child note. */
+ }
+ /*
+ * Internal flag indicating registration done by kernel (for other than
+ * NOTE_CHILD).
+ */
+ if (kn->kn_flags & EV_FLAG1) {
+ kn->kn_flags &= ~EV_FLAG1;
+ }
+
+ knlist_add(pr->pr_klist, kn, 1);
+
+ /* Immediately activate any child notes. */
+ if (immediate)
+ KNOTE_ACTIVATE(kn, 0);
+
+ mtx_unlock(&pr->pr_mtx);
+ return (0);
+}
+
+void
+filt_jaildetach(struct knote *kn)
+{
+ if (kn->kn_ptr.p_prison != NULL) {
+ knlist_remove(kn->kn_knlist, kn, 0);
+ kn->kn_ptr.p_prison = NULL;
+ } else
+ kn->kn_status |= KN_DETACHED;
+}
+
+int
+filt_jail(struct knote *kn, long hint)
+{
+ struct prison *pr;
+ u_int event;
+
+ pr = kn->kn_ptr.p_prison;
+ if (pr == NULL) /* already activated, from attach filter */
+ return (0);
+
+ /* Mask off extra data. */
+ event = (u_int)hint & NOTE_JAIL_CTRLMASK;
+
+ /* If the user is interested in this event, record it. */
+ if (kn->kn_sfflags & event)
+ kn->kn_fflags |= event;
+
+ /* Report the attached process id. */
+ if (event == NOTE_JAIL_ATTACH) {
+ if (kn->kn_data != 0)
+ kn->kn_fflags |= NOTE_JAIL_ATTACH_MULTI;
+ kn->kn_data = hint & NOTE_JAIL_DATAMASK;
+ }
+
+ /* Prison is gone, so flag the event as finished. */
+ if (event == NOTE_JAIL_REMOVE) {
+ kn->kn_flags |= EV_EOF | EV_ONESHOT;
+ kn->kn_ptr.p_prison = NULL;
+ if (kn->kn_fflags == 0)
+ kn->kn_flags |= EV_DROP;
+ return (1);
+ }
+
+ return (kn->kn_fflags != 0);
+}
+
/*
* XXX: EVFILT_TIMER should perhaps live in kern_time.c beside the
* interval timer support code.
@@ -1597,8 +1729,8 @@ findkn:
/*
* If possible, find an existing knote to use for this kevent.
*/
- if (kev->filter == EVFILT_PROC &&
- (kev->flags & (EV_FLAG1 | EV_FLAG2)) != 0) {
+ if ((kev->filter == EVFILT_PROC || kev->filter == EVFILT_JAIL)
+ && (kev->flags & (EV_FLAG1 | EV_FLAG2)) != 0) {
/* This is an internal creation of a process tracking
* note. Don't attempt to coalesce this with an
* existing note.
@@ -2800,6 +2932,7 @@ knote_init(void)
knote_zone = uma_zcreate("KNOTE", sizeof(struct knote), NULL, NULL,
NULL, NULL, UMA_ALIGN_PTR, 0);
ast_register(TDA_KQUEUE, ASTR_ASTF_REQUIRED, 0, ast_kqueue);
+ prison0.pr_klist = knlist_alloc(&prison0.pr_mtx);
}
SYSINIT(knote, SI_SUB_PSEUDO, SI_ORDER_ANY, knote_init, NULL);
diff --git a/sys/kern/kern_jail.c b/sys/kern/kern_jail.c
index 7c9a15ae18f3..5a1fbe23ddeb 100644
--- a/sys/kern/kern_jail.c
+++ b/sys/kern/kern_jail.c
@@ -39,15 +39,18 @@
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/errno.h>
+#include <sys/file.h>
#include <sys/sysproto.h>
#include <sys/malloc.h>
#include <sys/osd.h>
#include <sys/priv.h>
#include <sys/proc.h>
#include <sys/epoch.h>
+#include <sys/event.h>
#include <sys/taskqueue.h>
#include <sys/fcntl.h>
#include <sys/jail.h>
+#include <sys/jaildesc.h>
#include <sys/linker.h>
#include <sys/lock.h>
#include <sys/mman.h>
@@ -154,7 +157,8 @@ static void prison_complete(void *context, int pending);
static void prison_deref(struct prison *pr, int flags);
static void prison_deref_kill(struct prison *pr, struct prisonlist *freeprison);
static int prison_lock_xlock(struct prison *pr, int flags);
-static void prison_cleanup(struct prison *pr);
+static void prison_cleanup_locked(struct prison *pr);
+static void prison_cleanup_unlocked(struct prison *pr);
static void prison_free_not_last(struct prison *pr);
static void prison_proc_free_not_last(struct prison *pr);
static void prison_proc_relink(struct prison *opr, struct prison *npr,
@@ -167,6 +171,7 @@ static void prison_racct_attach(struct prison *pr);
static void prison_racct_modify(struct prison *pr);
static void prison_racct_detach(struct prison *pr);
#endif
+static void prison_knote(struct prison *pr, long hint);
/* Flags for prison_deref */
#define PD_DEREF 0x01 /* Decrement pr_ref */
@@ -985,6 +990,8 @@ prison_ip_cnt(const struct prison *pr, const pr_family_t af)
int
kern_jail_set(struct thread *td, struct uio *optuio, int flags)
{
+ struct file *jfp_out;
+ struct jaildesc *desc_in;
struct nameidata nd;
#ifdef INET
struct prison_ip *ip4;
@@ -995,6 +1002,7 @@ kern_jail_set(struct thread *td, struct uio *optuio, int flags)
struct vfsopt *opt;
struct vfsoptlist *opts;
struct prison *pr, *deadpr, *dinspr, *inspr, *mypr, *ppr, *tpr;
+ struct ucred *jdcred;
struct vnode *root;
char *domain, *errmsg, *host, *name, *namelc, *p, *path, *uuid;
char *g_path, *osrelstr;
@@ -1008,7 +1016,7 @@ kern_jail_set(struct thread *td, struct uio *optuio, int flags)
int created, cuflags, descend, drflags, enforce;
int error, errmsg_len, errmsg_pos;
int gotchildmax, gotenforce, gothid, gotrsnum, gotslevel;
- int deadid, jid, jsys, len, level;
+ int deadid, jfd_in, jfd_out, jfd_pos, jid, jsys, len, level;
int childmax, osreldt, rsnum, slevel;
#ifdef INET
int ip4s;
@@ -1018,22 +1026,32 @@ kern_jail_set(struct thread *td, struct uio *optuio, int flags)
int ip6s;
bool redo_ip6;
#endif
+ bool maybe_changed;
uint64_t pr_allow, ch_allow, pr_flags, ch_flags;
uint64_t pr_allow_diff;
unsigned tallow;
char numbuf[12];
- error = priv_check(td, PRIV_JAIL_SET);
- if (!error && (flags & JAIL_ATTACH))
- error = priv_check(td, PRIV_JAIL_ATTACH);
- if (error)
- return (error);
mypr = td->td_ucred->cr_prison;
- if ((flags & JAIL_CREATE) && mypr->pr_childmax == 0)
+ if (((flags & (JAIL_CREATE | JAIL_AT_DESC)) == JAIL_CREATE)
+ && mypr->pr_childmax == 0)
return (EPERM);
if (flags & ~JAIL_SET_MASK)
return (EINVAL);
+ if ((flags & (JAIL_USE_DESC | JAIL_AT_DESC))
+ == (JAIL_USE_DESC | JAIL_AT_DESC))
+ return (EINVAL);
+ prison_hold(mypr);
+#ifdef INET
+ ip4 = NULL;
+#endif
+#ifdef INET6
+ ip6 = NULL;
+#endif
+ g_path = NULL;
+ jfp_out = NULL;
+ jfd_out = -1;
/*
* Check all the parameters before committing to anything. Not all
* errors can be caught early, but we may as well try. Also, this
@@ -1046,14 +1064,7 @@ kern_jail_set(struct thread *td, struct uio *optuio, int flags)
*/
error = vfs_buildopts(optuio, &opts);
if (error)
- return (error);
-#ifdef INET
- ip4 = NULL;
-#endif
-#ifdef INET6
- ip6 = NULL;
-#endif
- g_path = NULL;
+ goto done_free;
cuflags = flags & (JAIL_CREATE | JAIL_UPDATE);
if (!cuflags) {
@@ -1062,6 +1073,72 @@ kern_jail_set(struct thread *td, struct uio *optuio, int flags)
goto done_errmsg;
}
+ error = vfs_copyopt(opts, "desc", &jfd_in, sizeof(jfd_in));
+ if (error == ENOENT) {
+ if (flags & (JAIL_USE_DESC | JAIL_AT_DESC | JAIL_GET_DESC |
+ JAIL_OWN_DESC)) {
+ vfs_opterror(opts, "missing desc");
+ goto done_errmsg;
+ }
+ jfd_in = -1;
+ } else if (error != 0)
+ goto done_free;
+ else {
+ if (!(flags & (JAIL_USE_DESC | JAIL_AT_DESC | JAIL_GET_DESC |
+ JAIL_OWN_DESC))) {
+ vfs_opterror(opts, "unexpected desc");
+ goto done_errmsg;
+ }
+ if (flags & JAIL_AT_DESC) {
+ /*
+ * Look up and create jails based on the
+ * descriptor's prison.
+ */
+ prison_free(mypr);
+ error = jaildesc_find(td, jfd_in, &desc_in, &mypr,
+ NULL);
+ if (error != 0) {
+ vfs_opterror(opts, error == ENOENT
+ ? "descriptor to dead jail"
+ : "not a jail descriptor");
+ goto done_errmsg;
+ }
+ /*
+ * Check file permissions using the current
+ * credentials, and operation permissions
+ * using the descriptor's credentials.
+ */
+ error = vaccess(VREG, desc_in->jd_mode, desc_in->jd_uid,
+ desc_in->jd_gid, VEXEC, td->td_ucred);
+ JAILDESC_UNLOCK(desc_in);
+ if (error != 0)
+ goto done_free;
+ if ((flags & JAIL_CREATE) && mypr->pr_childmax == 0) {
+ error = EPERM;
+ goto done_free;
+ }
+ }
+ if (flags & (JAIL_GET_DESC | JAIL_OWN_DESC)) {
+ /* Allocate a jail descriptor to return later. */
+ error = jaildesc_alloc(td, &jfp_out, &jfd_out,
+ flags & JAIL_OWN_DESC);
+ if (error)
+ goto done_free;
+ }
+ }
+
+ /*
+ * Delay the permission check if using a jail descriptor,
+ * until we get the descriptor's credentials.
+ */
+ if (!(flags & JAIL_USE_DESC)) {
+ error = priv_check(td, PRIV_JAIL_SET);
+ if (error == 0 && (flags & JAIL_ATTACH))
+ error = priv_check(td, PRIV_JAIL_ATTACH);
+ if (error)
+ goto done_free;
+ }
+
error = vfs_copyopt(opts, "jid", &jid, sizeof(jid));
if (error == ENOENT)
jid = 0;
@@ -1422,6 +1499,7 @@ kern_jail_set(struct thread *td, struct uio *optuio, int flags)
pr = NULL;
inspr = NULL;
deadpr = NULL;
+ maybe_changed = false;
if (cuflags == JAIL_CREATE && jid == 0 && name != NULL) {
namelc = strrchr(name, '.');
jid = strtoul(namelc != NULL ? namelc + 1 : name, &p, 10);
@@ -1436,7 +1514,57 @@ kern_jail_set(struct thread *td, struct uio *optuio, int flags)
error = EAGAIN;
goto done_deref;
}
- if (jid != 0) {
+ if (flags & JAIL_USE_DESC) {
+ /* Get the jail from its descriptor. */
+ error = jaildesc_find(td, jfd_in, &desc_in, &pr, &jdcred);
+ if (error) {
+ vfs_opterror(opts, error == ENOENT
+ ? "descriptor to dead jail"
+ : "not a jail descriptor");
+ goto done_deref;
+ }
+ drflags |= PD_DEREF;
+ /*
+ * Check file permissions using the current credentials,
+ * and operation permissions using the descriptor's
+ * credentials.
+ */
+ error = vaccess(VREG, desc_in->jd_mode, desc_in->jd_uid,
+ desc_in->jd_gid, VWRITE, td->td_ucred);
+ if (error == 0 && (flags & JAIL_ATTACH))
+ error = vaccess(VREG, desc_in->jd_mode, desc_in->jd_uid,
+ desc_in->jd_gid, VEXEC, td->td_ucred);
+ JAILDESC_UNLOCK(desc_in);
+ if (error == 0)
+ error = priv_check_cred(jdcred, PRIV_JAIL_SET);
+ if (error == 0 && (flags & JAIL_ATTACH))
+ error = priv_check_cred(jdcred, PRIV_JAIL_ATTACH);
+ crfree(jdcred);
+ if (error)
+ goto done_deref;
+ mtx_lock(&pr->pr_mtx);
+ drflags |= PD_LOCKED;
+ if (cuflags == JAIL_CREATE) {
+ error = EEXIST;
+ vfs_opterror(opts, "jail %d already exists",
+ pr->pr_id);
+ goto done_deref;
+ }
+ if (!prison_isalive(pr)) {
+ /* While a jid can be resurrected, the prison
+ * itself cannot.
+ */
+ error = ENOENT;
+ vfs_opterror(opts, "jail %d is dying", pr->pr_id);
+ goto done_deref;
+ }
+ if (jid != 0 && jid != pr->pr_id) {
+ error = EINVAL;
+ vfs_opterror(opts, "cannot change jid");
+ goto done_deref;
+ }
+ jid = pr->pr_id;
+ } else if (jid != 0) {
if (jid < 0) {
error = EINVAL;
vfs_opterror(opts, "negative jid");
@@ -1570,7 +1698,7 @@ kern_jail_set(struct thread *td, struct uio *optuio, int flags)
}
}
}
- /* Update: must provide a jid or name. */
+ /* Update: must provide a desc, jid, or name. */
else if (cuflags == JAIL_UPDATE && pr == NULL) {
error = ENOENT;
vfs_opterror(opts, "update specified no jail");
@@ -1643,6 +1771,7 @@ kern_jail_set(struct thread *td, struct uio *optuio, int flags)
LIST_INSERT_HEAD(&ppr->pr_children, pr, pr_sibling);
for (tpr = ppr; tpr != NULL; tpr = tpr->pr_parent)
tpr->pr_childcount++;
+ pr->pr_klist = knlist_alloc(&pr->pr_mtx);
/* Set some default values, and inherit some from the parent. */
if (namelc == NULL)
@@ -1722,8 +1851,10 @@ kern_jail_set(struct thread *td, struct uio *optuio, int flags)
* Grab a reference for existing prisons, to ensure they
* continue to exist for the duration of the call.
*/
- prison_hold(pr);
- drflags |= PD_DEREF;
+ if (!(drflags & PD_DEREF)) {
+ prison_hold(pr);
+ drflags |= PD_DEREF;
+ }
#if defined(VIMAGE) && (defined(INET) || defined(INET6))
if ((pr->pr_flags & PR_VNET) &&
(ch_flags & (PR_IP4_USER | PR_IP6_USER))) {
@@ -1880,6 +2011,7 @@ kern_jail_set(struct thread *td, struct uio *optuio, int flags)
goto done_deref;
}
}
+ maybe_changed = true;
/* Set the parameters of the prison. */
#ifdef INET
@@ -2112,7 +2244,12 @@ kern_jail_set(struct thread *td, struct uio *optuio, int flags)
* reference via persistence, or is about to gain one via attachment.
*/
if (created) {
- drflags = prison_lock_xlock(pr, drflags);
+ sx_assert(&allprison_lock, SX_XLOCKED);
+ mtx_lock(&ppr->pr_mtx);
+ knote_fork(ppr->pr_klist, pr->pr_id);
+ mtx_unlock(&ppr->pr_mtx);
+ mtx_lock(&pr->pr_mtx);
+ drflags |= PD_LOCKED;
pr->pr_state = PRISON_STATE_ALIVE;
}
@@ -2146,10 +2283,37 @@ kern_jail_set(struct thread *td, struct uio *optuio, int flags)
printf("Warning jail jid=%d: mountd/nfsd requires a separate"
" file system\n", pr->pr_id);
+ /*
+ * Now that the prison is fully created without error, set the
+ * jail descriptor if one was requested. This is the only
+ * parameter that is returned to the caller (except the error
+ * message).
+ */
+ if (jfd_out >= 0) {
+ if (!(drflags & PD_LOCKED)) {
+ mtx_lock(&pr->pr_mtx);
+ drflags |= PD_LOCKED;
+ }
+ jfd_pos = 2 * vfs_getopt_pos(opts, "desc") + 1;
+ if (optuio->uio_segflg == UIO_SYSSPACE)
+ *(int*)optuio->uio_iov[jfd_pos].iov_base = jfd_out;
+ else
+ (void)copyout(&jfd_out,
+ optuio->uio_iov[jfd_pos].iov_base, sizeof(jfd_out));
+ jaildesc_set_prison(jfp_out, pr);
+ }
+
drflags &= ~PD_KILL;
td->td_retval[0] = pr->pr_id;
done_deref:
+ /*
+ * Report changes to kevent. This can happen even if the
+ * system call fails, as changes might have been made before
+ * the failure.
+ */
+ if (maybe_changed && !created)
+ prison_knote(pr, NOTE_JAIL_SET);
/* Release any temporary prison holds and/or locks. */
if (pr != NULL)
prison_deref(pr, drflags);
@@ -2176,15 +2340,21 @@ kern_jail_set(struct thread *td, struct uio *optuio, int flags)
}
}
done_free:
+ /* Clean up other resources. */
#ifdef INET
prison_ip_free(ip4);
#endif
#ifdef INET6
prison_ip_free(ip6);
#endif
+ if (jfp_out != NULL)
+ fdrop(jfp_out, td);
+ if (error && jfd_out >= 0)
+ (void)kern_close(td, jfd_out);
if (g_path != NULL)
free(g_path, M_TEMP);
vfs_freeopts(opts);
+ prison_free(mypr);
return (error);
}
@@ -2329,16 +2499,22 @@ int
kern_jail_get(struct thread *td, struct uio *optuio, int flags)
{
struct bool_flags *bf;
+ struct file *jfp_out;
+ struct jaildesc *desc_in;
struct jailsys_flags *jsf;
struct prison *pr, *mypr;
struct vfsopt *opt;
struct vfsoptlist *opts;
char *errmsg, *name;
int drflags, error, errmsg_len, errmsg_pos, i, jid, len, pos;
+ int jfd_in, jfd_out;
unsigned f;
if (flags & ~JAIL_GET_MASK)
return (EINVAL);
+ if ((flags & (JAIL_USE_DESC | JAIL_AT_DESC))
+ == (JAIL_USE_DESC | JAIL_AT_DESC))
+ return (EINVAL);
/* Get the parameter list. */
error = vfs_buildopts(optuio, &opts);
@@ -2346,13 +2522,81 @@ kern_jail_get(struct thread *td, struct uio *optuio, int flags)
return (error);
errmsg_pos = vfs_getopt_pos(opts, "errmsg");
mypr = td->td_ucred->cr_prison;
+ prison_hold(mypr);
pr = NULL;
+ jfp_out = NULL;
+ jfd_out = -1;
/*
- * Find the prison specified by one of: lastjid, jid, name.
+ * Find the prison specified by one of: desc, lastjid, jid, name.
*/
sx_slock(&allprison_lock);
drflags = PD_LIST_SLOCKED;
+
+ error = vfs_copyopt(opts, "desc", &jfd_in, sizeof(jfd_in));
+ if (error == ENOENT) {
+ if (flags & (JAIL_AT_DESC | JAIL_GET_DESC | JAIL_OWN_DESC)) {
+ vfs_opterror(opts, "missing desc");
+ goto done;
+ }
+ } else if (error == 0) {
+ if (!(flags & (JAIL_USE_DESC | JAIL_AT_DESC | JAIL_GET_DESC |
+ JAIL_OWN_DESC))) {
+ vfs_opterror(opts, "unexpected desc");
+ goto done;
+ }
+ if (flags & JAIL_USE_DESC) {
+ /* Get the jail from its descriptor. */
+ error = jaildesc_find(td, jfd_in, &desc_in, &pr, NULL);
+ if (error) {
+ vfs_opterror(opts, error == ENOENT
+ ? "descriptor to dead jail"
+ : "not a jail descriptor");
+ goto done;
+ }
+ drflags |= PD_DEREF;
+ error = vaccess(VREG, desc_in->jd_mode, desc_in->jd_uid,
+ desc_in->jd_gid, VREAD, td->td_ucred);
+ JAILDESC_UNLOCK(desc_in);
+ if (error != 0)
+ goto done;
+ mtx_lock(&pr->pr_mtx);
+ drflags |= PD_LOCKED;
+ if (!(prison_isalive(pr) || (flags & JAIL_DYING))) {
+ error = ENOENT;
+ vfs_opterror(opts, "jail %d is dying",
+ pr->pr_id);
+ goto done;
+ }
+ goto found_prison;
+ }
+ if (flags & JAIL_AT_DESC) {
+ /* Look up jails based on the descriptor's prison. */
+ prison_free(mypr);
+ error = jaildesc_find(td, jfd_in, &desc_in, &mypr,
+ NULL);
+ if (error != 0) {
+ vfs_opterror(opts, error == ENOENT
+ ? "descriptor to dead jail"
+ : "not a jail descriptor");
+ goto done;
+ }
+ error = vaccess(VREG, desc_in->jd_mode, desc_in->jd_uid,
+ desc_in->jd_gid, VEXEC, td->td_ucred);
+ JAILDESC_UNLOCK(desc_in);
+ if (error != 0)
+ goto done;
+ }
+ if (flags & (JAIL_GET_DESC | JAIL_OWN_DESC)) {
+ /* Allocate a jail descriptor to return later. */
+ error = jaildesc_alloc(td, &jfp_out, &jfd_out,
+ flags & JAIL_OWN_DESC);
+ if (error)
+ goto done;
+ }
+ } else
+ goto done;
+
error = vfs_copyopt(opts, "lastjid", &jid, sizeof(jid));
if (error == 0) {
TAILQ_FOREACH(pr, &allprison, pr_list) {
@@ -2421,9 +2665,17 @@ kern_jail_get(struct thread *td, struct uio *optuio, int flags)
found_prison:
/* Get the parameters of the prison. */
- prison_hold(pr);
- drflags |= PD_DEREF;
+ if (!(drflags & PD_DEREF)) {
+ prison_hold(pr);
+ drflags |= PD_DEREF;
+ }
td->td_retval[0] = pr->pr_id;
+ if (jfd_out >= 0) {
+ error = vfs_setopt(opts, "desc", &jfd_out, sizeof(jfd_out));
+ if (error != 0 && error != ENOENT)
+ goto done;
+ jaildesc_set_prison(jfp_out, pr);
+ }
error = vfs_setopt(opts, "jid", &pr->pr_id, sizeof(pr->pr_id));
if (error != 0 && error != ENOENT)
goto done;
@@ -2603,6 +2855,13 @@ kern_jail_get(struct thread *td, struct uio *optuio, int flags)
prison_deref(pr, drflags);
else if (drflags & PD_LIST_SLOCKED)
sx_sunlock(&allprison_lock);
+ else if (drflags & PD_LIST_XLOCKED)
+ sx_xunlock(&allprison_lock);
+ /* Clean up other resources. */
+ if (jfp_out != NULL)
+ (void)fdrop(jfp_out, td);
+ if (error && jfd_out >= 0)
+ (void)kern_close(td, jfd_out);
if (error && errmsg_pos >= 0) {
/* Write the error message back to userspace. */
vfs_getopt(opts, "errmsg", (void **)&errmsg, &errmsg_len);
@@ -2619,6 +2878,7 @@ kern_jail_get(struct thread *td, struct uio *optuio, int flags)
}
}
vfs_freeopts(opts);
+ prison_free(mypr);
return (error);
}
@@ -2643,14 +2903,63 @@ sys_jail_remove(struct thread *td, struct jail_remove_args *uap)
sx_xunlock(&allprison_lock);
return (EINVAL);
}
+ prison_hold(pr);
+ prison_remove(pr);
+ return (0);
+}
+
+/*
+ * struct jail_remove_jd_args {
+ * int fd;
+ * };
+ */
+int
+sys_jail_remove_jd(struct thread *td, struct jail_remove_jd_args *uap)
+{
+ struct jaildesc *jd;
+ struct prison *pr;
+ struct ucred *jdcred;
+ int error;
+
+ error = jaildesc_find(td, uap->fd, &jd, &pr, &jdcred);
+ if (error)
+ return (error);
+ /*
+ * Check file permissions using the current credentials, and
+ * operation permissions using the descriptor's credentials.
+ */
+ error = vaccess(VREG, jd->jd_mode, jd->jd_uid, jd->jd_gid, VWRITE,
+ td->td_ucred);
+ JAILDESC_UNLOCK(jd);
+ if (error == 0)
+ error = priv_check_cred(jdcred, PRIV_JAIL_REMOVE);
+ crfree(jdcred);
+ if (error) {
+ prison_free(pr);
+ return (error);
+ }
+ sx_xlock(&allprison_lock);
+ mtx_lock(&pr->pr_mtx);
+ prison_remove(pr);
+ return (0);
+}
+
+/*
+ * Begin the removal process for a prison. The allprison lock should
+ * be held exclusively, and the prison should be both locked and held.
+ */
+void
+prison_remove(struct prison *pr)
+{
+ sx_assert(&allprison_lock, SA_XLOCKED);
+ mtx_assert(&pr->pr_mtx, MA_OWNED);
if (!prison_isalive(pr)) {
/* Silently ignore already-dying prisons. */
mtx_unlock(&pr->pr_mtx);
sx_xunlock(&allprison_lock);
- return (0);
+ return;
}
- prison_deref(pr, PD_KILL | PD_LOCKED | PD_LIST_XLOCKED);
- return (0);
+ prison_deref(pr, PD_KILL | PD_DEREF | PD_LOCKED | PD_LIST_XLOCKED);
}
/*
@@ -2685,6 +2994,53 @@ sys_jail_attach(struct thread *td, struct jail_attach_args *uap)
return (do_jail_attach(td, pr, PD_LOCKED | PD_LIST_SLOCKED));
}
+/*
+ * struct jail_attach_jd_args {
+ * int fd;
+ * };
+ */
+int
+sys_jail_attach_jd(struct thread *td, struct jail_attach_jd_args *uap)
+{
+ struct jaildesc *jd;
+ struct prison *pr;
+ struct ucred *jdcred;
+ int drflags, error;
+
+ sx_slock(&allprison_lock);
+ drflags = PD_LIST_SLOCKED;
+ error = jaildesc_find(td, uap->fd, &jd, &pr, &jdcred);
+ if (error)
+ goto fail;
+ drflags |= PD_DEREF;
+ /*
+ * Check file permissions using the current credentials, and
+ * operation permissions using the descriptor's credentials.
+ */
+ error = vaccess(VREG, jd->jd_mode, jd->jd_uid, jd->jd_gid, VEXEC,
+ td->td_ucred);
+ JAILDESC_UNLOCK(jd);
+ if (error == 0)
+ error = priv_check_cred(jdcred, PRIV_JAIL_ATTACH);
+ crfree(jdcred);
+ if (error)
+ goto fail;
+ mtx_lock(&pr->pr_mtx);
+ drflags |= PD_LOCKED;
+
+ /* Do not allow a process to attach to a prison that is not alive. */
+ if (!prison_isalive(pr)) {
+ error = EINVAL;
+ goto fail;
+ }
+
+ return (do_jail_attach(td, pr, drflags));
+
+ fail:
+ prison_deref(pr, drflags);
+ return (error);
+}
+
static int
do_jail_attach(struct thread *td, struct prison *pr, int drflags)
{
@@ -2703,9 +3059,12 @@ do_jail_attach(struct thread *td, struct prison *pr, int drflags)
* a process root from one prison, but attached to the jail
* of another.
*/
- prison_hold(pr);
+ if (!(drflags & PD_DEREF)) {
+ prison_hold(pr);
+ drflags |= PD_DEREF;
+ }
refcount_acquire(&pr->pr_uref);
- drflags |= PD_DEREF | PD_DEUREF;
+ drflags |= PD_DEUREF;
mtx_unlock(&pr->pr_mtx);
drflags &= ~PD_LOCKED;
@@ -2755,6 +3114,7 @@ do_jail_attach(struct thread *td, struct prison *pr, int drflags)
prison_proc_relink(oldcred->cr_prison, pr, p);
prison_deref(oldcred->cr_prison, drflags);
crfree(oldcred);
+ prison_knote(pr, NOTE_JAIL_ATTACH | td->td_proc->p_pid);
/*
* If the prison was killed while changing credentials, die along
@@ -3182,9 +3542,10 @@ prison_deref(struct prison *pr, int flags)
refcount_load(&prison0.pr_uref) > 0,
("prison0 pr_uref=0"));
pr->pr_state = PRISON_STATE_DYING;
+ prison_cleanup_locked(pr);
mtx_unlock(&pr->pr_mtx);
flags &= ~PD_LOCKED;
- prison_cleanup(pr);
+ prison_cleanup_unlocked(pr);
}
}
}
@@ -3327,8 +3688,9 @@ prison_deref_kill(struct prison *pr, struct prisonlist *freeprison)
}
if (!(cpr->pr_flags & PR_REMOVE))
continue;
- prison_cleanup(cpr);
+ prison_cleanup_unlocked(cpr);
mtx_lock(&cpr->pr_mtx);
+ prison_cleanup_locked(cpr);
cpr->pr_flags &= ~PR_REMOVE;
if (cpr->pr_flags & PR_PERSIST) {
cpr->pr_flags &= ~PR_PERSIST;
@@ -3363,8 +3725,9 @@ prison_deref_kill(struct prison *pr, struct prisonlist *freeprison)
if (rpr != NULL)
LIST_REMOVE(rpr, pr_sibling);
- prison_cleanup(pr);
+ prison_cleanup_unlocked(pr);
mtx_lock(&pr->pr_mtx);
+ prison_cleanup_locked(pr);
if (pr->pr_flags & PR_PERSIST) {
pr->pr_flags &= ~PR_PERSIST;
prison_proc_free_not_last(pr);
@@ -3411,10 +3774,22 @@ prison_lock_xlock(struct prison *pr, int flags)
/*
* Release a prison's resources when it starts dying (when the last user
- * reference is dropped, or when it is killed).
+ * reference is dropped, or when it is killed). Two functions are called,
+ * for work that requires a locked prison or an unlocked one.
*/
static void
-prison_cleanup(struct prison *pr)
+prison_cleanup_locked(struct prison *pr)
+{
+ sx_assert(&allprison_lock, SA_XLOCKED);
+ mtx_assert(&pr->pr_mtx, MA_OWNED);
+ prison_knote(pr, NOTE_JAIL_REMOVE);
+ knlist_detach(pr->pr_klist);
+ jaildesc_prison_cleanup(pr);
+ pr->pr_klist = NULL;
+}
+
+static void
+prison_cleanup_unlocked(struct prison *pr)
{
sx_assert(&allprison_lock, SA_XLOCKED);
mtx_assert(&pr->pr_mtx, MA_NOTOWNED);
@@ -4616,6 +4991,7 @@ sysctl_jail_param(SYSCTL_HANDLER_ARGS)
* jail creation time but cannot be changed in an existing jail.
*/
SYSCTL_JAIL_PARAM(, jid, CTLTYPE_INT | CTLFLAG_RDTUN, "I", "Jail ID");
+SYSCTL_JAIL_PARAM(, desc, CTLTYPE_INT | CTLFLAG_RW, "I", "Jail descriptor");
SYSCTL_JAIL_PARAM(, parent, CTLTYPE_INT | CTLFLAG_RD, "I", "Jail parent ID");
SYSCTL_JAIL_PARAM_STRING(, name, CTLFLAG_RW, MAXHOSTNAMELEN, "Jail name");
SYSCTL_JAIL_PARAM_STRING(, path, CTLFLAG_RDTUN, MAXPATHLEN, "Jail root path");
@@ -5039,6 +5415,22 @@ prison_racct_detach(struct prison *pr)
}
#endif /* RACCT */
+/*
+ * Submit a knote for a prison, locking if necessary.
+ */
+static void
+prison_knote(struct prison *pr, long hint)
+{
+ int locked;
+
+ locked = mtx_owned(&pr->pr_mtx);
+ if (!locked)
+ mtx_lock(&pr->pr_mtx);
+ KNOTE_LOCKED(pr->pr_klist, hint);
+ if (!locked)
+ mtx_unlock(&pr->pr_mtx);
+}
+
#ifdef DDB
static void
diff --git a/sys/kern/kern_jaildesc.c b/sys/kern/kern_jaildesc.c
new file mode 100644
index 000000000000..e00ec9a4bfff
--- /dev/null
+++ b/sys/kern/kern_jaildesc.c
@@ -0,0 +1,337 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2025 James Gritton.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/param.h>
+#include <sys/fcntl.h>
+#include <sys/file.h>
+#include <sys/filedesc.h>
+#include <sys/kernel.h>
+#include <sys/jail.h>
+#include <sys/jaildesc.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/mutex.h>
+#include <sys/priv.h>
+#include <sys/stat.h>
+#include <sys/sysproto.h>
+#include <sys/systm.h>
+#include <sys/ucred.h>
+#include <sys/vnode.h>
+
+MALLOC_DEFINE(M_JAILDESC, "jaildesc", "jail descriptors");
+
+static fo_stat_t jaildesc_stat;
+static fo_close_t jaildesc_close;
+static fo_chmod_t jaildesc_chmod;
+static fo_chown_t jaildesc_chown;
+static fo_fill_kinfo_t jaildesc_fill_kinfo;
+static fo_cmp_t jaildesc_cmp;
+
+static struct fileops jaildesc_ops = {
+ .fo_read = invfo_rdwr,
+ .fo_write = invfo_rdwr,
+ .fo_truncate = invfo_truncate,
+ .fo_ioctl = invfo_ioctl,
+ .fo_poll = invfo_poll,
+ .fo_kqfilter = invfo_kqfilter,
+ .fo_stat = jaildesc_stat,
+ .fo_close = jaildesc_close,
+ .fo_chmod = jaildesc_chmod,
+ .fo_chown = jaildesc_chown,
+ .fo_sendfile = invfo_sendfile,
+ .fo_fill_kinfo = jaildesc_fill_kinfo,
+ .fo_cmp = jaildesc_cmp,
+ .fo_flags = DFLAG_PASSABLE,
+};
+
+/*
+ * Given a jail descriptor number, return the jaildesc, its prison,
+ * and its credential. The jaildesc will be returned locked, and
+ * prison and the credential will be returned held.
+ */
+int
+jaildesc_find(struct thread *td, int fd, struct jaildesc **jdp,
+ struct prison **prp, struct ucred **ucredp)
+{
+ struct file *fp;
+ struct jaildesc *jd;
+ struct prison *pr;
+ int error;
+
+ error = fget(td, fd, &cap_no_rights, &fp);
+ if (error != 0)
+ return (error);
+ if (fp->f_type != DTYPE_JAILDESC) {
+ error = EBADF;
+ goto out;
+ }
+ jd = fp->f_data;
+ JAILDESC_LOCK(jd);
+ pr = jd->jd_prison;
+ if (pr == NULL || !prison_isvalid(pr)) {
+ error = ENOENT;
+ JAILDESC_UNLOCK(jd);
+ goto out;
+ }
+ prison_hold(pr);
+ *prp = pr;
+ if (jdp != NULL)
+ *jdp = jd;
+ else
+ JAILDESC_UNLOCK(jd);
+ if (ucredp != NULL)
+ *ucredp = crhold(fp->f_cred);
+ out:
+ fdrop(fp, td);
+ return (error);
+}
+
+/*
+ * Allocate a new jail decriptor, not yet associated with a prison.
+ * Return the file pointer (with a reference held) and the descriptor
+ * number.
+ */
+int
+jaildesc_alloc(struct thread *td, struct file **fpp, int *fdp, int owning)
+{
+ struct file *fp;
+ struct jaildesc *jd;
+ int error;
+ mode_t mode;
+
+ if (owning) {
+ error = priv_check(td, PRIV_JAIL_REMOVE);
+ if (error != 0)
+ return (error);
+ mode = S_ISTXT;
+ } else
+ mode = 0;
+ jd = malloc(sizeof(*jd), M_JAILDESC, M_WAITOK | M_ZERO);
+ error = falloc_caps(td, &fp, fdp, 0, NULL);
+ finit(fp, priv_check_cred(fp->f_cred, PRIV_JAIL_SET) == 0
+ ? FREAD | FWRITE : FREAD, DTYPE_JAILDESC, jd, &jaildesc_ops);
+ if (error != 0) {
+ free(jd, M_JAILDESC);
+ return (error);
+ }
+ JAILDESC_LOCK_INIT(jd);
+ jd->jd_uid = fp->f_cred->cr_uid;
+ jd->jd_gid = fp->f_cred->cr_gid;
+ jd->jd_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH | mode
+ | (priv_check(td, PRIV_JAIL_SET) == 0 ? S_IWUSR | S_IXUSR : 0)
+ | (priv_check(td, PRIV_JAIL_ATTACH) == 0 ? S_IXUSR : 0);
+ *fpp = fp;
+ return (0);
+}
+
+/*
+ * Assocate a jail descriptor with its prison.
+ */
+void
+jaildesc_set_prison(struct file *fp, struct prison *pr)
+{
+ struct jaildesc *jd;
+
+ mtx_assert(&pr->pr_mtx, MA_OWNED);
+ jd = fp->f_data;
+ JAILDESC_LOCK(jd);
+ jd->jd_prison = pr;
+ LIST_INSERT_HEAD(&pr->pr_descs, jd, jd_list);
+ prison_hold(pr);
+ JAILDESC_UNLOCK(jd);
+}
+
+/*
+ * Detach the all jail descriptors from a prison.
+ */
+void
+jaildesc_prison_cleanup(struct prison *pr)
+{
+ struct jaildesc *jd;
+
+ mtx_assert(&pr->pr_mtx, MA_OWNED);
+ while ((jd = LIST_FIRST(&pr->pr_descs))) {
+ JAILDESC_LOCK(jd);
+ LIST_REMOVE(jd, jd_list);
+ jd->jd_prison = NULL;
+ JAILDESC_UNLOCK(jd);
+ prison_free(pr);
+ }
+}
+
+static int
+jaildesc_close(struct file *fp, struct thread *td)
+{
+ struct jaildesc *jd;
+ struct prison *pr;
+
+ jd = fp->f_data;
+ fp->f_data = NULL;
+ if (jd != NULL) {
+ JAILDESC_LOCK(jd);
+ pr = jd->jd_prison;
+ if (pr != NULL) {
+ /*
+ * Free or remove the associated prison.
+ * This requires a second check after re-
+ * ordering locks. This jaildesc can remain
+ * unlocked once we have a prison reference,
+ * because that prison is the only place that
+ * still points back to it.
+ */
+ prison_hold(pr);
+ JAILDESC_UNLOCK(jd);
+ if (jd->jd_mode & S_ISTXT) {
+ sx_xlock(&allprison_lock);
+ prison_lock(pr);
+ if (jd->jd_prison != NULL) {
+ /*
+ * Unlink the prison, but don't free
+ * it; that will be done as part of
+ * of prison_remove.
+ */
+ LIST_REMOVE(jd, jd_list);
+ prison_remove(pr);
+ } else {
+ prison_unlock(pr);
+ sx_xunlock(&allprison_lock);
+ }
+ } else {
+ prison_lock(pr);
+ if (jd->jd_prison != NULL) {
+ LIST_REMOVE(jd, jd_list);
+ prison_free(pr);
+ }
+ prison_unlock(pr);
+ }
+ prison_free(pr);
+ }
+ JAILDESC_LOCK_DESTROY(jd);
+ free(jd, M_JAILDESC);
+ }
+ finit(fp, 0, DTYPE_NONE, NULL, &badfileops);
+ return (0);
+}
+
+static int
+jaildesc_stat(struct file *fp, struct stat *sb, struct ucred *active_cred)
+{
+ struct jaildesc *jd;
+
+ bzero(sb, sizeof(struct stat));
+ jd = fp->f_data;
+ JAILDESC_LOCK(jd);
+ if (jd->jd_prison != NULL) {
+ sb->st_ino = jd->jd_prison ? jd->jd_prison->pr_id : 0;
+ sb->st_uid = jd->jd_uid;
+ sb->st_gid = jd->jd_gid;
+ sb->st_mode = jd->jd_mode;
+ } else
+ sb->st_mode = S_IFREG;
+ JAILDESC_UNLOCK(jd);
+ return (0);
+}
+
+static int
+jaildesc_chmod(struct file *fp, mode_t mode, struct ucred *active_cred,
+ struct thread *td)
+{
+ struct jaildesc *jd;
+ int error;
+
+ /* Reject permissions that the creator doesn't have. */
+ if (((mode & (S_IWUSR | S_IWGRP | S_IWOTH))
+ && priv_check_cred(fp->f_cred, PRIV_JAIL_SET) != 0)
+ || ((mode & (S_IXUSR | S_IXGRP | S_IXOTH))
+ && priv_check_cred(fp->f_cred, PRIV_JAIL_ATTACH) != 0
+ && priv_check_cred(fp->f_cred, PRIV_JAIL_SET) != 0)
+ || ((mode & S_ISTXT)
+ && priv_check_cred(fp->f_cred, PRIV_JAIL_REMOVE) != 0))
+ return (EPERM);
+ if (mode & (S_ISUID | S_ISGID))
+ return (EINVAL);
+ jd = fp->f_data;
+ JAILDESC_LOCK(jd);
+ error = vaccess(VREG, jd->jd_mode, jd->jd_uid, jd->jd_gid, VADMIN,
+ active_cred);
+ if (error == 0)
+ jd->jd_mode = S_IFREG | (mode & ALLPERMS);
+ JAILDESC_UNLOCK(jd);
+ return (error);
+}
+
+static int
+jaildesc_chown(struct file *fp, uid_t uid, gid_t gid, struct ucred *active_cred,
+ struct thread *td)
+{
+ struct jaildesc *jd;
+ int error;
+
+ error = 0;
+ jd = fp->f_data;
+ JAILDESC_LOCK(jd);
+ if (uid == (uid_t)-1)
+ uid = jd->jd_uid;
+ if (gid == (gid_t)-1)
+ gid = jd->jd_gid;
+ if ((uid != jd->jd_uid && uid != active_cred->cr_uid) ||
+ (gid != jd->jd_gid && !groupmember(gid, active_cred)))
+ error = priv_check_cred(active_cred, PRIV_VFS_CHOWN);
+ if (error == 0) {
+ jd->jd_uid = uid;
+ jd->jd_gid = gid;
+ }
+ JAILDESC_UNLOCK(jd);
+ return (error);
+}
+
+static int
+jaildesc_fill_kinfo(struct file *fp, struct kinfo_file *kif,
+ struct filedesc *fdp)
+{
+ return (EINVAL);
+}
+
+static int
+jaildesc_cmp(struct file *fp1, struct file *fp2, struct thread *td)
+{
+ struct jaildesc *jd1, *jd2;
+ int jid1, jid2;
+
+ if (fp2->f_type != DTYPE_JAILDESC)
+ return (3);
+ jd1 = fp1->f_data;
+ JAILDESC_LOCK(jd1);
+ jid1 = jd1->jd_prison ? (uintptr_t)jd1->jd_prison->pr_id : 0;
+ JAILDESC_UNLOCK(jd1);
+ jd2 = fp2->f_data;
+ JAILDESC_LOCK(jd2);
+ jid2 = jd2->jd_prison ? (uintptr_t)jd2->jd_prison->pr_id : 0;
+ JAILDESC_UNLOCK(jd2);
+ return (kcmp_cmp(jid1, jid2));
+}
diff --git a/sys/kern/syscalls.c b/sys/kern/syscalls.c
index 4122f9261871..4cef89cd5219 100644
--- a/sys/kern/syscalls.c
+++ b/sys/kern/syscalls.c
@@ -602,4 +602,6 @@ const char *syscallnames[] = {
"inotify_rm_watch", /* 594 = inotify_rm_watch */
"getgroups", /* 595 = getgroups */
"setgroups", /* 596 = setgroups */
+ "jail_attach_jd", /* 597 = jail_attach_jd */
+ "jail_remove_jd", /* 598 = jail_remove_jd */
};
diff --git a/sys/kern/syscalls.master b/sys/kern/syscalls.master
index fa64597d14a5..911f9093824b 100644
--- a/sys/kern/syscalls.master
+++ b/sys/kern/syscalls.master
@@ -3383,5 +3383,15 @@
_In_reads_(gidsetsize) const gid_t *gidset
);
}
+597 AUE_JAIL_ATTACH STD {
+ int jail_attach_jd(
+ int fd
+ );
+ }
+598 AUE_JAIL_REMOVE STD {
+ int jail_remove_jd(
+ int fd
+ );
+ }
; vim: syntax=off
diff --git a/sys/kern/systrace_args.c b/sys/kern/systrace_args.c
index 2b1ea9eed8d4..e28fef931ea8 100644
--- a/sys/kern/systrace_args.c
+++ b/sys/kern/systrace_args.c
@@ -3500,6 +3500,20 @@ systrace_args(int sysnum, void *params, uint64_t *uarg, int *n_args)
*n_args = 2;
break;
}
+ /* jail_attach_jd */
+ case 597: {
+ struct jail_attach_jd_args *p = params;
+ iarg[a++] = p->fd; /* int */
+ *n_args = 1;
+ break;
+ }
+ /* jail_remove_jd */
+ case 598: {
+ struct jail_remove_jd_args *p = params;
+ iarg[a++] = p->fd; /* int */
+ *n_args = 1;
+ break;
+ }
default:
*n_args = 0;
break;
@@ -9367,6 +9381,26 @@ systrace_entry_setargdesc(int sysnum, int ndx, char *desc, size_t descsz)
break;
};
break;
+ /* jail_attach_jd */
+ case 597:
+ switch (ndx) {
+ case 0:
+ p = "int";
+ break;
+ default:
+ break;
+ };
+ break;
+ /* jail_remove_jd */
+ case 598:
+ switch (ndx) {
+ case 0:
+ p = "int";
+ break;
+ default:
+ break;
+ };
+ break;
default:
break;
};
@@ -11365,6 +11399,16 @@ systrace_return_setargdesc(int sysnum, int ndx, char *desc, size_t descsz)
if (ndx == 0 || ndx == 1)
p = "int";
break;
+ /* jail_attach_jd */
+ case 597:
+ if (ndx == 0 || ndx == 1)
+ p = "int";
+ break;
+ /* jail_remove_jd */
+ case 598:
+ if (ndx == 0 || ndx == 1)
+ p = "int";
+ break;
default:
break;
};
diff --git a/sys/modules/sound/driver/hda/Makefile b/sys/modules/sound/driver/hda/Makefile
index 0eec98fc53e1..1e137dc5671c 100644
--- a/sys/modules/sound/driver/hda/Makefile
+++ b/sys/modules/sound/driver/hda/Makefile
@@ -2,7 +2,7 @@
KMOD= snd_hda
SRCS= device_if.h bus_if.h pci_if.h channel_if.h mixer_if.h hdac_if.h
-SRCS+= hdaa.c hdaa.h hdaa_patches.c hdac.c hdac_if.h hdac_if.c
-SRCS+= hdacc.c hdac_private.h hdac_reg.h hda_reg.h hdac.h
+SRCS+= hdaa.c hdaa.h hdaa_patches.c hdacc.c hdac.c hdac_if.c
+SRCS+= hdac_private.h hdac_reg.h hda_reg.h hdac.h
.include <bsd.kmod.mk>
diff --git a/sys/netinet/tcp_sack.c b/sys/netinet/tcp_sack.c
index b6c55fac50b3..6e08ad2796a8 100644
--- a/sys/netinet/tcp_sack.c
+++ b/sys/netinet/tcp_sack.c
@@ -128,8 +128,25 @@ SYSCTL_INT(_net_inet_tcp_sack, OID_AUTO, enable, CTLFLAG_VNET | CTLFLAG_RW,
"Enable/Disable TCP SACK support");
VNET_DEFINE(int, tcp_do_newsack) = 1;
-SYSCTL_INT(_net_inet_tcp_sack, OID_AUTO, revised, CTLFLAG_VNET | CTLFLAG_RW,
- &VNET_NAME(tcp_do_newsack), 0,
+
+static int
+sysctl_net_inet_tcp_sack_revised(SYSCTL_HANDLER_ARGS)
+{
+ int error;
+ int new;
+
+ new = V_tcp_do_newsack;
+ error = sysctl_handle_int(oidp, &new, 0, req);
+ if (error == 0 && req->newptr) {
+ V_tcp_do_newsack = new;
+ gone_in(16, "net.inet.tcp.sack.revised will be deprecated."
+ " net.inet.tcp.sack.enable will always follow RFC6675 SACK.\n");
+ }
+ return (error);
+}
+
+SYSCTL_PROC(_net_inet_tcp_sack, OID_AUTO, revised, CTLFLAG_VNET | CTLFLAG_RW | CTLTYPE_INT,
+ &VNET_NAME(tcp_do_newsack), 0, sysctl_net_inet_tcp_sack_revised, "CU",
"Use revised SACK loss recovery per RFC 6675");
VNET_DEFINE(int, tcp_do_lrd) = 1;
diff --git a/sys/powerpc/conf/GENERIC b/sys/powerpc/conf/GENERIC
index 7c7d2809d784..1346fa8f9476 100644
--- a/sys/powerpc/conf/GENERIC
+++ b/sys/powerpc/conf/GENERIC
@@ -90,8 +90,6 @@ options RCTL # Resource limits
# Debugging support. Always need this:
options KDB # Enable kernel debugger support.
options KDB_TRACE # Print a stack trace for a panic.
-# For full debugger support use (turn off in stable branch):
-include "std.debug"
# Kernel dump features.
options EKCD # Support for encrypted kernel dumps
diff --git a/sys/powerpc/conf/GENERIC-NODEBUG b/sys/powerpc/conf/GENERIC-NODEBUG
deleted file mode 100644
index 0761376a8160..000000000000
--- a/sys/powerpc/conf/GENERIC-NODEBUG
+++ /dev/null
@@ -1,31 +0,0 @@
-#
-# GENERIC-NODEBUG -- WITNESS and INVARIANTS free kernel configuration file
-# for FreeBSD/powerpc
-#
-# This configuration file removes several debugging options, including
-# WITNESS and INVARIANTS checking, which are known to have significant
-# performance impact on running systems. When benchmarking new features
-# this kernel should be used instead of the standard GENERIC.
-# This kernel configuration should never appear outside of the HEAD
-# of the FreeBSD tree.
-#
-# For more information on this file, please read the config(5) manual page,
-# and/or the handbook section on Kernel Configuration Files:
-#
-# https://docs.freebsd.org/en/books/handbook/kernelconfig/#kernelconfig-config
-#
-# The handbook is also available locally in /usr/share/doc/handbook
-# if you've installed the doc distribution, otherwise always see the
-# FreeBSD World Wide Web server (https://www.FreeBSD.org/) for the
-# latest information.
-#
-# An exhaustive list of options and more detailed explanations of the
-# device lines is also present in the ../../conf/NOTES and NOTES files.
-# If you are in doubt as to the purpose or necessity of a line, check first
-# in NOTES.
-#
-
-include GENERIC
-include "std.nodebug"
-
-ident GENERIC-NODEBUG
diff --git a/sys/powerpc/conf/GENERIC64 b/sys/powerpc/conf/GENERIC64
index 630c88b97dd7..6675a4d299f4 100644
--- a/sys/powerpc/conf/GENERIC64
+++ b/sys/powerpc/conf/GENERIC64
@@ -100,8 +100,6 @@ options RCTL # Resource limits
# Debugging support. Always need this:
options KDB # Enable kernel debugger support.
options KDB_TRACE # Print a stack trace for a panic.
-# For full debugger support use (turn off in stable branch):
-include "std.debug"
# Kernel dump features.
options EKCD # Support for encrypted kernel dumps
diff --git a/sys/powerpc/conf/GENERIC64-NODEBUG b/sys/powerpc/conf/GENERIC64-NODEBUG
deleted file mode 100644
index a4c3dbd856e2..000000000000
--- a/sys/powerpc/conf/GENERIC64-NODEBUG
+++ /dev/null
@@ -1,31 +0,0 @@
-#
-# GENERIC64-NODEBUG -- WITNESS and INVARIANTS free kernel configuration file
-# for FreeBSD/powerpc
-#
-# This configuration file removes several debugging options, including
-# WITNESS and INVARIANTS checking, which are known to have significant
-# performance impact on running systems. When benchmarking new features
-# this kernel should be used instead of the standard GENERIC64.
-# This kernel configuration should never appear outside of the HEAD
-# of the FreeBSD tree.
-#
-# For more information on this file, please read the config(5) manual page,
-# and/or the handbook section on Kernel Configuration Files:
-#
-# https://docs.freebsd.org/en/books/handbook/kernelconfig/#kernelconfig-config
-#
-# The handbook is also available locally in /usr/share/doc/handbook
-# if you've installed the doc distribution, otherwise always see the
-# FreeBSD World Wide Web server (https://www.FreeBSD.org/) for the
-# latest information.
-#
-# An exhaustive list of options and more detailed explanations of the
-# device lines is also present in the ../../conf/NOTES and NOTES files.
-# If you are in doubt as to the purpose or necessity of a line, check first
-# in NOTES.
-#
-
-include GENERIC64
-include "std.nodebug"
-
-ident GENERIC64-NODEBUG
diff --git a/sys/powerpc/conf/GENERIC64LE b/sys/powerpc/conf/GENERIC64LE
index eb9a9441425d..f6f7d44c424b 100644
--- a/sys/powerpc/conf/GENERIC64LE
+++ b/sys/powerpc/conf/GENERIC64LE
@@ -96,8 +96,6 @@ options RCTL # Resource limits
# Debugging support. Always need this:
options KDB # Enable kernel debugger support.
options KDB_TRACE # Print a stack trace for a panic.
-# For full debugger support use (turn off in stable branch):
-include "std.debug"
# Kernel dump features.
options EKCD # Support for encrypted kernel dumps
diff --git a/sys/powerpc/conf/GENERIC64LE-NODEBUG b/sys/powerpc/conf/GENERIC64LE-NODEBUG
deleted file mode 100644
index fd2d3ca84a19..000000000000
--- a/sys/powerpc/conf/GENERIC64LE-NODEBUG
+++ /dev/null
@@ -1,31 +0,0 @@
-#
-# GENERIC64LE-NODEBUG -- WITNESS and INVARIANTS free kernel configuration file
-# for FreeBSD/powerpc
-#
-# This configuration file removes several debugging options, including
-# WITNESS and INVARIANTS checking, which are known to have significant
-# performance impact on running systems. When benchmarking new features
-# this kernel should be used instead of the standard GENERIC64LE.
-# This kernel configuration should never appear outside of the HEAD
-# of the FreeBSD tree.
-#
-# For more information on this file, please read the config(5) manual page,
-# and/or the handbook section on Kernel Configuration Files:
-#
-# https://docs.freebsd.org/en/books/handbook/kernelconfig/#kernelconfig-config
-#
-# The handbook is also available locally in /usr/share/doc/handbook
-# if you've installed the doc distribution, otherwise always see the
-# FreeBSD World Wide Web server (https://www.FreeBSD.org/) for the
-# latest information.
-#
-# An exhaustive list of options and more detailed explanations of the
-# device lines is also present in the ../../conf/NOTES and NOTES files.
-# If you are in doubt as to the purpose or necessity of a line, check first
-# in NOTES.
-#
-
-include GENERIC64LE
-include "std.nodebug"
-
-ident GENERIC64LE-NODEBUG
diff --git a/sys/riscv/conf/GENERIC b/sys/riscv/conf/GENERIC
index 2ff711e80127..187e7396e884 100644
--- a/sys/riscv/conf/GENERIC
+++ b/sys/riscv/conf/GENERIC
@@ -167,8 +167,6 @@ device xilinx_spi # Xilinx AXI SPI Controller
# Debugging support. Always need this:
options KDB # Enable kernel debugger support.
options KDB_TRACE # Print a stack trace for a panic.
-# For full debugger support use (turn off in stable branch):
-include "std.debug"
# options EARLY_PRINTF=sbi
# Kernel dump features.
diff --git a/sys/riscv/conf/GENERIC-NODEBUG b/sys/riscv/conf/GENERIC-NODEBUG
deleted file mode 100644
index e4f4b41f2c41..000000000000
--- a/sys/riscv/conf/GENERIC-NODEBUG
+++ /dev/null
@@ -1,31 +0,0 @@
-#
-# GENERIC-NODEBUG -- WITNESS and INVARIANTS free kernel configuration file
-# for FreeBSD/riscv
-#
-# This configuration file removes several debugging options, including
-# WITNESS and INVARIANTS checking, which are known to have significant
-# performance impact on running systems. When benchmarking new features
-# this kernel should be used instead of the standard GENERIC.
-# This kernel configuration should never appear outside of the HEAD
-# of the FreeBSD tree.
-#
-# For more information on this file, please read the config(5) manual page,
-# and/or the handbook section on Kernel Configuration Files:
-#
-# https://docs.freebsd.org/en/books/handbook/kernelconfig/#kernelconfig-config
-#
-# The handbook is also available locally in /usr/share/doc/handbook
-# if you've installed the doc distribution, otherwise always see the
-# FreeBSD World Wide Web server (https://www.FreeBSD.org/) for the
-# latest information.
-#
-# An exhaustive list of options and more detailed explanations of the
-# device lines is also present in the ../../conf/NOTES and NOTES files.
-# If you are in doubt as to the purpose or necessity of a line, check first
-# in NOTES.
-#
-
-include GENERIC
-include "std.nodebug"
-
-ident GENERIC-NODEBUG
diff --git a/sys/sys/event.h b/sys/sys/event.h
index 1b30e4292de8..f161d2c938c1 100644
--- a/sys/sys/event.h
+++ b/sys/sys/event.h
@@ -45,7 +45,8 @@
#define EVFILT_USER (-11) /* User events */
#define EVFILT_SENDFILE (-12) /* attached to sendfile requests */
#define EVFILT_EMPTY (-13) /* empty send socket buf */
-#define EVFILT_SYSCOUNT 13
+#define EVFILT_JAIL (-14) /* attached to struct prison */
+#define EVFILT_SYSCOUNT 14
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
#define EV_SET(kevp_, a, b, c, d, e, f) do { \
@@ -204,10 +205,19 @@ struct freebsd11_kevent32 {
#define NOTE_PCTRLMASK 0xf0000000 /* mask for hint bits */
#define NOTE_PDATAMASK 0x000fffff /* mask for pid */
-/* additional flags for EVFILT_PROC */
-#define NOTE_TRACK 0x00000001 /* follow across forks */
+/* data/hint flags for EVFILT_JAIL */
+#define NOTE_JAIL_SET 0x80000000 /* jail was modified */
+#define NOTE_JAIL_CHILD 0x40000000 /* child jail was created */
+#define NOTE_JAIL_ATTACH 0x20000000 /* jail was attached to */
+#define NOTE_JAIL_REMOVE 0x10000000 /* jail was removed */
+#define NOTE_JAIL_ATTACH_MULTI 0x08000000 /* multiple procs attached */
+#define NOTE_JAIL_CTRLMASK 0xf0000000 /* mask for hint bits */
+#define NOTE_JAIL_DATAMASK 0x000fffff /* mask for pid */
+
+/* additional flags for EVFILT_PROC and EVFILT_JAIL */
+#define NOTE_TRACK 0x00000001 /* follow across fork/create */
#define NOTE_TRACKERR 0x00000002 /* could not track child */
-#define NOTE_CHILD 0x00000004 /* am a child process */
+#define NOTE_CHILD 0x00000004 /* am a child process/jail */
/* additional flags for EVFILT_TIMER */
#define NOTE_SECONDS 0x00000001 /* data is seconds */
@@ -309,6 +319,7 @@ struct knote {
struct proc *p_proc; /* proc pointer */
struct kaiocb *p_aio; /* AIO job pointer */
struct aioliojob *p_lio; /* LIO job pointer */
+ struct prison *p_prison; /* prison pointer */
void *p_v; /* generic other pointer */
} kn_ptr;
const struct filterops *kn_fop;
diff --git a/sys/sys/file.h b/sys/sys/file.h
index 63313926c4f0..cc3c733580fd 100644
--- a/sys/sys/file.h
+++ b/sys/sys/file.h
@@ -72,6 +72,7 @@ struct nameidata;
#define DTYPE_EVENTFD 13 /* eventfd */
#define DTYPE_TIMERFD 14 /* timerfd */
#define DTYPE_INOTIFY 15 /* inotify descriptor */
+#define DTYPE_JAILDESC 16 /* jail descriptor */
#ifdef _KERNEL
diff --git a/sys/sys/jail.h b/sys/sys/jail.h
index d2655c52e832..e12e8c3178c9 100644
--- a/sys/sys/jail.h
+++ b/sys/sys/jail.h
@@ -99,8 +99,12 @@ enum prison_state {
#define JAIL_UPDATE 0x02 /* Update parameters of existing jail */
#define JAIL_ATTACH 0x04 /* Attach to jail upon creation */
#define JAIL_DYING 0x08 /* Allow getting a dying jail */
-#define JAIL_SET_MASK 0x0f /* JAIL_DYING is deprecated/ignored here */
-#define JAIL_GET_MASK 0x08
+#define JAIL_USE_DESC 0x10 /* Get/set jail in descriptor */
+#define JAIL_AT_DESC 0x20 /* Find/add jail under descriptor */
+#define JAIL_GET_DESC 0x40 /* Return a new jail descriptor */
+#define JAIL_OWN_DESC 0x80 /* Return a new owning jail descriptor */
+#define JAIL_SET_MASK 0xff /* JAIL_DYING is deprecated/ignored here */
+#define JAIL_GET_MASK 0xf8
#define JAIL_SYS_DISABLE 0
#define JAIL_SYS_NEW 1
@@ -115,7 +119,9 @@ int jail(struct jail *);
int jail_set(struct iovec *, unsigned int, int);
int jail_get(struct iovec *, unsigned int, int);
int jail_attach(int);
+int jail_attach_jd(int);
int jail_remove(int);
+int jail_remove_jd(int);
__END_DECLS
#else /* _KERNEL */
@@ -144,6 +150,8 @@ MALLOC_DECLARE(M_PRISON);
#define JAIL_META_PRIVATE "meta"
#define JAIL_META_SHARED "env"
+struct jaildesc;
+struct knlist;
struct racct;
struct prison_racct;
@@ -189,7 +197,9 @@ struct prison {
struct vnode *pr_root; /* (c) vnode to rdir */
struct prison_ip *pr_addrs[PR_FAMILY_MAX]; /* (p,n) IPs of jail */
struct prison_racct *pr_prison_racct; /* (c) racct jail proxy */
- void *pr_sparep[3];
+ struct knlist *pr_klist; /* (m) attached knotes */
+ LIST_HEAD(, jaildesc) pr_descs; /* (a) attached descriptors */
+ void *pr_sparep;
int pr_childcount; /* (a) number of child jails */
int pr_childmax; /* (p) maximum child jails */
unsigned pr_allow; /* (p) PR_ALLOW_* flags */
@@ -425,10 +435,11 @@ SYSCTL_DECL(_security_jail_param);
/*
* Kernel support functions for jail().
*/
-struct ucred;
+struct knote;
struct mount;
struct sockaddr;
struct statfs;
+struct ucred;
struct vfsconf;
/*
@@ -463,6 +474,7 @@ void prison_proc_free(struct prison *);
void prison_proc_link(struct prison *, struct proc *);
void prison_proc_unlink(struct prison *, struct proc *);
void prison_proc_iterate(struct prison *, void (*)(struct proc *, void *), void *);
+void prison_remove(struct prison *);
void prison_set_allow(struct ucred *cred, unsigned flag, int enable);
bool prison_ischild(struct prison *, struct prison *);
bool prison_isalive(const struct prison *);
diff --git a/sys/sys/jaildesc.h b/sys/sys/jaildesc.h
new file mode 100644
index 000000000000..4bed1ab3b88a
--- /dev/null
+++ b/sys/sys/jaildesc.h
@@ -0,0 +1,85 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2025 James Gritton.
+ * All rights reserved.
+ *
+ * This software was developed at the University of Cambridge Computer
+ * Laboratory with support from a grant from Google, Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _SYS_JAILDESC_H_
+#define _SYS_JAILDESC_H_
+
+#ifdef _KERNEL
+
+#include <sys/queue.h>
+#include <sys/_lock.h>
+#include <sys/_mutex.h>
+#include <sys/_types.h>
+
+struct prison;
+
+/*-
+ * struct jaildesc describes a jail descriptor, which points to a struct
+ * prison. struct prison in turn has a linked list of struct jaildesc.
+ *
+ * Locking key:
+ * (c) set on creation, remains unchanged
+ * (d) jd_lock
+ * (p) jd_prison->pr_mtx
+ */
+struct jaildesc {
+ LIST_ENTRY(jaildesc) jd_list; /* (d,p) this prison's descs */
+ struct prison *jd_prison; /* (d) the prison */
+ struct mtx jd_lock;
+ uid_t jd_uid; /* (d) nominal file owner */
+ gid_t jd_gid; /* (d) nominal file group */
+ mode_t jd_mode; /* (d) descriptor permissions */
+ unsigned jd_flags; /* (d) JDF_* flags */
+};
+
+/*
+ * Locking macros for the jaildesc.
+ */
+#define JAILDESC_LOCK_DESTROY(jd) mtx_destroy(&(jd)->jd_lock)
+#define JAILDESC_LOCK_INIT(jd) mtx_init(&(jd)->jd_lock, "jaildesc", \
+ NULL, MTX_DEF)
+#define JAILDESC_LOCK(jd) mtx_lock(&(jd)->jd_lock)
+#define JAILDESC_UNLOCK(jd) mtx_unlock(&(jd)->jd_lock)
+
+/*
+ * Flags for the jd_flags field
+ */
+#define JDF_REMOVED 0x00000002 /* jail was removed */
+
+int jaildesc_find(struct thread *td, int fd, struct jaildesc **jdp,
+ struct prison **prp, struct ucred **ucredp);
+int jaildesc_alloc(struct thread *td, struct file **fpp, int *fdp, int owning);
+void jaildesc_set_prison(struct file *jd, struct prison *pr);
+void jaildesc_prison_cleanup(struct prison *pr);
+
+#endif /* _KERNEL */
+
+#endif /* !_SYS_JAILDESC_H_ */
diff --git a/sys/sys/param.h b/sys/sys/param.h
index fc2a78883f1e..c21c086e15ad 100644
--- a/sys/sys/param.h
+++ b/sys/sys/param.h
@@ -74,7 +74,7 @@
* cannot include sys/param.h and should only be updated here.
*/
#undef __FreeBSD_version
-#define __FreeBSD_version 1500063
+#define __FreeBSD_version 1500064
/*
* __FreeBSD_kernel__ indicates that this system uses the kernel of FreeBSD,
diff --git a/sys/sys/syscall.h b/sys/sys/syscall.h
index 2d6903967e15..cff27b8be316 100644
--- a/sys/sys/syscall.h
+++ b/sys/sys/syscall.h
@@ -535,4 +535,6 @@
#define SYS_inotify_rm_watch 594
#define SYS_getgroups 595
#define SYS_setgroups 596
-#define SYS_MAXSYSCALL 597
+#define SYS_jail_attach_jd 597
+#define SYS_jail_remove_jd 598
+#define SYS_MAXSYSCALL 599
diff --git a/sys/sys/syscall.mk b/sys/sys/syscall.mk
index d1172c2dc7bf..443dbadcfbff 100644
--- a/sys/sys/syscall.mk
+++ b/sys/sys/syscall.mk
@@ -438,4 +438,6 @@ MIASM = \
inotify_add_watch_at.o \
inotify_rm_watch.o \
getgroups.o \
- setgroups.o
+ setgroups.o \
+ jail_attach_jd.o \
+ jail_remove_jd.o
diff --git a/sys/sys/sysproto.h b/sys/sys/sysproto.h
index 98311a6dbf94..8dda4b4533ea 100644
--- a/sys/sys/sysproto.h
+++ b/sys/sys/sysproto.h
@@ -1901,6 +1901,12 @@ struct setgroups_args {
char gidsetsize_l_[PADL_(int)]; int gidsetsize; char gidsetsize_r_[PADR_(int)];
char gidset_l_[PADL_(const gid_t *)]; const gid_t * gidset; char gidset_r_[PADR_(const gid_t *)];
};
+struct jail_attach_jd_args {
+ char fd_l_[PADL_(int)]; int fd; char fd_r_[PADR_(int)];
+};
+struct jail_remove_jd_args {
+ char fd_l_[PADL_(int)]; int fd; char fd_r_[PADR_(int)];
+};
int sys__exit(struct thread *, struct _exit_args *);
int sys_fork(struct thread *, struct fork_args *);
int sys_read(struct thread *, struct read_args *);
@@ -2305,6 +2311,8 @@ int sys_inotify_add_watch_at(struct thread *, struct inotify_add_watch_at_args *
int sys_inotify_rm_watch(struct thread *, struct inotify_rm_watch_args *);
int sys_getgroups(struct thread *, struct getgroups_args *);
int sys_setgroups(struct thread *, struct setgroups_args *);
+int sys_jail_attach_jd(struct thread *, struct jail_attach_jd_args *);
+int sys_jail_remove_jd(struct thread *, struct jail_remove_jd_args *);
#ifdef COMPAT_43
@@ -3301,6 +3309,8 @@ int freebsd14_setgroups(struct thread *, struct freebsd14_setgroups_args *);
#define SYS_AUE_inotify_rm_watch AUE_INOTIFY
#define SYS_AUE_getgroups AUE_GETGROUPS
#define SYS_AUE_setgroups AUE_SETGROUPS
+#define SYS_AUE_jail_attach_jd AUE_JAIL_ATTACH
+#define SYS_AUE_jail_remove_jd AUE_JAIL_REMOVE
#undef PAD_
#undef PADL_
diff --git a/sys/sys/user.h b/sys/sys/user.h
index 103236b6ed1b..3183f0792256 100644
--- a/sys/sys/user.h
+++ b/sys/sys/user.h
@@ -266,6 +266,7 @@ struct user {
#define KF_TYPE_EVENTFD 13
#define KF_TYPE_TIMERFD 14
#define KF_TYPE_INOTIFY 15
+#define KF_TYPE_JAILDESC 16
#define KF_TYPE_UNKNOWN 255
#define KF_VTYPE_VNON 0
@@ -453,6 +454,9 @@ struct kinfo_file {
uint64_t kf_timerfd_addr;
} kf_timerfd;
struct {
+ int32_t kf_jid;
+ } kf_jail;
+ struct {
uint64_t kf_kqueue_addr;
int32_t kf_kqueue_count;
int32_t kf_kqueue_state;
diff --git a/tools/build/mk/OptionalObsoleteFiles.inc b/tools/build/mk/OptionalObsoleteFiles.inc
index 5eea402c4526..a2fb28f1a186 100644
--- a/tools/build/mk/OptionalObsoleteFiles.inc
+++ b/tools/build/mk/OptionalObsoleteFiles.inc
@@ -3793,11 +3793,14 @@ OLD_FILES+=usr/share/man/man1/host.1.gz
.endif
.if ${MK_LEGACY_CONSOLE} == no
+OLD_FILES+=etc/moused.conf
OLD_FILES+=etc/rc.d/moused
+OLD_FILES+=etc/rc.d/msconvd
OLD_FILES+=etc/rc.d/syscons
OLD_FILES+=usr/sbin/kbdcontrol
OLD_FILES+=usr/sbin/kbdmap
OLD_FILES+=usr/sbin/moused
+OLD_FILES+=usr/sbin/msconvd
OLD_FILES+=usr/sbin/vidcontrol
OLD_FILES+=usr/sbin/vidfont
OLD_FILES+=usr/share/man/man1/kbdcontrol.1.gz
@@ -3806,7 +3809,11 @@ OLD_FILES+=usr/share/man/man1/vidcontrol.1.gz
OLD_FILES+=usr/share/man/man1/vidfont.1.gz
OLD_FILES+=usr/share/man/man5/kbdmap.5.gz
OLD_FILES+=usr/share/man/man5/keymap.5.gz
+OLD_FILES+=usr/share/man/man5/moused.conf.5.gz
OLD_FILES+=usr/share/man/man8/moused.8.gz
+OLD_FILES+=usr/share/man/man8/msconvd.8.gz
+OLD_FILES+=usr/share/moused/5-generic-touchpad.quirks
+OLD_DIRS+=usr/share/moused
.endif
.for LIBCOMPAT libcompat in ${_ALL_LIBCOMPATS_libcompats}
diff --git a/usr.sbin/moused/Makefile b/usr.sbin/moused/Makefile
index 2a7aa0484542..b6319b6fef20 100644
--- a/usr.sbin/moused/Makefile
+++ b/usr.sbin/moused/Makefile
@@ -1,10 +1,4 @@
-PACKAGE= console-tools
-PROG= moused
-MAN= moused.8
+SUBDIR+=moused
+SUBDIR+=msconvd
-LIBADD= m util
-
-#BINMODE=4555
-#PRECIOUSPROG=
-
-.include <bsd.prog.mk>
+.include <bsd.subdir.mk>
diff --git a/usr.sbin/moused/Makefile.depend b/usr.sbin/moused/Makefile.depend
deleted file mode 100644
index af3b7054df7a..000000000000
--- a/usr.sbin/moused/Makefile.depend
+++ /dev/null
@@ -1,17 +0,0 @@
-# Autogenerated - do NOT edit!
-
-DIRDEPS = \
- include \
- include/xlocale \
- lib/${CSU_DIR} \
- lib/libc \
- lib/libcompiler_rt \
- lib/libutil \
- lib/msun \
-
-
-.include <dirdeps.mk>
-
-.if ${DEP_RELDIR} == ${_DEP_RELDIR}
-# local dependencies - needed for -jN in clean tree
-.endif
diff --git a/usr.sbin/moused/moused/Makefile b/usr.sbin/moused/moused/Makefile
new file mode 100644
index 000000000000..8479764b710b
--- /dev/null
+++ b/usr.sbin/moused/moused/Makefile
@@ -0,0 +1,28 @@
+PACKAGE= console-tools
+PROG= moused
+
+SRCS= moused.c \
+ event-names.h \
+ quirks.c \
+ quirks.h \
+ util.c \
+ util.h \
+ util-evdev.c \
+ util-evdev.h \
+ util-list.c \
+ util-list.h
+MAN= moused.8 \
+ moused.conf.5
+CONFS= moused.conf
+QUIRKS= 5-generic-touchpad.quirks
+
+CWARNFLAGS.quirks.c+= -Wno-cast-align -Wno-shadow -Wno-cast-qual \
+ -Wno-unused-variable -Wno-unused-parameter
+CWARNFLAGS.util.c+= -Wno-shadow
+LIBADD= m util
+BINDIR= /usr/sbin
+
+FILES= ${QUIRKS:S|^|quirks/|}
+FILESDIR= /usr/share/${PROG}
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/moused/moused/event-names.h b/usr.sbin/moused/moused/event-names.h
new file mode 100644
index 000000000000..05093a1d0db3
--- /dev/null
+++ b/usr.sbin/moused/moused/event-names.h
@@ -0,0 +1,1656 @@
+/* THIS FILE IS GENERATED, DO NOT EDIT */
+
+#ifndef EVENT_NAMES_H
+#define EVENT_NAMES_H
+
+static const char * const ev_map[EV_MAX + 1] = {
+ [EV_SYN] = "EV_SYN",
+ [EV_KEY] = "EV_KEY",
+ [EV_REL] = "EV_REL",
+ [EV_ABS] = "EV_ABS",
+ [EV_MSC] = "EV_MSC",
+ [EV_SW] = "EV_SW",
+ [EV_LED] = "EV_LED",
+ [EV_SND] = "EV_SND",
+ [EV_REP] = "EV_REP",
+ [EV_FF] = "EV_FF",
+ [EV_PWR] = "EV_PWR",
+ [EV_FF_STATUS] = "EV_FF_STATUS",
+ [EV_MAX] = "EV_MAX",
+};
+
+static const char * const rel_map[REL_MAX + 1] = {
+ [REL_X] = "REL_X",
+ [REL_Y] = "REL_Y",
+ [REL_Z] = "REL_Z",
+ [REL_RX] = "REL_RX",
+ [REL_RY] = "REL_RY",
+ [REL_RZ] = "REL_RZ",
+ [REL_HWHEEL] = "REL_HWHEEL",
+ [REL_DIAL] = "REL_DIAL",
+ [REL_WHEEL] = "REL_WHEEL",
+ [REL_MISC] = "REL_MISC",
+ [REL_RESERVED] = "REL_RESERVED",
+ [REL_WHEEL_HI_RES] = "REL_WHEEL_HI_RES",
+ [REL_HWHEEL_HI_RES] = "REL_HWHEEL_HI_RES",
+ [REL_MAX] = "REL_MAX",
+};
+
+static const char * const abs_map[ABS_MAX + 1] = {
+ [ABS_X] = "ABS_X",
+ [ABS_Y] = "ABS_Y",
+ [ABS_Z] = "ABS_Z",
+ [ABS_RX] = "ABS_RX",
+ [ABS_RY] = "ABS_RY",
+ [ABS_RZ] = "ABS_RZ",
+ [ABS_THROTTLE] = "ABS_THROTTLE",
+ [ABS_RUDDER] = "ABS_RUDDER",
+ [ABS_WHEEL] = "ABS_WHEEL",
+ [ABS_GAS] = "ABS_GAS",
+ [ABS_BRAKE] = "ABS_BRAKE",
+ [ABS_HAT0X] = "ABS_HAT0X",
+ [ABS_HAT0Y] = "ABS_HAT0Y",
+ [ABS_HAT1X] = "ABS_HAT1X",
+ [ABS_HAT1Y] = "ABS_HAT1Y",
+ [ABS_HAT2X] = "ABS_HAT2X",
+ [ABS_HAT2Y] = "ABS_HAT2Y",
+ [ABS_HAT3X] = "ABS_HAT3X",
+ [ABS_HAT3Y] = "ABS_HAT3Y",
+ [ABS_PRESSURE] = "ABS_PRESSURE",
+ [ABS_DISTANCE] = "ABS_DISTANCE",
+ [ABS_TILT_X] = "ABS_TILT_X",
+ [ABS_TILT_Y] = "ABS_TILT_Y",
+ [ABS_TOOL_WIDTH] = "ABS_TOOL_WIDTH",
+ [ABS_VOLUME] = "ABS_VOLUME",
+ // [ABS_PROFILE] = "ABS_PROFILE",
+ [ABS_MISC] = "ABS_MISC",
+ [ABS_RESERVED] = "ABS_RESERVED",
+ [ABS_MT_SLOT] = "ABS_MT_SLOT",
+ [ABS_MT_TOUCH_MAJOR] = "ABS_MT_TOUCH_MAJOR",
+ [ABS_MT_TOUCH_MINOR] = "ABS_MT_TOUCH_MINOR",
+ [ABS_MT_WIDTH_MAJOR] = "ABS_MT_WIDTH_MAJOR",
+ [ABS_MT_WIDTH_MINOR] = "ABS_MT_WIDTH_MINOR",
+ [ABS_MT_ORIENTATION] = "ABS_MT_ORIENTATION",
+ [ABS_MT_POSITION_X] = "ABS_MT_POSITION_X",
+ [ABS_MT_POSITION_Y] = "ABS_MT_POSITION_Y",
+ [ABS_MT_TOOL_TYPE] = "ABS_MT_TOOL_TYPE",
+ [ABS_MT_BLOB_ID] = "ABS_MT_BLOB_ID",
+ [ABS_MT_TRACKING_ID] = "ABS_MT_TRACKING_ID",
+ [ABS_MT_PRESSURE] = "ABS_MT_PRESSURE",
+ [ABS_MT_DISTANCE] = "ABS_MT_DISTANCE",
+ [ABS_MT_TOOL_X] = "ABS_MT_TOOL_X",
+ [ABS_MT_TOOL_Y] = "ABS_MT_TOOL_Y",
+ [ABS_MAX] = "ABS_MAX",
+};
+
+static const char * const key_map[KEY_MAX + 1] = {
+ [KEY_RESERVED] = "KEY_RESERVED",
+ [KEY_ESC] = "KEY_ESC",
+ [KEY_1] = "KEY_1",
+ [KEY_2] = "KEY_2",
+ [KEY_3] = "KEY_3",
+ [KEY_4] = "KEY_4",
+ [KEY_5] = "KEY_5",
+ [KEY_6] = "KEY_6",
+ [KEY_7] = "KEY_7",
+ [KEY_8] = "KEY_8",
+ [KEY_9] = "KEY_9",
+ [KEY_0] = "KEY_0",
+ [KEY_MINUS] = "KEY_MINUS",
+ [KEY_EQUAL] = "KEY_EQUAL",
+ [KEY_BACKSPACE] = "KEY_BACKSPACE",
+ [KEY_TAB] = "KEY_TAB",
+ [KEY_Q] = "KEY_Q",
+ [KEY_W] = "KEY_W",
+ [KEY_E] = "KEY_E",
+ [KEY_R] = "KEY_R",
+ [KEY_T] = "KEY_T",
+ [KEY_Y] = "KEY_Y",
+ [KEY_U] = "KEY_U",
+ [KEY_I] = "KEY_I",
+ [KEY_O] = "KEY_O",
+ [KEY_P] = "KEY_P",
+ [KEY_LEFTBRACE] = "KEY_LEFTBRACE",
+ [KEY_RIGHTBRACE] = "KEY_RIGHTBRACE",
+ [KEY_ENTER] = "KEY_ENTER",
+ [KEY_LEFTCTRL] = "KEY_LEFTCTRL",
+ [KEY_A] = "KEY_A",
+ [KEY_S] = "KEY_S",
+ [KEY_D] = "KEY_D",
+ [KEY_F] = "KEY_F",
+ [KEY_G] = "KEY_G",
+ [KEY_H] = "KEY_H",
+ [KEY_J] = "KEY_J",
+ [KEY_K] = "KEY_K",
+ [KEY_L] = "KEY_L",
+ [KEY_SEMICOLON] = "KEY_SEMICOLON",
+ [KEY_APOSTROPHE] = "KEY_APOSTROPHE",
+ [KEY_GRAVE] = "KEY_GRAVE",
+ [KEY_LEFTSHIFT] = "KEY_LEFTSHIFT",
+ [KEY_BACKSLASH] = "KEY_BACKSLASH",
+ [KEY_Z] = "KEY_Z",
+ [KEY_X] = "KEY_X",
+ [KEY_C] = "KEY_C",
+ [KEY_V] = "KEY_V",
+ [KEY_B] = "KEY_B",
+ [KEY_N] = "KEY_N",
+ [KEY_M] = "KEY_M",
+ [KEY_COMMA] = "KEY_COMMA",
+ [KEY_DOT] = "KEY_DOT",
+ [KEY_SLASH] = "KEY_SLASH",
+ [KEY_RIGHTSHIFT] = "KEY_RIGHTSHIFT",
+ [KEY_KPASTERISK] = "KEY_KPASTERISK",
+ [KEY_LEFTALT] = "KEY_LEFTALT",
+ [KEY_SPACE] = "KEY_SPACE",
+ [KEY_CAPSLOCK] = "KEY_CAPSLOCK",
+ [KEY_F1] = "KEY_F1",
+ [KEY_F2] = "KEY_F2",
+ [KEY_F3] = "KEY_F3",
+ [KEY_F4] = "KEY_F4",
+ [KEY_F5] = "KEY_F5",
+ [KEY_F6] = "KEY_F6",
+ [KEY_F7] = "KEY_F7",
+ [KEY_F8] = "KEY_F8",
+ [KEY_F9] = "KEY_F9",
+ [KEY_F10] = "KEY_F10",
+ [KEY_NUMLOCK] = "KEY_NUMLOCK",
+ [KEY_SCROLLLOCK] = "KEY_SCROLLLOCK",
+ [KEY_KP7] = "KEY_KP7",
+ [KEY_KP8] = "KEY_KP8",
+ [KEY_KP9] = "KEY_KP9",
+ [KEY_KPMINUS] = "KEY_KPMINUS",
+ [KEY_KP4] = "KEY_KP4",
+ [KEY_KP5] = "KEY_KP5",
+ [KEY_KP6] = "KEY_KP6",
+ [KEY_KPPLUS] = "KEY_KPPLUS",
+ [KEY_KP1] = "KEY_KP1",
+ [KEY_KP2] = "KEY_KP2",
+ [KEY_KP3] = "KEY_KP3",
+ [KEY_KP0] = "KEY_KP0",
+ [KEY_KPDOT] = "KEY_KPDOT",
+ [KEY_ZENKAKUHANKAKU] = "KEY_ZENKAKUHANKAKU",
+ [KEY_102ND] = "KEY_102ND",
+ [KEY_F11] = "KEY_F11",
+ [KEY_F12] = "KEY_F12",
+ [KEY_RO] = "KEY_RO",
+ [KEY_KATAKANA] = "KEY_KATAKANA",
+ [KEY_HIRAGANA] = "KEY_HIRAGANA",
+ [KEY_HENKAN] = "KEY_HENKAN",
+ [KEY_KATAKANAHIRAGANA] = "KEY_KATAKANAHIRAGANA",
+ [KEY_MUHENKAN] = "KEY_MUHENKAN",
+ [KEY_KPJPCOMMA] = "KEY_KPJPCOMMA",
+ [KEY_KPENTER] = "KEY_KPENTER",
+ [KEY_RIGHTCTRL] = "KEY_RIGHTCTRL",
+ [KEY_KPSLASH] = "KEY_KPSLASH",
+ [KEY_SYSRQ] = "KEY_SYSRQ",
+ [KEY_RIGHTALT] = "KEY_RIGHTALT",
+ [KEY_LINEFEED] = "KEY_LINEFEED",
+ [KEY_HOME] = "KEY_HOME",
+ [KEY_UP] = "KEY_UP",
+ [KEY_PAGEUP] = "KEY_PAGEUP",
+ [KEY_LEFT] = "KEY_LEFT",
+ [KEY_RIGHT] = "KEY_RIGHT",
+ [KEY_END] = "KEY_END",
+ [KEY_DOWN] = "KEY_DOWN",
+ [KEY_PAGEDOWN] = "KEY_PAGEDOWN",
+ [KEY_INSERT] = "KEY_INSERT",
+ [KEY_DELETE] = "KEY_DELETE",
+ [KEY_MACRO] = "KEY_MACRO",
+ [KEY_MUTE] = "KEY_MUTE",
+ [KEY_VOLUMEDOWN] = "KEY_VOLUMEDOWN",
+ [KEY_VOLUMEUP] = "KEY_VOLUMEUP",
+ [KEY_POWER] = "KEY_POWER",
+ [KEY_KPEQUAL] = "KEY_KPEQUAL",
+ [KEY_KPPLUSMINUS] = "KEY_KPPLUSMINUS",
+ [KEY_PAUSE] = "KEY_PAUSE",
+ [KEY_SCALE] = "KEY_SCALE",
+ [KEY_KPCOMMA] = "KEY_KPCOMMA",
+ [KEY_HANGEUL] = "KEY_HANGEUL",
+ [KEY_HANJA] = "KEY_HANJA",
+ [KEY_YEN] = "KEY_YEN",
+ [KEY_LEFTMETA] = "KEY_LEFTMETA",
+ [KEY_RIGHTMETA] = "KEY_RIGHTMETA",
+ [KEY_COMPOSE] = "KEY_COMPOSE",
+ [KEY_STOP] = "KEY_STOP",
+ [KEY_AGAIN] = "KEY_AGAIN",
+ [KEY_PROPS] = "KEY_PROPS",
+ [KEY_UNDO] = "KEY_UNDO",
+ [KEY_FRONT] = "KEY_FRONT",
+ [KEY_COPY] = "KEY_COPY",
+ [KEY_OPEN] = "KEY_OPEN",
+ [KEY_PASTE] = "KEY_PASTE",
+ [KEY_FIND] = "KEY_FIND",
+ [KEY_CUT] = "KEY_CUT",
+ [KEY_HELP] = "KEY_HELP",
+ [KEY_MENU] = "KEY_MENU",
+ [KEY_CALC] = "KEY_CALC",
+ [KEY_SETUP] = "KEY_SETUP",
+ [KEY_SLEEP] = "KEY_SLEEP",
+ [KEY_WAKEUP] = "KEY_WAKEUP",
+ [KEY_FILE] = "KEY_FILE",
+ [KEY_SENDFILE] = "KEY_SENDFILE",
+ [KEY_DELETEFILE] = "KEY_DELETEFILE",
+ [KEY_XFER] = "KEY_XFER",
+ [KEY_PROG1] = "KEY_PROG1",
+ [KEY_PROG2] = "KEY_PROG2",
+ [KEY_WWW] = "KEY_WWW",
+ [KEY_MSDOS] = "KEY_MSDOS",
+ [KEY_COFFEE] = "KEY_COFFEE",
+ [KEY_ROTATE_DISPLAY] = "KEY_ROTATE_DISPLAY",
+ [KEY_CYCLEWINDOWS] = "KEY_CYCLEWINDOWS",
+ [KEY_MAIL] = "KEY_MAIL",
+ [KEY_BOOKMARKS] = "KEY_BOOKMARKS",
+ [KEY_COMPUTER] = "KEY_COMPUTER",
+ [KEY_BACK] = "KEY_BACK",
+ [KEY_FORWARD] = "KEY_FORWARD",
+ [KEY_CLOSECD] = "KEY_CLOSECD",
+ [KEY_EJECTCD] = "KEY_EJECTCD",
+ [KEY_EJECTCLOSECD] = "KEY_EJECTCLOSECD",
+ [KEY_NEXTSONG] = "KEY_NEXTSONG",
+ [KEY_PLAYPAUSE] = "KEY_PLAYPAUSE",
+ [KEY_PREVIOUSSONG] = "KEY_PREVIOUSSONG",
+ [KEY_STOPCD] = "KEY_STOPCD",
+ [KEY_RECORD] = "KEY_RECORD",
+ [KEY_REWIND] = "KEY_REWIND",
+ [KEY_PHONE] = "KEY_PHONE",
+ [KEY_ISO] = "KEY_ISO",
+ [KEY_CONFIG] = "KEY_CONFIG",
+ [KEY_HOMEPAGE] = "KEY_HOMEPAGE",
+ [KEY_REFRESH] = "KEY_REFRESH",
+ [KEY_EXIT] = "KEY_EXIT",
+ [KEY_MOVE] = "KEY_MOVE",
+ [KEY_EDIT] = "KEY_EDIT",
+ [KEY_SCROLLUP] = "KEY_SCROLLUP",
+ [KEY_SCROLLDOWN] = "KEY_SCROLLDOWN",
+ [KEY_KPLEFTPAREN] = "KEY_KPLEFTPAREN",
+ [KEY_KPRIGHTPAREN] = "KEY_KPRIGHTPAREN",
+ [KEY_NEW] = "KEY_NEW",
+ [KEY_REDO] = "KEY_REDO",
+ [KEY_F13] = "KEY_F13",
+ [KEY_F14] = "KEY_F14",
+ [KEY_F15] = "KEY_F15",
+ [KEY_F16] = "KEY_F16",
+ [KEY_F17] = "KEY_F17",
+ [KEY_F18] = "KEY_F18",
+ [KEY_F19] = "KEY_F19",
+ [KEY_F20] = "KEY_F20",
+ [KEY_F21] = "KEY_F21",
+ [KEY_F22] = "KEY_F22",
+ [KEY_F23] = "KEY_F23",
+ [KEY_F24] = "KEY_F24",
+ [KEY_PLAYCD] = "KEY_PLAYCD",
+ [KEY_PAUSECD] = "KEY_PAUSECD",
+ [KEY_PROG3] = "KEY_PROG3",
+ [KEY_PROG4] = "KEY_PROG4",
+ // [KEY_ALL_APPLICATIONS] = "KEY_ALL_APPLICATIONS",
+ [KEY_SUSPEND] = "KEY_SUSPEND",
+ [KEY_CLOSE] = "KEY_CLOSE",
+ [KEY_PLAY] = "KEY_PLAY",
+ [KEY_FASTFORWARD] = "KEY_FASTFORWARD",
+ [KEY_BASSBOOST] = "KEY_BASSBOOST",
+ [KEY_PRINT] = "KEY_PRINT",
+ [KEY_HP] = "KEY_HP",
+ [KEY_CAMERA] = "KEY_CAMERA",
+ [KEY_SOUND] = "KEY_SOUND",
+ [KEY_QUESTION] = "KEY_QUESTION",
+ [KEY_EMAIL] = "KEY_EMAIL",
+ [KEY_CHAT] = "KEY_CHAT",
+ [KEY_SEARCH] = "KEY_SEARCH",
+ [KEY_CONNECT] = "KEY_CONNECT",
+ [KEY_FINANCE] = "KEY_FINANCE",
+ [KEY_SPORT] = "KEY_SPORT",
+ [KEY_SHOP] = "KEY_SHOP",
+ [KEY_ALTERASE] = "KEY_ALTERASE",
+ [KEY_CANCEL] = "KEY_CANCEL",
+ [KEY_BRIGHTNESSDOWN] = "KEY_BRIGHTNESSDOWN",
+ [KEY_BRIGHTNESSUP] = "KEY_BRIGHTNESSUP",
+ [KEY_MEDIA] = "KEY_MEDIA",
+ [KEY_SWITCHVIDEOMODE] = "KEY_SWITCHVIDEOMODE",
+ [KEY_KBDILLUMTOGGLE] = "KEY_KBDILLUMTOGGLE",
+ [KEY_KBDILLUMDOWN] = "KEY_KBDILLUMDOWN",
+ [KEY_KBDILLUMUP] = "KEY_KBDILLUMUP",
+ [KEY_SEND] = "KEY_SEND",
+ [KEY_REPLY] = "KEY_REPLY",
+ [KEY_FORWARDMAIL] = "KEY_FORWARDMAIL",
+ [KEY_SAVE] = "KEY_SAVE",
+ [KEY_DOCUMENTS] = "KEY_DOCUMENTS",
+ [KEY_BATTERY] = "KEY_BATTERY",
+ [KEY_BLUETOOTH] = "KEY_BLUETOOTH",
+ [KEY_WLAN] = "KEY_WLAN",
+ [KEY_UWB] = "KEY_UWB",
+ [KEY_UNKNOWN] = "KEY_UNKNOWN",
+ [KEY_VIDEO_NEXT] = "KEY_VIDEO_NEXT",
+ [KEY_VIDEO_PREV] = "KEY_VIDEO_PREV",
+ [KEY_BRIGHTNESS_CYCLE] = "KEY_BRIGHTNESS_CYCLE",
+ [KEY_BRIGHTNESS_AUTO] = "KEY_BRIGHTNESS_AUTO",
+ [KEY_DISPLAY_OFF] = "KEY_DISPLAY_OFF",
+ [KEY_WWAN] = "KEY_WWAN",
+ [KEY_RFKILL] = "KEY_RFKILL",
+ [KEY_MICMUTE] = "KEY_MICMUTE",
+ [KEY_OK] = "KEY_OK",
+ [KEY_SELECT] = "KEY_SELECT",
+ [KEY_GOTO] = "KEY_GOTO",
+ [KEY_CLEAR] = "KEY_CLEAR",
+ [KEY_POWER2] = "KEY_POWER2",
+ [KEY_OPTION] = "KEY_OPTION",
+ [KEY_INFO] = "KEY_INFO",
+ [KEY_TIME] = "KEY_TIME",
+ [KEY_VENDOR] = "KEY_VENDOR",
+ [KEY_ARCHIVE] = "KEY_ARCHIVE",
+ [KEY_PROGRAM] = "KEY_PROGRAM",
+ [KEY_CHANNEL] = "KEY_CHANNEL",
+ [KEY_FAVORITES] = "KEY_FAVORITES",
+ [KEY_EPG] = "KEY_EPG",
+ [KEY_PVR] = "KEY_PVR",
+ [KEY_MHP] = "KEY_MHP",
+ [KEY_LANGUAGE] = "KEY_LANGUAGE",
+ [KEY_TITLE] = "KEY_TITLE",
+ [KEY_SUBTITLE] = "KEY_SUBTITLE",
+ [KEY_ANGLE] = "KEY_ANGLE",
+ [KEY_FULL_SCREEN] = "KEY_FULL_SCREEN",
+ [KEY_MODE] = "KEY_MODE",
+ [KEY_KEYBOARD] = "KEY_KEYBOARD",
+ [KEY_ASPECT_RATIO] = "KEY_ASPECT_RATIO",
+ [KEY_PC] = "KEY_PC",
+ [KEY_TV] = "KEY_TV",
+ [KEY_TV2] = "KEY_TV2",
+ [KEY_VCR] = "KEY_VCR",
+ [KEY_VCR2] = "KEY_VCR2",
+ [KEY_SAT] = "KEY_SAT",
+ [KEY_SAT2] = "KEY_SAT2",
+ [KEY_CD] = "KEY_CD",
+ [KEY_TAPE] = "KEY_TAPE",
+ [KEY_RADIO] = "KEY_RADIO",
+ [KEY_TUNER] = "KEY_TUNER",
+ [KEY_PLAYER] = "KEY_PLAYER",
+ [KEY_TEXT] = "KEY_TEXT",
+ [KEY_DVD] = "KEY_DVD",
+ [KEY_AUX] = "KEY_AUX",
+ [KEY_MP3] = "KEY_MP3",
+ [KEY_AUDIO] = "KEY_AUDIO",
+ [KEY_VIDEO] = "KEY_VIDEO",
+ [KEY_DIRECTORY] = "KEY_DIRECTORY",
+ [KEY_LIST] = "KEY_LIST",
+ [KEY_MEMO] = "KEY_MEMO",
+ [KEY_CALENDAR] = "KEY_CALENDAR",
+ [KEY_RED] = "KEY_RED",
+ [KEY_GREEN] = "KEY_GREEN",
+ [KEY_YELLOW] = "KEY_YELLOW",
+ [KEY_BLUE] = "KEY_BLUE",
+ [KEY_CHANNELUP] = "KEY_CHANNELUP",
+ [KEY_CHANNELDOWN] = "KEY_CHANNELDOWN",
+ [KEY_FIRST] = "KEY_FIRST",
+ [KEY_LAST] = "KEY_LAST",
+ [KEY_AB] = "KEY_AB",
+ [KEY_NEXT] = "KEY_NEXT",
+ [KEY_RESTART] = "KEY_RESTART",
+ [KEY_SLOW] = "KEY_SLOW",
+ [KEY_SHUFFLE] = "KEY_SHUFFLE",
+ [KEY_BREAK] = "KEY_BREAK",
+ [KEY_PREVIOUS] = "KEY_PREVIOUS",
+ [KEY_DIGITS] = "KEY_DIGITS",
+ [KEY_TEEN] = "KEY_TEEN",
+ [KEY_TWEN] = "KEY_TWEN",
+ [KEY_VIDEOPHONE] = "KEY_VIDEOPHONE",
+ [KEY_GAMES] = "KEY_GAMES",
+ [KEY_ZOOMIN] = "KEY_ZOOMIN",
+ [KEY_ZOOMOUT] = "KEY_ZOOMOUT",
+ [KEY_ZOOMRESET] = "KEY_ZOOMRESET",
+ [KEY_WORDPROCESSOR] = "KEY_WORDPROCESSOR",
+ [KEY_EDITOR] = "KEY_EDITOR",
+ [KEY_SPREADSHEET] = "KEY_SPREADSHEET",
+ [KEY_GRAPHICSEDITOR] = "KEY_GRAPHICSEDITOR",
+ [KEY_PRESENTATION] = "KEY_PRESENTATION",
+ [KEY_DATABASE] = "KEY_DATABASE",
+ [KEY_NEWS] = "KEY_NEWS",
+ [KEY_VOICEMAIL] = "KEY_VOICEMAIL",
+ [KEY_ADDRESSBOOK] = "KEY_ADDRESSBOOK",
+ [KEY_MESSENGER] = "KEY_MESSENGER",
+ [KEY_DISPLAYTOGGLE] = "KEY_DISPLAYTOGGLE",
+ [KEY_SPELLCHECK] = "KEY_SPELLCHECK",
+ [KEY_LOGOFF] = "KEY_LOGOFF",
+ [KEY_DOLLAR] = "KEY_DOLLAR",
+ [KEY_EURO] = "KEY_EURO",
+ [KEY_FRAMEBACK] = "KEY_FRAMEBACK",
+ [KEY_FRAMEFORWARD] = "KEY_FRAMEFORWARD",
+ [KEY_CONTEXT_MENU] = "KEY_CONTEXT_MENU",
+ [KEY_MEDIA_REPEAT] = "KEY_MEDIA_REPEAT",
+ [KEY_10CHANNELSUP] = "KEY_10CHANNELSUP",
+ [KEY_10CHANNELSDOWN] = "KEY_10CHANNELSDOWN",
+ [KEY_IMAGES] = "KEY_IMAGES",
+ // [KEY_NOTIFICATION_CENTER] = "KEY_NOTIFICATION_CENTER",
+ // [KEY_PICKUP_PHONE] = "KEY_PICKUP_PHONE",
+ // [KEY_HANGUP_PHONE] = "KEY_HANGUP_PHONE",
+ [KEY_DEL_EOL] = "KEY_DEL_EOL",
+ [KEY_DEL_EOS] = "KEY_DEL_EOS",
+ [KEY_INS_LINE] = "KEY_INS_LINE",
+ [KEY_DEL_LINE] = "KEY_DEL_LINE",
+ [KEY_FN] = "KEY_FN",
+ [KEY_FN_ESC] = "KEY_FN_ESC",
+ [KEY_FN_F1] = "KEY_FN_F1",
+ [KEY_FN_F2] = "KEY_FN_F2",
+ [KEY_FN_F3] = "KEY_FN_F3",
+ [KEY_FN_F4] = "KEY_FN_F4",
+ [KEY_FN_F5] = "KEY_FN_F5",
+ [KEY_FN_F6] = "KEY_FN_F6",
+ [KEY_FN_F7] = "KEY_FN_F7",
+ [KEY_FN_F8] = "KEY_FN_F8",
+ [KEY_FN_F9] = "KEY_FN_F9",
+ [KEY_FN_F10] = "KEY_FN_F10",
+ [KEY_FN_F11] = "KEY_FN_F11",
+ [KEY_FN_F12] = "KEY_FN_F12",
+ [KEY_FN_1] = "KEY_FN_1",
+ [KEY_FN_2] = "KEY_FN_2",
+ [KEY_FN_D] = "KEY_FN_D",
+ [KEY_FN_E] = "KEY_FN_E",
+ [KEY_FN_F] = "KEY_FN_F",
+ [KEY_FN_S] = "KEY_FN_S",
+ [KEY_FN_B] = "KEY_FN_B",
+ // [KEY_FN_RIGHT_SHIFT] = "KEY_FN_RIGHT_SHIFT",
+ [KEY_BRL_DOT1] = "KEY_BRL_DOT1",
+ [KEY_BRL_DOT2] = "KEY_BRL_DOT2",
+ [KEY_BRL_DOT3] = "KEY_BRL_DOT3",
+ [KEY_BRL_DOT4] = "KEY_BRL_DOT4",
+ [KEY_BRL_DOT5] = "KEY_BRL_DOT5",
+ [KEY_BRL_DOT6] = "KEY_BRL_DOT6",
+ [KEY_BRL_DOT7] = "KEY_BRL_DOT7",
+ [KEY_BRL_DOT8] = "KEY_BRL_DOT8",
+ [KEY_BRL_DOT9] = "KEY_BRL_DOT9",
+ [KEY_BRL_DOT10] = "KEY_BRL_DOT10",
+ [KEY_NUMERIC_0] = "KEY_NUMERIC_0",
+ [KEY_NUMERIC_1] = "KEY_NUMERIC_1",
+ [KEY_NUMERIC_2] = "KEY_NUMERIC_2",
+ [KEY_NUMERIC_3] = "KEY_NUMERIC_3",
+ [KEY_NUMERIC_4] = "KEY_NUMERIC_4",
+ [KEY_NUMERIC_5] = "KEY_NUMERIC_5",
+ [KEY_NUMERIC_6] = "KEY_NUMERIC_6",
+ [KEY_NUMERIC_7] = "KEY_NUMERIC_7",
+ [KEY_NUMERIC_8] = "KEY_NUMERIC_8",
+ [KEY_NUMERIC_9] = "KEY_NUMERIC_9",
+ [KEY_NUMERIC_STAR] = "KEY_NUMERIC_STAR",
+ [KEY_NUMERIC_POUND] = "KEY_NUMERIC_POUND",
+ [KEY_NUMERIC_A] = "KEY_NUMERIC_A",
+ [KEY_NUMERIC_B] = "KEY_NUMERIC_B",
+ [KEY_NUMERIC_C] = "KEY_NUMERIC_C",
+ [KEY_NUMERIC_D] = "KEY_NUMERIC_D",
+ [KEY_CAMERA_FOCUS] = "KEY_CAMERA_FOCUS",
+ [KEY_WPS_BUTTON] = "KEY_WPS_BUTTON",
+ [KEY_TOUCHPAD_TOGGLE] = "KEY_TOUCHPAD_TOGGLE",
+ [KEY_TOUCHPAD_ON] = "KEY_TOUCHPAD_ON",
+ [KEY_TOUCHPAD_OFF] = "KEY_TOUCHPAD_OFF",
+ [KEY_CAMERA_ZOOMIN] = "KEY_CAMERA_ZOOMIN",
+ [KEY_CAMERA_ZOOMOUT] = "KEY_CAMERA_ZOOMOUT",
+ [KEY_CAMERA_UP] = "KEY_CAMERA_UP",
+ [KEY_CAMERA_DOWN] = "KEY_CAMERA_DOWN",
+ [KEY_CAMERA_LEFT] = "KEY_CAMERA_LEFT",
+ [KEY_CAMERA_RIGHT] = "KEY_CAMERA_RIGHT",
+ [KEY_ATTENDANT_ON] = "KEY_ATTENDANT_ON",
+ [KEY_ATTENDANT_OFF] = "KEY_ATTENDANT_OFF",
+ [KEY_ATTENDANT_TOGGLE] = "KEY_ATTENDANT_TOGGLE",
+ [KEY_LIGHTS_TOGGLE] = "KEY_LIGHTS_TOGGLE",
+ [KEY_ALS_TOGGLE] = "KEY_ALS_TOGGLE",
+ [KEY_ROTATE_LOCK_TOGGLE] = "KEY_ROTATE_LOCK_TOGGLE",
+ [KEY_BUTTONCONFIG] = "KEY_BUTTONCONFIG",
+ [KEY_TASKMANAGER] = "KEY_TASKMANAGER",
+ [KEY_JOURNAL] = "KEY_JOURNAL",
+ [KEY_CONTROLPANEL] = "KEY_CONTROLPANEL",
+ [KEY_APPSELECT] = "KEY_APPSELECT",
+ [KEY_SCREENSAVER] = "KEY_SCREENSAVER",
+ [KEY_VOICECOMMAND] = "KEY_VOICECOMMAND",
+ [KEY_ASSISTANT] = "KEY_ASSISTANT",
+ [KEY_KBD_LAYOUT_NEXT] = "KEY_KBD_LAYOUT_NEXT",
+ // [KEY_EMOJI_PICKER] = "KEY_EMOJI_PICKER",
+ //[KEY_DICTATE] = "KEY_DICTATE",
+ //[KEY_CAMERA_ACCESS_ENABLE] = "KEY_CAMERA_ACCESS_ENABLE",
+ //[KEY_CAMERA_ACCESS_DISABLE] = "KEY_CAMERA_ACCESS_DISABLE",
+ //[KEY_CAMERA_ACCESS_TOGGLE] = "KEY_CAMERA_ACCESS_TOGGLE",
+ [KEY_BRIGHTNESS_MIN] = "KEY_BRIGHTNESS_MIN",
+ [KEY_BRIGHTNESS_MAX] = "KEY_BRIGHTNESS_MAX",
+ [KEY_KBDINPUTASSIST_PREV] = "KEY_KBDINPUTASSIST_PREV",
+ [KEY_KBDINPUTASSIST_NEXT] = "KEY_KBDINPUTASSIST_NEXT",
+ [KEY_KBDINPUTASSIST_PREVGROUP] = "KEY_KBDINPUTASSIST_PREVGROUP",
+ [KEY_KBDINPUTASSIST_NEXTGROUP] = "KEY_KBDINPUTASSIST_NEXTGROUP",
+ [KEY_KBDINPUTASSIST_ACCEPT] = "KEY_KBDINPUTASSIST_ACCEPT",
+ [KEY_KBDINPUTASSIST_CANCEL] = "KEY_KBDINPUTASSIST_CANCEL",
+ [KEY_RIGHT_UP] = "KEY_RIGHT_UP",
+ [KEY_RIGHT_DOWN] = "KEY_RIGHT_DOWN",
+ [KEY_LEFT_UP] = "KEY_LEFT_UP",
+ [KEY_LEFT_DOWN] = "KEY_LEFT_DOWN",
+ [KEY_ROOT_MENU] = "KEY_ROOT_MENU",
+ [KEY_MEDIA_TOP_MENU] = "KEY_MEDIA_TOP_MENU",
+ [KEY_NUMERIC_11] = "KEY_NUMERIC_11",
+ [KEY_NUMERIC_12] = "KEY_NUMERIC_12",
+ [KEY_AUDIO_DESC] = "KEY_AUDIO_DESC",
+ [KEY_3D_MODE] = "KEY_3D_MODE",
+ [KEY_NEXT_FAVORITE] = "KEY_NEXT_FAVORITE",
+ [KEY_STOP_RECORD] = "KEY_STOP_RECORD",
+ [KEY_PAUSE_RECORD] = "KEY_PAUSE_RECORD",
+ [KEY_VOD] = "KEY_VOD",
+ [KEY_UNMUTE] = "KEY_UNMUTE",
+ [KEY_FASTREVERSE] = "KEY_FASTREVERSE",
+ [KEY_SLOWREVERSE] = "KEY_SLOWREVERSE",
+ [KEY_DATA] = "KEY_DATA",
+ [KEY_ONSCREEN_KEYBOARD] = "KEY_ONSCREEN_KEYBOARD",
+ [KEY_PRIVACY_SCREEN_TOGGLE] = "KEY_PRIVACY_SCREEN_TOGGLE",
+ [KEY_SELECTIVE_SCREENSHOT] = "KEY_SELECTIVE_SCREENSHOT",
+ // [KEY_NEXT_ELEMENT] = "KEY_NEXT_ELEMENT",
+ // [KEY_PREVIOUS_ELEMENT] = "KEY_PREVIOUS_ELEMENT",
+ // [KEY_AUTOPILOT_ENGAGE_TOGGLE] = "KEY_AUTOPILOT_ENGAGE_TOGGLE",
+ // [KEY_MARK_WAYPOINT] = "KEY_MARK_WAYPOINT",
+ // [KEY_SOS] = "KEY_SOS",
+ // [KEY_NAV_CHART] = "KEY_NAV_CHART",
+ // [KEY_FISHING_CHART] = "KEY_FISHING_CHART",
+ // [KEY_SINGLE_RANGE_RADAR] = "KEY_SINGLE_RANGE_RADAR",
+ // [KEY_DUAL_RANGE_RADAR] = "KEY_DUAL_RANGE_RADAR",
+ // [KEY_RADAR_OVERLAY] = "KEY_RADAR_OVERLAY",
+ // [KEY_TRADITIONAL_SONAR] = "KEY_TRADITIONAL_SONAR",
+ // [KEY_CLEARVU_SONAR] = "KEY_CLEARVU_SONAR",
+ // [KEY_SIDEVU_SONAR] = "KEY_SIDEVU_SONAR",
+ // [KEY_NAV_INFO] = "KEY_NAV_INFO",
+ // [KEY_BRIGHTNESS_MENU] = "KEY_BRIGHTNESS_MENU",
+ [KEY_MACRO1] = "KEY_MACRO1",
+ [KEY_MACRO2] = "KEY_MACRO2",
+ [KEY_MACRO3] = "KEY_MACRO3",
+ [KEY_MACRO4] = "KEY_MACRO4",
+ [KEY_MACRO5] = "KEY_MACRO5",
+ [KEY_MACRO6] = "KEY_MACRO6",
+ [KEY_MACRO7] = "KEY_MACRO7",
+ [KEY_MACRO8] = "KEY_MACRO8",
+ [KEY_MACRO9] = "KEY_MACRO9",
+ [KEY_MACRO10] = "KEY_MACRO10",
+ [KEY_MACRO11] = "KEY_MACRO11",
+ [KEY_MACRO12] = "KEY_MACRO12",
+ [KEY_MACRO13] = "KEY_MACRO13",
+ [KEY_MACRO14] = "KEY_MACRO14",
+ [KEY_MACRO15] = "KEY_MACRO15",
+ [KEY_MACRO16] = "KEY_MACRO16",
+ [KEY_MACRO17] = "KEY_MACRO17",
+ [KEY_MACRO18] = "KEY_MACRO18",
+ [KEY_MACRO19] = "KEY_MACRO19",
+ [KEY_MACRO20] = "KEY_MACRO20",
+ [KEY_MACRO21] = "KEY_MACRO21",
+ [KEY_MACRO22] = "KEY_MACRO22",
+ [KEY_MACRO23] = "KEY_MACRO23",
+ [KEY_MACRO24] = "KEY_MACRO24",
+ [KEY_MACRO25] = "KEY_MACRO25",
+ [KEY_MACRO26] = "KEY_MACRO26",
+ [KEY_MACRO27] = "KEY_MACRO27",
+ [KEY_MACRO28] = "KEY_MACRO28",
+ [KEY_MACRO29] = "KEY_MACRO29",
+ [KEY_MACRO30] = "KEY_MACRO30",
+ [KEY_MACRO_RECORD_START] = "KEY_MACRO_RECORD_START",
+ [KEY_MACRO_RECORD_STOP] = "KEY_MACRO_RECORD_STOP",
+ [KEY_MACRO_PRESET_CYCLE] = "KEY_MACRO_PRESET_CYCLE",
+ [KEY_MACRO_PRESET1] = "KEY_MACRO_PRESET1",
+ [KEY_MACRO_PRESET2] = "KEY_MACRO_PRESET2",
+ [KEY_MACRO_PRESET3] = "KEY_MACRO_PRESET3",
+ [KEY_KBD_LCD_MENU1] = "KEY_KBD_LCD_MENU1",
+ [KEY_KBD_LCD_MENU2] = "KEY_KBD_LCD_MENU2",
+ [KEY_KBD_LCD_MENU3] = "KEY_KBD_LCD_MENU3",
+ [KEY_KBD_LCD_MENU4] = "KEY_KBD_LCD_MENU4",
+ [KEY_KBD_LCD_MENU5] = "KEY_KBD_LCD_MENU5",
+ [KEY_MAX] = "KEY_MAX",
+ [BTN_0] = "BTN_0",
+ [BTN_1] = "BTN_1",
+ [BTN_2] = "BTN_2",
+ [BTN_3] = "BTN_3",
+ [BTN_4] = "BTN_4",
+ [BTN_5] = "BTN_5",
+ [BTN_6] = "BTN_6",
+ [BTN_7] = "BTN_7",
+ [BTN_8] = "BTN_8",
+ [BTN_9] = "BTN_9",
+ [BTN_LEFT] = "BTN_LEFT",
+ [BTN_RIGHT] = "BTN_RIGHT",
+ [BTN_MIDDLE] = "BTN_MIDDLE",
+ [BTN_SIDE] = "BTN_SIDE",
+ [BTN_EXTRA] = "BTN_EXTRA",
+ [BTN_FORWARD] = "BTN_FORWARD",
+ [BTN_BACK] = "BTN_BACK",
+ [BTN_TASK] = "BTN_TASK",
+ [BTN_TRIGGER] = "BTN_TRIGGER",
+ [BTN_THUMB] = "BTN_THUMB",
+ [BTN_THUMB2] = "BTN_THUMB2",
+ [BTN_TOP] = "BTN_TOP",
+ [BTN_TOP2] = "BTN_TOP2",
+ [BTN_PINKIE] = "BTN_PINKIE",
+ [BTN_BASE] = "BTN_BASE",
+ [BTN_BASE2] = "BTN_BASE2",
+ [BTN_BASE3] = "BTN_BASE3",
+ [BTN_BASE4] = "BTN_BASE4",
+ [BTN_BASE5] = "BTN_BASE5",
+ [BTN_BASE6] = "BTN_BASE6",
+ [BTN_DEAD] = "BTN_DEAD",
+ [BTN_SOUTH] = "BTN_SOUTH",
+ [BTN_EAST] = "BTN_EAST",
+ [BTN_C] = "BTN_C",
+ [BTN_NORTH] = "BTN_NORTH",
+ [BTN_WEST] = "BTN_WEST",
+ [BTN_Z] = "BTN_Z",
+ [BTN_TL] = "BTN_TL",
+ [BTN_TR] = "BTN_TR",
+ [BTN_TL2] = "BTN_TL2",
+ [BTN_TR2] = "BTN_TR2",
+ [BTN_SELECT] = "BTN_SELECT",
+ [BTN_START] = "BTN_START",
+ [BTN_MODE] = "BTN_MODE",
+ [BTN_THUMBL] = "BTN_THUMBL",
+ [BTN_THUMBR] = "BTN_THUMBR",
+ [BTN_TOOL_PEN] = "BTN_TOOL_PEN",
+ [BTN_TOOL_RUBBER] = "BTN_TOOL_RUBBER",
+ [BTN_TOOL_BRUSH] = "BTN_TOOL_BRUSH",
+ [BTN_TOOL_PENCIL] = "BTN_TOOL_PENCIL",
+ [BTN_TOOL_AIRBRUSH] = "BTN_TOOL_AIRBRUSH",
+ [BTN_TOOL_FINGER] = "BTN_TOOL_FINGER",
+ [BTN_TOOL_MOUSE] = "BTN_TOOL_MOUSE",
+ [BTN_TOOL_LENS] = "BTN_TOOL_LENS",
+ [BTN_TOOL_QUINTTAP] = "BTN_TOOL_QUINTTAP",
+ [BTN_STYLUS3] = "BTN_STYLUS3",
+ [BTN_TOUCH] = "BTN_TOUCH",
+ [BTN_STYLUS] = "BTN_STYLUS",
+ [BTN_STYLUS2] = "BTN_STYLUS2",
+ [BTN_TOOL_DOUBLETAP] = "BTN_TOOL_DOUBLETAP",
+ [BTN_TOOL_TRIPLETAP] = "BTN_TOOL_TRIPLETAP",
+ [BTN_TOOL_QUADTAP] = "BTN_TOOL_QUADTAP",
+ [BTN_GEAR_DOWN] = "BTN_GEAR_DOWN",
+ [BTN_GEAR_UP] = "BTN_GEAR_UP",
+ [BTN_DPAD_UP] = "BTN_DPAD_UP",
+ [BTN_DPAD_DOWN] = "BTN_DPAD_DOWN",
+ [BTN_DPAD_LEFT] = "BTN_DPAD_LEFT",
+ [BTN_DPAD_RIGHT] = "BTN_DPAD_RIGHT",
+ [BTN_TRIGGER_HAPPY1] = "BTN_TRIGGER_HAPPY1",
+ [BTN_TRIGGER_HAPPY2] = "BTN_TRIGGER_HAPPY2",
+ [BTN_TRIGGER_HAPPY3] = "BTN_TRIGGER_HAPPY3",
+ [BTN_TRIGGER_HAPPY4] = "BTN_TRIGGER_HAPPY4",
+ [BTN_TRIGGER_HAPPY5] = "BTN_TRIGGER_HAPPY5",
+ [BTN_TRIGGER_HAPPY6] = "BTN_TRIGGER_HAPPY6",
+ [BTN_TRIGGER_HAPPY7] = "BTN_TRIGGER_HAPPY7",
+ [BTN_TRIGGER_HAPPY8] = "BTN_TRIGGER_HAPPY8",
+ [BTN_TRIGGER_HAPPY9] = "BTN_TRIGGER_HAPPY9",
+ [BTN_TRIGGER_HAPPY10] = "BTN_TRIGGER_HAPPY10",
+ [BTN_TRIGGER_HAPPY11] = "BTN_TRIGGER_HAPPY11",
+ [BTN_TRIGGER_HAPPY12] = "BTN_TRIGGER_HAPPY12",
+ [BTN_TRIGGER_HAPPY13] = "BTN_TRIGGER_HAPPY13",
+ [BTN_TRIGGER_HAPPY14] = "BTN_TRIGGER_HAPPY14",
+ [BTN_TRIGGER_HAPPY15] = "BTN_TRIGGER_HAPPY15",
+ [BTN_TRIGGER_HAPPY16] = "BTN_TRIGGER_HAPPY16",
+ [BTN_TRIGGER_HAPPY17] = "BTN_TRIGGER_HAPPY17",
+ [BTN_TRIGGER_HAPPY18] = "BTN_TRIGGER_HAPPY18",
+ [BTN_TRIGGER_HAPPY19] = "BTN_TRIGGER_HAPPY19",
+ [BTN_TRIGGER_HAPPY20] = "BTN_TRIGGER_HAPPY20",
+ [BTN_TRIGGER_HAPPY21] = "BTN_TRIGGER_HAPPY21",
+ [BTN_TRIGGER_HAPPY22] = "BTN_TRIGGER_HAPPY22",
+ [BTN_TRIGGER_HAPPY23] = "BTN_TRIGGER_HAPPY23",
+ [BTN_TRIGGER_HAPPY24] = "BTN_TRIGGER_HAPPY24",
+ [BTN_TRIGGER_HAPPY25] = "BTN_TRIGGER_HAPPY25",
+ [BTN_TRIGGER_HAPPY26] = "BTN_TRIGGER_HAPPY26",
+ [BTN_TRIGGER_HAPPY27] = "BTN_TRIGGER_HAPPY27",
+ [BTN_TRIGGER_HAPPY28] = "BTN_TRIGGER_HAPPY28",
+ [BTN_TRIGGER_HAPPY29] = "BTN_TRIGGER_HAPPY29",
+ [BTN_TRIGGER_HAPPY30] = "BTN_TRIGGER_HAPPY30",
+ [BTN_TRIGGER_HAPPY31] = "BTN_TRIGGER_HAPPY31",
+ [BTN_TRIGGER_HAPPY32] = "BTN_TRIGGER_HAPPY32",
+ [BTN_TRIGGER_HAPPY33] = "BTN_TRIGGER_HAPPY33",
+ [BTN_TRIGGER_HAPPY34] = "BTN_TRIGGER_HAPPY34",
+ [BTN_TRIGGER_HAPPY35] = "BTN_TRIGGER_HAPPY35",
+ [BTN_TRIGGER_HAPPY36] = "BTN_TRIGGER_HAPPY36",
+ [BTN_TRIGGER_HAPPY37] = "BTN_TRIGGER_HAPPY37",
+ [BTN_TRIGGER_HAPPY38] = "BTN_TRIGGER_HAPPY38",
+ [BTN_TRIGGER_HAPPY39] = "BTN_TRIGGER_HAPPY39",
+ [BTN_TRIGGER_HAPPY40] = "BTN_TRIGGER_HAPPY40",
+};
+
+static const char * const led_map[LED_MAX + 1] = {
+ [LED_NUML] = "LED_NUML",
+ [LED_CAPSL] = "LED_CAPSL",
+ [LED_SCROLLL] = "LED_SCROLLL",
+ [LED_COMPOSE] = "LED_COMPOSE",
+ [LED_KANA] = "LED_KANA",
+ [LED_SLEEP] = "LED_SLEEP",
+ [LED_SUSPEND] = "LED_SUSPEND",
+ [LED_MUTE] = "LED_MUTE",
+ [LED_MISC] = "LED_MISC",
+ [LED_MAIL] = "LED_MAIL",
+ [LED_CHARGING] = "LED_CHARGING",
+ [LED_MAX] = "LED_MAX",
+};
+
+static const char * const snd_map[SND_MAX + 1] = {
+ [SND_CLICK] = "SND_CLICK",
+ [SND_BELL] = "SND_BELL",
+ [SND_TONE] = "SND_TONE",
+ [SND_MAX] = "SND_MAX",
+};
+
+static const char * const msc_map[MSC_MAX + 1] = {
+ [MSC_SERIAL] = "MSC_SERIAL",
+ [MSC_PULSELED] = "MSC_PULSELED",
+ [MSC_GESTURE] = "MSC_GESTURE",
+ [MSC_RAW] = "MSC_RAW",
+ [MSC_SCAN] = "MSC_SCAN",
+ [MSC_TIMESTAMP] = "MSC_TIMESTAMP",
+ [MSC_MAX] = "MSC_MAX",
+};
+
+static const char * const sw_map[SW_MAX + 1] = {
+ [SW_LID] = "SW_LID",
+ [SW_TABLET_MODE] = "SW_TABLET_MODE",
+ [SW_HEADPHONE_INSERT] = "SW_HEADPHONE_INSERT",
+ [SW_RFKILL_ALL] = "SW_RFKILL_ALL",
+ [SW_MICROPHONE_INSERT] = "SW_MICROPHONE_INSERT",
+ [SW_DOCK] = "SW_DOCK",
+ [SW_LINEOUT_INSERT] = "SW_LINEOUT_INSERT",
+ [SW_JACK_PHYSICAL_INSERT] = "SW_JACK_PHYSICAL_INSERT",
+ [SW_VIDEOOUT_INSERT] = "SW_VIDEOOUT_INSERT",
+ [SW_CAMERA_LENS_COVER] = "SW_CAMERA_LENS_COVER",
+ [SW_KEYPAD_SLIDE] = "SW_KEYPAD_SLIDE",
+ [SW_FRONT_PROXIMITY] = "SW_FRONT_PROXIMITY",
+ [SW_ROTATE_LOCK] = "SW_ROTATE_LOCK",
+ [SW_LINEIN_INSERT] = "SW_LINEIN_INSERT",
+ [SW_MUTE_DEVICE] = "SW_MUTE_DEVICE",
+ [SW_PEN_INSERTED] = "SW_PEN_INSERTED",
+ [SW_MACHINE_COVER] = "SW_MACHINE_COVER",
+};
+
+static const char * const ff_map[FF_MAX + 1] = {
+ [FF_STATUS_STOPPED] = "FF_STATUS_STOPPED",
+ [FF_STATUS_MAX] = "FF_STATUS_MAX",
+ [FF_RUMBLE] = "FF_RUMBLE",
+ [FF_PERIODIC] = "FF_PERIODIC",
+ [FF_CONSTANT] = "FF_CONSTANT",
+ [FF_SPRING] = "FF_SPRING",
+ [FF_FRICTION] = "FF_FRICTION",
+ [FF_DAMPER] = "FF_DAMPER",
+ [FF_INERTIA] = "FF_INERTIA",
+ [FF_RAMP] = "FF_RAMP",
+ [FF_SQUARE] = "FF_SQUARE",
+ [FF_TRIANGLE] = "FF_TRIANGLE",
+ [FF_SINE] = "FF_SINE",
+ [FF_SAW_UP] = "FF_SAW_UP",
+ [FF_SAW_DOWN] = "FF_SAW_DOWN",
+ [FF_CUSTOM] = "FF_CUSTOM",
+ [FF_GAIN] = "FF_GAIN",
+ [FF_AUTOCENTER] = "FF_AUTOCENTER",
+ [FF_MAX] = "FF_MAX",
+};
+
+static const char * const syn_map[SYN_MAX + 1] = {
+ [SYN_REPORT] = "SYN_REPORT",
+ [SYN_CONFIG] = "SYN_CONFIG",
+ [SYN_MT_REPORT] = "SYN_MT_REPORT",
+ [SYN_DROPPED] = "SYN_DROPPED",
+ [SYN_MAX] = "SYN_MAX",
+};
+
+static const char * const rep_map[REP_MAX + 1] = {
+ [REP_DELAY] = "REP_DELAY",
+ [REP_PERIOD] = "REP_PERIOD",
+};
+
+static const char * const input_prop_map[INPUT_PROP_MAX + 1] = {
+ [INPUT_PROP_POINTER] = "INPUT_PROP_POINTER",
+ [INPUT_PROP_DIRECT] = "INPUT_PROP_DIRECT",
+ [INPUT_PROP_BUTTONPAD] = "INPUT_PROP_BUTTONPAD",
+ [INPUT_PROP_SEMI_MT] = "INPUT_PROP_SEMI_MT",
+ [INPUT_PROP_TOPBUTTONPAD] = "INPUT_PROP_TOPBUTTONPAD",
+ [INPUT_PROP_POINTING_STICK] = "INPUT_PROP_POINTING_STICK",
+ [INPUT_PROP_ACCELEROMETER] = "INPUT_PROP_ACCELEROMETER",
+ [INPUT_PROP_MAX] = "INPUT_PROP_MAX",
+};
+
+static const char * const mt_tool_map[MT_TOOL_MAX + 1] = {
+ [MT_TOOL_FINGER] = "MT_TOOL_FINGER",
+ [MT_TOOL_PEN] = "MT_TOOL_PEN",
+ [MT_TOOL_PALM] = "MT_TOOL_PALM",
+ [MT_TOOL_DIAL] = "MT_TOOL_DIAL",
+ [MT_TOOL_MAX] = "MT_TOOL_MAX",
+};
+
+static const char * const * const event_type_map[EV_MAX + 1] = {
+ [EV_REL] = rel_map,
+ [EV_ABS] = abs_map,
+ [EV_KEY] = key_map,
+ [EV_LED] = led_map,
+ [EV_SND] = snd_map,
+ [EV_MSC] = msc_map,
+ [EV_SW] = sw_map,
+ [EV_FF] = ff_map,
+ [EV_SYN] = syn_map,
+ [EV_REP] = rep_map,
+};
+
+#if __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Winitializer-overrides"
+#elif __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Woverride-init"
+#endif
+static const int ev_max[EV_MAX + 1] = {
+ SYN_MAX,
+ KEY_MAX,
+ REL_MAX,
+ ABS_MAX,
+ MSC_MAX,
+ SW_MAX,
+ -1,
+ -1,
+ -1,
+ -1,
+ -1,
+ -1,
+ -1,
+ -1,
+ -1,
+ -1,
+ -1,
+ LED_MAX,
+ SND_MAX,
+ -1,
+ REP_MAX,
+ FF_MAX,
+ -1,
+ -1,
+ -1,
+ -1,
+ -1,
+ -1,
+ -1,
+ -1,
+ -1,
+ -1,
+};
+#if __clang__
+#pragma clang diagnostic pop /* "-Winitializer-overrides" */
+#elif __GNUC__
+#pragma GCC diagnostic pop /* "-Woverride-init" */
+#endif
+
+struct name_entry {
+ const char *name;
+ unsigned int value;
+};
+
+static const struct name_entry tool_type_names[] = {
+ { .name = "MT_TOOL_DIAL", .value = MT_TOOL_DIAL },
+ { .name = "MT_TOOL_FINGER", .value = MT_TOOL_FINGER },
+ { .name = "MT_TOOL_MAX", .value = MT_TOOL_MAX },
+ { .name = "MT_TOOL_PALM", .value = MT_TOOL_PALM },
+ { .name = "MT_TOOL_PEN", .value = MT_TOOL_PEN },
+};
+
+static const struct name_entry ev_names[] = {
+ { .name = "EV_ABS", .value = EV_ABS },
+ { .name = "EV_FF", .value = EV_FF },
+ { .name = "EV_FF_STATUS", .value = EV_FF_STATUS },
+ { .name = "EV_KEY", .value = EV_KEY },
+ { .name = "EV_LED", .value = EV_LED },
+ { .name = "EV_MAX", .value = EV_MAX },
+ { .name = "EV_MSC", .value = EV_MSC },
+ { .name = "EV_PWR", .value = EV_PWR },
+ { .name = "EV_REL", .value = EV_REL },
+ { .name = "EV_REP", .value = EV_REP },
+ { .name = "EV_SND", .value = EV_SND },
+ { .name = "EV_SW", .value = EV_SW },
+ { .name = "EV_SYN", .value = EV_SYN },
+};
+
+static const struct name_entry code_names[] = {
+ { .name = "ABS_BRAKE", .value = ABS_BRAKE },
+ { .name = "ABS_DISTANCE", .value = ABS_DISTANCE },
+ { .name = "ABS_GAS", .value = ABS_GAS },
+ { .name = "ABS_HAT0X", .value = ABS_HAT0X },
+ { .name = "ABS_HAT0Y", .value = ABS_HAT0Y },
+ { .name = "ABS_HAT1X", .value = ABS_HAT1X },
+ { .name = "ABS_HAT1Y", .value = ABS_HAT1Y },
+ { .name = "ABS_HAT2X", .value = ABS_HAT2X },
+ { .name = "ABS_HAT2Y", .value = ABS_HAT2Y },
+ { .name = "ABS_HAT3X", .value = ABS_HAT3X },
+ { .name = "ABS_HAT3Y", .value = ABS_HAT3Y },
+ { .name = "ABS_MAX", .value = ABS_MAX },
+ { .name = "ABS_MISC", .value = ABS_MISC },
+ { .name = "ABS_MT_BLOB_ID", .value = ABS_MT_BLOB_ID },
+ { .name = "ABS_MT_DISTANCE", .value = ABS_MT_DISTANCE },
+ { .name = "ABS_MT_ORIENTATION", .value = ABS_MT_ORIENTATION },
+ { .name = "ABS_MT_POSITION_X", .value = ABS_MT_POSITION_X },
+ { .name = "ABS_MT_POSITION_Y", .value = ABS_MT_POSITION_Y },
+ { .name = "ABS_MT_PRESSURE", .value = ABS_MT_PRESSURE },
+ { .name = "ABS_MT_SLOT", .value = ABS_MT_SLOT },
+ { .name = "ABS_MT_TOOL_TYPE", .value = ABS_MT_TOOL_TYPE },
+ { .name = "ABS_MT_TOOL_X", .value = ABS_MT_TOOL_X },
+ { .name = "ABS_MT_TOOL_Y", .value = ABS_MT_TOOL_Y },
+ { .name = "ABS_MT_TOUCH_MAJOR", .value = ABS_MT_TOUCH_MAJOR },
+ { .name = "ABS_MT_TOUCH_MINOR", .value = ABS_MT_TOUCH_MINOR },
+ { .name = "ABS_MT_TRACKING_ID", .value = ABS_MT_TRACKING_ID },
+ { .name = "ABS_MT_WIDTH_MAJOR", .value = ABS_MT_WIDTH_MAJOR },
+ { .name = "ABS_MT_WIDTH_MINOR", .value = ABS_MT_WIDTH_MINOR },
+ { .name = "ABS_PRESSURE", .value = ABS_PRESSURE },
+ // { .name = "ABS_PROFILE", .value = ABS_PROFILE },
+ { .name = "ABS_RESERVED", .value = ABS_RESERVED },
+ { .name = "ABS_RUDDER", .value = ABS_RUDDER },
+ { .name = "ABS_RX", .value = ABS_RX },
+ { .name = "ABS_RY", .value = ABS_RY },
+ { .name = "ABS_RZ", .value = ABS_RZ },
+ { .name = "ABS_THROTTLE", .value = ABS_THROTTLE },
+ { .name = "ABS_TILT_X", .value = ABS_TILT_X },
+ { .name = "ABS_TILT_Y", .value = ABS_TILT_Y },
+ { .name = "ABS_TOOL_WIDTH", .value = ABS_TOOL_WIDTH },
+ { .name = "ABS_VOLUME", .value = ABS_VOLUME },
+ { .name = "ABS_WHEEL", .value = ABS_WHEEL },
+ { .name = "ABS_X", .value = ABS_X },
+ { .name = "ABS_Y", .value = ABS_Y },
+ { .name = "ABS_Z", .value = ABS_Z },
+ { .name = "BTN_0", .value = BTN_0 },
+ { .name = "BTN_1", .value = BTN_1 },
+ { .name = "BTN_2", .value = BTN_2 },
+ { .name = "BTN_3", .value = BTN_3 },
+ { .name = "BTN_4", .value = BTN_4 },
+ { .name = "BTN_5", .value = BTN_5 },
+ { .name = "BTN_6", .value = BTN_6 },
+ { .name = "BTN_7", .value = BTN_7 },
+ { .name = "BTN_8", .value = BTN_8 },
+ { .name = "BTN_9", .value = BTN_9 },
+ { .name = "BTN_A", .value = BTN_A },
+ { .name = "BTN_B", .value = BTN_B },
+ { .name = "BTN_BACK", .value = BTN_BACK },
+ { .name = "BTN_BASE", .value = BTN_BASE },
+ { .name = "BTN_BASE2", .value = BTN_BASE2 },
+ { .name = "BTN_BASE3", .value = BTN_BASE3 },
+ { .name = "BTN_BASE4", .value = BTN_BASE4 },
+ { .name = "BTN_BASE5", .value = BTN_BASE5 },
+ { .name = "BTN_BASE6", .value = BTN_BASE6 },
+ { .name = "BTN_C", .value = BTN_C },
+ { .name = "BTN_DEAD", .value = BTN_DEAD },
+ { .name = "BTN_DPAD_DOWN", .value = BTN_DPAD_DOWN },
+ { .name = "BTN_DPAD_LEFT", .value = BTN_DPAD_LEFT },
+ { .name = "BTN_DPAD_RIGHT", .value = BTN_DPAD_RIGHT },
+ { .name = "BTN_DPAD_UP", .value = BTN_DPAD_UP },
+ { .name = "BTN_EAST", .value = BTN_EAST },
+ { .name = "BTN_EXTRA", .value = BTN_EXTRA },
+ { .name = "BTN_FORWARD", .value = BTN_FORWARD },
+ { .name = "BTN_GEAR_DOWN", .value = BTN_GEAR_DOWN },
+ { .name = "BTN_GEAR_UP", .value = BTN_GEAR_UP },
+ { .name = "BTN_LEFT", .value = BTN_LEFT },
+ { .name = "BTN_MIDDLE", .value = BTN_MIDDLE },
+ { .name = "BTN_MODE", .value = BTN_MODE },
+ { .name = "BTN_NORTH", .value = BTN_NORTH },
+ { .name = "BTN_PINKIE", .value = BTN_PINKIE },
+ { .name = "BTN_RIGHT", .value = BTN_RIGHT },
+ { .name = "BTN_SELECT", .value = BTN_SELECT },
+ { .name = "BTN_SIDE", .value = BTN_SIDE },
+ { .name = "BTN_SOUTH", .value = BTN_SOUTH },
+ { .name = "BTN_START", .value = BTN_START },
+ { .name = "BTN_STYLUS", .value = BTN_STYLUS },
+ { .name = "BTN_STYLUS2", .value = BTN_STYLUS2 },
+ { .name = "BTN_STYLUS3", .value = BTN_STYLUS3 },
+ { .name = "BTN_TASK", .value = BTN_TASK },
+ { .name = "BTN_THUMB", .value = BTN_THUMB },
+ { .name = "BTN_THUMB2", .value = BTN_THUMB2 },
+ { .name = "BTN_THUMBL", .value = BTN_THUMBL },
+ { .name = "BTN_THUMBR", .value = BTN_THUMBR },
+ { .name = "BTN_TL", .value = BTN_TL },
+ { .name = "BTN_TL2", .value = BTN_TL2 },
+ { .name = "BTN_TOOL_AIRBRUSH", .value = BTN_TOOL_AIRBRUSH },
+ { .name = "BTN_TOOL_BRUSH", .value = BTN_TOOL_BRUSH },
+ { .name = "BTN_TOOL_DOUBLETAP", .value = BTN_TOOL_DOUBLETAP },
+ { .name = "BTN_TOOL_FINGER", .value = BTN_TOOL_FINGER },
+ { .name = "BTN_TOOL_LENS", .value = BTN_TOOL_LENS },
+ { .name = "BTN_TOOL_MOUSE", .value = BTN_TOOL_MOUSE },
+ { .name = "BTN_TOOL_PEN", .value = BTN_TOOL_PEN },
+ { .name = "BTN_TOOL_PENCIL", .value = BTN_TOOL_PENCIL },
+ { .name = "BTN_TOOL_QUADTAP", .value = BTN_TOOL_QUADTAP },
+ { .name = "BTN_TOOL_QUINTTAP", .value = BTN_TOOL_QUINTTAP },
+ { .name = "BTN_TOOL_RUBBER", .value = BTN_TOOL_RUBBER },
+ { .name = "BTN_TOOL_TRIPLETAP", .value = BTN_TOOL_TRIPLETAP },
+ { .name = "BTN_TOP", .value = BTN_TOP },
+ { .name = "BTN_TOP2", .value = BTN_TOP2 },
+ { .name = "BTN_TOUCH", .value = BTN_TOUCH },
+ { .name = "BTN_TR", .value = BTN_TR },
+ { .name = "BTN_TR2", .value = BTN_TR2 },
+ { .name = "BTN_TRIGGER", .value = BTN_TRIGGER },
+ { .name = "BTN_TRIGGER_HAPPY1", .value = BTN_TRIGGER_HAPPY1 },
+ { .name = "BTN_TRIGGER_HAPPY10", .value = BTN_TRIGGER_HAPPY10 },
+ { .name = "BTN_TRIGGER_HAPPY11", .value = BTN_TRIGGER_HAPPY11 },
+ { .name = "BTN_TRIGGER_HAPPY12", .value = BTN_TRIGGER_HAPPY12 },
+ { .name = "BTN_TRIGGER_HAPPY13", .value = BTN_TRIGGER_HAPPY13 },
+ { .name = "BTN_TRIGGER_HAPPY14", .value = BTN_TRIGGER_HAPPY14 },
+ { .name = "BTN_TRIGGER_HAPPY15", .value = BTN_TRIGGER_HAPPY15 },
+ { .name = "BTN_TRIGGER_HAPPY16", .value = BTN_TRIGGER_HAPPY16 },
+ { .name = "BTN_TRIGGER_HAPPY17", .value = BTN_TRIGGER_HAPPY17 },
+ { .name = "BTN_TRIGGER_HAPPY18", .value = BTN_TRIGGER_HAPPY18 },
+ { .name = "BTN_TRIGGER_HAPPY19", .value = BTN_TRIGGER_HAPPY19 },
+ { .name = "BTN_TRIGGER_HAPPY2", .value = BTN_TRIGGER_HAPPY2 },
+ { .name = "BTN_TRIGGER_HAPPY20", .value = BTN_TRIGGER_HAPPY20 },
+ { .name = "BTN_TRIGGER_HAPPY21", .value = BTN_TRIGGER_HAPPY21 },
+ { .name = "BTN_TRIGGER_HAPPY22", .value = BTN_TRIGGER_HAPPY22 },
+ { .name = "BTN_TRIGGER_HAPPY23", .value = BTN_TRIGGER_HAPPY23 },
+ { .name = "BTN_TRIGGER_HAPPY24", .value = BTN_TRIGGER_HAPPY24 },
+ { .name = "BTN_TRIGGER_HAPPY25", .value = BTN_TRIGGER_HAPPY25 },
+ { .name = "BTN_TRIGGER_HAPPY26", .value = BTN_TRIGGER_HAPPY26 },
+ { .name = "BTN_TRIGGER_HAPPY27", .value = BTN_TRIGGER_HAPPY27 },
+ { .name = "BTN_TRIGGER_HAPPY28", .value = BTN_TRIGGER_HAPPY28 },
+ { .name = "BTN_TRIGGER_HAPPY29", .value = BTN_TRIGGER_HAPPY29 },
+ { .name = "BTN_TRIGGER_HAPPY3", .value = BTN_TRIGGER_HAPPY3 },
+ { .name = "BTN_TRIGGER_HAPPY30", .value = BTN_TRIGGER_HAPPY30 },
+ { .name = "BTN_TRIGGER_HAPPY31", .value = BTN_TRIGGER_HAPPY31 },
+ { .name = "BTN_TRIGGER_HAPPY32", .value = BTN_TRIGGER_HAPPY32 },
+ { .name = "BTN_TRIGGER_HAPPY33", .value = BTN_TRIGGER_HAPPY33 },
+ { .name = "BTN_TRIGGER_HAPPY34", .value = BTN_TRIGGER_HAPPY34 },
+ { .name = "BTN_TRIGGER_HAPPY35", .value = BTN_TRIGGER_HAPPY35 },
+ { .name = "BTN_TRIGGER_HAPPY36", .value = BTN_TRIGGER_HAPPY36 },
+ { .name = "BTN_TRIGGER_HAPPY37", .value = BTN_TRIGGER_HAPPY37 },
+ { .name = "BTN_TRIGGER_HAPPY38", .value = BTN_TRIGGER_HAPPY38 },
+ { .name = "BTN_TRIGGER_HAPPY39", .value = BTN_TRIGGER_HAPPY39 },
+ { .name = "BTN_TRIGGER_HAPPY4", .value = BTN_TRIGGER_HAPPY4 },
+ { .name = "BTN_TRIGGER_HAPPY40", .value = BTN_TRIGGER_HAPPY40 },
+ { .name = "BTN_TRIGGER_HAPPY5", .value = BTN_TRIGGER_HAPPY5 },
+ { .name = "BTN_TRIGGER_HAPPY6", .value = BTN_TRIGGER_HAPPY6 },
+ { .name = "BTN_TRIGGER_HAPPY7", .value = BTN_TRIGGER_HAPPY7 },
+ { .name = "BTN_TRIGGER_HAPPY8", .value = BTN_TRIGGER_HAPPY8 },
+ { .name = "BTN_TRIGGER_HAPPY9", .value = BTN_TRIGGER_HAPPY9 },
+ { .name = "BTN_WEST", .value = BTN_WEST },
+ { .name = "BTN_X", .value = BTN_X },
+ { .name = "BTN_Y", .value = BTN_Y },
+ { .name = "BTN_Z", .value = BTN_Z },
+ { .name = "FF_AUTOCENTER", .value = FF_AUTOCENTER },
+ { .name = "FF_CONSTANT", .value = FF_CONSTANT },
+ { .name = "FF_CUSTOM", .value = FF_CUSTOM },
+ { .name = "FF_DAMPER", .value = FF_DAMPER },
+ { .name = "FF_FRICTION", .value = FF_FRICTION },
+ { .name = "FF_GAIN", .value = FF_GAIN },
+ { .name = "FF_INERTIA", .value = FF_INERTIA },
+ { .name = "FF_MAX", .value = FF_MAX },
+ { .name = "FF_PERIODIC", .value = FF_PERIODIC },
+ { .name = "FF_RAMP", .value = FF_RAMP },
+ { .name = "FF_RUMBLE", .value = FF_RUMBLE },
+ { .name = "FF_SAW_DOWN", .value = FF_SAW_DOWN },
+ { .name = "FF_SAW_UP", .value = FF_SAW_UP },
+ { .name = "FF_SINE", .value = FF_SINE },
+ { .name = "FF_SPRING", .value = FF_SPRING },
+ { .name = "FF_SQUARE", .value = FF_SQUARE },
+ { .name = "FF_STATUS_MAX", .value = FF_STATUS_MAX },
+ { .name = "FF_STATUS_STOPPED", .value = FF_STATUS_STOPPED },
+ { .name = "FF_TRIANGLE", .value = FF_TRIANGLE },
+ { .name = "KEY_0", .value = KEY_0 },
+ { .name = "KEY_1", .value = KEY_1 },
+ { .name = "KEY_102ND", .value = KEY_102ND },
+ { .name = "KEY_10CHANNELSDOWN", .value = KEY_10CHANNELSDOWN },
+ { .name = "KEY_10CHANNELSUP", .value = KEY_10CHANNELSUP },
+ { .name = "KEY_2", .value = KEY_2 },
+ { .name = "KEY_3", .value = KEY_3 },
+ { .name = "KEY_3D_MODE", .value = KEY_3D_MODE },
+ { .name = "KEY_4", .value = KEY_4 },
+ { .name = "KEY_5", .value = KEY_5 },
+ { .name = "KEY_6", .value = KEY_6 },
+ { .name = "KEY_7", .value = KEY_7 },
+ { .name = "KEY_8", .value = KEY_8 },
+ { .name = "KEY_9", .value = KEY_9 },
+ { .name = "KEY_A", .value = KEY_A },
+ { .name = "KEY_AB", .value = KEY_AB },
+ { .name = "KEY_ADDRESSBOOK", .value = KEY_ADDRESSBOOK },
+ { .name = "KEY_AGAIN", .value = KEY_AGAIN },
+ // { .name = "KEY_ALL_APPLICATIONS", .value = KEY_ALL_APPLICATIONS },
+ { .name = "KEY_ALS_TOGGLE", .value = KEY_ALS_TOGGLE },
+ { .name = "KEY_ALTERASE", .value = KEY_ALTERASE },
+ { .name = "KEY_ANGLE", .value = KEY_ANGLE },
+ { .name = "KEY_APOSTROPHE", .value = KEY_APOSTROPHE },
+ { .name = "KEY_APPSELECT", .value = KEY_APPSELECT },
+ { .name = "KEY_ARCHIVE", .value = KEY_ARCHIVE },
+ { .name = "KEY_ASPECT_RATIO", .value = KEY_ASPECT_RATIO },
+ { .name = "KEY_ASSISTANT", .value = KEY_ASSISTANT },
+ { .name = "KEY_ATTENDANT_OFF", .value = KEY_ATTENDANT_OFF },
+ { .name = "KEY_ATTENDANT_ON", .value = KEY_ATTENDANT_ON },
+ { .name = "KEY_ATTENDANT_TOGGLE", .value = KEY_ATTENDANT_TOGGLE },
+ { .name = "KEY_AUDIO", .value = KEY_AUDIO },
+ { .name = "KEY_AUDIO_DESC", .value = KEY_AUDIO_DESC },
+ // { .name = "KEY_AUTOPILOT_ENGAGE_TOGGLE", .value = KEY_AUTOPILOT_ENGAGE_TOGGLE },
+ { .name = "KEY_AUX", .value = KEY_AUX },
+ { .name = "KEY_B", .value = KEY_B },
+ { .name = "KEY_BACK", .value = KEY_BACK },
+ { .name = "KEY_BACKSLASH", .value = KEY_BACKSLASH },
+ { .name = "KEY_BACKSPACE", .value = KEY_BACKSPACE },
+ { .name = "KEY_BASSBOOST", .value = KEY_BASSBOOST },
+ { .name = "KEY_BATTERY", .value = KEY_BATTERY },
+ { .name = "KEY_BLUE", .value = KEY_BLUE },
+ { .name = "KEY_BLUETOOTH", .value = KEY_BLUETOOTH },
+ { .name = "KEY_BOOKMARKS", .value = KEY_BOOKMARKS },
+ { .name = "KEY_BREAK", .value = KEY_BREAK },
+ { .name = "KEY_BRIGHTNESSDOWN", .value = KEY_BRIGHTNESSDOWN },
+ { .name = "KEY_BRIGHTNESSUP", .value = KEY_BRIGHTNESSUP },
+ { .name = "KEY_BRIGHTNESS_AUTO", .value = KEY_BRIGHTNESS_AUTO },
+ { .name = "KEY_BRIGHTNESS_CYCLE", .value = KEY_BRIGHTNESS_CYCLE },
+ { .name = "KEY_BRIGHTNESS_MAX", .value = KEY_BRIGHTNESS_MAX },
+ // { .name = "KEY_BRIGHTNESS_MENU", .value = KEY_BRIGHTNESS_MENU },
+ { .name = "KEY_BRIGHTNESS_MIN", .value = KEY_BRIGHTNESS_MIN },
+ { .name = "KEY_BRL_DOT1", .value = KEY_BRL_DOT1 },
+ { .name = "KEY_BRL_DOT10", .value = KEY_BRL_DOT10 },
+ { .name = "KEY_BRL_DOT2", .value = KEY_BRL_DOT2 },
+ { .name = "KEY_BRL_DOT3", .value = KEY_BRL_DOT3 },
+ { .name = "KEY_BRL_DOT4", .value = KEY_BRL_DOT4 },
+ { .name = "KEY_BRL_DOT5", .value = KEY_BRL_DOT5 },
+ { .name = "KEY_BRL_DOT6", .value = KEY_BRL_DOT6 },
+ { .name = "KEY_BRL_DOT7", .value = KEY_BRL_DOT7 },
+ { .name = "KEY_BRL_DOT8", .value = KEY_BRL_DOT8 },
+ { .name = "KEY_BRL_DOT9", .value = KEY_BRL_DOT9 },
+ { .name = "KEY_BUTTONCONFIG", .value = KEY_BUTTONCONFIG },
+ { .name = "KEY_C", .value = KEY_C },
+ { .name = "KEY_CALC", .value = KEY_CALC },
+ { .name = "KEY_CALENDAR", .value = KEY_CALENDAR },
+ { .name = "KEY_CAMERA", .value = KEY_CAMERA },
+ // { .name = "KEY_CAMERA_ACCESS_DISABLE", .value = KEY_CAMERA_ACCESS_DISABLE },
+ // { .name = "KEY_CAMERA_ACCESS_ENABLE", .value = KEY_CAMERA_ACCESS_ENABLE },
+ // { .name = "KEY_CAMERA_ACCESS_TOGGLE", .value = KEY_CAMERA_ACCESS_TOGGLE },
+ { .name = "KEY_CAMERA_DOWN", .value = KEY_CAMERA_DOWN },
+ { .name = "KEY_CAMERA_FOCUS", .value = KEY_CAMERA_FOCUS },
+ { .name = "KEY_CAMERA_LEFT", .value = KEY_CAMERA_LEFT },
+ { .name = "KEY_CAMERA_RIGHT", .value = KEY_CAMERA_RIGHT },
+ { .name = "KEY_CAMERA_UP", .value = KEY_CAMERA_UP },
+ { .name = "KEY_CAMERA_ZOOMIN", .value = KEY_CAMERA_ZOOMIN },
+ { .name = "KEY_CAMERA_ZOOMOUT", .value = KEY_CAMERA_ZOOMOUT },
+ { .name = "KEY_CANCEL", .value = KEY_CANCEL },
+ { .name = "KEY_CAPSLOCK", .value = KEY_CAPSLOCK },
+ { .name = "KEY_CD", .value = KEY_CD },
+ { .name = "KEY_CHANNEL", .value = KEY_CHANNEL },
+ { .name = "KEY_CHANNELDOWN", .value = KEY_CHANNELDOWN },
+ { .name = "KEY_CHANNELUP", .value = KEY_CHANNELUP },
+ { .name = "KEY_CHAT", .value = KEY_CHAT },
+ { .name = "KEY_CLEAR", .value = KEY_CLEAR },
+ // { .name = "KEY_CLEARVU_SONAR", .value = KEY_CLEARVU_SONAR },
+ { .name = "KEY_CLOSE", .value = KEY_CLOSE },
+ { .name = "KEY_CLOSECD", .value = KEY_CLOSECD },
+ { .name = "KEY_COFFEE", .value = KEY_COFFEE },
+ { .name = "KEY_COMMA", .value = KEY_COMMA },
+ { .name = "KEY_COMPOSE", .value = KEY_COMPOSE },
+ { .name = "KEY_COMPUTER", .value = KEY_COMPUTER },
+ { .name = "KEY_CONFIG", .value = KEY_CONFIG },
+ { .name = "KEY_CONNECT", .value = KEY_CONNECT },
+ { .name = "KEY_CONTEXT_MENU", .value = KEY_CONTEXT_MENU },
+ { .name = "KEY_CONTROLPANEL", .value = KEY_CONTROLPANEL },
+ { .name = "KEY_COPY", .value = KEY_COPY },
+ { .name = "KEY_CUT", .value = KEY_CUT },
+ { .name = "KEY_CYCLEWINDOWS", .value = KEY_CYCLEWINDOWS },
+ { .name = "KEY_D", .value = KEY_D },
+ { .name = "KEY_DATA", .value = KEY_DATA },
+ { .name = "KEY_DATABASE", .value = KEY_DATABASE },
+ { .name = "KEY_DELETE", .value = KEY_DELETE },
+ { .name = "KEY_DELETEFILE", .value = KEY_DELETEFILE },
+ { .name = "KEY_DEL_EOL", .value = KEY_DEL_EOL },
+ { .name = "KEY_DEL_EOS", .value = KEY_DEL_EOS },
+ { .name = "KEY_DEL_LINE", .value = KEY_DEL_LINE },
+ // { .name = "KEY_DICTATE", .value = KEY_DICTATE },
+ { .name = "KEY_DIGITS", .value = KEY_DIGITS },
+ { .name = "KEY_DIRECTORY", .value = KEY_DIRECTORY },
+ { .name = "KEY_DISPLAYTOGGLE", .value = KEY_DISPLAYTOGGLE },
+ { .name = "KEY_DISPLAY_OFF", .value = KEY_DISPLAY_OFF },
+ { .name = "KEY_DOCUMENTS", .value = KEY_DOCUMENTS },
+ { .name = "KEY_DOLLAR", .value = KEY_DOLLAR },
+ { .name = "KEY_DOT", .value = KEY_DOT },
+ { .name = "KEY_DOWN", .value = KEY_DOWN },
+ // { .name = "KEY_DUAL_RANGE_RADAR", .value = KEY_DUAL_RANGE_RADAR },
+ { .name = "KEY_DVD", .value = KEY_DVD },
+ { .name = "KEY_E", .value = KEY_E },
+ { .name = "KEY_EDIT", .value = KEY_EDIT },
+ { .name = "KEY_EDITOR", .value = KEY_EDITOR },
+ { .name = "KEY_EJECTCD", .value = KEY_EJECTCD },
+ { .name = "KEY_EJECTCLOSECD", .value = KEY_EJECTCLOSECD },
+ { .name = "KEY_EMAIL", .value = KEY_EMAIL },
+ // { .name = "KEY_EMOJI_PICKER", .value = KEY_EMOJI_PICKER },
+ { .name = "KEY_END", .value = KEY_END },
+ { .name = "KEY_ENTER", .value = KEY_ENTER },
+ { .name = "KEY_EPG", .value = KEY_EPG },
+ { .name = "KEY_EQUAL", .value = KEY_EQUAL },
+ { .name = "KEY_ESC", .value = KEY_ESC },
+ { .name = "KEY_EURO", .value = KEY_EURO },
+ { .name = "KEY_EXIT", .value = KEY_EXIT },
+ { .name = "KEY_F", .value = KEY_F },
+ { .name = "KEY_F1", .value = KEY_F1 },
+ { .name = "KEY_F10", .value = KEY_F10 },
+ { .name = "KEY_F11", .value = KEY_F11 },
+ { .name = "KEY_F12", .value = KEY_F12 },
+ { .name = "KEY_F13", .value = KEY_F13 },
+ { .name = "KEY_F14", .value = KEY_F14 },
+ { .name = "KEY_F15", .value = KEY_F15 },
+ { .name = "KEY_F16", .value = KEY_F16 },
+ { .name = "KEY_F17", .value = KEY_F17 },
+ { .name = "KEY_F18", .value = KEY_F18 },
+ { .name = "KEY_F19", .value = KEY_F19 },
+ { .name = "KEY_F2", .value = KEY_F2 },
+ { .name = "KEY_F20", .value = KEY_F20 },
+ { .name = "KEY_F21", .value = KEY_F21 },
+ { .name = "KEY_F22", .value = KEY_F22 },
+ { .name = "KEY_F23", .value = KEY_F23 },
+ { .name = "KEY_F24", .value = KEY_F24 },
+ { .name = "KEY_F3", .value = KEY_F3 },
+ { .name = "KEY_F4", .value = KEY_F4 },
+ { .name = "KEY_F5", .value = KEY_F5 },
+ { .name = "KEY_F6", .value = KEY_F6 },
+ { .name = "KEY_F7", .value = KEY_F7 },
+ { .name = "KEY_F8", .value = KEY_F8 },
+ { .name = "KEY_F9", .value = KEY_F9 },
+ { .name = "KEY_FASTFORWARD", .value = KEY_FASTFORWARD },
+ { .name = "KEY_FASTREVERSE", .value = KEY_FASTREVERSE },
+ { .name = "KEY_FAVORITES", .value = KEY_FAVORITES },
+ { .name = "KEY_FILE", .value = KEY_FILE },
+ { .name = "KEY_FINANCE", .value = KEY_FINANCE },
+ { .name = "KEY_FIND", .value = KEY_FIND },
+ { .name = "KEY_FIRST", .value = KEY_FIRST },
+ // { .name = "KEY_FISHING_CHART", .value = KEY_FISHING_CHART },
+ { .name = "KEY_FN", .value = KEY_FN },
+ { .name = "KEY_FN_1", .value = KEY_FN_1 },
+ { .name = "KEY_FN_2", .value = KEY_FN_2 },
+ { .name = "KEY_FN_B", .value = KEY_FN_B },
+ { .name = "KEY_FN_D", .value = KEY_FN_D },
+ { .name = "KEY_FN_E", .value = KEY_FN_E },
+ { .name = "KEY_FN_ESC", .value = KEY_FN_ESC },
+ { .name = "KEY_FN_F", .value = KEY_FN_F },
+ { .name = "KEY_FN_F1", .value = KEY_FN_F1 },
+ { .name = "KEY_FN_F10", .value = KEY_FN_F10 },
+ { .name = "KEY_FN_F11", .value = KEY_FN_F11 },
+ { .name = "KEY_FN_F12", .value = KEY_FN_F12 },
+ { .name = "KEY_FN_F2", .value = KEY_FN_F2 },
+ { .name = "KEY_FN_F3", .value = KEY_FN_F3 },
+ { .name = "KEY_FN_F4", .value = KEY_FN_F4 },
+ { .name = "KEY_FN_F5", .value = KEY_FN_F5 },
+ { .name = "KEY_FN_F6", .value = KEY_FN_F6 },
+ { .name = "KEY_FN_F7", .value = KEY_FN_F7 },
+ { .name = "KEY_FN_F8", .value = KEY_FN_F8 },
+ { .name = "KEY_FN_F9", .value = KEY_FN_F9 },
+ // { .name = "KEY_FN_RIGHT_SHIFT", .value = KEY_FN_RIGHT_SHIFT },
+ { .name = "KEY_FN_S", .value = KEY_FN_S },
+ { .name = "KEY_FORWARD", .value = KEY_FORWARD },
+ { .name = "KEY_FORWARDMAIL", .value = KEY_FORWARDMAIL },
+ { .name = "KEY_FRAMEBACK", .value = KEY_FRAMEBACK },
+ { .name = "KEY_FRAMEFORWARD", .value = KEY_FRAMEFORWARD },
+ { .name = "KEY_FRONT", .value = KEY_FRONT },
+ { .name = "KEY_FULL_SCREEN", .value = KEY_FULL_SCREEN },
+ { .name = "KEY_G", .value = KEY_G },
+ { .name = "KEY_GAMES", .value = KEY_GAMES },
+ { .name = "KEY_GOTO", .value = KEY_GOTO },
+ { .name = "KEY_GRAPHICSEDITOR", .value = KEY_GRAPHICSEDITOR },
+ { .name = "KEY_GRAVE", .value = KEY_GRAVE },
+ { .name = "KEY_GREEN", .value = KEY_GREEN },
+ { .name = "KEY_H", .value = KEY_H },
+ { .name = "KEY_HANGEUL", .value = KEY_HANGEUL },
+ // { .name = "KEY_HANGUP_PHONE", .value = KEY_HANGUP_PHONE },
+ { .name = "KEY_HANJA", .value = KEY_HANJA },
+ { .name = "KEY_HELP", .value = KEY_HELP },
+ { .name = "KEY_HENKAN", .value = KEY_HENKAN },
+ { .name = "KEY_HIRAGANA", .value = KEY_HIRAGANA },
+ { .name = "KEY_HOME", .value = KEY_HOME },
+ { .name = "KEY_HOMEPAGE", .value = KEY_HOMEPAGE },
+ { .name = "KEY_HP", .value = KEY_HP },
+ { .name = "KEY_I", .value = KEY_I },
+ { .name = "KEY_IMAGES", .value = KEY_IMAGES },
+ { .name = "KEY_INFO", .value = KEY_INFO },
+ { .name = "KEY_INSERT", .value = KEY_INSERT },
+ { .name = "KEY_INS_LINE", .value = KEY_INS_LINE },
+ { .name = "KEY_ISO", .value = KEY_ISO },
+ { .name = "KEY_J", .value = KEY_J },
+ { .name = "KEY_JOURNAL", .value = KEY_JOURNAL },
+ { .name = "KEY_K", .value = KEY_K },
+ { .name = "KEY_KATAKANA", .value = KEY_KATAKANA },
+ { .name = "KEY_KATAKANAHIRAGANA", .value = KEY_KATAKANAHIRAGANA },
+ { .name = "KEY_KBDILLUMDOWN", .value = KEY_KBDILLUMDOWN },
+ { .name = "KEY_KBDILLUMTOGGLE", .value = KEY_KBDILLUMTOGGLE },
+ { .name = "KEY_KBDILLUMUP", .value = KEY_KBDILLUMUP },
+ { .name = "KEY_KBDINPUTASSIST_ACCEPT", .value = KEY_KBDINPUTASSIST_ACCEPT },
+ { .name = "KEY_KBDINPUTASSIST_CANCEL", .value = KEY_KBDINPUTASSIST_CANCEL },
+ { .name = "KEY_KBDINPUTASSIST_NEXT", .value = KEY_KBDINPUTASSIST_NEXT },
+ { .name = "KEY_KBDINPUTASSIST_NEXTGROUP", .value = KEY_KBDINPUTASSIST_NEXTGROUP },
+ { .name = "KEY_KBDINPUTASSIST_PREV", .value = KEY_KBDINPUTASSIST_PREV },
+ { .name = "KEY_KBDINPUTASSIST_PREVGROUP", .value = KEY_KBDINPUTASSIST_PREVGROUP },
+ { .name = "KEY_KBD_LAYOUT_NEXT", .value = KEY_KBD_LAYOUT_NEXT },
+ { .name = "KEY_KBD_LCD_MENU1", .value = KEY_KBD_LCD_MENU1 },
+ { .name = "KEY_KBD_LCD_MENU2", .value = KEY_KBD_LCD_MENU2 },
+ { .name = "KEY_KBD_LCD_MENU3", .value = KEY_KBD_LCD_MENU3 },
+ { .name = "KEY_KBD_LCD_MENU4", .value = KEY_KBD_LCD_MENU4 },
+ { .name = "KEY_KBD_LCD_MENU5", .value = KEY_KBD_LCD_MENU5 },
+ { .name = "KEY_KEYBOARD", .value = KEY_KEYBOARD },
+ { .name = "KEY_KP0", .value = KEY_KP0 },
+ { .name = "KEY_KP1", .value = KEY_KP1 },
+ { .name = "KEY_KP2", .value = KEY_KP2 },
+ { .name = "KEY_KP3", .value = KEY_KP3 },
+ { .name = "KEY_KP4", .value = KEY_KP4 },
+ { .name = "KEY_KP5", .value = KEY_KP5 },
+ { .name = "KEY_KP6", .value = KEY_KP6 },
+ { .name = "KEY_KP7", .value = KEY_KP7 },
+ { .name = "KEY_KP8", .value = KEY_KP8 },
+ { .name = "KEY_KP9", .value = KEY_KP9 },
+ { .name = "KEY_KPASTERISK", .value = KEY_KPASTERISK },
+ { .name = "KEY_KPCOMMA", .value = KEY_KPCOMMA },
+ { .name = "KEY_KPDOT", .value = KEY_KPDOT },
+ { .name = "KEY_KPENTER", .value = KEY_KPENTER },
+ { .name = "KEY_KPEQUAL", .value = KEY_KPEQUAL },
+ { .name = "KEY_KPJPCOMMA", .value = KEY_KPJPCOMMA },
+ { .name = "KEY_KPLEFTPAREN", .value = KEY_KPLEFTPAREN },
+ { .name = "KEY_KPMINUS", .value = KEY_KPMINUS },
+ { .name = "KEY_KPPLUS", .value = KEY_KPPLUS },
+ { .name = "KEY_KPPLUSMINUS", .value = KEY_KPPLUSMINUS },
+ { .name = "KEY_KPRIGHTPAREN", .value = KEY_KPRIGHTPAREN },
+ { .name = "KEY_KPSLASH", .value = KEY_KPSLASH },
+ { .name = "KEY_L", .value = KEY_L },
+ { .name = "KEY_LANGUAGE", .value = KEY_LANGUAGE },
+ { .name = "KEY_LAST", .value = KEY_LAST },
+ { .name = "KEY_LEFT", .value = KEY_LEFT },
+ { .name = "KEY_LEFTALT", .value = KEY_LEFTALT },
+ { .name = "KEY_LEFTBRACE", .value = KEY_LEFTBRACE },
+ { .name = "KEY_LEFTCTRL", .value = KEY_LEFTCTRL },
+ { .name = "KEY_LEFTMETA", .value = KEY_LEFTMETA },
+ { .name = "KEY_LEFTSHIFT", .value = KEY_LEFTSHIFT },
+ { .name = "KEY_LEFT_DOWN", .value = KEY_LEFT_DOWN },
+ { .name = "KEY_LEFT_UP", .value = KEY_LEFT_UP },
+ { .name = "KEY_LIGHTS_TOGGLE", .value = KEY_LIGHTS_TOGGLE },
+ { .name = "KEY_LINEFEED", .value = KEY_LINEFEED },
+ { .name = "KEY_LIST", .value = KEY_LIST },
+ { .name = "KEY_LOGOFF", .value = KEY_LOGOFF },
+ { .name = "KEY_M", .value = KEY_M },
+ { .name = "KEY_MACRO", .value = KEY_MACRO },
+ { .name = "KEY_MACRO1", .value = KEY_MACRO1 },
+ { .name = "KEY_MACRO10", .value = KEY_MACRO10 },
+ { .name = "KEY_MACRO11", .value = KEY_MACRO11 },
+ { .name = "KEY_MACRO12", .value = KEY_MACRO12 },
+ { .name = "KEY_MACRO13", .value = KEY_MACRO13 },
+ { .name = "KEY_MACRO14", .value = KEY_MACRO14 },
+ { .name = "KEY_MACRO15", .value = KEY_MACRO15 },
+ { .name = "KEY_MACRO16", .value = KEY_MACRO16 },
+ { .name = "KEY_MACRO17", .value = KEY_MACRO17 },
+ { .name = "KEY_MACRO18", .value = KEY_MACRO18 },
+ { .name = "KEY_MACRO19", .value = KEY_MACRO19 },
+ { .name = "KEY_MACRO2", .value = KEY_MACRO2 },
+ { .name = "KEY_MACRO20", .value = KEY_MACRO20 },
+ { .name = "KEY_MACRO21", .value = KEY_MACRO21 },
+ { .name = "KEY_MACRO22", .value = KEY_MACRO22 },
+ { .name = "KEY_MACRO23", .value = KEY_MACRO23 },
+ { .name = "KEY_MACRO24", .value = KEY_MACRO24 },
+ { .name = "KEY_MACRO25", .value = KEY_MACRO25 },
+ { .name = "KEY_MACRO26", .value = KEY_MACRO26 },
+ { .name = "KEY_MACRO27", .value = KEY_MACRO27 },
+ { .name = "KEY_MACRO28", .value = KEY_MACRO28 },
+ { .name = "KEY_MACRO29", .value = KEY_MACRO29 },
+ { .name = "KEY_MACRO3", .value = KEY_MACRO3 },
+ { .name = "KEY_MACRO30", .value = KEY_MACRO30 },
+ { .name = "KEY_MACRO4", .value = KEY_MACRO4 },
+ { .name = "KEY_MACRO5", .value = KEY_MACRO5 },
+ { .name = "KEY_MACRO6", .value = KEY_MACRO6 },
+ { .name = "KEY_MACRO7", .value = KEY_MACRO7 },
+ { .name = "KEY_MACRO8", .value = KEY_MACRO8 },
+ { .name = "KEY_MACRO9", .value = KEY_MACRO9 },
+ { .name = "KEY_MACRO_PRESET1", .value = KEY_MACRO_PRESET1 },
+ { .name = "KEY_MACRO_PRESET2", .value = KEY_MACRO_PRESET2 },
+ { .name = "KEY_MACRO_PRESET3", .value = KEY_MACRO_PRESET3 },
+ { .name = "KEY_MACRO_PRESET_CYCLE", .value = KEY_MACRO_PRESET_CYCLE },
+ { .name = "KEY_MACRO_RECORD_START", .value = KEY_MACRO_RECORD_START },
+ { .name = "KEY_MACRO_RECORD_STOP", .value = KEY_MACRO_RECORD_STOP },
+ { .name = "KEY_MAIL", .value = KEY_MAIL },
+ // { .name = "KEY_MARK_WAYPOINT", .value = KEY_MARK_WAYPOINT },
+ { .name = "KEY_MAX", .value = KEY_MAX },
+ { .name = "KEY_MEDIA", .value = KEY_MEDIA },
+ { .name = "KEY_MEDIA_REPEAT", .value = KEY_MEDIA_REPEAT },
+ { .name = "KEY_MEDIA_TOP_MENU", .value = KEY_MEDIA_TOP_MENU },
+ { .name = "KEY_MEMO", .value = KEY_MEMO },
+ { .name = "KEY_MENU", .value = KEY_MENU },
+ { .name = "KEY_MESSENGER", .value = KEY_MESSENGER },
+ { .name = "KEY_MHP", .value = KEY_MHP },
+ { .name = "KEY_MICMUTE", .value = KEY_MICMUTE },
+ { .name = "KEY_MINUS", .value = KEY_MINUS },
+ { .name = "KEY_MODE", .value = KEY_MODE },
+ { .name = "KEY_MOVE", .value = KEY_MOVE },
+ { .name = "KEY_MP3", .value = KEY_MP3 },
+ { .name = "KEY_MSDOS", .value = KEY_MSDOS },
+ { .name = "KEY_MUHENKAN", .value = KEY_MUHENKAN },
+ { .name = "KEY_MUTE", .value = KEY_MUTE },
+ { .name = "KEY_N", .value = KEY_N },
+ // { .name = "KEY_NAV_CHART", .value = KEY_NAV_CHART },
+ // { .name = "KEY_NAV_INFO", .value = KEY_NAV_INFO },
+ { .name = "KEY_NEW", .value = KEY_NEW },
+ { .name = "KEY_NEWS", .value = KEY_NEWS },
+ { .name = "KEY_NEXT", .value = KEY_NEXT },
+ { .name = "KEY_NEXTSONG", .value = KEY_NEXTSONG },
+ // { .name = "KEY_NEXT_ELEMENT", .value = KEY_NEXT_ELEMENT },
+ { .name = "KEY_NEXT_FAVORITE", .value = KEY_NEXT_FAVORITE },
+ // { .name = "KEY_NOTIFICATION_CENTER", .value = KEY_NOTIFICATION_CENTER },
+ { .name = "KEY_NUMERIC_0", .value = KEY_NUMERIC_0 },
+ { .name = "KEY_NUMERIC_1", .value = KEY_NUMERIC_1 },
+ { .name = "KEY_NUMERIC_11", .value = KEY_NUMERIC_11 },
+ { .name = "KEY_NUMERIC_12", .value = KEY_NUMERIC_12 },
+ { .name = "KEY_NUMERIC_2", .value = KEY_NUMERIC_2 },
+ { .name = "KEY_NUMERIC_3", .value = KEY_NUMERIC_3 },
+ { .name = "KEY_NUMERIC_4", .value = KEY_NUMERIC_4 },
+ { .name = "KEY_NUMERIC_5", .value = KEY_NUMERIC_5 },
+ { .name = "KEY_NUMERIC_6", .value = KEY_NUMERIC_6 },
+ { .name = "KEY_NUMERIC_7", .value = KEY_NUMERIC_7 },
+ { .name = "KEY_NUMERIC_8", .value = KEY_NUMERIC_8 },
+ { .name = "KEY_NUMERIC_9", .value = KEY_NUMERIC_9 },
+ { .name = "KEY_NUMERIC_A", .value = KEY_NUMERIC_A },
+ { .name = "KEY_NUMERIC_B", .value = KEY_NUMERIC_B },
+ { .name = "KEY_NUMERIC_C", .value = KEY_NUMERIC_C },
+ { .name = "KEY_NUMERIC_D", .value = KEY_NUMERIC_D },
+ { .name = "KEY_NUMERIC_POUND", .value = KEY_NUMERIC_POUND },
+ { .name = "KEY_NUMERIC_STAR", .value = KEY_NUMERIC_STAR },
+ { .name = "KEY_NUMLOCK", .value = KEY_NUMLOCK },
+ { .name = "KEY_O", .value = KEY_O },
+ { .name = "KEY_OK", .value = KEY_OK },
+ { .name = "KEY_ONSCREEN_KEYBOARD", .value = KEY_ONSCREEN_KEYBOARD },
+ { .name = "KEY_OPEN", .value = KEY_OPEN },
+ { .name = "KEY_OPTION", .value = KEY_OPTION },
+ { .name = "KEY_P", .value = KEY_P },
+ { .name = "KEY_PAGEDOWN", .value = KEY_PAGEDOWN },
+ { .name = "KEY_PAGEUP", .value = KEY_PAGEUP },
+ { .name = "KEY_PASTE", .value = KEY_PASTE },
+ { .name = "KEY_PAUSE", .value = KEY_PAUSE },
+ { .name = "KEY_PAUSECD", .value = KEY_PAUSECD },
+ { .name = "KEY_PAUSE_RECORD", .value = KEY_PAUSE_RECORD },
+ { .name = "KEY_PC", .value = KEY_PC },
+ { .name = "KEY_PHONE", .value = KEY_PHONE },
+ // { .name = "KEY_PICKUP_PHONE", .value = KEY_PICKUP_PHONE },
+ { .name = "KEY_PLAY", .value = KEY_PLAY },
+ { .name = "KEY_PLAYCD", .value = KEY_PLAYCD },
+ { .name = "KEY_PLAYER", .value = KEY_PLAYER },
+ { .name = "KEY_PLAYPAUSE", .value = KEY_PLAYPAUSE },
+ { .name = "KEY_POWER", .value = KEY_POWER },
+ { .name = "KEY_POWER2", .value = KEY_POWER2 },
+ { .name = "KEY_PRESENTATION", .value = KEY_PRESENTATION },
+ { .name = "KEY_PREVIOUS", .value = KEY_PREVIOUS },
+ { .name = "KEY_PREVIOUSSONG", .value = KEY_PREVIOUSSONG },
+ // { .name = "KEY_PREVIOUS_ELEMENT", .value = KEY_PREVIOUS_ELEMENT },
+ { .name = "KEY_PRINT", .value = KEY_PRINT },
+ { .name = "KEY_PRIVACY_SCREEN_TOGGLE", .value = KEY_PRIVACY_SCREEN_TOGGLE },
+ { .name = "KEY_PROG1", .value = KEY_PROG1 },
+ { .name = "KEY_PROG2", .value = KEY_PROG2 },
+ { .name = "KEY_PROG3", .value = KEY_PROG3 },
+ { .name = "KEY_PROG4", .value = KEY_PROG4 },
+ { .name = "KEY_PROGRAM", .value = KEY_PROGRAM },
+ { .name = "KEY_PROPS", .value = KEY_PROPS },
+ { .name = "KEY_PVR", .value = KEY_PVR },
+ { .name = "KEY_Q", .value = KEY_Q },
+ { .name = "KEY_QUESTION", .value = KEY_QUESTION },
+ { .name = "KEY_R", .value = KEY_R },
+ // { .name = "KEY_RADAR_OVERLAY", .value = KEY_RADAR_OVERLAY },
+ { .name = "KEY_RADIO", .value = KEY_RADIO },
+ { .name = "KEY_RECORD", .value = KEY_RECORD },
+ { .name = "KEY_RED", .value = KEY_RED },
+ { .name = "KEY_REDO", .value = KEY_REDO },
+ { .name = "KEY_REFRESH", .value = KEY_REFRESH },
+ { .name = "KEY_REPLY", .value = KEY_REPLY },
+ { .name = "KEY_RESERVED", .value = KEY_RESERVED },
+ { .name = "KEY_RESTART", .value = KEY_RESTART },
+ { .name = "KEY_REWIND", .value = KEY_REWIND },
+ { .name = "KEY_RFKILL", .value = KEY_RFKILL },
+ { .name = "KEY_RIGHT", .value = KEY_RIGHT },
+ { .name = "KEY_RIGHTALT", .value = KEY_RIGHTALT },
+ { .name = "KEY_RIGHTBRACE", .value = KEY_RIGHTBRACE },
+ { .name = "KEY_RIGHTCTRL", .value = KEY_RIGHTCTRL },
+ { .name = "KEY_RIGHTMETA", .value = KEY_RIGHTMETA },
+ { .name = "KEY_RIGHTSHIFT", .value = KEY_RIGHTSHIFT },
+ { .name = "KEY_RIGHT_DOWN", .value = KEY_RIGHT_DOWN },
+ { .name = "KEY_RIGHT_UP", .value = KEY_RIGHT_UP },
+ { .name = "KEY_RO", .value = KEY_RO },
+ { .name = "KEY_ROOT_MENU", .value = KEY_ROOT_MENU },
+ { .name = "KEY_ROTATE_DISPLAY", .value = KEY_ROTATE_DISPLAY },
+ { .name = "KEY_ROTATE_LOCK_TOGGLE", .value = KEY_ROTATE_LOCK_TOGGLE },
+ { .name = "KEY_S", .value = KEY_S },
+ { .name = "KEY_SAT", .value = KEY_SAT },
+ { .name = "KEY_SAT2", .value = KEY_SAT2 },
+ { .name = "KEY_SAVE", .value = KEY_SAVE },
+ { .name = "KEY_SCALE", .value = KEY_SCALE },
+ { .name = "KEY_SCREENSAVER", .value = KEY_SCREENSAVER },
+ { .name = "KEY_SCROLLDOWN", .value = KEY_SCROLLDOWN },
+ { .name = "KEY_SCROLLLOCK", .value = KEY_SCROLLLOCK },
+ { .name = "KEY_SCROLLUP", .value = KEY_SCROLLUP },
+ { .name = "KEY_SEARCH", .value = KEY_SEARCH },
+ { .name = "KEY_SELECT", .value = KEY_SELECT },
+ { .name = "KEY_SELECTIVE_SCREENSHOT", .value = KEY_SELECTIVE_SCREENSHOT },
+ { .name = "KEY_SEMICOLON", .value = KEY_SEMICOLON },
+ { .name = "KEY_SEND", .value = KEY_SEND },
+ { .name = "KEY_SENDFILE", .value = KEY_SENDFILE },
+ { .name = "KEY_SETUP", .value = KEY_SETUP },
+ { .name = "KEY_SHOP", .value = KEY_SHOP },
+ { .name = "KEY_SHUFFLE", .value = KEY_SHUFFLE },
+ // { .name = "KEY_SIDEVU_SONAR", .value = KEY_SIDEVU_SONAR },
+ // { .name = "KEY_SINGLE_RANGE_RADAR", .value = KEY_SINGLE_RANGE_RADAR },
+ { .name = "KEY_SLASH", .value = KEY_SLASH },
+ { .name = "KEY_SLEEP", .value = KEY_SLEEP },
+ { .name = "KEY_SLOW", .value = KEY_SLOW },
+ { .name = "KEY_SLOWREVERSE", .value = KEY_SLOWREVERSE },
+ // { .name = "KEY_SOS", .value = KEY_SOS },
+ { .name = "KEY_SOUND", .value = KEY_SOUND },
+ { .name = "KEY_SPACE", .value = KEY_SPACE },
+ { .name = "KEY_SPELLCHECK", .value = KEY_SPELLCHECK },
+ { .name = "KEY_SPORT", .value = KEY_SPORT },
+ { .name = "KEY_SPREADSHEET", .value = KEY_SPREADSHEET },
+ { .name = "KEY_STOP", .value = KEY_STOP },
+ { .name = "KEY_STOPCD", .value = KEY_STOPCD },
+ { .name = "KEY_STOP_RECORD", .value = KEY_STOP_RECORD },
+ { .name = "KEY_SUBTITLE", .value = KEY_SUBTITLE },
+ { .name = "KEY_SUSPEND", .value = KEY_SUSPEND },
+ { .name = "KEY_SWITCHVIDEOMODE", .value = KEY_SWITCHVIDEOMODE },
+ { .name = "KEY_SYSRQ", .value = KEY_SYSRQ },
+ { .name = "KEY_T", .value = KEY_T },
+ { .name = "KEY_TAB", .value = KEY_TAB },
+ { .name = "KEY_TAPE", .value = KEY_TAPE },
+ { .name = "KEY_TASKMANAGER", .value = KEY_TASKMANAGER },
+ { .name = "KEY_TEEN", .value = KEY_TEEN },
+ { .name = "KEY_TEXT", .value = KEY_TEXT },
+ { .name = "KEY_TIME", .value = KEY_TIME },
+ { .name = "KEY_TITLE", .value = KEY_TITLE },
+ { .name = "KEY_TOUCHPAD_OFF", .value = KEY_TOUCHPAD_OFF },
+ { .name = "KEY_TOUCHPAD_ON", .value = KEY_TOUCHPAD_ON },
+ { .name = "KEY_TOUCHPAD_TOGGLE", .value = KEY_TOUCHPAD_TOGGLE },
+ // { .name = "KEY_TRADITIONAL_SONAR", .value = KEY_TRADITIONAL_SONAR },
+ { .name = "KEY_TUNER", .value = KEY_TUNER },
+ { .name = "KEY_TV", .value = KEY_TV },
+ { .name = "KEY_TV2", .value = KEY_TV2 },
+ { .name = "KEY_TWEN", .value = KEY_TWEN },
+ { .name = "KEY_U", .value = KEY_U },
+ { .name = "KEY_UNDO", .value = KEY_UNDO },
+ { .name = "KEY_UNKNOWN", .value = KEY_UNKNOWN },
+ { .name = "KEY_UNMUTE", .value = KEY_UNMUTE },
+ { .name = "KEY_UP", .value = KEY_UP },
+ { .name = "KEY_UWB", .value = KEY_UWB },
+ { .name = "KEY_V", .value = KEY_V },
+ { .name = "KEY_VCR", .value = KEY_VCR },
+ { .name = "KEY_VCR2", .value = KEY_VCR2 },
+ { .name = "KEY_VENDOR", .value = KEY_VENDOR },
+ { .name = "KEY_VIDEO", .value = KEY_VIDEO },
+ { .name = "KEY_VIDEOPHONE", .value = KEY_VIDEOPHONE },
+ { .name = "KEY_VIDEO_NEXT", .value = KEY_VIDEO_NEXT },
+ { .name = "KEY_VIDEO_PREV", .value = KEY_VIDEO_PREV },
+ { .name = "KEY_VOD", .value = KEY_VOD },
+ { .name = "KEY_VOICECOMMAND", .value = KEY_VOICECOMMAND },
+ { .name = "KEY_VOICEMAIL", .value = KEY_VOICEMAIL },
+ { .name = "KEY_VOLUMEDOWN", .value = KEY_VOLUMEDOWN },
+ { .name = "KEY_VOLUMEUP", .value = KEY_VOLUMEUP },
+ { .name = "KEY_W", .value = KEY_W },
+ { .name = "KEY_WAKEUP", .value = KEY_WAKEUP },
+ { .name = "KEY_WLAN", .value = KEY_WLAN },
+ { .name = "KEY_WORDPROCESSOR", .value = KEY_WORDPROCESSOR },
+ { .name = "KEY_WPS_BUTTON", .value = KEY_WPS_BUTTON },
+ { .name = "KEY_WWAN", .value = KEY_WWAN },
+ { .name = "KEY_WWW", .value = KEY_WWW },
+ { .name = "KEY_X", .value = KEY_X },
+ { .name = "KEY_XFER", .value = KEY_XFER },
+ { .name = "KEY_Y", .value = KEY_Y },
+ { .name = "KEY_YELLOW", .value = KEY_YELLOW },
+ { .name = "KEY_YEN", .value = KEY_YEN },
+ { .name = "KEY_Z", .value = KEY_Z },
+ { .name = "KEY_ZENKAKUHANKAKU", .value = KEY_ZENKAKUHANKAKU },
+ { .name = "KEY_ZOOMIN", .value = KEY_ZOOMIN },
+ { .name = "KEY_ZOOMOUT", .value = KEY_ZOOMOUT },
+ { .name = "KEY_ZOOMRESET", .value = KEY_ZOOMRESET },
+ { .name = "LED_CAPSL", .value = LED_CAPSL },
+ { .name = "LED_CHARGING", .value = LED_CHARGING },
+ { .name = "LED_COMPOSE", .value = LED_COMPOSE },
+ { .name = "LED_KANA", .value = LED_KANA },
+ { .name = "LED_MAIL", .value = LED_MAIL },
+ { .name = "LED_MAX", .value = LED_MAX },
+ { .name = "LED_MISC", .value = LED_MISC },
+ { .name = "LED_MUTE", .value = LED_MUTE },
+ { .name = "LED_NUML", .value = LED_NUML },
+ { .name = "LED_SCROLLL", .value = LED_SCROLLL },
+ { .name = "LED_SLEEP", .value = LED_SLEEP },
+ { .name = "LED_SUSPEND", .value = LED_SUSPEND },
+ { .name = "MSC_GESTURE", .value = MSC_GESTURE },
+ { .name = "MSC_MAX", .value = MSC_MAX },
+ { .name = "MSC_PULSELED", .value = MSC_PULSELED },
+ { .name = "MSC_RAW", .value = MSC_RAW },
+ { .name = "MSC_SCAN", .value = MSC_SCAN },
+ { .name = "MSC_SERIAL", .value = MSC_SERIAL },
+ { .name = "MSC_TIMESTAMP", .value = MSC_TIMESTAMP },
+ { .name = "REL_DIAL", .value = REL_DIAL },
+ { .name = "REL_HWHEEL", .value = REL_HWHEEL },
+ { .name = "REL_HWHEEL_HI_RES", .value = REL_HWHEEL_HI_RES },
+ { .name = "REL_MAX", .value = REL_MAX },
+ { .name = "REL_MISC", .value = REL_MISC },
+ { .name = "REL_RESERVED", .value = REL_RESERVED },
+ { .name = "REL_RX", .value = REL_RX },
+ { .name = "REL_RY", .value = REL_RY },
+ { .name = "REL_RZ", .value = REL_RZ },
+ { .name = "REL_WHEEL", .value = REL_WHEEL },
+ { .name = "REL_WHEEL_HI_RES", .value = REL_WHEEL_HI_RES },
+ { .name = "REL_X", .value = REL_X },
+ { .name = "REL_Y", .value = REL_Y },
+ { .name = "REL_Z", .value = REL_Z },
+ { .name = "REP_DELAY", .value = REP_DELAY },
+ { .name = "REP_MAX", .value = REP_MAX },
+ { .name = "REP_PERIOD", .value = REP_PERIOD },
+ { .name = "SND_BELL", .value = SND_BELL },
+ { .name = "SND_CLICK", .value = SND_CLICK },
+ { .name = "SND_MAX", .value = SND_MAX },
+ { .name = "SND_TONE", .value = SND_TONE },
+ { .name = "SW_CAMERA_LENS_COVER", .value = SW_CAMERA_LENS_COVER },
+ { .name = "SW_DOCK", .value = SW_DOCK },
+ { .name = "SW_FRONT_PROXIMITY", .value = SW_FRONT_PROXIMITY },
+ { .name = "SW_HEADPHONE_INSERT", .value = SW_HEADPHONE_INSERT },
+ { .name = "SW_JACK_PHYSICAL_INSERT", .value = SW_JACK_PHYSICAL_INSERT },
+ { .name = "SW_KEYPAD_SLIDE", .value = SW_KEYPAD_SLIDE },
+ { .name = "SW_LID", .value = SW_LID },
+ { .name = "SW_LINEIN_INSERT", .value = SW_LINEIN_INSERT },
+ { .name = "SW_LINEOUT_INSERT", .value = SW_LINEOUT_INSERT },
+ { .name = "SW_MACHINE_COVER", .value = SW_MACHINE_COVER },
+ { .name = "SW_MAX", .value = SW_MAX },
+ { .name = "SW_MICROPHONE_INSERT", .value = SW_MICROPHONE_INSERT },
+ { .name = "SW_MUTE_DEVICE", .value = SW_MUTE_DEVICE },
+ { .name = "SW_PEN_INSERTED", .value = SW_PEN_INSERTED },
+ { .name = "SW_RFKILL_ALL", .value = SW_RFKILL_ALL },
+ { .name = "SW_ROTATE_LOCK", .value = SW_ROTATE_LOCK },
+ { .name = "SW_TABLET_MODE", .value = SW_TABLET_MODE },
+ { .name = "SW_VIDEOOUT_INSERT", .value = SW_VIDEOOUT_INSERT },
+ { .name = "SYN_CONFIG", .value = SYN_CONFIG },
+ { .name = "SYN_DROPPED", .value = SYN_DROPPED },
+ { .name = "SYN_MAX", .value = SYN_MAX },
+ { .name = "SYN_MT_REPORT", .value = SYN_MT_REPORT },
+ { .name = "SYN_REPORT", .value = SYN_REPORT },
+};
+
+static const struct name_entry prop_names[] = {
+ { .name = "INPUT_PROP_ACCELEROMETER", .value = INPUT_PROP_ACCELEROMETER },
+ { .name = "INPUT_PROP_BUTTONPAD", .value = INPUT_PROP_BUTTONPAD },
+ { .name = "INPUT_PROP_DIRECT", .value = INPUT_PROP_DIRECT },
+ { .name = "INPUT_PROP_MAX", .value = INPUT_PROP_MAX },
+ { .name = "INPUT_PROP_POINTER", .value = INPUT_PROP_POINTER },
+ { .name = "INPUT_PROP_POINTING_STICK", .value = INPUT_PROP_POINTING_STICK },
+ { .name = "INPUT_PROP_SEMI_MT", .value = INPUT_PROP_SEMI_MT },
+ { .name = "INPUT_PROP_TOPBUTTONPAD", .value = INPUT_PROP_TOPBUTTONPAD },
+};
+
+#endif /* EVENT_NAMES_H */
diff --git a/usr.sbin/moused/moused/moused.8 b/usr.sbin/moused/moused/moused.8
new file mode 100644
index 000000000000..96feeda336c9
--- /dev/null
+++ b/usr.sbin/moused/moused/moused.8
@@ -0,0 +1,538 @@
+.\" SPDX-License-Identifier: BSD-4-Clause
+.\"
+.\" Copyright (c) 1996 Mike Pritchard <mpp@FreeBSD.org>
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. All advertising materials mentioning features or use of this software
+.\" must display the following acknowledgement:
+.\" This product includes software developed by Mike Pritchard.
+.\" 4. Neither the name of the author nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd June 14, 2025
+.Dt MOUSED 8
+.Os
+.Sh NAME
+.Nm moused
+.Nd pass mouse data to the console driver
+.Sh SYNOPSIS
+.Nm
+.Op Fl dfg
+.Op Fl I Ar file
+.Op Fl F Ar rate
+.Op Fl r Ar resolution
+.Op Fl VH Op Fl U Ar distance Fl L Ar distance
+.Op Fl A Ar exp Ns Op , Ns Ar offset
+.Op Fl a Ar X Ns Op , Ns Ar Y
+.Op Fl C Ar threshold
+.Op Fl m Ar N=M
+.Op Fl w Ar N
+.Op Fl z Ar target
+.Op Fl t Ar mousetype
+.Op Fl l Ar level
+.Op Fl 3 Op Fl E Ar timeout
+.Op Fl T Ar distance Ns Op , Ns Ar time Ns Op , Ns Ar after
+.Fl p Ar port
+.Pp
+.Nm
+.Op Fl d
+.Fl p Ar port
+.Fl i Ar info
+.Sh DESCRIPTION
+The
+.Nm
+utility and the console driver work together to support
+mouse operation in the text console and user programs.
+They virtualize the mouse and provide user programs with mouse data
+in the standard format
+(see
+.Xr sysmouse 4 ) .
+.Pp
+The mouse daemon listens to the specified port for mouse data,
+interprets and then passes it via ioctls to the console driver.
+Supported data interfaces are
+.Qq input event device
+AKA evdev and
+.Xr sysmouse 4
+level 1.
+The mouse daemon
+reports translation movement, button press/release
+events and movement of the roller or the wheel if available.
+The roller/wheel movement is reported as
+.Dq Z
+axis movement.
+.Pp
+The console driver will display the mouse pointer on the screen
+and provide cut and paste functions if the mouse pointer is enabled
+in the virtual console via
+.Xr vidcontrol 1 .
+If
+.Xr sysmouse 4
+is opened by the user program, the console driver also passes the mouse
+data to the device so that the user program will see it.
+.Pp
+If the mouse daemon receives the signal
+.Dv SIGHUP ,
+it will reopen the mouse port and reinitialize itself.
+Useful if
+the mouse is attached/detached while the system is suspended.
+.Pp
+If the mouse daemon receives the signal
+.Dv SIGUSR1 ,
+it will stop passing mouse events.
+Sending the signal
+.Dv SIGUSR1
+again will resume passing mouse events.
+Useful if your typing on a laptop is
+interrupted by accidentally touching the mouse pad.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl 3
+Emulate the third (middle) button for 2-button mice.
+It is emulated
+by pressing the left and right physical buttons simultaneously.
+.It Fl C Ar threshold
+Set double click speed as the maximum interval in msec between button clicks.
+Without this option, the default value of 500 msec will be assumed.
+This option will have effect only on the cut and paste operations
+in the text mode console.
+The user program which is reading mouse data
+via
+.Xr sysmouse 4
+will not be affected.
+.It Fl E Ar timeout
+When the third button emulation is enabled
+(see above),
+the
+.Nm
+utility waits
+.Ar timeout
+msec at most before deciding whether two buttons are being pressed
+simultaneously.
+The default timeout is 100 msec.
+.It Fl F Ar rate
+Only for
+.Xr sysmouse 4
+interface.
+Set the report rate (reports/sec) of the device if supported.
+.It Fl L Ar distance
+When
+.Dq Virtual Scrolling
+is enabled, the
+.Fl L
+option can be used to set the
+.Ar distance
+(in pixels) that the mouse must move before a scroll event
+is generated. This effectively controls the scrolling speed.
+The default
+.Ar distance
+is 2 pixels.
+.It Fl H
+Enable
+.Dq Horizontal Virtual Scrolling .
+With this option set, holding the middle mouse
+button down will cause motion to be interpreted as
+horizontal scrolling.
+Use the
+.Fl U
+option to set the distance the mouse must move before the scrolling mode is
+activated and the
+.Fl L
+option to set the scrolling speed.
+This option may be used with or without the
+.Fl V
+option.
+.It Fl I Ar file
+Write the process id of the
+.Nm
+utility in the specified file.
+Without this option, the process id will be stored in
+.Pa /var/run/moused.pid .
+.It Fl T Ar distance Ns Op , Ns Ar time Ns Op , Ns Ar after
+Terminate drift.
+Use this option if mouse pointer slowly wanders when mouse is not moved.
+Movements up to
+.Ar distance
+(for example 4) pixels (X+Y) in
+.Ar time
+msec (default 500) are ignored, except during
+.Ar after
+msec (default 4000) since last real mouse movement.
+.It Fl V
+Enable
+.Dq Virtual Scrolling .
+With this option set, holding the middle mouse
+button down will cause motion to be interpreted as scrolling.
+Use the
+.Fl U
+option to set the distance the mouse must move before the scrolling mode is
+activated and the
+.Fl L
+option to set the scrolling speed.
+.It Fl U Ar distance
+When
+.Dq Virtual Scrolling
+is enabled, the
+.Fl U
+option can be used to set the
+.Ar distance
+(in pixels) that the mouse must move before the scrolling
+mode is activated.
+The default
+.Ar distance
+is 3 pixels.
+.It Fl A Ar exp Ns Op , Ns Ar offset
+Apply exponential (dynamic) acceleration to mouse movements:
+the faster you move the mouse, the more it will be accelerated.
+That means that small mouse movements are not accelerated,
+so they are still very accurate, while a faster movement will
+drive the pointer quickly across the screen.
+.Pp
+The
+.Ar exp
+value specifies the exponent, which is basically
+the amount of acceleration. Useful values are in the
+range 1.1 to 2.0, but it depends on your mouse hardware
+and your personal preference. A value of 1.0 means no
+exponential acceleration. A value of 2.0 means squared
+acceleration (i.e. if you move the mouse twice as fast,
+the pointer will move four times as fast on the screen).
+Values beyond 2.0 are possible but not recommended.
+A good value to start is probably 1.5.
+.Pp
+The optional
+.Ar offset
+value specifies the distance at which the acceleration
+begins. The default is 1.0, which means that the
+acceleration is applied to movements larger than one unit.
+If you specify a larger value, it takes more speed for
+the acceleration to kick in, i.e. the speed range for
+small and accurate movements is wider.
+Usually the default should be sufficient, but if you're
+not satisfied with the behaviour, try a value of 2.0.
+.Pp
+Note that the
+.Fl A
+option interacts badly with the X server's own acceleration,
+which doesn't work very well anyway. Therefore it is
+recommended to switch it off if necessary:
+.Dq xset m 1 .
+.It Fl a Ar X Ns Op , Ns Ar Y
+Accelerate or decelerate the mouse input.
+This is a linear acceleration only.
+Values less than 1.0 slow down movement, values greater than 1.0 speed it
+up.
+Specifying only one value sets the acceleration for both axes.
+.Pp
+You can use the
+.Fl a
+and
+.Fl A
+options at the same time to have the combined effect
+of linear and exponential acceleration.
+.It Fl d
+Enable debugging messages.
+.It Fl f
+Do not become a daemon and instead run as a foreground process.
+Useful for testing and debugging.
+.It Fl g
+Only for evdev interface.
+Become the sole recipient of all incoming input events.
+This prevents other processes from getting input events on the device.
+.It Fl i Ar info
+Print specified information and quit.
+Available pieces of
+information are:
+.Pp
+.Bl -tag -compact -width modelxxx
+.It Ar port
+Port (device file) name, i.e.\&
+.Pa /dev/input/event0 ,
+.Pa /dev/ums0
+and
+.Pa /dev/psm0 .
+.It Ar if
+Interface type:
+.Dq evdev
+or
+.Dq sysmouse .
+.It Ar type
+Device type:
+.Dq mouse
+or
+.Dq touchpad .
+.It Ar model
+Mouse model.
+.It Ar all
+All of the above items.
+Print port, type and model in this order
+in one line.
+.El
+.Pp
+If the
+.Nm
+utility cannot determine the requested information, it prints
+.Dq Li unknown
+or
+.Dq Li generic .
+.It Fl l Ar level
+Ignored.
+Used for compatibiliy with legacy
+.Nm .
+.It Fl m Ar N=M
+Assign the physical button
+.Ar M
+to the logical button
+.Ar N .
+You may specify as many instances of this option as you like.
+More than one physical button may be assigned to a logical button at the
+same time.
+In this case the logical button will be down,
+if either of the assigned physical buttons is held down.
+Do not put space around
+.Ql = .
+.It Fl p Ar port
+Use
+.Ar port
+to communicate with the mouse.
+.It Fl r Ar resolution
+Only for
+.Xr sysmouse 4
+interface.
+Set the resolution of the device; in Dots Per Inch, or
+.Ar low ,
+.Ar medium-low ,
+.Ar medium-high
+or
+.Ar high .
+This option may not be supported by all the device.
+.It Fl t Ar type
+Ignored.
+Used for compatibiliy with legacy
+.Nm .
+.It Fl q Ar config
+Path to configuration file.
+.It Fl Q Ar quirks
+Path to quirks directory.
+.It Fl w Ar N
+Make the physical button
+.Ar N
+act as the wheel mode button.
+While this button is pressed, X and Y axis movement is reported to be zero
+and the Y axis movement is mapped to Z axis.
+You may further map the Z axis movement to virtual buttons by the
+.Fl z
+option below.
+.It Fl z Ar target
+Map Z axis (roller/wheel) movement to another axis or to virtual buttons.
+Valid
+.Ar target
+maybe:
+.Bl -tag -compact -width x__
+.It Ar x
+.It Ar y
+X or Y axis movement will be reported when the Z axis movement is detected.
+.It Ar N
+Report down events for the virtual buttons
+.Ar N
+and
+.Ar N+1
+respectively when negative and positive Z axis movement
+is detected.
+There do not need to be physical buttons
+.Ar N
+and
+.Ar N+1 .
+Note that mapping to logical buttons is carried out after mapping
+from the Z axis movement to the virtual buttons is done.
+.It Ar N1 N2
+Report down events for the virtual buttons
+.Ar N1
+and
+.Ar N2
+respectively when negative and positive Z axis movement
+is detected.
+.It Ar N1 N2 N3 N4
+This is useful for the mouse with two wheels of which
+the second wheel is used to generate horizontal scroll action,
+and for the mouse which has a knob or a stick which can detect
+the horizontal force applied by the user.
+.Pp
+The motion of the second wheel will be mapped to the buttons
+.Ar N3 ,
+for the negative direction, and
+.Ar N4 ,
+for the positive direction.
+If the buttons
+.Ar N3
+and
+.Ar N4
+actually exist in this mouse, their actions will not be detected.
+.Pp
+Note that horizontal movement or second roller/wheel movement may not
+always be detected,
+because there appears to be no accepted standard as to how it is encoded.
+.Pp
+Note also that some mice think left is the negative horizontal direction;
+others may think otherwise.
+Moreover, there are some mice whose two wheels are both mounted vertically,
+and the direction of the second vertical wheel does not match the
+first one.
+.El
+.Ss Multiple Mice
+The
+.Nm
+utility may operate in 2 different modes depending on the value of
+.Fl p
+option.
+When started with
+.Fl p Ar auto
+option specified the
+.Nm
+handles all recognized pointing devices in a single instance.
+Device hotplug is supported through
+.Xr devd 8 .
+Only evdev interface is available in this mode.
+When started with
+.Fl p Ar <selected_port>
+option specified the
+.Nm
+handles single device located at
+.Ar <selected_port> .
+Both evdev and
+.Xr sysmouse 4
+level 1 interfaces are available in this mode.
+Multiple
+.Nm
+instances may be run simultaneously.
+.Sh FILES
+.Bl -tag -width /var/run/moused.pid -compact
+.It Pa /dev/consolectl
+device to control the console
+.It Pa /dev/input/event%d
+Input event device
+.It Pa /dev/psm%d
+PS/2 mouse driver
+.It Pa /dev/sysmouse
+virtualized mouse driver
+.It Pa /dev/ums%d
+USB mouse driver
+.It Pa /var/run/moused.pid
+process id of the currently running
+.Nm
+utility
+.El
+.Sh EXAMPLES
+.Bd -literal -offset indent
+moused -p auto
+vidcontrol -m on
+.Ed
+.Pp
+Start
+.Nm
+utility to handle all evdev pointing devices automatically with hotplug
+support.
+And enable the mouse pointer in the text console after than.
+The daemon can be started without the
+.Fl p
+option as well.
+.Pp
+.Dl "moused -f -d -g -p /dev/input/event0"
+.Ed
+.Pp
+Start the mouse daemon on the
+.Pa /dev/input/event0
+in the exclusive foreground debug mode.
+Exclusive mode may disable mouse in Xorg session.
+.Pp
+.Dl "moused -p /dev/input/event0 -m 1=3 -m 3=1"
+.Pp
+Assign the physical button 3 (right button) to the logical button 1
+(logical left) and the physical button 1 (left) to the logical
+button 3 (logical right).
+This will effectively swap the left and right buttons.
+.Pp
+.Dl "moused -p /dev/input/event0 -z 4"
+.Pp
+Report negative Z axis movement (i.e., mouse wheel) as the button 4 pressed
+and positive Z axis movement (i.e., mouse wheel) as the button 5 pressed.
+.Pp
+If you add
+.Pp
+.Dl "ALL ALL = NOPASSWD: /usr/bin/killall -USR1 moused"
+.Pp
+to your
+.Pa /usr/local/etc/sudoers
+file, and bind
+.Pp
+.Dl "killall -USR1 moused"
+.Pp
+to a key in your window manager, you can suspend mouse events on your laptop if
+you keep brushing over the mouse pad while typing.
+.Sh SEE ALSO
+.Xr moused.conf 5 ,
+.Xr kill 1 ,
+.Xr vidcontrol 1 ,
+.Xr xset 1 ,
+.Xr keyboard 4 ,
+.Xr psm 4 ,
+.Xr sysmouse 4 ,
+.Xr ums 4 ,
+.Xr devd 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 2.2 .
+It was rewriten to support multiple input event devices in
+.Fx 15.0 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+utility was originally written by
+.An Michael Smith Aq Mt msmith@FreeBSD.org .
+This manual page was written by
+.An Mike Pritchard Aq Mt mpp@FreeBSD.org .
+The command and manual page have since been updated by
+.An Kazutaka Yokota Aq Mt yokota@FreeBSD.org .
+Multiple input event devices support was added by
+.An Vladimir Kondratyev Aq Mt wulf@FreeBSD.org .
+.Sh CAVEATS
+Cut and paste functions in the virtual console assume that there
+are three buttons on the mouse.
+The logical button 1 (logical left) selects a region of text in the
+console and copies it to the cut buffer.
+The logical button 3 (logical right) extends the selected region.
+The logical button 2 (logical middle) pastes the selected text
+at the text cursor position.
+If the mouse has only two buttons, the middle, `paste' button
+is not available.
+To obtain the paste function, use the
+.Fl 3
+option to emulate the middle button, or use the
+.Fl m
+option to assign the physical right button to the logical middle button:
+.Dq Fl m Li 2=3 .
diff --git a/usr.sbin/moused/moused/moused.c b/usr.sbin/moused/moused/moused.c
new file mode 100644
index 000000000000..33bcf359a1a0
--- /dev/null
+++ b/usr.sbin/moused/moused/moused.c
@@ -0,0 +1,3205 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 1997-2000 Kazutaka YOKOTA <yokota@FreeBSD.org>
+ * Copyright (c) 2004-2008 Philip Paeps <philip@FreeBSD.org>
+ * Copyright (c) 2008 Jean-Sebastien Pedron <dumbbell@FreeBSD.org>
+ * Copyright (c) 2021,2024 Vladimir Kondratyev <wulf@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * MOUSED.C
+ *
+ * Mouse daemon : listens to a evdev device node for mouse data stream,
+ * interprets data and passes ioctls off to the console driver.
+ *
+ */
+
+#include <sys/param.h>
+#include <sys/consio.h>
+#include <sys/event.h>
+#include <sys/mouse.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/un.h>
+
+#include <dev/evdev/input.h>
+
+#include <bitstring.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fnmatch.h>
+#include <libutil.h>
+#include <math.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "util.h"
+#include "quirks.h"
+
+/*
+ * bitstr_t implementation must be identical to one found in EVIOCG*
+ * libevdev ioctls. Our bitstring(3) API is compatible since r299090.
+ */
+_Static_assert(sizeof(bitstr_t) == sizeof(unsigned long),
+ "bitstr_t size mismatch");
+
+#define MAX_CLICKTHRESHOLD 2000 /* 2 seconds */
+#define MAX_BUTTON2TIMEOUT 2000 /* 2 seconds */
+#define DFLT_CLICKTHRESHOLD 500 /* 0.5 second */
+#define DFLT_BUTTON2TIMEOUT 100 /* 0.1 second */
+#define DFLT_SCROLLTHRESHOLD 3 /* 3 pixels */
+#define DFLT_SCROLLSPEED 2 /* 2 pixels */
+#define DFLT_MOUSE_RESOLUTION 8 /* dpmm, == 200dpi */
+#define DFLT_TPAD_RESOLUTION 40 /* dpmm, typical X res for Synaptics */
+#define DFLT_LINEHEIGHT 10 /* pixels per line */
+
+/* Abort 3-button emulation delay after this many movement events. */
+#define BUTTON2_MAXMOVE 3
+
+#define MOUSE_XAXIS (-1)
+#define MOUSE_YAXIS (-2)
+
+#define ZMAP_MAXBUTTON 4 /* Number of zmap items */
+#define MAX_FINGERS 10
+
+#define ID_NONE 0
+#define ID_PORT 1
+#define ID_IF 2
+#define ID_TYPE 4
+#define ID_MODEL 8
+#define ID_ALL (ID_PORT | ID_IF | ID_TYPE | ID_MODEL)
+
+/* Operations on timespecs */
+#define tsclr(tvp) timespecclear(tvp)
+#define tscmp(tvp, uvp, cmp) timespeccmp(tvp, uvp, cmp)
+#define tssub(tvp, uvp, vvp) timespecsub(tvp, uvp, vvp)
+#define msec2ts(msec) (struct timespec) { \
+ .tv_sec = (msec) / 1000, \
+ .tv_nsec = (msec) % 1000 * 1000000, \
+}
+static inline struct timespec
+tsaddms(struct timespec* tsp, u_int ms)
+{
+ struct timespec ret;
+
+ ret = msec2ts(ms);
+ timespecadd(tsp, &ret, &ret);
+
+ return (ret);
+};
+
+static inline struct timespec
+tssubms(struct timespec* tsp, u_int ms)
+{
+ struct timespec ret;
+
+ ret = msec2ts(ms);
+ timespecsub(tsp, &ret, &ret);
+
+ return (ret);
+};
+
+#define debug(...) do { \
+ if (debug && nodaemon) \
+ warnx(__VA_ARGS__); \
+} while (0)
+
+#define logerr(e, ...) do { \
+ log_or_warn(LOG_DAEMON | LOG_ERR, errno, __VA_ARGS__); \
+ exit(e); \
+} while (0)
+
+#define logerrx(e, ...) do { \
+ log_or_warn(LOG_DAEMON | LOG_ERR, 0, __VA_ARGS__); \
+ exit(e); \
+} while (0)
+
+#define logwarn(...) \
+ log_or_warn(LOG_DAEMON | LOG_WARNING, errno, __VA_ARGS__)
+
+#define logwarnx(...) \
+ log_or_warn(LOG_DAEMON | LOG_WARNING, 0, __VA_ARGS__)
+
+/* structures */
+
+enum gesture {
+ GEST_IGNORE,
+ GEST_ACCUMULATE,
+ GEST_MOVE,
+ GEST_VSCROLL,
+ GEST_HSCROLL,
+};
+
+/* interfaces (the table must be ordered by DEVICE_IF_XXX in util.h) */
+static const struct {
+ const char *name;
+ size_t p_size;
+} rifs[] = {
+ [DEVICE_IF_EVDEV] = { "evdev", sizeof(struct input_event) },
+ [DEVICE_IF_SYSMOUSE] = { "sysmouse", MOUSE_SYS_PACKETSIZE },
+};
+
+/* types (the table must be ordered by DEVICE_TYPE_XXX in util.h) */
+static const char *rnames[] = {
+ [DEVICE_TYPE_MOUSE] = "mouse",
+ [DEVICE_TYPE_POINTINGSTICK] = "pointing stick",
+ [DEVICE_TYPE_TOUCHPAD] = "touchpad",
+ [DEVICE_TYPE_TOUCHSCREEN] = "touchscreen",
+ [DEVICE_TYPE_TABLET] = "tablet",
+ [DEVICE_TYPE_TABLET_PAD] = "tablet pad",
+ [DEVICE_TYPE_KEYBOARD] = "keyboard",
+ [DEVICE_TYPE_JOYSTICK] = "joystick",
+};
+
+/* Default phisical to logical button mapping */
+static const u_int default_p2l[MOUSE_MAXBUTTON] = {
+ MOUSE_BUTTON1DOWN, MOUSE_BUTTON2DOWN, MOUSE_BUTTON3DOWN, MOUSE_BUTTON4DOWN,
+ MOUSE_BUTTON5DOWN, MOUSE_BUTTON6DOWN, MOUSE_BUTTON7DOWN, MOUSE_BUTTON8DOWN,
+ 0x00000100, 0x00000200, 0x00000400, 0x00000800,
+ 0x00001000, 0x00002000, 0x00004000, 0x00008000,
+ 0x00010000, 0x00020000, 0x00040000, 0x00080000,
+ 0x00100000, 0x00200000, 0x00400000, 0x00800000,
+ 0x01000000, 0x02000000, 0x04000000, 0x08000000,
+ 0x10000000, 0x20000000, 0x40000000,
+};
+
+struct tpcaps {
+ bool is_clickpad;
+ bool is_topbuttonpad;
+ bool is_mt;
+ bool cap_touch;
+ bool cap_pressure;
+ bool cap_width;
+ int min_x;
+ int max_x;
+ int min_y;
+ int max_y;
+ int res_x; /* dots per mm */
+ int res_y; /* dots per mm */
+ int min_p;
+ int max_p;
+};
+
+struct tpinfo {
+ bool two_finger_scroll; /* Enable two finger scrolling */
+ bool natural_scroll; /* Enable natural scrolling */
+ bool three_finger_drag; /* Enable dragging with three fingers */
+ u_int min_pressure_hi; /* Min pressure to start an action */
+ u_int min_pressure_lo; /* Min pressure to continue an action */
+ u_int max_pressure; /* Maximum pressure to detect palm */
+ u_int max_width; /* Max finger width to detect palm */
+ int margin_top; /* Top margin */
+ int margin_right; /* Right margin */
+ int margin_bottom; /* Bottom margin */
+ int margin_left; /* Left margin */
+ u_int tap_timeout; /* */
+ u_int tap_threshold; /* Minimum pressure to detect a tap */
+ double tap_max_delta; /* Length of segments above which a tap is ignored */
+ u_int taphold_timeout; /* Maximum elapsed time between two taps to consider a tap-hold action */
+ double vscroll_ver_area; /* Area reserved for vertical virtual scrolling */
+ double vscroll_hor_area; /* Area reserved for horizontal virtual scrolling */
+ double vscroll_min_delta; /* Minimum movement to consider virtual scrolling */
+ int softbuttons_y; /* Vertical size of softbuttons area */
+ int softbutton2_x; /* Horizontal offset of 2-nd softbutton left edge */
+ int softbutton3_x; /* Horizontal offset of 3-rd softbutton left edge */
+};
+
+struct tpstate {
+ int start_x;
+ int start_y;
+ int prev_x;
+ int prev_y;
+ int prev_nfingers;
+ int fingers_nb;
+ int tap_button;
+ bool fingerdown;
+ bool in_taphold;
+ int in_vscroll;
+ u_int zmax; /* maximum pressure value */
+ struct timespec taptimeout; /* tap timeout for touchpads */
+ int idletimeout;
+ bool timer_armed;
+};
+
+struct tpad {
+ struct tpcaps hw; /* touchpad capabilities */
+ struct tpinfo info; /* touchpad gesture parameters */
+ struct tpstate gest; /* touchpad gesture state */
+};
+
+struct finger {
+ int x;
+ int y;
+ int p;
+ int w;
+ int id; /* id=0 - no touch, id>1 - touch id */
+};
+
+struct evstate {
+ int buttons;
+ /* Relative */
+ int dx;
+ int dy;
+ int dz;
+ int dw;
+ int acc_dx;
+ int acc_dy;
+ /* Absolute single-touch */
+ int nfingers;
+ struct finger st;
+ /* Absolute multi-touch */
+ int slot;
+ struct finger mt[MAX_FINGERS];
+ bitstr_t bit_decl(key_ignore, KEY_CNT);
+ bitstr_t bit_decl(rel_ignore, REL_CNT);
+ bitstr_t bit_decl(abs_ignore, ABS_CNT);
+ bitstr_t bit_decl(prop_ignore, INPUT_PROP_CNT);
+};
+
+/* button status */
+struct button_state {
+ int count; /* 0: up, 1: single click, 2: double click,... */
+ struct timespec ts; /* timestamp on the last button event */
+};
+
+struct btstate {
+ u_int wmode; /* wheel mode button number */
+ u_int clickthreshold; /* double click speed in msec */
+ struct button_state bstate[MOUSE_MAXBUTTON]; /* button state */
+ struct button_state *mstate[MOUSE_MAXBUTTON];/* mapped button st.*/
+ u_int p2l[MOUSE_MAXBUTTON];/* phisical to logical button mapping */
+ int zmap[ZMAP_MAXBUTTON];/* MOUSE_{X|Y}AXIS or a button number */
+ struct button_state zstate[ZMAP_MAXBUTTON]; /* Z/W axis state */
+};
+
+/* state machine for 3 button emulation */
+
+enum bt3_emul_state {
+ S0, /* start */
+ S1, /* button 1 delayed down */
+ S2, /* button 3 delayed down */
+ S3, /* both buttons down -> button 2 down */
+ S4, /* button 1 delayed up */
+ S5, /* button 1 down */
+ S6, /* button 3 down */
+ S7, /* both buttons down */
+ S8, /* button 3 delayed up */
+ S9, /* button 1 or 3 up after S3 */
+};
+
+#define A(b1, b3) (((b1) ? 2 : 0) | ((b3) ? 1 : 0))
+#define A_TIMEOUT 4
+#define S_DELAYED(st) (states[st].s[A_TIMEOUT] != (st))
+
+static const struct {
+ enum bt3_emul_state s[A_TIMEOUT + 1];
+ int buttons;
+ int mask;
+ bool timeout;
+} states[10] = {
+ /* S0 */
+ { { S0, S2, S1, S3, S0 }, 0, ~(MOUSE_BUTTON1DOWN | MOUSE_BUTTON3DOWN), false },
+ /* S1 */
+ { { S4, S2, S1, S3, S5 }, 0, ~MOUSE_BUTTON1DOWN, false },
+ /* S2 */
+ { { S8, S2, S1, S3, S6 }, 0, ~MOUSE_BUTTON3DOWN, false },
+ /* S3 */
+ { { S0, S9, S9, S3, S3 }, MOUSE_BUTTON2DOWN, ~0, false },
+ /* S4 */
+ { { S0, S2, S1, S3, S0 }, MOUSE_BUTTON1DOWN, ~0, true },
+ /* S5 */
+ { { S0, S2, S5, S7, S5 }, MOUSE_BUTTON1DOWN, ~0, false },
+ /* S6 */
+ { { S0, S6, S1, S7, S6 }, MOUSE_BUTTON3DOWN, ~0, false },
+ /* S7 */
+ { { S0, S6, S5, S7, S7 }, MOUSE_BUTTON1DOWN | MOUSE_BUTTON3DOWN, ~0, false },
+ /* S8 */
+ { { S0, S2, S1, S3, S0 }, MOUSE_BUTTON3DOWN, ~0, true },
+ /* S9 */
+ { { S0, S9, S9, S3, S9 }, 0, ~(MOUSE_BUTTON1DOWN | MOUSE_BUTTON3DOWN), false },
+};
+
+struct e3bstate {
+ bool enabled;
+ u_int button2timeout; /* 3 button emulation timeout */
+ enum bt3_emul_state mouse_button_state;
+ struct timespec mouse_button_state_ts;
+ int mouse_move_delayed;
+ bool timer_armed;
+};
+
+enum scroll_state {
+ SCROLL_NOTSCROLLING,
+ SCROLL_PREPARE,
+ SCROLL_SCROLLING,
+};
+
+struct scroll {
+ bool enable_vert;
+ bool enable_hor;
+ u_int threshold; /* Movement distance before virtual scrolling */
+ u_int speed; /* Movement distance to rate of scrolling */
+ enum scroll_state state;
+ int movement;
+ int hmovement;
+};
+
+struct drift_xy {
+ int x;
+ int y;
+};
+struct drift {
+ u_int distance; /* max steps X+Y */
+ u_int time; /* ms */
+ struct timespec time_ts;
+ struct timespec twotime_ts; /* 2*drift_time */
+ u_int after; /* ms */
+ struct timespec after_ts;
+ bool terminate;
+ struct timespec current_ts;
+ struct timespec last_activity;
+ struct timespec since;
+ struct drift_xy last; /* steps in last drift_time */
+ struct drift_xy previous; /* steps in prev. drift_time */
+};
+
+struct accel {
+ bool is_exponential; /* Exponential acceleration is enabled */
+ double accelx; /* Acceleration in the X axis */
+ double accely; /* Acceleration in the Y axis */
+ double accelz; /* Acceleration in the wheel axis */
+ double expoaccel; /* Exponential acceleration */
+ double expoffset; /* Movement offset for exponential accel. */
+ double remainx; /* Remainder on X, Y and wheel axis, ... */
+ double remainy; /* ... respectively to compensate */
+ double remainz; /* ... for rounding errors. */
+ double lastlength[3];
+};
+
+struct rodent {
+ struct device dev; /* Device */
+ int mfd; /* mouse file descriptor */
+ struct btstate btstate; /* button status */
+ struct e3bstate e3b; /* 3 button emulation state */
+ struct drift drift;
+ struct accel accel; /* cursor acceleration state */
+ struct scroll scroll; /* virtual scroll state */
+ struct tpad tp; /* touchpad info and gesture state */
+ struct evstate ev; /* event device state */
+ SLIST_ENTRY(rodent) next;
+};
+
+/* global variables */
+
+static SLIST_HEAD(rodent_list, rodent) rodents = SLIST_HEAD_INITIALIZER();
+
+static int debug = 0;
+static bool nodaemon = false;
+static bool background = false;
+static bool paused = false;
+static bool opt_grab = false;
+static int identify = ID_NONE;
+static int cfd = -1; /* /dev/consolectl file descriptor */
+static int kfd = -1; /* kqueue file descriptor */
+static int dfd = -1; /* devd socket descriptor */
+static const char *portname = NULL;
+static const char *pidfile = "/var/run/moused.pid";
+static struct pidfh *pfh;
+#ifndef CONFDIR
+#define CONFDIR "/etc"
+#endif
+static const char *config_file = CONFDIR "/moused.conf";
+#ifndef QUIRKSDIR
+#define QUIRKSDIR "/usr/share/moused"
+#endif
+static const char *quirks_path = QUIRKSDIR;
+static struct quirks_context *quirks;
+
+static int opt_rate = 0;
+static int opt_resolution = MOUSE_RES_UNKNOWN;
+
+static u_int opt_wmode = 0;
+static int opt_clickthreshold = -1;
+static bool opt_e3b_enabled = false;
+static int opt_e3b_button2timeout = -1;
+static struct btstate opt_btstate;
+
+static bool opt_drift_terminate = false;
+static u_int opt_drift_distance = 4; /* max steps X+Y */
+static u_int opt_drift_time = 500; /* ms */
+static u_int opt_drift_after = 4000; /* ms */
+
+static double opt_accelx = 1.0;
+static double opt_accely = 1.0;
+static bool opt_exp_accel = false;
+static double opt_expoaccel = 1.0;
+static double opt_expoffset = 1.0;
+
+static bool opt_virtual_scroll = false;
+static bool opt_hvirtual_scroll = false;
+static int opt_scroll_speed = -1;
+static int opt_scroll_threshold = -1;
+
+static jmp_buf env;
+
+/* function prototypes */
+
+static moused_log_handler log_or_warn_va;
+
+static void linacc(struct accel *, int, int, int, int*, int*, int*);
+static void expoacc(struct accel *, int, int, int, int*, int*, int*);
+static void moused(void);
+static void reset(int sig);
+static void pause_mouse(int sig);
+static int connect_devd(void);
+static void fetch_and_parse_devd(void);
+static void usage(void);
+static void log_or_warn(int log_pri, int errnum, const char *fmt, ...)
+ __printflike(3, 4);
+
+static int r_daemon(void);
+static enum device_if r_identify_if(int fd);
+static enum device_type r_identify_evdev(int fd);
+static enum device_type r_identify_sysmouse(int fd);
+static const char *r_if(enum device_if type);
+static const char *r_name(enum device_type type);
+static struct rodent *r_init(const char *path);
+static void r_init_all(void);
+static void r_deinit(struct rodent *r);
+static void r_deinit_all(void);
+static int r_protocol_evdev(enum device_type type, struct tpad *tp,
+ struct evstate *ev, struct input_event *ie,
+ mousestatus_t *act);
+static int r_protocol_sysmouse(uint8_t *pBuf, mousestatus_t *act);
+static void r_vscroll_detect(struct rodent *r, struct scroll *sc,
+ mousestatus_t *act);
+static void r_vscroll(struct scroll *sc, mousestatus_t *act);
+static int r_statetrans(struct rodent *r, mousestatus_t *a1,
+ mousestatus_t *a2, int trans);
+static bool r_installmap(char *arg, struct btstate *bt);
+static char * r_installzmap(char **argv, int argc, int* idx, struct btstate *bt);
+static void r_map(mousestatus_t *act1, mousestatus_t *act2,
+ struct btstate *bt);
+static void r_timestamp(mousestatus_t *act, struct btstate *bt,
+ struct e3bstate *e3b, struct drift *drift);
+static bool r_timeout(struct e3bstate *e3b);
+static void r_move(mousestatus_t *act, struct accel *acc);
+static void r_click(mousestatus_t *act, struct btstate *bt);
+static bool r_drift(struct drift *, mousestatus_t *);
+static enum gesture r_gestures(struct tpad *tp, int x0, int y0, u_int z, int w,
+ int nfingers, struct timespec *time, mousestatus_t *ms);
+
+int
+main(int argc, char *argv[])
+{
+ struct rodent *r;
+ pid_t mpid;
+ int c;
+ int i;
+ u_long ul;
+ char *errstr;
+
+ while ((c = getopt(argc, argv, "3A:C:E:F:HI:L:T:VU:a:dfghi:l:m:p:r:t:q:w:z:")) != -1) {
+ switch(c) {
+
+ case '3':
+ opt_e3b_enabled = true;
+ break;
+
+ case 'E':
+ errno = 0;
+ ul = strtoul(optarg, NULL, 10);
+ if ((ul == 0 && errno != 0) ||
+ ul > MAX_BUTTON2TIMEOUT) {
+ warnx("invalid argument `%s'", optarg);
+ usage();
+ }
+ opt_e3b_button2timeout = ul;
+ break;
+
+ case 'a':
+ i = sscanf(optarg, "%lf,%lf", &opt_accelx, &opt_accely);
+ if (i == 0) {
+ warnx("invalid linear acceleration argument "
+ "'%s'", optarg);
+ usage();
+ }
+ if (i == 1)
+ opt_accely = opt_accelx;
+ break;
+
+ case 'A':
+ opt_exp_accel = true;
+ i = sscanf(optarg, "%lf,%lf", &opt_expoaccel,
+ &opt_expoffset);
+ if (i == 0) {
+ warnx("invalid exponential acceleration "
+ "argument '%s'", optarg);
+ usage();
+ }
+ if (i == 1)
+ opt_expoffset = 1.0;
+ break;
+
+ case 'd':
+ ++debug;
+ break;
+
+ case 'f':
+ nodaemon = true;
+ break;
+
+ case 'g':
+ opt_grab = true;
+ break;
+
+ case 'i':
+ if (strcmp(optarg, "all") == 0)
+ identify = ID_ALL;
+ else if (strcmp(optarg, "port") == 0)
+ identify = ID_PORT;
+ else if (strcmp(optarg, "if") == 0)
+ identify = ID_IF;
+ else if (strcmp(optarg, "type") == 0)
+ identify = ID_TYPE;
+ else if (strcmp(optarg, "model") == 0)
+ identify = ID_MODEL;
+ else {
+ warnx("invalid argument `%s'", optarg);
+ usage();
+ }
+ nodaemon = true;
+ break;
+
+ case 'l':
+ ul = strtoul(optarg, NULL, 10);
+ if (ul != 1)
+ warnx("ignore mouse level `%s'", optarg);
+ break;
+
+ case 'm':
+ if (!r_installmap(optarg, &opt_btstate)) {
+ warnx("invalid argument `%s'", optarg);
+ usage();
+ }
+ break;
+
+ case 'p':
+ /* "auto" is an alias to no portname */
+ if (strcmp(optarg, "auto") != 0)
+ portname = optarg;
+ break;
+
+ case 'r':
+ if (strcmp(optarg, "high") == 0)
+ opt_resolution = MOUSE_RES_HIGH;
+ else if (strcmp(optarg, "medium-high") == 0)
+ opt_resolution = MOUSE_RES_HIGH;
+ else if (strcmp(optarg, "medium-low") == 0)
+ opt_resolution = MOUSE_RES_MEDIUMLOW;
+ else if (strcmp(optarg, "low") == 0)
+ opt_resolution = MOUSE_RES_LOW;
+ else if (strcmp(optarg, "default") == 0)
+ opt_resolution = MOUSE_RES_DEFAULT;
+ else {
+ ul= strtoul(optarg, NULL, 10);
+ if (ul == 0) {
+ warnx("invalid argument `%s'", optarg);
+ usage();
+ }
+ opt_resolution = ul;
+ }
+ break;
+
+ case 't':
+ if (strcmp(optarg, "auto") != 0)
+ warnx("ignore mouse type `%s'", optarg);
+ break;
+
+ case 'w':
+ ul = strtoul(optarg, NULL, 10);
+ if (ul == 0 || ul > MOUSE_MAXBUTTON) {
+ warnx("invalid argument `%s'", optarg);
+ usage();
+ }
+ opt_wmode = ul;
+ break;
+
+ case 'z':
+ --optind;
+ errstr = r_installzmap(argv, argc, &optind, &opt_btstate);
+ if (errstr != NULL) {
+ warnx("%s", errstr);
+ free(errstr);
+ usage();
+ }
+ break;
+
+ case 'C':
+ ul = strtoul(optarg, NULL, 10);
+ if (ul > MAX_CLICKTHRESHOLD) {
+ warnx("invalid argument `%s'", optarg);
+ usage();
+ }
+ opt_clickthreshold = ul;
+ break;
+
+ case 'F':
+ ul = strtoul(optarg, NULL, 10);
+ if (ul == 0) {
+ warnx("invalid argument `%s'", optarg);
+ usage();
+ }
+ opt_rate = ul;
+ break;
+
+ case 'H':
+ opt_hvirtual_scroll = true;
+ break;
+
+ case 'I':
+ pidfile = optarg;
+ break;
+
+ case 'L':
+ errno = 0;
+ ul = strtoul(optarg, NULL, 10);
+ if ((ul == 0 && errno != 0) || ul > INT_MAX) {
+ warnx("invalid argument `%s'", optarg);
+ usage();
+ }
+ opt_scroll_speed = ul;
+ break;
+
+ case 'q':
+ config_file = optarg;
+ break;
+
+ case 'Q':
+ quirks_path = optarg;
+ break;
+
+ case 'T':
+ opt_drift_terminate = true;
+ sscanf(optarg, "%u,%u,%u", &opt_drift_distance,
+ &opt_drift_time, &opt_drift_after);
+ if (opt_drift_distance == 0 ||
+ opt_drift_time == 0 ||
+ opt_drift_after == 0) {
+ warnx("invalid argument `%s'", optarg);
+ usage();
+ }
+ break;
+
+ case 'V':
+ opt_virtual_scroll = true;
+ break;
+
+ case 'U':
+ errno = 0;
+ ul = strtoul(optarg, NULL, 10);
+ if ((ul == 0 && errno != 0) || ul > INT_MAX) {
+ warnx("invalid argument `%s'", optarg);
+ usage();
+ }
+ opt_scroll_threshold = ul;
+ break;
+
+ case 'h':
+ case '?':
+ default:
+ usage();
+ }
+ }
+
+ if ((cfd = open("/dev/consolectl", O_RDWR, 0)) == -1)
+ logerr(1, "cannot open /dev/consolectl");
+ if ((kfd = kqueue()) == -1)
+ logerr(1, "cannot create kqueue");
+ if (portname == NULL && (dfd = connect_devd()) == -1)
+ logwarnx("cannot open devd socket");
+
+ switch (setjmp(env)) {
+ case SIGHUP:
+ quirks_context_unref(quirks);
+ r_deinit_all();
+ /* FALLTHROUGH */
+ case 0:
+ break;
+ case SIGINT:
+ case SIGQUIT:
+ case SIGTERM:
+ exit(0);
+ /* NOT REACHED */
+ default:
+ goto out;
+ }
+
+ signal(SIGHUP , reset);
+ signal(SIGINT , reset);
+ signal(SIGQUIT, reset);
+ signal(SIGTERM, reset);
+ signal(SIGUSR1, pause_mouse);
+
+ quirks = quirks_init_subsystem(quirks_path, config_file,
+ log_or_warn_va,
+ background ? QLOG_MOUSED_LOGGING : QLOG_CUSTOM_LOG_PRIORITIES);
+ if (quirks == NULL)
+ logwarnx("cannot open configuration file %s", config_file);
+
+ if (portname == NULL) {
+ r_init_all();
+ } else {
+ if ((r = r_init(portname)) == NULL)
+ logerrx(1, "Can not initialize device");
+ }
+
+ /* print some information */
+ if (identify != ID_NONE) {
+ SLIST_FOREACH(r, &rodents, next) {
+ if (identify == ID_ALL)
+ printf("%s %s %s %s\n",
+ r->dev.path, r_if(r->dev.iftype),
+ r_name(r->dev.type), r->dev.name);
+ else if (identify & ID_PORT)
+ printf("%s\n", r->dev.path);
+ else if (identify & ID_IF)
+ printf("%s\n", r_if(r->dev.iftype));
+ else if (identify & ID_TYPE)
+ printf("%s\n", r_name(r->dev.type));
+ else if (identify & ID_MODEL)
+ printf("%s\n", r->dev.name);
+ }
+ exit(0);
+ }
+
+ if (!nodaemon && !background) {
+ pfh = pidfile_open(pidfile, 0600, &mpid);
+ if (pfh == NULL) {
+ if (errno == EEXIST)
+ logerrx(1, "moused already running, pid: %d", mpid);
+ logwarn("cannot open pid file");
+ }
+ if (r_daemon()) {
+ int saved_errno = errno;
+ pidfile_remove(pfh);
+ errno = saved_errno;
+ logerr(1, "failed to become a daemon");
+ } else {
+ background = true;
+ pidfile_write(pfh);
+ }
+ }
+
+ moused();
+
+out:
+ quirks_context_unref(quirks);
+
+ r_deinit_all();
+ if (dfd != -1)
+ close(dfd);
+ if (kfd != -1)
+ close(kfd);
+ if (cfd != -1)
+ close(cfd);
+
+ exit(0);
+}
+
+/*
+ * Function to calculate linear acceleration.
+ *
+ * If there are any rounding errors, the remainder
+ * is stored in the remainx and remainy variables
+ * and taken into account upon the next movement.
+ */
+
+static void
+linacc(struct accel *acc, int dx, int dy, int dz,
+ int *movex, int *movey, int *movez)
+{
+ double fdx, fdy, fdz;
+
+ if (dx == 0 && dy == 0 && dz == 0) {
+ *movex = *movey = *movez = 0;
+ return;
+ }
+ fdx = dx * acc->accelx + acc->remainx;
+ fdy = dy * acc->accely + acc->remainy;
+ fdz = dz * acc->accelz + acc->remainz;
+ *movex = lround(fdx);
+ *movey = lround(fdy);
+ *movez = lround(fdz);
+ acc->remainx = fdx - *movex;
+ acc->remainy = fdy - *movey;
+ acc->remainz = fdz - *movez;
+}
+
+/*
+ * Function to calculate exponential acceleration.
+ * (Also includes linear acceleration if enabled.)
+ *
+ * In order to give a smoother behaviour, we record the four
+ * most recent non-zero movements and use their average value
+ * to calculate the acceleration.
+ */
+
+static void
+expoacc(struct accel *acc, int dx, int dy, int dz,
+ int *movex, int *movey, int *movez)
+{
+ double fdx, fdy, fdz, length, lbase, accel;
+
+ if (dx == 0 && dy == 0 && dz == 0) {
+ *movex = *movey = *movez = 0;
+ return;
+ }
+ fdx = dx * acc->accelx;
+ fdy = dy * acc->accely;
+ fdz = dz * acc->accelz;
+ length = sqrt((fdx * fdx) + (fdy * fdy)); /* Pythagoras */
+ length = (length + acc->lastlength[0] + acc->lastlength[1] +
+ acc->lastlength[2]) / 4;
+ lbase = length / acc->expoffset;
+ accel = pow(lbase, acc->expoaccel) / lbase;
+ fdx = fdx * accel + acc->remainx;
+ fdy = fdy * accel + acc->remainy;
+ *movex = lround(fdx);
+ *movey = lround(fdy);
+ *movez = lround(fdz);
+ acc->remainx = fdx - *movex;
+ acc->remainy = fdy - *movey;
+ acc->remainz = fdz - *movez;
+ acc->lastlength[2] = acc->lastlength[1];
+ acc->lastlength[1] = acc->lastlength[0];
+ /* Insert new average, not original length! */
+ acc->lastlength[0] = length;
+}
+
+static void
+moused(void)
+{
+ struct rodent *r = NULL;
+ mousestatus_t action0; /* original mouse action */
+ mousestatus_t action; /* interim buffer */
+ mousestatus_t action2; /* mapped action */
+ struct kevent ke[3];
+ int nchanges;
+ union {
+ struct input_event ie;
+ uint8_t se[MOUSE_SYS_PACKETSIZE];
+ } b;
+ size_t b_size;
+ ssize_t r_size;
+ int flags;
+ int c;
+
+ /* clear mouse data */
+ bzero(&action0, sizeof(action0));
+ bzero(&action, sizeof(action));
+ bzero(&action2, sizeof(action2));
+ /* process mouse data */
+ for (;;) {
+
+ if (dfd == -1 && portname == NULL)
+ dfd = connect_devd();
+ nchanges = 0;
+ if (r != NULL && r->e3b.enabled &&
+ S_DELAYED(r->e3b.mouse_button_state)) {
+ EV_SET(ke + nchanges, r->mfd << 1, EVFILT_TIMER,
+ EV_ADD | EV_ENABLE | EV_DISPATCH, 0, 20, r);
+ nchanges++;
+ r->e3b.timer_armed = true;
+ }
+ if (r != NULL && r->tp.gest.idletimeout > 0) {
+ EV_SET(ke + nchanges, r->mfd << 1 | 1, EVFILT_TIMER,
+ EV_ADD | EV_ENABLE | EV_DISPATCH,
+ 0, r->tp.gest.idletimeout, r);
+ nchanges++;
+ r->tp.gest.timer_armed = true;
+ }
+ if (dfd == -1 && nchanges == 0 && portname == NULL) {
+ EV_SET(ke + nchanges, UINTPTR_MAX, EVFILT_TIMER,
+ EV_ADD | EV_ENABLE | EV_ONESHOT, 0, 1000, NULL);
+ nchanges++;
+ }
+
+ if (!(r != NULL && r->tp.gest.idletimeout == 0)) {
+ c = kevent(kfd, ke, nchanges, ke, 1, NULL);
+ if (c <= 0) { /* error */
+ logwarn("failed to read from mouse");
+ continue;
+ }
+ } else
+ c = 0;
+ /* Devd event */
+ if (c > 0 && ke[0].udata == NULL) {
+ if (ke[0].filter == EVFILT_READ) {
+ if ((ke[0].flags & EV_EOF) != 0) {
+ logwarn("devd connection is closed");
+ close(dfd);
+ dfd = -1;
+ } else
+ fetch_and_parse_devd();
+ } else if (ke[0].filter == EVFILT_TIMER) {
+ /* DO NOTHING */
+ }
+ continue;
+ }
+ if (c > 0)
+ r = ke[0].udata;
+ /* E3B timeout */
+ if (c > 0 && ke[0].filter == EVFILT_TIMER &&
+ (ke[0].ident & 1) == 0) {
+ /* assert(rodent.flags & Emulate3Button) */
+ action0.button = action0.obutton;
+ action0.dx = action0.dy = action0.dz = 0;
+ action0.flags = flags = 0;
+ r->e3b.timer_armed = false;
+ if (r_timeout(&r->e3b) &&
+ r_statetrans(r, &action0, &action, A_TIMEOUT)) {
+ if (debug > 2)
+ debug("flags:%08x buttons:%08x obuttons:%08x",
+ action.flags, action.button, action.obutton);
+ } else {
+ action0.obutton = action0.button;
+ continue;
+ }
+ } else {
+ /* mouse movement */
+ if (c > 0 && ke[0].filter == EVFILT_READ) {
+ b_size = rifs[r->dev.iftype].p_size;
+ r_size = read(r->mfd, &b, b_size);
+ if (r_size == -1) {
+ if (errno == EWOULDBLOCK)
+ continue;
+ else if (portname == NULL) {
+ r_deinit(r);
+ r = NULL;
+ continue;
+ } else
+ return;
+ }
+ if (r_size != (ssize_t)b_size) {
+ logwarn("Short read from mouse: "
+ "%zd bytes", r_size);
+ continue;
+ }
+ /* Disarm nonexpired timers */
+ nchanges = 0;
+ if (r->e3b.timer_armed) {
+ EV_SET(ke + nchanges, r->mfd << 1,
+ EVFILT_TIMER, EV_DISABLE, 0, 0, r);
+ nchanges++;
+ r->e3b.timer_armed = false;
+ }
+ if (r->tp.gest.timer_armed) {
+ EV_SET(ke + nchanges, r->mfd << 1 | 1,
+ EVFILT_TIMER, EV_DISABLE, 0, 0, r);
+ nchanges++;
+ r->tp.gest.timer_armed = false;
+ }
+ if (nchanges != 0)
+ kevent(kfd, ke, nchanges, NULL, 0, NULL);
+ } else {
+ /*
+ * Gesture timeout expired.
+ * Notify r_gestures by empty packet.
+ */
+#ifdef DONE_RIGHT
+ struct timespec ts;
+ clock_gettime(CLOCK_REALTIME, &ts);
+ b.ie.time.tv_sec = ts.tv_sec;
+ b.ie.time.tv_usec = ts.tv_nsec / 1000;
+#else
+ /* Hacky but cheap */
+ b.ie.time.tv_sec =
+ r->tp.gest.idletimeout == 0 ? 0 : LONG_MAX;
+ b.ie.time.tv_usec = 0;
+#endif
+ b.ie.type = EV_SYN;
+ b.ie.code = SYN_REPORT;
+ b.ie.value = 1;
+ if (c > 0)
+ r->tp.gest.timer_armed = false;
+ }
+ r->tp.gest.idletimeout = -1;
+ flags = r->dev.iftype == DEVICE_IF_EVDEV ?
+ r_protocol_evdev(r->dev.type,
+ &r->tp, &r->ev, &b.ie, &action0) :
+ r_protocol_sysmouse(b.se, &action0);
+ if (flags == 0)
+ continue;
+
+ if (r->scroll.enable_vert || r->scroll.enable_hor) {
+ if (action0.button == MOUSE_BUTTON2DOWN) {
+ debug("[BUTTON2] flags:%08x buttons:%08x obuttons:%08x",
+ action.flags, action.button, action.obutton);
+ } else {
+ debug("[NOTBUTTON2] flags:%08x buttons:%08x obuttons:%08x",
+ action.flags, action.button, action.obutton);
+ }
+ r_vscroll_detect(r, &r->scroll, &action0);
+ }
+
+ r_timestamp(&action0, &r->btstate, &r->e3b, &r->drift);
+ r_statetrans(r, &action0, &action,
+ A(action0.button & MOUSE_BUTTON1DOWN,
+ action0.button & MOUSE_BUTTON3DOWN));
+ debug("flags:%08x buttons:%08x obuttons:%08x", action.flags,
+ action.button, action.obutton);
+ }
+ action0.obutton = action0.button;
+ flags &= MOUSE_POSCHANGED;
+ flags |= action.obutton ^ action.button;
+ action.flags = flags;
+
+ if (flags == 0)
+ continue;
+
+ /* handler detected action */
+ r_map(&action, &action2, &r->btstate);
+ debug("activity : buttons 0x%08x dx %d dy %d dz %d",
+ action2.button, action2.dx, action2.dy, action2.dz);
+
+ if (r->scroll.enable_vert || r->scroll.enable_hor) {
+ /*
+ * If *only* the middle button is pressed AND we are moving
+ * the stick/trackpoint/nipple, scroll!
+ */
+ r_vscroll(&r->scroll, &action2);
+ }
+
+ if (r->drift.terminate) {
+ if ((flags & MOUSE_POSCHANGED) == 0 ||
+ action.dz || action2.dz)
+ r->drift.last_activity = r->drift.current_ts;
+ else {
+ if (r_drift (&r->drift, &action2))
+ continue;
+ }
+ }
+
+ /* Defer clicks until we aren't VirtualScroll'ing. */
+ if (r->scroll.state == SCROLL_NOTSCROLLING)
+ r_click(&action2, &r->btstate);
+
+ if (action2.flags & MOUSE_POSCHANGED)
+ r_move(&action2, &r->accel);
+
+ /*
+ * If the Z axis movement is mapped to an imaginary physical
+ * button, we need to cook up a corresponding button `up' event
+ * after sending a button `down' event.
+ */
+ if ((r->btstate.zmap[0] > 0) && (action.dz != 0)) {
+ action.obutton = action.button;
+ action.dx = action.dy = action.dz = 0;
+ r_map(&action, &action2, &r->btstate);
+ debug("activity : buttons 0x%08x dx %d dy %d dz %d",
+ action2.button, action2.dx, action2.dy, action2.dz);
+
+ r_click(&action2, &r->btstate);
+ }
+ }
+ /* NOT REACHED */
+}
+
+static void
+reset(int sig)
+{
+ longjmp(env, sig);
+}
+
+static void
+pause_mouse(__unused int sig)
+{
+ paused = !paused;
+}
+
+static int
+connect_devd(void)
+{
+ const static struct sockaddr_un sa = {
+ .sun_family = AF_UNIX,
+ .sun_path = "/var/run/devd.seqpacket.pipe",
+ };
+ struct kevent kev;
+ int fd;
+
+ fd = socket(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0);
+ if (fd < 0)
+ return (-1);
+ if (connect(fd, (const struct sockaddr *) &sa, sizeof(sa)) < 0) {
+ close(fd);
+ return (-1);
+ }
+ EV_SET(&kev, fd, EVFILT_READ, EV_ADD, 0, 0, 0);
+ if (kevent(kfd, &kev, 1, NULL, 0, NULL) < 0) {
+ close(fd);
+ return (-1);
+ }
+
+ return (fd);
+}
+
+static void
+fetch_and_parse_devd(void)
+{
+ char ev[1024];
+ char path[22] = "/dev/";
+ char *cdev, *cr;
+ ssize_t len;
+
+ if ((len = recv(dfd, ev, sizeof(ev), MSG_WAITALL)) <= 0) {
+ close(dfd);
+ dfd = -1;
+ return;
+ }
+
+ if (ev[0] != '!')
+ return;
+ if (strnstr(ev, "system=DEVFS", len) == NULL)
+ return;
+ if (strnstr(ev, "subsystem=CDEV", len) == NULL)
+ return;
+ if (strnstr(ev, "type=CREATE", len) == NULL)
+ return;
+ if ((cdev = strnstr(ev, "cdev=input/event", len)) == NULL)
+ return;
+ cr = strchr(cdev, '\n');
+ if (cr != NULL)
+ *cr = '\0';
+ cr = strchr(cdev, ' ');
+ if (cr != NULL)
+ *cr = '\0';
+ strncpy(path + 5, cdev + 5, 17);
+ (void)r_init(path);
+ return;
+}
+
+/*
+ * usage
+ *
+ * Complain, and free the CPU for more worthy tasks
+ */
+static void
+usage(void)
+{
+ fprintf(stderr, "%s\n%s\n%s\n%s\n%s\n",
+ "usage: moused [-dfg] [-I file] [-F rate] [-r resolution]",
+ " [-VH [-U threshold]] [-a X[,Y]] [-C threshold] [-m N=M] [-w N]",
+ " [-z N] [-t <mousetype>] [-l level] [-3 [-E timeout]]",
+ " [-T distance[,time[,after]]] -p <port> [-q config] [-Q quirks]",
+ " moused [-d] -i <port|if|type|model|all> -p <port>");
+ exit(1);
+}
+
+/*
+ * Output an error message to syslog or stderr as appropriate. If
+ * `errnum' is non-zero, append its string form to the message.
+ */
+static void
+log_or_warn_va(int log_pri, int errnum, const char *fmt, va_list ap)
+{
+ char buf[256];
+ size_t len;
+
+ if (debug == 0 && log_pri > LOG_ERR)
+ return;
+
+ vsnprintf(buf, sizeof(buf), fmt, ap);
+
+ /* Strip trailing line-feed appended by quirk subsystem */
+ len = strlen(buf);
+ if (len != 0 && buf[len - 1] == '\n')
+ buf[len - 1] = '\0';
+
+ if (errnum) {
+ strlcat(buf, ": ", sizeof(buf));
+ strlcat(buf, strerror(errnum), sizeof(buf));
+ }
+
+ if (background)
+ syslog(log_pri, "%s", buf);
+ else
+ warnx("%s", buf);
+}
+
+static void
+log_or_warn(int log_pri, int errnum, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ log_or_warn_va(log_pri, errnum, fmt, ap);
+ va_end(ap);
+}
+
+static int
+r_daemon(void)
+{
+ struct sigaction osa, sa;
+ pid_t newgrp;
+ int oerrno;
+ int osa_ok;
+ int nullfd;
+
+ /* A SIGHUP may be thrown when the parent exits below. */
+ sigemptyset(&sa.sa_mask);
+ sa.sa_handler = SIG_IGN;
+ sa.sa_flags = 0;
+ osa_ok = sigaction(SIGHUP, &sa, &osa);
+
+ /* Keep kqueue fd alive */
+ switch (rfork(RFPROC)) {
+ case -1:
+ return (-1);
+ case 0:
+ break;
+ default:
+ /*
+ * A fine point: _exit(0), not exit(0), to avoid triggering
+ * atexit(3) processing
+ */
+ _exit(0);
+ }
+
+ newgrp = setsid();
+ oerrno = errno;
+ if (osa_ok != -1)
+ sigaction(SIGHUP, &osa, NULL);
+
+ if (newgrp == -1) {
+ errno = oerrno;
+ return (-1);
+ }
+
+ (void)chdir("/");
+
+ nullfd = open("/dev/null", O_RDWR, 0);
+ if (nullfd != -1) {
+ (void)dup2(nullfd, STDIN_FILENO);
+ (void)dup2(nullfd, STDOUT_FILENO);
+ (void)dup2(nullfd, STDERR_FILENO);
+ }
+ if (nullfd > 2)
+ close(nullfd);
+
+ return (0);
+}
+
+static inline int
+bit_find(bitstr_t *array, int start, int stop)
+{
+ int res;
+
+ bit_ffs_at(array, start, stop + 1, &res);
+ return (res != -1);
+}
+
+static enum device_if
+r_identify_if(int fd)
+{
+ int dummy;
+
+ if (ioctl(fd, EVIOCGVERSION, &dummy) >= 0)
+ return (DEVICE_IF_EVDEV);
+ if (ioctl(fd, MOUSE_GETLEVEL, &dummy) >= 0)
+ return (DEVICE_IF_SYSMOUSE);
+ return (DEVICE_IF_UNKNOWN);
+}
+
+/* Derived from EvdevProbe() function of xf86-input-evdev driver */
+static enum device_type
+r_identify_evdev(int fd)
+{
+ enum device_type type;
+ bitstr_t bit_decl(key_bits, KEY_CNT); /* */
+ bitstr_t bit_decl(rel_bits, REL_CNT); /* Evdev capabilities */
+ bitstr_t bit_decl(abs_bits, ABS_CNT); /* */
+ bitstr_t bit_decl(prop_bits, INPUT_PROP_CNT);
+ bool has_keys, has_buttons, has_lmr, has_rel_axes, has_abs_axes;
+ bool has_mt;
+
+ /* maybe this is a evdev mouse... */
+ if (ioctl(fd, EVIOCGBIT(EV_REL, sizeof(rel_bits)), rel_bits) < 0 ||
+ ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(abs_bits)), abs_bits) < 0 ||
+ ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(key_bits)), key_bits) < 0 ||
+ ioctl(fd, EVIOCGPROP(sizeof(prop_bits)), prop_bits) < 0) {
+ return (DEVICE_TYPE_UNKNOWN);
+ }
+
+ has_keys = bit_find(key_bits, 0, BTN_MISC - 1);
+ has_buttons = bit_find(key_bits, BTN_MISC, BTN_JOYSTICK - 1);
+ has_lmr = bit_find(key_bits, BTN_LEFT, BTN_MIDDLE);
+ has_rel_axes = bit_find(rel_bits, 0, REL_MAX);
+ has_abs_axes = bit_find(abs_bits, 0, ABS_MAX);
+ has_mt = bit_find(abs_bits, ABS_MT_SLOT, ABS_MAX);
+ type = DEVICE_TYPE_UNKNOWN;
+
+ if (has_abs_axes) {
+ if (has_mt && !has_buttons) {
+ /* TBD:Improve joystick detection */
+ if (bit_test(key_bits, BTN_JOYSTICK)) {
+ return (DEVICE_TYPE_JOYSTICK);
+ } else {
+ has_buttons = true;
+ }
+ }
+
+ if (bit_test(abs_bits, ABS_X) &&
+ bit_test(abs_bits, ABS_Y)) {
+ if (bit_test(key_bits, BTN_TOOL_PEN) ||
+ bit_test(key_bits, BTN_STYLUS) ||
+ bit_test(key_bits, BTN_STYLUS2)) {
+ type = DEVICE_TYPE_TABLET;
+ } else if (bit_test(abs_bits, ABS_PRESSURE) ||
+ bit_test(key_bits, BTN_TOUCH)) {
+ if (has_lmr ||
+ bit_test(key_bits, BTN_TOOL_FINGER)) {
+ type = DEVICE_TYPE_TOUCHPAD;
+ } else {
+ type = DEVICE_TYPE_TOUCHSCREEN;
+ }
+ /* some touchscreens use BTN_LEFT rather than BTN_TOUCH */
+ } else if (!(bit_test(rel_bits, REL_X) &&
+ bit_test(rel_bits, REL_Y)) &&
+ has_lmr) {
+ type = DEVICE_TYPE_TOUCHSCREEN;
+ }
+ }
+ }
+
+ if (type == DEVICE_TYPE_UNKNOWN) {
+ if (has_keys)
+ type = DEVICE_TYPE_KEYBOARD;
+ else if (has_rel_axes || has_buttons)
+ type = DEVICE_TYPE_MOUSE;
+ }
+
+ return (type);
+}
+
+static enum device_type
+r_identify_sysmouse(int fd __unused)
+{
+ /* All sysmouse devices act like mices */
+ return (DEVICE_TYPE_MOUSE);
+}
+
+static const char *
+r_if(enum device_if type)
+{
+ const char *unknown = "unknown";
+
+ return (type == DEVICE_IF_UNKNOWN || type >= (int)nitems(rifs) ?
+ unknown : rifs[type].name);
+}
+
+static const char *
+r_name(enum device_type type)
+{
+ const char *unknown = "unknown";
+
+ return (type == DEVICE_TYPE_UNKNOWN || type >= (int)nitems(rnames) ?
+ unknown : rnames[type]);
+}
+
+static int
+r_init_dev_evdev(int fd, struct device *dev)
+{
+ if (ioctl(fd, EVIOCGNAME(sizeof(dev->name) - 1), dev->name) < 0) {
+ logwarnx("unable to get device %s name", dev->path);
+ return (errno);
+ }
+ /* Do not loop events */
+ if (strncmp(dev->name, "System mouse", sizeof(dev->name)) == 0) {
+ return (ENOTSUP);
+ }
+ if (ioctl(fd, EVIOCGID, &dev->id) < 0) {
+ logwarnx("unable to get device %s ID", dev->path);
+ return (errno);
+ }
+ (void)ioctl(fd, EVIOCGUNIQ(sizeof(dev->uniq) - 1), dev->uniq);
+
+ return (0);
+}
+
+static int
+r_init_dev_sysmouse(int fd, struct device *dev)
+{
+ mousemode_t *mode = &dev->mode;
+ int level;
+
+ level = 1;
+ if (ioctl(fd, MOUSE_SETLEVEL, &level) < 0) {
+ logwarnx("unable to MOUSE_SETLEVEL for device %s", dev->path);
+ return (errno);
+ }
+ if (ioctl(fd, MOUSE_GETLEVEL, &level) < 0) {
+ logwarnx("unable to MOUSE_GETLEVEL for device %s", dev->path);
+ return (errno);
+ }
+ if (level != 1) {
+ logwarnx("unable to set level to 1 for device %s", dev->path);
+ return (ENOTSUP);
+ }
+ memset(mode, 0, sizeof(*mode));
+ if (ioctl(fd, MOUSE_GETMODE, mode) < 0) {
+ logwarnx("unable to MOUSE_GETMODE for device %s", dev->path);
+ return (errno);
+ }
+ if (mode->protocol != MOUSE_PROTO_SYSMOUSE) {
+ logwarnx("unable to set sysmouse protocol for device %s",
+ dev->path);
+ return (ENOTSUP);
+ }
+ if (mode->packetsize != MOUSE_SYS_PACKETSIZE) {
+ logwarnx("unable to set sysmouse packet size for device %s",
+ dev->path);
+ return (ENOTSUP);
+ }
+
+ /* TODO: Fill name, id and uniq from dev.* sysctls */
+ strlcpy(dev->name, dev->path, sizeof(dev->name));
+
+ return (0);
+}
+
+static void
+r_init_evstate(struct quirks *q, struct evstate *ev)
+{
+ const struct quirk_tuples *t;
+ bitstr_t *bitstr;
+ int maxbit;
+
+ if (quirks_get_tuples(q, QUIRK_ATTR_EVENT_CODE, &t)) {
+ for (size_t i = 0; i < t->ntuples; i++) {
+ int type = t->tuples[i].first;
+ int code = t->tuples[i].second;
+ bool enable = t->tuples[i].third;
+
+ switch (type) {
+ case EV_KEY:
+ bitstr = (bitstr_t *)&ev->key_ignore;
+ maxbit = KEY_MAX;
+ break;
+ case EV_REL:
+ bitstr = (bitstr_t *)&ev->rel_ignore;
+ maxbit = REL_MAX;
+ break;
+ case EV_ABS:
+ bitstr = (bitstr_t *)&ev->abs_ignore;
+ maxbit = ABS_MAX;
+ break;
+ default:
+ continue;
+ }
+
+ if (code == EVENT_CODE_UNDEFINED) {
+ if (enable)
+ bit_nclear(bitstr, 0, maxbit);
+ else
+ bit_nset(bitstr, 0, maxbit);
+ } else {
+ if (code > maxbit)
+ continue;
+ if (enable)
+ bit_clear(bitstr, code);
+ else
+ bit_set(bitstr, code);
+ }
+ }
+ }
+
+ if (quirks_get_tuples(q, QUIRK_ATTR_INPUT_PROP, &t)) {
+ for (size_t idx = 0; idx < t->ntuples; idx++) {
+ unsigned int p = t->tuples[idx].first;
+ bool enable = t->tuples[idx].second;
+
+ if (p > INPUT_PROP_MAX)
+ continue;
+ if (enable)
+ bit_clear(ev->prop_ignore, p);
+ else
+ bit_set(ev->prop_ignore, p);
+ }
+ }
+}
+
+static void
+r_init_buttons(struct quirks *q, struct btstate *bt, struct e3bstate *e3b)
+{
+ struct timespec ts;
+ int i, j;
+
+ *bt = (struct btstate) {
+ .clickthreshold = DFLT_CLICKTHRESHOLD,
+ .zmap = { 0, 0, 0, 0 },
+ };
+
+ memcpy(bt->p2l, default_p2l, sizeof(bt->p2l));
+ for (i = 0; i < MOUSE_MAXBUTTON; ++i) {
+ j = i;
+ if (opt_btstate.p2l[i] != 0)
+ bt->p2l[i] = opt_btstate.p2l[i];
+ if (opt_btstate.mstate[i] != NULL)
+ j = opt_btstate.mstate[i] - opt_btstate.bstate;
+ bt->mstate[i] = bt->bstate + j;
+ }
+
+ if (opt_btstate.zmap[0] != 0)
+ memcpy(bt->zmap, opt_btstate.zmap, sizeof(bt->zmap));
+ if (opt_clickthreshold >= 0)
+ bt->clickthreshold = opt_clickthreshold;
+ else
+ quirks_get_uint32(q, MOUSED_CLICK_THRESHOLD, &bt->clickthreshold);
+ if (opt_wmode != 0)
+ bt->wmode = opt_wmode;
+ else
+ quirks_get_uint32(q, MOUSED_WMODE, &bt->wmode);
+ if (bt->wmode != 0)
+ bt->wmode = 1 << (bt->wmode - 1);
+
+ /* fix Z axis mapping */
+ for (i = 0; i < ZMAP_MAXBUTTON; ++i) {
+ if (bt->zmap[i] <= 0)
+ continue;
+ for (j = 0; j < MOUSE_MAXBUTTON; ++j) {
+ if (bt->mstate[j] == &bt->bstate[bt->zmap[i] - 1])
+ bt->mstate[j] = &bt->zstate[i];
+ }
+ bt->zmap[i] = 1 << (bt->zmap[i] - 1);
+ }
+
+ clock_gettime(CLOCK_MONOTONIC_FAST, &ts);
+
+ *e3b = (struct e3bstate) {
+ .enabled = false,
+ .button2timeout = DFLT_BUTTON2TIMEOUT,
+ };
+ e3b->enabled = opt_e3b_enabled;
+ if (!e3b->enabled)
+ quirks_get_bool(q, MOUSED_EMULATE_THIRD_BUTTON, &e3b->enabled);
+ if (opt_e3b_button2timeout >= 0)
+ e3b->button2timeout = opt_e3b_button2timeout;
+ else
+ quirks_get_uint32(q, MOUSED_EMULATE_THIRD_BUTTON_TIMEOUT,
+ &e3b->button2timeout);
+ e3b->mouse_button_state = S0;
+ e3b->mouse_button_state_ts = ts;
+ e3b->mouse_move_delayed = 0;
+
+ for (i = 0; i < MOUSE_MAXBUTTON; ++i) {
+ bt->bstate[i].count = 0;
+ bt->bstate[i].ts = ts;
+ }
+ for (i = 0; i < ZMAP_MAXBUTTON; ++i) {
+ bt->zstate[i].count = 0;
+ bt->zstate[i].ts = ts;
+ }
+}
+
+static void
+r_init_touchpad_hw(int fd, struct quirks *q, struct tpcaps *tphw,
+ struct evstate *ev)
+{
+ struct input_absinfo ai;
+ bitstr_t bit_decl(key_bits, KEY_CNT);
+ bitstr_t bit_decl(abs_bits, ABS_CNT);
+ bitstr_t bit_decl(prop_bits, INPUT_PROP_CNT);
+ struct quirk_range r;
+ struct quirk_dimensions dim;
+ u_int u;
+
+ ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(abs_bits)), abs_bits);
+ ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(key_bits)), key_bits);
+
+ if (!bit_test(ev->abs_ignore, ABS_X) &&
+ ioctl(fd, EVIOCGABS(ABS_X), &ai) >= 0) {
+ tphw->min_x = (ai.maximum > ai.minimum) ? ai.minimum : INT_MIN;
+ tphw->max_x = (ai.maximum > ai.minimum) ? ai.maximum : INT_MAX;
+ tphw->res_x = ai.resolution == 0 ?
+ DFLT_TPAD_RESOLUTION : ai.resolution;
+ }
+ if (!bit_test(ev->abs_ignore, ABS_Y) &&
+ ioctl(fd, EVIOCGABS(ABS_Y), &ai) >= 0) {
+ tphw->min_y = (ai.maximum > ai.minimum) ? ai.minimum : INT_MIN;
+ tphw->max_y = (ai.maximum > ai.minimum) ? ai.maximum : INT_MAX;
+ tphw->res_y = ai.resolution == 0 ?
+ DFLT_TPAD_RESOLUTION : ai.resolution;
+ }
+ if (quirks_get_dimensions(q, QUIRK_ATTR_RESOLUTION_HINT, &dim)) {
+ tphw->res_x = dim.x;
+ tphw->res_y = dim.y;
+ } else if (tphw->max_x != INT_MAX && tphw->max_y != INT_MAX &&
+ quirks_get_dimensions(q, QUIRK_ATTR_SIZE_HINT, &dim)) {
+ tphw->res_x = (tphw->max_x - tphw->min_x) / dim.x;
+ tphw->res_y = (tphw->max_y - tphw->min_y) / dim.y;
+ }
+ if (!bit_test(ev->key_ignore, BTN_TOUCH) &&
+ bit_test(key_bits, BTN_TOUCH))
+ tphw->cap_touch = true;
+ /* XXX: libinput uses ABS_MT_PRESSURE where available */
+ if (!bit_test(ev->abs_ignore, ABS_PRESSURE) &&
+ bit_test(abs_bits, ABS_PRESSURE) &&
+ ioctl(fd, EVIOCGABS(ABS_PRESSURE), &ai) >= 0) {
+ tphw->cap_pressure = true;
+ tphw->min_p = ai.minimum;
+ tphw->max_p = ai.maximum;
+ }
+ if (tphw->cap_pressure &&
+ quirks_get_range(q, QUIRK_ATTR_PRESSURE_RANGE, &r)) {
+ if (r.upper == 0 && r.lower == 0) {
+ debug("pressure-based touch detection disabled");
+ tphw->cap_pressure = false;
+ } else if (r.upper > tphw->max_p || r.upper < tphw->min_p ||
+ r.lower > tphw->max_p || r.lower < tphw->min_p) {
+ debug("discarding out-of-bounds pressure range %d:%d",
+ r.lower, r.upper);
+ tphw->cap_pressure = false;
+ }
+ }
+ /* XXX: libinput uses ABS_MT_TOUCH_MAJOR where available */
+ if (!bit_test(ev->abs_ignore, ABS_TOOL_WIDTH) &&
+ bit_test(abs_bits, ABS_TOOL_WIDTH) &&
+ quirks_get_uint32(q, QUIRK_ATTR_PALM_SIZE_THRESHOLD, &u) &&
+ u != 0)
+ tphw->cap_width = true;
+ if (!bit_test(ev->abs_ignore, ABS_MT_SLOT) &&
+ bit_test(abs_bits, ABS_MT_SLOT) &&
+ !bit_test(ev->abs_ignore, ABS_MT_TRACKING_ID) &&
+ bit_test(abs_bits, ABS_MT_TRACKING_ID) &&
+ !bit_test(ev->abs_ignore, ABS_MT_POSITION_X) &&
+ bit_test(abs_bits, ABS_MT_POSITION_X) &&
+ !bit_test(ev->abs_ignore, ABS_MT_POSITION_Y) &&
+ bit_test(abs_bits, ABS_MT_POSITION_Y))
+ tphw->is_mt = true;
+ if ( ioctl(fd, EVIOCGPROP(sizeof(prop_bits)), prop_bits) >= 0 &&
+ !bit_test(ev->prop_ignore, INPUT_PROP_BUTTONPAD) &&
+ bit_test(prop_bits, INPUT_PROP_BUTTONPAD))
+ tphw->is_clickpad = true;
+ if ( tphw->is_clickpad &&
+ !bit_test(ev->prop_ignore, INPUT_PROP_TOPBUTTONPAD) &&
+ bit_test(prop_bits, INPUT_PROP_TOPBUTTONPAD))
+ tphw->is_topbuttonpad = true;
+}
+
+static void
+r_init_touchpad_info(struct quirks *q, struct tpcaps *tphw,
+ struct tpinfo *tpinfo)
+{
+ struct quirk_range r;
+ int i;
+ u_int u;
+ int sz_x, sz_y;
+
+ *tpinfo = (struct tpinfo) {
+ .two_finger_scroll = true,
+ .natural_scroll = false,
+ .three_finger_drag = false,
+ .min_pressure_hi = 1,
+ .min_pressure_lo = 1,
+ .max_pressure = 130,
+ .max_width = 16,
+ .tap_timeout = 180, /* ms */
+ .tap_threshold = 0,
+ .tap_max_delta = 1.3, /* mm */
+ .taphold_timeout = 300, /* ms */
+ .vscroll_min_delta = 1.25, /* mm */
+ .vscroll_hor_area = 0.0, /* mm */
+ .vscroll_ver_area = -15.0, /* mm */
+ };
+
+ quirks_get_bool(q, MOUSED_TWO_FINGER_SCROLL, &tpinfo->two_finger_scroll);
+ quirks_get_bool(q, MOUSED_NATURAL_SCROLL, &tpinfo->natural_scroll);
+ quirks_get_bool(q, MOUSED_THREE_FINGER_DRAG, &tpinfo->three_finger_drag);
+ quirks_get_uint32(q, MOUSED_TAP_TIMEOUT, &tpinfo->tap_timeout);
+ quirks_get_double(q, MOUSED_TAP_MAX_DELTA, &tpinfo->tap_max_delta);
+ quirks_get_uint32(q, MOUSED_TAPHOLD_TIMEOUT, &tpinfo->taphold_timeout);
+ quirks_get_double(q, MOUSED_VSCROLL_MIN_DELTA, &tpinfo->vscroll_min_delta);
+ quirks_get_double(q, MOUSED_VSCROLL_HOR_AREA, &tpinfo->vscroll_hor_area);
+ quirks_get_double(q, MOUSED_VSCROLL_VER_AREA, &tpinfo->vscroll_ver_area);
+
+ if (tphw->cap_pressure &&
+ quirks_get_range(q, QUIRK_ATTR_PRESSURE_RANGE, &r)) {
+ tpinfo->min_pressure_lo = r.lower;
+ tpinfo->min_pressure_hi = r.upper;
+ quirks_get_uint32(q, QUIRK_ATTR_PALM_PRESSURE_THRESHOLD,
+ &tpinfo->max_pressure);
+ quirks_get_uint32(q, MOUSED_TAP_PRESSURE_THRESHOLD,
+ &tpinfo->tap_threshold);
+ }
+ if (tphw->cap_width)
+ quirks_get_uint32(q, QUIRK_ATTR_PALM_SIZE_THRESHOLD,
+ &tpinfo->max_width);
+ /* Set bottom quarter as 42% - 16% - 42% sized softbuttons */
+ if (tphw->is_clickpad) {
+ sz_x = tphw->max_x - tphw->min_x;
+ sz_y = tphw->max_y - tphw->min_y;
+ i = 25;
+ if (tphw->is_topbuttonpad)
+ i = -i;
+ quirks_get_int32(q, MOUSED_SOFTBUTTONS_Y, &i);
+ tpinfo->softbuttons_y = sz_y * i / 100;
+ u = 42;
+ quirks_get_uint32(q, MOUSED_SOFTBUTTON2_X, &u);
+ tpinfo->softbutton2_x = sz_x * u / 100;
+ u = 58;
+ quirks_get_uint32(q, MOUSED_SOFTBUTTON3_X, &u);
+ tpinfo->softbutton3_x = sz_x * u / 100;
+ }
+}
+
+static void
+r_init_touchpad_accel(struct tpcaps *tphw, struct accel *accel)
+{
+ /* Normalize pointer movement to match 200dpi mouse */
+ accel->accelx *= DFLT_MOUSE_RESOLUTION;
+ accel->accelx /= tphw->res_x;
+ accel->accely *= DFLT_MOUSE_RESOLUTION;
+ accel->accely /= tphw->res_y;
+ accel->accelz *= DFLT_MOUSE_RESOLUTION;
+ accel->accelz /= (tphw->res_x * DFLT_LINEHEIGHT);
+}
+
+static void
+r_init_touchpad_gesture(struct tpstate *gest)
+{
+ gest->idletimeout = -1;
+}
+
+static void
+r_init_drift(struct quirks *q, struct drift *d)
+{
+ if (opt_drift_terminate) {
+ d->terminate = true;
+ d->distance = opt_drift_distance;
+ d->time = opt_drift_time;
+ d->after = opt_drift_after;
+ } else if (quirks_get_bool(q, MOUSED_DRIFT_TERMINATE, &d->terminate) &&
+ d->terminate) {
+ quirks_get_uint32(q, MOUSED_DRIFT_DISTANCE, &d->distance);
+ quirks_get_uint32(q, MOUSED_DRIFT_TIME, &d->time);
+ quirks_get_uint32(q, MOUSED_DRIFT_AFTER, &d->after);
+ } else
+ return;
+
+ if (d->distance == 0 || d->time == 0 || d->after == 0) {
+ warnx("invalid drift parameter");
+ exit(1);
+ }
+
+ debug("terminate drift: distance %d, time %d, after %d",
+ d->distance, d->time, d->after);
+
+ d->time_ts = msec2ts(d->time);
+ d->twotime_ts = msec2ts(d->time * 2);
+ d->after_ts = msec2ts(d->after);
+}
+
+static void
+r_init_accel(struct quirks *q, struct accel *acc)
+{
+ bool r1, r2;
+
+ acc->accelx = opt_accelx;
+ if (opt_accelx == 1.0)
+ quirks_get_double(q, MOUSED_LINEAR_ACCEL_X, &acc->accelx);
+ acc->accely = opt_accely;
+ if (opt_accely == 1.0)
+ quirks_get_double(q, MOUSED_LINEAR_ACCEL_Y, &acc->accely);
+ if (!quirks_get_double(q, MOUSED_LINEAR_ACCEL_Z, &acc->accelz))
+ acc->accelz = 1.0;
+ acc->lastlength[0] = acc->lastlength[1] = acc->lastlength[2] = 0.0;
+ if (opt_exp_accel) {
+ acc->is_exponential = true;
+ acc->expoaccel = opt_expoaccel;
+ acc->expoffset = opt_expoffset;
+ return;
+ }
+ acc->expoaccel = acc->expoffset = 1.0;
+ r1 = quirks_get_double(q, MOUSED_EXPONENTIAL_ACCEL, &acc->expoaccel);
+ r2 = quirks_get_double(q, MOUSED_EXPONENTIAL_OFFSET, &acc->expoffset);
+ if (r1 || r2)
+ acc->is_exponential = true;
+}
+
+static void
+r_init_scroll(struct quirks *q, struct scroll *scroll)
+{
+ *scroll = (struct scroll) {
+ .threshold = DFLT_SCROLLTHRESHOLD,
+ .speed = DFLT_SCROLLSPEED,
+ .state = SCROLL_NOTSCROLLING,
+ };
+ scroll->enable_vert = opt_virtual_scroll;
+ if (!opt_virtual_scroll)
+ quirks_get_bool(q, MOUSED_VIRTUAL_SCROLL_ENABLE, &scroll->enable_vert);
+ scroll->enable_hor = opt_hvirtual_scroll;
+ if (!opt_hvirtual_scroll)
+ quirks_get_bool(q, MOUSED_HOR_VIRTUAL_SCROLL_ENABLE, &scroll->enable_hor);
+ if (opt_scroll_speed >= 0)
+ scroll->speed = opt_scroll_speed;
+ else
+ quirks_get_uint32(q, MOUSED_VIRTUAL_SCROLL_SPEED, &scroll->speed);
+ if (opt_scroll_threshold >= 0)
+ scroll->threshold = opt_scroll_threshold;
+ else
+ quirks_get_uint32(q, MOUSED_VIRTUAL_SCROLL_THRESHOLD, &scroll->threshold);
+}
+
+static struct rodent *
+r_init(const char *path)
+{
+ struct rodent *r;
+ struct device dev;
+ struct quirks *q;
+ struct kevent kev;
+ enum device_if iftype;
+ enum device_type type;
+ int fd, err;
+ bool grab;
+ bool ignore;
+ bool qvalid;
+
+ fd = open(path, O_RDWR | O_NONBLOCK);
+ if (fd == -1) {
+ logwarnx("unable to open %s", path);
+ return (NULL);
+ }
+
+ iftype = r_identify_if(fd);
+ switch (iftype) {
+ case DEVICE_IF_UNKNOWN:
+ debug("cannot determine interface type on %s", path);
+ close(fd);
+ errno = ENOTSUP;
+ return (NULL);
+ case DEVICE_IF_EVDEV:
+ type = r_identify_evdev(fd);
+ break;
+ case DEVICE_IF_SYSMOUSE:
+ type = r_identify_sysmouse(fd);
+ break;
+ default:
+ debug("unsupported interface type: %s on %s",
+ r_if(iftype), path);
+ close(fd);
+ errno = ENXIO;
+ return (NULL);
+ }
+
+ switch (type) {
+ case DEVICE_TYPE_UNKNOWN:
+ debug("cannot determine device type on %s", path);
+ close(fd);
+ errno = ENOTSUP;
+ return (NULL);
+ case DEVICE_TYPE_MOUSE:
+ case DEVICE_TYPE_TOUCHPAD:
+ break;
+ default:
+ debug("unsupported device type: %s on %s",
+ r_name(type), path);
+ close(fd);
+ errno = ENXIO;
+ return (NULL);
+ }
+
+ memset(&dev, 0, sizeof(struct device));
+ strlcpy(dev.path, path, sizeof(dev.path));
+ dev.iftype = iftype;
+ dev.type = type;
+ switch (iftype) {
+ case DEVICE_IF_EVDEV:
+ err = r_init_dev_evdev(fd, &dev);
+ break;
+ case DEVICE_IF_SYSMOUSE:
+ err = r_init_dev_sysmouse(fd, &dev);
+ break;
+ default:
+ debug("unsupported interface type: %s on %s",
+ r_if(iftype), path);
+ err = ENXIO;
+ }
+ if (err != 0) {
+ debug("failed to initialize device: %s %s on %s",
+ r_if(iftype), r_name(type), path);
+ close(fd);
+ errno = err;
+ return (NULL);
+ }
+
+ debug("port: %s interface: %s type: %s model: %s",
+ path, r_if(iftype), r_name(type), dev.name);
+
+ q = quirks_fetch_for_device(quirks, &dev);
+
+ qvalid = quirks_get_bool(q, MOUSED_IGNORE_DEVICE, &ignore);
+ if (qvalid && ignore) {
+ debug("%s: device ignored", path);
+ close(fd);
+ quirks_unref(q);
+ errno = EPERM;
+ return (NULL);
+ }
+
+ switch (iftype) {
+ case DEVICE_IF_EVDEV:
+ grab = opt_grab;
+ if (!grab)
+ qvalid = quirks_get_bool(q, MOUSED_GRAB_DEVICE, &grab);
+ if (qvalid && grab && ioctl(fd, EVIOCGRAB, 1) == -1) {
+ logwarnx("failed to grab %s", path);
+ err = errno;
+ }
+ break;
+ case DEVICE_IF_SYSMOUSE:
+ if (opt_resolution == MOUSE_RES_UNKNOWN && opt_rate == 0)
+ break;
+ if (opt_resolution != MOUSE_RES_UNKNOWN)
+ dev.mode.resolution = opt_resolution;
+ if (opt_resolution != 0)
+ dev.mode.rate = opt_rate;
+ if (ioctl(fd, MOUSE_SETMODE, &dev.mode) < 0)
+ debug("failed to MOUSE_SETMODE for device %s", path);
+ break;
+ default:
+ debug("unsupported interface type: %s on %s",
+ r_if(iftype), path);
+ err = ENXIO;
+ }
+ if (err != 0) {
+ debug("failed to initialize device: %s %s on %s",
+ r_if(iftype), r_name(type), path);
+ close(fd);
+ quirks_unref(q);
+ errno = err;
+ return (NULL);
+ }
+
+ r = calloc(1, sizeof(struct rodent));
+ memcpy(&r->dev, &dev, sizeof(struct device));
+ r->mfd = fd;
+
+ EV_SET(&kev, fd, EVFILT_READ, EV_ADD, 0, 0, r);
+ err = kevent(kfd, &kev, 1, NULL, 0, NULL);
+ if (err == -1) {
+ logwarnx("failed to register kevent on %s", path);
+ close(fd);
+ free(r);
+ quirks_unref(q);
+ return (NULL);
+ }
+
+ if (iftype == DEVICE_IF_EVDEV)
+ r_init_evstate(q, &r->ev);
+ r_init_buttons(q, &r->btstate, &r->e3b);
+ r_init_scroll(q, &r->scroll);
+ r_init_accel(q, &r->accel);
+ switch (type) {
+ case DEVICE_TYPE_TOUCHPAD:
+ r_init_touchpad_hw(fd, q, &r->tp.hw, &r->ev);
+ r_init_touchpad_info(q, &r->tp.hw, &r->tp.info);
+ r_init_touchpad_accel(&r->tp.hw, &r->accel);
+ r_init_touchpad_gesture(&r->tp.gest);
+ break;
+
+ case DEVICE_TYPE_MOUSE:
+ r_init_drift(q, &r->drift);
+ break;
+
+ default:
+ debug("unsupported device type: %s", r_name(type));
+ break;
+ }
+
+ quirks_unref(q);
+
+ SLIST_INSERT_HEAD(&rodents, r, next);
+
+ return (r);
+}
+
+static void
+r_init_all(void)
+{
+ char path[22] = "/dev/input/";
+ DIR *dirp;
+ struct dirent *dp;
+
+ dirp = opendir("/dev/input");
+ if (dirp == NULL)
+ logerr(1, "Failed to open /dev/input");
+
+ while ((dp = readdir(dirp)) != NULL) {
+ if (fnmatch("event[0-9]*", dp->d_name, 0) == 0) {
+ strncpy(path + 11, dp->d_name, 10);
+ (void)r_init(path);
+ }
+ }
+ (void)closedir(dirp);
+
+ return;
+}
+
+static void
+r_deinit(struct rodent *r)
+{
+ struct kevent ke[3];
+
+ if (r == NULL)
+ return;
+ if (r->mfd != -1) {
+ EV_SET(ke, r->mfd, EVFILT_READ, EV_DELETE, 0, 0, r);
+ EV_SET(ke + 1, r->mfd << 1, EVFILT_TIMER, EV_DELETE, 0, 0, r);
+ EV_SET(ke + 2, r->mfd << 1 | 1,
+ EVFILT_TIMER, EV_DELETE, 0, 0, r);
+ kevent(kfd, ke, nitems(ke), NULL, 0, NULL);
+ close(r->mfd);
+ }
+ SLIST_REMOVE(&rodents, r, rodent, next);
+ debug("destroy device: port: %s model: %s", r->dev.path, r->dev.name);
+ free(r);
+}
+
+static void
+r_deinit_all(void)
+{
+ while (!SLIST_EMPTY(&rodents))
+ r_deinit(SLIST_FIRST(&rodents));
+}
+
+static int
+r_protocol_evdev(enum device_type type, struct tpad *tp, struct evstate *ev,
+ struct input_event *ie, mousestatus_t *act)
+{
+ const struct tpcaps *tphw = &tp->hw;
+ const struct tpinfo *tpinfo = &tp->info;
+
+ static int butmapev[8] = { /* evdev */
+ 0,
+ MOUSE_BUTTON1DOWN,
+ MOUSE_BUTTON3DOWN,
+ MOUSE_BUTTON1DOWN | MOUSE_BUTTON3DOWN,
+ MOUSE_BUTTON2DOWN,
+ MOUSE_BUTTON1DOWN | MOUSE_BUTTON2DOWN,
+ MOUSE_BUTTON2DOWN | MOUSE_BUTTON3DOWN,
+ MOUSE_BUTTON1DOWN | MOUSE_BUTTON2DOWN | MOUSE_BUTTON3DOWN
+ };
+ struct timespec ietime;
+
+ /* Drop ignored codes */
+ switch (ie->type) {
+ case EV_REL:
+ if (bit_test(ev->rel_ignore, ie->code))
+ return (0);
+ case EV_ABS:
+ if (bit_test(ev->abs_ignore, ie->code))
+ return (0);
+ case EV_KEY:
+ if (bit_test(ev->key_ignore, ie->code))
+ return (0);
+ }
+
+ if (debug > 1)
+ debug("received event 0x%02x, 0x%04x, %d",
+ ie->type, ie->code, ie->value);
+
+ switch (ie->type) {
+ case EV_REL:
+ switch (ie->code) {
+ case REL_X:
+ ev->dx += ie->value;
+ break;
+ case REL_Y:
+ ev->dy += ie->value;
+ break;
+ case REL_WHEEL:
+ ev->dz += ie->value;
+ break;
+ case REL_HWHEEL:
+ ev->dw += ie->value;
+ break;
+ }
+ break;
+ case EV_ABS:
+ switch (ie->code) {
+ case ABS_X:
+ if (!tphw->is_mt)
+ ev->dx += ie->value - ev->st.x;
+ ev->st.x = ie->value;
+ break;
+ case ABS_Y:
+ if (!tphw->is_mt)
+ ev->dy += ie->value - ev->st.y;
+ ev->st.y = ie->value;
+ break;
+ case ABS_PRESSURE:
+ ev->st.p = ie->value;
+ break;
+ case ABS_TOOL_WIDTH:
+ ev->st.w = ie->value;
+ break;
+ case ABS_MT_SLOT:
+ if (tphw->is_mt)
+ ev->slot = ie->value;
+ break;
+ case ABS_MT_TRACKING_ID:
+ if (tphw->is_mt &&
+ ev->slot >= 0 && ev->slot < MAX_FINGERS) {
+ if (ie->value != -1 && ev->mt[ev->slot].id > 0 &&
+ ie->value + 1 != ev->mt[ev->slot].id) {
+ debug("tracking id changed %d->%d",
+ ie->value, ev->mt[ev->slot].id - 1);
+ ev->mt[ev->slot].id = 0;
+ } else
+ ev->mt[ev->slot].id = ie->value + 1;
+ }
+ break;
+ case ABS_MT_POSITION_X:
+ if (tphw->is_mt &&
+ ev->slot >= 0 && ev->slot < MAX_FINGERS) {
+ /* Find fastest finger */
+ int dx = ie->value - ev->mt[ev->slot].x;
+ if (abs(dx) > abs(ev->dx))
+ ev->dx = dx;
+ ev->mt[ev->slot].x = ie->value;
+ }
+ break;
+ case ABS_MT_POSITION_Y:
+ if (tphw->is_mt &&
+ ev->slot >= 0 && ev->slot < MAX_FINGERS) {
+ /* Find fastest finger */
+ int dy = ie->value - ev->mt[ev->slot].y;
+ if (abs(dy) > abs(ev->dy))
+ ev->dy = dy;
+ ev->mt[ev->slot].y = ie->value;
+ }
+ break;
+ }
+ break;
+ case EV_KEY:
+ switch (ie->code) {
+ case BTN_TOUCH:
+ ev->st.id = ie->value != 0 ? 1 : 0;
+ break;
+ case BTN_TOOL_FINGER:
+ ev->nfingers = ie->value != 0 ? 1 : ev->nfingers;
+ break;
+ case BTN_TOOL_DOUBLETAP:
+ ev->nfingers = ie->value != 0 ? 2 : ev->nfingers;
+ break;
+ case BTN_TOOL_TRIPLETAP:
+ ev->nfingers = ie->value != 0 ? 3 : ev->nfingers;
+ break;
+ case BTN_TOOL_QUADTAP:
+ ev->nfingers = ie->value != 0 ? 4 : ev->nfingers;
+ break;
+ case BTN_TOOL_QUINTTAP:
+ ev->nfingers = ie->value != 0 ? 5 : ev->nfingers;
+ break;
+ case BTN_LEFT ... BTN_LEFT + 7:
+ ev->buttons &= ~(1 << (ie->code - BTN_LEFT));
+ ev->buttons |= ((!!ie->value) << (ie->code - BTN_LEFT));
+ break;
+ }
+ break;
+ }
+
+ if ( ie->type != EV_SYN ||
+ (ie->code != SYN_REPORT && ie->code != SYN_DROPPED))
+ return (0);
+
+ /*
+ * assembly full package
+ */
+
+ ietime.tv_sec = ie->time.tv_sec;
+ ietime.tv_nsec = ie->time.tv_usec * 1000;
+
+ if (!tphw->cap_pressure && ev->st.id != 0)
+ ev->st.p = MAX(tpinfo->min_pressure_hi, tpinfo->tap_threshold);
+ if (tphw->cap_touch && ev->st.id == 0)
+ ev->st.p = 0;
+
+ act->obutton = act->button;
+ act->button = butmapev[ev->buttons & MOUSE_SYS_STDBUTTONS];
+ act->button |= (ev->buttons & ~MOUSE_SYS_STDBUTTONS);
+
+ if (type == DEVICE_TYPE_TOUCHPAD) {
+ if (debug > 1)
+ debug("absolute data %d,%d,%d,%d", ev->st.x, ev->st.y,
+ ev->st.p, ev->st.w);
+ switch (r_gestures(tp, ev->st.x, ev->st.y, ev->st.p, ev->st.w,
+ ev->nfingers, &ietime, act)) {
+ case GEST_IGNORE:
+ ev->dx = 0;
+ ev->dy = 0;
+ ev->dz = 0;
+ ev->acc_dx = ev->acc_dy = 0;
+ debug("gesture IGNORE");
+ break;
+ case GEST_ACCUMULATE: /* Revertable pointer movement. */
+ ev->acc_dx += ev->dx;
+ ev->acc_dy += ev->dy;
+ debug("gesture ACCUMULATE %d,%d", ev->dx, ev->dy);
+ ev->dx = 0;
+ ev->dy = 0;
+ break;
+ case GEST_MOVE: /* Pointer movement. */
+ ev->dx += ev->acc_dx;
+ ev->dy += ev->acc_dy;
+ ev->acc_dx = ev->acc_dy = 0;
+ debug("gesture MOVE %d,%d", ev->dx, ev->dy);
+ break;
+ case GEST_VSCROLL: /* Vertical scrolling. */
+ if (tpinfo->natural_scroll)
+ ev->dz = -ev->dy;
+ else
+ ev->dz = ev->dy;
+ ev->dx = -ev->acc_dx;
+ ev->dy = -ev->acc_dy;
+ ev->acc_dx = ev->acc_dy = 0;
+ debug("gesture VSCROLL %d", ev->dz);
+ break;
+ case GEST_HSCROLL: /* Horizontal scrolling. */
+/*
+ if (ev.dx != 0) {
+ if (tpinfo->natural_scroll)
+ act->button |= (ev.dx > 0)
+ ? MOUSE_BUTTON6DOWN
+ : MOUSE_BUTTON7DOWN;
+ else
+ act->button |= (ev.dx > 0)
+ ? MOUSE_BUTTON7DOWN
+ : MOUSE_BUTTON6DOWN;
+ }
+*/
+ ev->dx = -ev->acc_dx;
+ ev->dy = -ev->acc_dy;
+ ev->acc_dx = ev->acc_dy = 0;
+ debug("gesture HSCROLL %d", ev->dw);
+ break;
+ }
+ }
+
+ debug("assembled full packet %d,%d,%d", ev->dx, ev->dy, ev->dz);
+ act->dx = ev->dx;
+ act->dy = ev->dy;
+ act->dz = ev->dz;
+ ev->dx = ev->dy = ev->dz = ev->dw = 0;
+
+ /* has something changed? */
+ act->flags = ((act->dx || act->dy || act->dz) ? MOUSE_POSCHANGED : 0)
+ | (act->obutton ^ act->button);
+
+ return (act->flags);
+}
+
+static int
+r_protocol_sysmouse(uint8_t *pBuf, mousestatus_t *act)
+{
+ static int butmapmsc[8] = { /* sysmouse */
+ 0,
+ MOUSE_BUTTON3DOWN,
+ MOUSE_BUTTON2DOWN,
+ MOUSE_BUTTON2DOWN | MOUSE_BUTTON3DOWN,
+ MOUSE_BUTTON1DOWN,
+ MOUSE_BUTTON1DOWN | MOUSE_BUTTON3DOWN,
+ MOUSE_BUTTON1DOWN | MOUSE_BUTTON2DOWN,
+ MOUSE_BUTTON1DOWN | MOUSE_BUTTON2DOWN | MOUSE_BUTTON3DOWN
+ };
+
+ debug("%02x %02x %02x %02x %02x %02x %02x %02x", pBuf[0], pBuf[1],
+ pBuf[2], pBuf[3], pBuf[4], pBuf[5], pBuf[6], pBuf[7]);
+
+ if ((pBuf[0] & MOUSE_SYS_SYNCMASK) != MOUSE_SYS_SYNC)
+ return (0);
+
+ act->button = butmapmsc[(~pBuf[0]) & MOUSE_SYS_STDBUTTONS];
+ act->dx = (signed char)(pBuf[1]) + (signed char)(pBuf[3]);
+ act->dy = - ((signed char)(pBuf[2]) + (signed char)(pBuf[4]));
+ act->dz = ((signed char)(pBuf[5] << 1) + (signed char)(pBuf[6] << 1)) >> 1;
+ act->button |= ((~pBuf[7] & MOUSE_SYS_EXTBUTTONS) << 3);
+
+ /* has something changed? */
+ act->flags = ((act->dx || act->dy || act->dz) ? MOUSE_POSCHANGED : 0)
+ | (act->obutton ^ act->button);
+
+ return (act->flags);
+}
+
+static void
+r_vscroll_detect(struct rodent *r, struct scroll *sc, mousestatus_t *act)
+{
+ mousestatus_t newaction;
+
+ /* Allow middle button drags to scroll up and down */
+ if (act->button == MOUSE_BUTTON2DOWN) {
+ if (sc->state == SCROLL_NOTSCROLLING) {
+ sc->state = SCROLL_PREPARE;
+ sc->movement = sc->hmovement = 0;
+ debug("PREPARING TO SCROLL");
+ }
+ return;
+ }
+
+ /* This isn't a middle button down... move along... */
+ switch (sc->state) {
+ case SCROLL_SCROLLING:
+ /*
+ * We were scrolling, someone let go of button 2.
+ * Now turn autoscroll off.
+ */
+ sc->state = SCROLL_NOTSCROLLING;
+ debug("DONE WITH SCROLLING / %d", sc->state);
+ break;
+ case SCROLL_PREPARE:
+ newaction = *act;
+
+ /* We were preparing to scroll, but we never moved... */
+ r_timestamp(act, &r->btstate, &r->e3b, &r->drift);
+ r_statetrans(r, act, &newaction,
+ A(newaction.button & MOUSE_BUTTON1DOWN,
+ act->button & MOUSE_BUTTON3DOWN));
+
+ /* Send middle down */
+ newaction.button = MOUSE_BUTTON2DOWN;
+ r_click(&newaction, &r->btstate);
+
+ /* Send middle up */
+ r_timestamp(&newaction, &r->btstate, &r->e3b, &r->drift);
+ newaction.obutton = newaction.button;
+ newaction.button = act->button;
+ r_click(&newaction, &r->btstate);
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+r_vscroll(struct scroll *sc, mousestatus_t *act)
+{
+ switch (sc->state) {
+ case SCROLL_PREPARE:
+ /* Middle button down, waiting for movement threshold */
+ if (act->dy == 0 && act->dx == 0)
+ break;
+ if (sc->enable_vert) {
+ sc->movement += act->dy;
+ if ((u_int)abs(sc->movement) > sc->threshold)
+ sc->state = SCROLL_SCROLLING;
+ }
+ if (sc->enable_hor) {
+ sc->hmovement += act->dx;
+ if ((u_int)abs(sc->hmovement) > sc->threshold)
+ sc->state = SCROLL_SCROLLING;
+ }
+ if (sc->state == SCROLL_SCROLLING)
+ sc->movement = sc->hmovement = 0;
+ break;
+ case SCROLL_SCROLLING:
+ if (sc->enable_vert) {
+ sc->movement += act->dy;
+ debug("SCROLL: %d", sc->movement);
+ if (sc->movement < -(int)sc->speed) {
+ /* Scroll down */
+ act->dz = -1;
+ sc->movement = 0;
+ }
+ else if (sc->movement > (int)sc->speed) {
+ /* Scroll up */
+ act->dz = 1;
+ sc->movement = 0;
+ }
+ }
+ if (sc->enable_hor) {
+ sc->hmovement += act->dx;
+ debug("HORIZONTAL SCROLL: %d", sc->hmovement);
+
+ if (sc->hmovement < -(int)sc->speed) {
+ act->dz = -2;
+ sc->hmovement = 0;
+ }
+ else if (sc->hmovement > (int)sc->speed) {
+ act->dz = 2;
+ sc->hmovement = 0;
+ }
+ }
+
+ /* Don't move while scrolling */
+ act->dx = act->dy = 0;
+ break;
+ default:
+ break;
+ }
+}
+
+static bool
+r_drift (struct drift *drift, mousestatus_t *act)
+{
+ struct timespec tmp;
+
+ /* X or/and Y movement only - possibly drift */
+ tssub(&drift->current_ts, &drift->last_activity, &tmp);
+ if (tscmp(&tmp, &drift->after_ts, >)) {
+ tssub(&drift->current_ts, &drift->since, &tmp);
+ if (tscmp(&tmp, &drift->time_ts, <)) {
+ drift->last.x += act->dx;
+ drift->last.y += act->dy;
+ } else {
+ /* discard old accumulated steps (drift) */
+ if (tscmp(&tmp, &drift->twotime_ts, >))
+ drift->previous.x = drift->previous.y = 0;
+ else
+ drift->previous = drift->last;
+ drift->last.x = act->dx;
+ drift->last.y = act->dy;
+ drift->since = drift->current_ts;
+ }
+ if ((u_int)abs(drift->last.x) + abs(drift->last.y) > drift->distance) {
+ /* real movement, pass all accumulated steps */
+ act->dx = drift->previous.x + drift->last.x;
+ act->dy = drift->previous.y + drift->last.y;
+ /* and reset accumulators */
+ tsclr(&drift->since);
+ drift->last.x = drift->last.y = 0;
+ /* drift_previous will be cleared at next movement*/
+ drift->last_activity = drift->current_ts;
+ } else {
+ return (true); /* don't pass current movement to
+ * console driver */
+ }
+ }
+ return (false);
+}
+
+static int
+r_statetrans(struct rodent *r, mousestatus_t *a1, mousestatus_t *a2, int trans)
+{
+ struct e3bstate *e3b = &r->e3b;
+ bool changed;
+ int flags;
+
+ a2->dx = a1->dx;
+ a2->dy = a1->dy;
+ a2->dz = a1->dz;
+ a2->obutton = a2->button;
+ a2->button = a1->button;
+ a2->flags = a1->flags;
+ changed = false;
+
+ if (!e3b->enabled)
+ return (false);
+
+ if (debug > 2)
+ debug("state:%d, trans:%d -> state:%d",
+ e3b->mouse_button_state, trans,
+ states[e3b->mouse_button_state].s[trans]);
+ /*
+ * Avoid re-ordering button and movement events. While a button
+ * event is deferred, throw away up to BUTTON2_MAXMOVE movement
+ * events to allow for mouse jitter. If more movement events
+ * occur, then complete the deferred button events immediately.
+ */
+ if ((a2->dx != 0 || a2->dy != 0) &&
+ S_DELAYED(states[e3b->mouse_button_state].s[trans])) {
+ if (++e3b->mouse_move_delayed > BUTTON2_MAXMOVE) {
+ e3b->mouse_move_delayed = 0;
+ e3b->mouse_button_state =
+ states[e3b->mouse_button_state].s[A_TIMEOUT];
+ changed = true;
+ } else
+ a2->dx = a2->dy = 0;
+ } else
+ e3b->mouse_move_delayed = 0;
+ if (e3b->mouse_button_state != states[e3b->mouse_button_state].s[trans])
+ changed = true;
+ if (changed)
+ clock_gettime(CLOCK_MONOTONIC_FAST,
+ &e3b->mouse_button_state_ts);
+ e3b->mouse_button_state = states[e3b->mouse_button_state].s[trans];
+ a2->button &= ~(MOUSE_BUTTON1DOWN | MOUSE_BUTTON2DOWN |
+ MOUSE_BUTTON3DOWN);
+ a2->button &= states[e3b->mouse_button_state].mask;
+ a2->button |= states[e3b->mouse_button_state].buttons;
+ flags = a2->flags & MOUSE_POSCHANGED;
+ flags |= a2->obutton ^ a2->button;
+ if (flags & MOUSE_BUTTON2DOWN) {
+ a2->flags = flags & MOUSE_BUTTON2DOWN;
+ r_timestamp(a2, &r->btstate, e3b, &r->drift);
+ }
+ a2->flags = flags;
+
+ return (changed);
+}
+
+static char *
+skipspace(char *s)
+{
+ while(isspace(*s))
+ ++s;
+ return (s);
+}
+
+static bool
+r_installmap(char *arg, struct btstate *bt)
+{
+ u_long pbutton;
+ u_long lbutton;
+ char *s;
+
+ while (*arg) {
+ arg = skipspace(arg);
+ s = arg;
+ while (isdigit(*arg))
+ ++arg;
+ arg = skipspace(arg);
+ if ((arg <= s) || (*arg != '='))
+ return (false);
+ lbutton = strtoul(s, NULL, 10);
+
+ arg = skipspace(++arg);
+ s = arg;
+ while (isdigit(*arg))
+ ++arg;
+ if ((arg <= s) || (!isspace(*arg) && (*arg != '\0')))
+ return (false);
+ pbutton = strtoul(s, NULL, 10);
+
+ if (lbutton == 0 || lbutton > MOUSE_MAXBUTTON)
+ return (false);
+ if (pbutton == 0 || pbutton > MOUSE_MAXBUTTON)
+ return (false);
+ bt->p2l[pbutton - 1] = 1 << (lbutton - 1);
+ bt->mstate[lbutton - 1] = &bt->bstate[pbutton - 1];
+ }
+
+ return (true);
+}
+
+static char *
+r_installzmap(char **argv, int argc, int* idx, struct btstate *bt)
+{
+ char *arg, *errstr;
+ u_long i, j;
+
+ arg = argv[*idx];
+ ++*idx;
+ if (strcmp(arg, "x") == 0) {
+ bt->zmap[0] = MOUSE_XAXIS;
+ return (NULL);
+ }
+ if (strcmp(arg, "y") == 0) {
+ bt->zmap[0] = MOUSE_YAXIS;
+ return (NULL);
+ }
+ i = strtoul(arg, NULL, 10);
+ /*
+ * Use button i for negative Z axis movement and
+ * button (i + 1) for positive Z axis movement.
+ */
+ if (i == 0 || i >= MOUSE_MAXBUTTON) {
+ asprintf(&errstr, "invalid argument `%s'", arg);
+ return (errstr);
+ }
+ bt->zmap[0] = i;
+ bt->zmap[1] = i + 1;
+ debug("optind: %d, optarg: '%s'", *idx, arg);
+ for (j = 1; j < ZMAP_MAXBUTTON; ++j) {
+ if ((*idx >= argc) || !isdigit(*argv[*idx]))
+ break;
+ i = strtoul(argv[*idx], NULL, 10);
+ if (i == 0 || i >= MOUSE_MAXBUTTON) {
+ asprintf(&errstr, "invalid argument `%s'", argv[*idx]);
+ return (errstr);
+ }
+ bt->zmap[j] = i;
+ ++*idx;
+ }
+ if ((bt->zmap[2] != 0) && (bt->zmap[3] == 0))
+ bt->zmap[3] = bt->zmap[2] + 1;
+
+ return (NULL);
+}
+
+static void
+r_map(mousestatus_t *act1, mousestatus_t *act2, struct btstate *bt)
+{
+ int pb;
+ int pbuttons;
+ int lbuttons;
+
+ pbuttons = act1->button;
+ lbuttons = 0;
+
+ act2->obutton = act2->button;
+ if (pbuttons & bt->wmode) {
+ pbuttons &= ~bt->wmode;
+ act1->dz = act1->dy;
+ act1->dx = 0;
+ act1->dy = 0;
+ }
+ act2->dx = act1->dx;
+ act2->dy = act1->dy;
+ act2->dz = act1->dz;
+
+ switch (bt->zmap[0]) {
+ case 0: /* do nothing */
+ break;
+ case MOUSE_XAXIS:
+ if (act1->dz != 0) {
+ act2->dx = act1->dz;
+ act2->dz = 0;
+ }
+ break;
+ case MOUSE_YAXIS:
+ if (act1->dz != 0) {
+ act2->dy = act1->dz;
+ act2->dz = 0;
+ }
+ break;
+ default: /* buttons */
+ pbuttons &= ~(bt->zmap[0] | bt->zmap[1]
+ | bt->zmap[2] | bt->zmap[3]);
+ if ((act1->dz < -1) && bt->zmap[2]) {
+ pbuttons |= bt->zmap[2];
+ bt->zstate[2].count = 1;
+ } else if (act1->dz < 0) {
+ pbuttons |= bt->zmap[0];
+ bt->zstate[0].count = 1;
+ } else if ((act1->dz > 1) && bt->zmap[3]) {
+ pbuttons |= bt->zmap[3];
+ bt->zstate[3].count = 1;
+ } else if (act1->dz > 0) {
+ pbuttons |= bt->zmap[1];
+ bt->zstate[1].count = 1;
+ }
+ act2->dz = 0;
+ break;
+ }
+
+ for (pb = 0; (pb < MOUSE_MAXBUTTON) && (pbuttons != 0); ++pb) {
+ lbuttons |= (pbuttons & 1) ? bt->p2l[pb] : 0;
+ pbuttons >>= 1;
+ }
+ act2->button = lbuttons;
+
+ act2->flags =
+ ((act2->dx || act2->dy || act2->dz) ? MOUSE_POSCHANGED : 0)
+ | (act2->obutton ^ act2->button);
+}
+
+static void
+r_timestamp(mousestatus_t *act, struct btstate *bt, struct e3bstate *e3b,
+ struct drift *drift)
+{
+ struct timespec ts;
+ struct timespec ts1;
+ struct timespec ts2;
+ int button;
+ int mask;
+ int i;
+
+ mask = act->flags & MOUSE_BUTTONS;
+#if 0
+ if (mask == 0)
+ return;
+#endif
+
+ clock_gettime(CLOCK_MONOTONIC_FAST, &ts1);
+ drift->current_ts = ts1;
+
+ /* double click threshold */
+ ts = tssubms(&ts1, bt->clickthreshold);
+ debug("ts: %jd %ld", (intmax_t)ts.tv_sec, ts.tv_nsec);
+
+ /* 3 button emulation timeout */
+ ts2 = tssubms(&ts1, e3b->button2timeout);
+
+ button = MOUSE_BUTTON1DOWN;
+ for (i = 0; (i < MOUSE_MAXBUTTON) && (mask != 0); ++i) {
+ if (mask & 1) {
+ if (act->button & button) {
+ /* the button is down */
+ debug(" : %jd %ld",
+ (intmax_t)bt->bstate[i].ts.tv_sec,
+ bt->bstate[i].ts.tv_nsec);
+ if (tscmp(&ts, &bt->bstate[i].ts, >)) {
+ bt->bstate[i].count = 1;
+ } else {
+ ++bt->bstate[i].count;
+ }
+ bt->bstate[i].ts = ts1;
+ } else {
+ /* the button is up */
+ bt->bstate[i].ts = ts1;
+ }
+ } else {
+ if (act->button & button) {
+ /* the button has been down */
+ if (tscmp(&ts2, &bt->bstate[i].ts, >)) {
+ bt->bstate[i].count = 1;
+ bt->bstate[i].ts = ts1;
+ act->flags |= button;
+ debug("button %d timeout", i + 1);
+ }
+ } else {
+ /* the button has been up */
+ }
+ }
+ button <<= 1;
+ mask >>= 1;
+ }
+}
+
+static bool
+r_timeout(struct e3bstate *e3b)
+{
+ struct timespec ts;
+ struct timespec ts1;
+
+ if (states[e3b->mouse_button_state].timeout)
+ return (true);
+ clock_gettime(CLOCK_MONOTONIC_FAST, &ts1);
+ ts = tssubms(&ts1, e3b->button2timeout);
+ return (tscmp(&ts, &e3b->mouse_button_state_ts, >));
+}
+
+static void
+r_move(mousestatus_t *act, struct accel *acc)
+{
+ struct mouse_info mouse;
+
+ bzero(&mouse, sizeof(mouse));
+ if (acc->is_exponential) {
+ expoacc(acc, act->dx, act->dy, act->dz,
+ &mouse.u.data.x, &mouse.u.data.y, &mouse.u.data.z);
+ } else {
+ linacc(acc, act->dx, act->dy, act->dz,
+ &mouse.u.data.x, &mouse.u.data.y, &mouse.u.data.z);
+ }
+ mouse.operation = MOUSE_MOTION_EVENT;
+ mouse.u.data.buttons = act->button;
+ if (debug < 2 && !paused)
+ ioctl(cfd, CONS_MOUSECTL, &mouse);
+}
+
+static void
+r_click(mousestatus_t *act, struct btstate *bt)
+{
+ struct mouse_info mouse;
+ int button;
+ int mask;
+ int i;
+
+ mask = act->flags & MOUSE_BUTTONS;
+ if (mask == 0)
+ return;
+
+ button = MOUSE_BUTTON1DOWN;
+ for (i = 0; (i < MOUSE_MAXBUTTON) && (mask != 0); ++i) {
+ if (mask & 1) {
+ debug("mstate[%d]->count:%d", i, bt->mstate[i]->count);
+ if (act->button & button) {
+ /* the button is down */
+ mouse.u.event.value = bt->mstate[i]->count;
+ } else {
+ /* the button is up */
+ mouse.u.event.value = 0;
+ }
+ mouse.operation = MOUSE_BUTTON_EVENT;
+ mouse.u.event.id = button;
+ if (debug < 2 && !paused)
+ ioctl(cfd, CONS_MOUSECTL, &mouse);
+ debug("button %d count %d", i + 1,
+ mouse.u.event.value);
+ }
+ button <<= 1;
+ mask >>= 1;
+ }
+}
+
+static enum gesture
+r_gestures(struct tpad *tp, int x0, int y0, u_int z, int w, int nfingers,
+ struct timespec *time, mousestatus_t *ms)
+{
+ struct tpstate *gest = &tp->gest;
+ const struct tpcaps *tphw = &tp->hw;
+ const struct tpinfo *tpinfo = &tp->info;
+ int tap_timeout = tpinfo->tap_timeout;
+
+ /*
+ * Check pressure to detect a real wanted action on the
+ * touchpad.
+ */
+ if (z >= tpinfo->min_pressure_hi ||
+ (gest->fingerdown && z >= tpinfo->min_pressure_lo)) {
+ /* XXX Verify values? */
+ bool two_finger_scroll = tpinfo->two_finger_scroll;
+ bool three_finger_drag = tpinfo->three_finger_drag;
+ int max_width = tpinfo->max_width;
+ u_int max_pressure = tpinfo->max_pressure;
+ int margin_top = tpinfo->margin_top;
+ int margin_right = tpinfo->margin_right;
+ int margin_bottom = tpinfo->margin_bottom;
+ int margin_left = tpinfo->margin_left;
+ int vscroll_hor_area = tpinfo->vscroll_hor_area * tphw->res_x;
+ int vscroll_ver_area = tpinfo->vscroll_ver_area * tphw->res_y;;
+
+ int max_x = tphw->max_x;
+ int max_y = tphw->max_y;
+ int min_x = tphw->min_x;
+ int min_y = tphw->min_y;
+
+ int dx, dy;
+ int start_x, start_y;
+ int tap_max_delta_x, tap_max_delta_y;
+ int prev_nfingers;
+
+ /* Palm detection. */
+ if (nfingers == 1 &&
+ ((tphw->cap_width && w > max_width) ||
+ (tphw->cap_pressure && z > max_pressure))) {
+ /*
+ * We consider the packet irrelevant for the current
+ * action when:
+ * - there is a single active touch
+ * - the width isn't comprised in:
+ * [0; max_width]
+ * - the pressure isn't comprised in:
+ * [min_pressure; max_pressure]
+ *
+ * Note that this doesn't terminate the current action.
+ */
+ debug("palm detected! (%d)", z);
+ return(GEST_IGNORE);
+ }
+
+ /*
+ * Limit the coordinates to the specified margins because
+ * this area isn't very reliable.
+ */
+ if (margin_left != 0 && x0 <= min_x + margin_left)
+ x0 = min_x + margin_left;
+ else if (margin_right != 0 && x0 >= max_x - margin_right)
+ x0 = max_x - margin_right;
+ if (margin_bottom != 0 && y0 <= min_y + margin_bottom)
+ y0 = min_y + margin_bottom;
+ else if (margin_top != 0 && y0 >= max_y - margin_top)
+ y0 = max_y - margin_top;
+
+ debug("packet: [%d, %d], %d, %d", x0, y0, z, w);
+
+ /*
+ * If the action is just beginning, init the structure and
+ * compute tap timeout.
+ */
+ if (!gest->fingerdown) {
+ debug("----");
+
+ /* Reset pressure peak. */
+ gest->zmax = 0;
+
+ /* Reset fingers count. */
+ gest->fingers_nb = 0;
+
+ /* Reset virtual scrolling state. */
+ gest->in_vscroll = 0;
+
+ /* Compute tap timeout. */
+ if (tap_timeout != 0)
+ gest->taptimeout = tsaddms(time, tap_timeout);
+ else
+ tsclr(&gest->taptimeout);
+
+ gest->fingerdown = true;
+
+ gest->start_x = x0;
+ gest->start_y = y0;
+ }
+
+ prev_nfingers = gest->prev_nfingers;
+
+ gest->prev_x = x0;
+ gest->prev_y = y0;
+ gest->prev_nfingers = nfingers;
+
+ start_x = gest->start_x;
+ start_y = gest->start_y;
+
+ /* Process ClickPad softbuttons */
+ if (tphw->is_clickpad && ms->button & MOUSE_BUTTON1DOWN) {
+ int y_ok, center_bt, center_x, right_bt, right_x;
+ y_ok = tpinfo->softbuttons_y < 0
+ ? start_y < min_y - tpinfo->softbuttons_y
+ : start_y > max_y - tpinfo->softbuttons_y;
+
+ center_bt = MOUSE_BUTTON2DOWN;
+ center_x = min_x + tpinfo->softbutton2_x;
+ right_bt = MOUSE_BUTTON3DOWN;
+ right_x = min_x + tpinfo->softbutton3_x;
+
+ if (center_x > 0 && right_x > 0 && center_x > right_x) {
+ center_bt = MOUSE_BUTTON3DOWN;
+ center_x = min_x + tpinfo->softbutton3_x;
+ right_bt = MOUSE_BUTTON2DOWN;
+ right_x = min_x + tpinfo->softbutton2_x;
+ }
+
+ if (right_x > 0 && start_x > right_x && y_ok)
+ ms->button = (ms->button &
+ ~MOUSE_BUTTON1DOWN) | right_bt;
+ else if (center_x > 0 && start_x > center_x && y_ok)
+ ms->button = (ms->button &
+ ~MOUSE_BUTTON1DOWN) | center_bt;
+ }
+
+ /* If in tap-hold or three fingers, add the recorded button. */
+ if (gest->in_taphold || (nfingers == 3 && three_finger_drag))
+ ms->button |= gest->tap_button;
+
+ /*
+ * For tap, we keep the maximum number of fingers and the
+ * pressure peak.
+ */
+ gest->fingers_nb = MAX(nfingers, gest->fingers_nb);
+ gest->zmax = MAX(z, gest->zmax);
+
+ dx = abs(x0 - start_x);
+ dy = abs(y0 - start_y);
+
+ /*
+ * A scrolling action must not conflict with a tap action.
+ * Here are the conditions to consider a scrolling action:
+ * - the action in a configurable area
+ * - one of the following:
+ * . the distance between the last packet and the
+ * first should be above a configurable minimum
+ * . tap timed out
+ */
+ if (!gest->in_taphold && !ms->button &&
+ (!gest->in_vscroll || two_finger_scroll) &&
+ (tscmp(time, &gest->taptimeout, >) ||
+ ((gest->fingers_nb == 2 || !two_finger_scroll) &&
+ (dx >= tpinfo->vscroll_min_delta * tphw->res_x ||
+ dy >= tpinfo->vscroll_min_delta * tphw->res_y)))) {
+ /*
+ * Handle two finger scrolling.
+ * Note that we don't rely on fingers_nb
+ * as that keeps the maximum number of fingers.
+ */
+ if (two_finger_scroll) {
+ if (nfingers == 2) {
+ gest->in_vscroll += dy ? 2 : 0;
+ gest->in_vscroll += dx ? 1 : 0;
+ }
+ } else {
+ /* Check for horizontal scrolling. */
+ if ((vscroll_hor_area > 0 &&
+ start_y <= min_y + vscroll_hor_area) ||
+ (vscroll_hor_area < 0 &&
+ start_y >= max_y + vscroll_hor_area))
+ gest->in_vscroll += 2;
+
+ /* Check for vertical scrolling. */
+ if ((vscroll_ver_area > 0 &&
+ start_x <= min_x + vscroll_ver_area) ||
+ (vscroll_ver_area < 0 &&
+ start_x >= max_x + vscroll_ver_area))
+ gest->in_vscroll += 1;
+ }
+ /* Avoid conflicts if area overlaps. */
+ if (gest->in_vscroll >= 3)
+ gest->in_vscroll = (dx > dy) ? 2 : 1;
+ }
+ /*
+ * Reset two finger scrolling when the number of fingers
+ * is different from two or any button is pressed.
+ */
+ if (two_finger_scroll && gest->in_vscroll != 0 &&
+ (nfingers != 2 || ms->button))
+ gest->in_vscroll = 0;
+
+ debug("virtual scrolling: %s "
+ "(direction=%d, dx=%d, dy=%d, fingers=%d)",
+ gest->in_vscroll != 0 ? "YES" : "NO",
+ gest->in_vscroll, dx, dy, gest->fingers_nb);
+
+ /* Workaround cursor jump on finger set changes */
+ if (prev_nfingers != nfingers)
+ return (GEST_IGNORE);
+
+ switch (gest->in_vscroll) {
+ case 1:
+ return (GEST_VSCROLL);
+ case 2:
+ return (GEST_HSCROLL);
+ default:
+ /* NO-OP */;
+ }
+
+ /* Max delta is disabled for multi-fingers tap. */
+ if (gest->fingers_nb == 1 &&
+ tscmp(time, &gest->taptimeout, <=)) {
+ tap_max_delta_x = tpinfo->tap_max_delta * tphw->res_x;
+ tap_max_delta_y = tpinfo->tap_max_delta * tphw->res_y;
+
+ debug("dx=%d, dy=%d, deltax=%d, deltay=%d",
+ dx, dy, tap_max_delta_x, tap_max_delta_y);
+ if (dx > tap_max_delta_x || dy > tap_max_delta_y) {
+ debug("not a tap");
+ tsclr(&gest->taptimeout);
+ }
+ }
+
+ if (tscmp(time, &gest->taptimeout, <=))
+ return (gest->fingers_nb > 1 ?
+ GEST_IGNORE : GEST_ACCUMULATE);
+ else
+ return (GEST_MOVE);
+ }
+
+ /*
+ * Handle a case when clickpad pressure drops before than
+ * button up event when surface is released after click.
+ * It interferes with softbuttons.
+ */
+ if (tphw->is_clickpad && tpinfo->softbuttons_y != 0)
+ ms->button &= ~MOUSE_BUTTON1DOWN;
+
+ gest->prev_nfingers = 0;
+
+ if (gest->fingerdown) {
+ /*
+ * An action is currently taking place but the pressure
+ * dropped under the minimum, putting an end to it.
+ */
+
+ gest->fingerdown = false;
+
+ /* Check for tap. */
+ debug("zmax=%d fingers=%d", gest->zmax, gest->fingers_nb);
+ if (!gest->in_vscroll && gest->zmax >= tpinfo->tap_threshold &&
+ tscmp(time, &gest->taptimeout, <=)) {
+ /*
+ * We have a tap if:
+ * - the maximum pressure went over tap_threshold
+ * - the action ended before tap_timeout
+ *
+ * To handle tap-hold, we must delay any button push to
+ * the next action.
+ */
+ if (gest->in_taphold) {
+ /*
+ * This is the second and last tap of a
+ * double tap action, not a tap-hold.
+ */
+ gest->in_taphold = false;
+
+ /*
+ * For double-tap to work:
+ * - no button press is emitted (to
+ * simulate a button release)
+ * - PSM_FLAGS_FINGERDOWN is set to
+ * force the next packet to emit a
+ * button press)
+ */
+ debug("button RELEASE: %d", gest->tap_button);
+ gest->fingerdown = true;
+
+ /* Schedule button press on next event */
+ gest->idletimeout = 0;
+ } else {
+ /*
+ * This is the first tap: we set the
+ * tap-hold state and notify the button
+ * down event.
+ */
+ gest->in_taphold = true;
+ gest->idletimeout = tpinfo->taphold_timeout;
+ gest->taptimeout = tsaddms(time, tap_timeout);
+
+ switch (gest->fingers_nb) {
+ case 3:
+ gest->tap_button =
+ MOUSE_BUTTON2DOWN;
+ break;
+ case 2:
+ gest->tap_button =
+ MOUSE_BUTTON3DOWN;
+ break;
+ default:
+ gest->tap_button =
+ MOUSE_BUTTON1DOWN;
+ }
+ debug("button PRESS: %d", gest->tap_button);
+ ms->button |= gest->tap_button;
+ }
+ } else {
+ /*
+ * Not enough pressure or timeout: reset
+ * tap-hold state.
+ */
+ if (gest->in_taphold) {
+ debug("button RELEASE: %d", gest->tap_button);
+ gest->in_taphold = false;
+ } else {
+ debug("not a tap-hold");
+ }
+ }
+ } else if (!gest->fingerdown && gest->in_taphold) {
+ /*
+ * For a tap-hold to work, the button must remain down at
+ * least until timeout (where the in_taphold flags will be
+ * cleared) or during the next action.
+ */
+ if (tscmp(time, &gest->taptimeout, <=)) {
+ ms->button |= gest->tap_button;
+ } else {
+ debug("button RELEASE: %d", gest->tap_button);
+ gest->in_taphold = false;
+ }
+ }
+
+ return (GEST_IGNORE);
+}
diff --git a/usr.sbin/moused/moused/moused.conf b/usr.sbin/moused/moused/moused.conf
new file mode 100644
index 000000000000..04970c820c7f
--- /dev/null
+++ b/usr.sbin/moused/moused/moused.conf
@@ -0,0 +1,43 @@
+[Default]
+MatchName=*
+
+MousedGrabDevice=0 # 1/0
+MousedIgnoreDevice=0 # 1/0
+
+MousedClickThreshold=500 # ms
+MousedEmulateThirdButton=0 # 1/0
+MousedEmulateThirdButtonTimeout=100 # ms
+MousedExponentialAccel=1.3 # float
+MousedExponentialOffset=2.0 # dots
+MousedLinearAccelX=1.0 # float
+MousedLinearAccelY=1.0 # float
+MousedLinearAccelZ=1.0 # float
+#MousedMapZAxis=0
+MousedVirtualScrollEnable=0 # 1/0
+MousedHorVirtualScrollEnable=0 # 1/0
+MousedVirtualScrollSpeed=2 # dots
+MousedVirtualScrollThreshold=3 # dots
+MousedWMode=0 # button num
+
+[Mouse drift termination]
+MatchDevType=mouse # mouse/touchpad
+MousedDriftTerminate=0 # 1/0
+MousedDriftDistance=4 # dots
+MousedDriftTime=500 # ms
+MousedDriftAfter=4000 # ms
+
+[Default touchpad gesture settings]
+MatchDevType=touchpad # mouse/touchpad
+MousedTwoFingerScroll=1 # 1/0
+MousedNaturalScroll=0 # 1/0
+MousedThreeFingerDrag=0 # 1/0
+MousedSoftButton2X=42 # pct
+MousedSoftButton3X=58 # pct
+MousedSoftButtonsY=25 # pct
+MousedTapTimeout=180 # ms
+#MousedTapPressureThreshold=20
+MousedTapMaxDelta=1.3 # mm
+MousedTapholdTimeout=300 # ms
+MousedVScrollMinDelta=1.25 # mm
+MousedVScrollHorArea=0.0 # mm
+MousedVScrollVerArea=-15.0 # mm
diff --git a/usr.sbin/moused/moused/moused.conf.5 b/usr.sbin/moused/moused/moused.conf.5
new file mode 100644
index 000000000000..bc62b5d00995
--- /dev/null
+++ b/usr.sbin/moused/moused/moused.conf.5
@@ -0,0 +1,422 @@
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.\" Copyright (c) 2025 Vladimir Kondratyev <wulf@FreeBSD.org>
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd May 19, 2025
+.Dt MOUSED.CONF 5
+.Os
+.Sh NAME
+.Nm moused.conf
+.Nd mouse daemon configuration file
+.Sh DESCRIPTION
+The
+.Nm
+file specifies how the
+.Xr moused 8
+(mouse daemon) should operate. It provides ability to adjust certain
+mice parameters on per-device basis.
+.Pp
+Configuration file format is derrived from
+.Xr libinput 1
+device quirk files.
+A file may contain multiple section headers ([some identifier])
+followed by one or more MatchFoo=Bar directives, followed by at least
+one of MousedFoo=bar or AttrFoo=bar directive.
+A configuration file must contain at least one section, each section
+must have at least one
+.Sq Match
+tag and at least one of either
+.Sq Attr
+or
+.Sq Moused .
+Section names are free-form and may contain spaces.
+.Ss List of currently available matches.
+.Bl -tag -width indent
+.It MatchName, MatchUniq
+Match on the NAME or UNIQ udev property on this device. These
+properties are typically derived from the device’s kernel name or uniq.
+These matches use
+.Fn fnmatch
+globs.
+.It MatchBus
+A lower-case bus name. Currently supported are usb, bluetooth, ps2,
+rmi, i2c, and spi.
+.It MatchVendor, MatchProduct, MatchVersion
+The hexadecmial 4-digit vendor ID, product ID or driver version as
+exported, without a 0x prefix.
+.It MatchDMIModalias, MatchDeviceTree
+An
+.Fn fnmatch
+glob for the DMI modalias or the DeviceTree compatible string.
+.It MatchDevType
+One of touchpad, mouse, pointingstick, keyboard, joystick, tablet,
+tablet-pad.
+Only touchpad and mouse types are suppported.
+.El
+.Ss List of currently available Moused tags.
+.Bl -tag -width indent
+.It MousedGrabDevice
+Only for evdev interface.
+Become the sole recipient of all incoming input events.
+This prevents other processes from getting input events on the device.
+.Pp
+Use
+.Fl g
+option alternatively.
+.It MousedIgnoreDevice
+Ignore given device.
+.It MousedClickThreshold
+Set double click speed as the maximum interval in msec between button clicks.
+Without this option, the default value of 500 msec will be assumed.
+This option will have effect only on the cut and paste operations
+in the text mode console.
+The user program which is reading mouse data
+via
+.Xr sysmouse 4
+will not be affected.
+.Pp
+Use
+.Fl C
+option alternatively.
+.It MousedEmulateThirdButton
+Emulate the third (middle) button for 2-button mice.
+It is emulated
+by pressing the left and right physical buttons simultaneously.
+.Pp
+Use
+.Fl 3
+option alternatively.
+.It MousedEmulateThirdButtonTimeout
+When the third button emulation is enabled
+(see above),
+the
+.Xr moused 8
+utility waits
+.Ar MousedEmulateThirdButtonTimeout
+msec at most before deciding whether two buttons are being pressed
+simultaneously.
+The default timeout is 100 msec.
+.Pp
+Use
+.Fl E
+option alternatively.
+.It MousedLinearAccelX
+.It MousedLinearAccelY
+.It MousedLinearAccelZ
+Accelerate or decelerate the mouse input.
+This is a linear acceleration only.
+Values less than 1.0 slow down movement, values greater than 1.0 speed it
+up.
+.Pp
+You can use the
+.Ar MousedLinearAccel
+and
+.Ar MousedExponentialAccel
+options at the same time to have the combined effect
+of linear and exponential acceleration.
+.Pp
+Use
+.Fl a
+option alternatively.
+.It MousedExponentialAccel
+.It MousedExponentialOffset
+Apply exponential (dynamic) acceleration to mouse movements:
+the faster you move the mouse, the more it will be accelerated.
+That means that small mouse movements are not accelerated,
+so they are still very accurate, while a faster movement will
+drive the pointer quickly across the screen.
+.Pp
+The
+.Ar MousedExponentialAccel
+value specifies the exponent, which is basically
+the amount of acceleration. Useful values are in the
+range 1.1 to 2.0, but it depends on your mouse hardware
+and your personal preference. A value of 1.0 means no
+exponential acceleration. A value of 2.0 means squared
+acceleration (i.e. if you move the mouse twice as fast,
+the pointer will move four times as fast on the screen).
+Values beyond 2.0 are possible but not recommended.
+A good value to start is probably 1.5.
+.Pp
+The optional
+.Ar MousedExponentialOffset
+value specifies the distance at which the acceleration
+begins. The default is 1.0, which means that the
+acceleration is applied to movements larger than one unit.
+If you specify a larger value, it takes more speed for
+the acceleration to kick in, i.e. the speed range for
+small and accurate movements is wider.
+Usually the default should be sufficient, but if you're
+not satisfied with the behaviour, try a value of 2.0.
+.Pp
+Note that the
+.Fl A
+option interacts badly with the X server's own acceleration,
+which doesn't work very well anyway. Therefore it is
+recommended to switch it off if necessary:
+.Dq xset m 1 .
+.Pp
+Use
+.Fl A
+option alternatively.
+.It MousedMapZAxis
+Map Z axis (roller/wheel) movement to another axis or to virtual buttons.
+Does not supported yet.
+Use
+.Fl z
+option instead.
+.It MousedVirtualScrollEnable
+Enable
+.Dq Virtual Scrolling .
+With this option set, holding the middle mouse
+button down will cause motion to be interpreted as scrolling.
+Use the
+.Ar MousedVirtualScrollThreshold
+option to set the distance the mouse must move before the scrolling mode is
+activated and the
+.Ar MousedVirtualScrollSpeed
+option to set the scrolling speed.
+.Pp
+Use
+.Fl V
+option alternatively.
+.It MousedHorVirtualScrollEnable
+Enable
+.Dq Horizontal Virtual Scrolling .
+With this option set, holding the middle mouse
+button down will cause motion to be interpreted as
+horizontal scrolling.
+Use the
+.Ar MousedVirtualScrollThreshold
+option to set the distance the mouse must move before the scrolling mode is
+activated and the
+.Ar MousedVirtualScrollSpeed
+option to set the scrolling speed.
+This option may be used with or without the
+.Ar MousedVirtualScrollEnable
+option.
+.Pp
+Use
+.Fl H
+option alternatively.
+.It MousedVirtualScrollSpeed= Ar distance
+When
+.Dq Virtual Scrolling
+is enabled, the
+.Ar MousedVirtualScrollSpeed
+option can be used to set the
+.Ar distance
+(in pixels) that the mouse must move before a scroll event
+is generated.
+This effectively controls the scrolling speed.
+The default
+.Ar distance
+is 2 pixels.
+.Pp
+Use
+.Fl L
+option alternatively.
+.It MousedVirtualScrollThreshold= Ar distance
+When
+.Dq Virtual Scrolling
+is enabled, the
+.Ar MousedVirtualScrollThreshold
+option can be used to set the
+.Ar distance
+(in pixels) that the mouse must move before the scrolling
+mode is activated.
+The default
+.Ar distance
+is 3 pixels.
+.Pp
+Use
+.Fl U
+option alternatively.
+.It MousedWMode= Ar N
+Make the physical button
+.Ar N
+act as the wheel mode button.
+While this button is pressed, X and Y axis movement is reported to be zero
+and the Y axis movement is mapped to Z axis.
+You may further map the Z axis movement to virtual buttons by the
+.Ar MousedMapZAxis
+tag.
+.Pp
+Use
+.Fl w
+option alternatively.
+.El
+.Ss List of currently available Moused mice specific tags.
+.Bl -tag -width indent
+.It MousedDriftTerminate
+.It MousedDriftDistance
+.It MousedDriftTime
+.It MousedDriftAfter
+Terminate drift.
+Use this option if mouse pointer slowly wanders when mouse is not moved.
+Movements up to
+.Ar MousedDriftDistance
+(for example 4) pixels (X+Y) in
+.Ar MousedDriftTime
+msec (default 500) are ignored, except during
+.Ar MousedDriftAfter
+msec (default 4000) since last real mouse movement.
+.Pp
+Use
+.Fl T
+option alternatively.
+.El
+.Ss List of currently available Moused touchpad specific tags.
+.Bl -tag -width indent
+.It MousedTwoFingerScroll
+Enable two finger scrolling.
+.It MousedNaturalScroll
+Enable natural scrolling.
+.It MousedThreeFingerDrag
+Enable dragging with three fingers.
+.It MousedSoftButton2X
+Horisontal position of 2-nd softbutton left edge in percents.
+(0-disable)
+.It MousedSoftButton3X
+Horisontal position of 3-rd softbutton left edge in percents.
+(0-disable)
+.It MousedSoftButtonsY
+Vertical size of softbuttons area in percents.
+Use negative values to place softbutton area at top of touchpad.
+.It MousedTapTimeout
+Tap timeout in milliseconds
+.It MousedTapPressureThreshold
+Pressure threshold to detect tap.
+.It MousedTapMaxDelta
+Length of finger movement above which a tap is ignored measured in mm.
+.It MousedTapholdTimeout
+Maximum elapsed time between two taps to consider a tap-hold action.
+.It MousedVScrollMinDelta
+Minimum movement to consider virtual scrolling.
+.It MousedVScrollHorArea
+ Area reserved for horizontal virtual scrolling in mm.
+.It MousedVScrollVerArea
+Area reserved for vertical virtual scrolling in mm.
+.El
+.Ss List of currently available libinput-compatible tags.
+.Bl -tag -width indent
+.It AttrSizeHint
+Hints at the width x height of the device in mm.
+.It AttrTouchSizeRange
+Not supported yet.
+.It AttrPalmSizeThreshold
+Maximum finger width to detect palm in mm.
+.It AttrLidSwitchReliability
+Not supported yet.
+.It AttrKeyboardIntegration
+Not supported yet.
+.It AttrPointingStickIntegration
+Not supported yet.
+.It AttrTPKComboLayout
+Not supported yet.
+.It AttrPressureRange= Ar N : Ar M
+Specifies the touch pressure required to trigger a press
+.Ar N
+and to trigger a release
+.Ar M .
+.It AttrPalmPressureThreshold
+Maximum pressure to detect palm.
+.It AttrResolutionHint
+Hints at the resolution of the x/y axis in units/mm.
+.It AttrTrackpointMultiplier
+Not supported yet.
+.It AttrThumbPressureThreshold
+Not supported yet.
+.It AttrUseVelocityAveraging
+Not supported yet.
+.It AttrTabletSmoothing
+Not supported yet.
+.It AttrThumbSizeThreshold
+Not supported yet.
+.It AttrMscTimestamp
+Not supported yet.
+.It AttrEventCode
+Enables or disables the evdev event type/code tuples on the device.
+The prefix for each entry is either
+.Sq +
+(enable) or
+.Sq -
+(disable).
+Entries may be a named event type, or a named event code, or a named
+event type with a hexadecimal event code, separated by a single colon.
+.It AttrInputProp
+Enables or disables the evdev input property on the device.
+The prefix for each entry is either
+,Sq +
+(enable) or
+.Sq -
+(disable).
+Entries may be a named input property or the hexadecimal value of that
+property.
+.El
+.Pp
+All
+.Xr libinput 1
+.Sq Model
+quirks are currently ignored.
+.Sh FILES
+.Bl -tag -width /usr/local/etc/moused.conf -compact
+.It Pa /usr/local/etc/moused.conf
+The file
+.Nm
+resides in
+.Pa /usr/local/etc .
+.It Pa /usr/local/share/moused/*.quirks
+Predefined quirks processed before
+.Nm .
+.El
+.Sh EXAMPLES
+Set touch pressure and palm detection thesholds for PS/2 Synaptics
+touchpad:
+.Bd -literal -offset indent
+[SynPS/2 Synaptics TouchPad]
+MatchDevType=touchpad
+MatchName=SynPS/2 Synaptics TouchPad
+AttrPressureRange=35:30
+AttrPalmPressureThreshold=220
+.Ed
+.Sh SEE ALSO
+.Xr moused 8
+.Pp
+.Xr libinput 1
+device quirk format:
+.Lk https://wayland.freedesktop.org/libinput/doc/latest/device-quirks.html
+.Sh HISTORY
+The
+.Nm
+file format first appeared in
+.Fx 15.0 .
+.Sh AUTHORS
+This manual page was written by
+.An Vladimir Kondratyev Aq Mt wulf@FreeBSD.org
+based on
+. Xr moused 8
+manual page and
+.Xr libinput 1
+documentation.
diff --git a/usr.sbin/moused/moused/quirks.c b/usr.sbin/moused/moused/quirks.c
new file mode 100644
index 000000000000..3b87b34419e9
--- /dev/null
+++ b/usr.sbin/moused/moused/quirks.c
@@ -0,0 +1,2033 @@
+/*
+ * Copyright © 2018 Red Hat, Inc.
+ *
+ * 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.
+ */
+
+/* This has the hallmarks of a library to make it re-usable from the tests
+ * and from the list-quirks tool. It doesn't have all of the features from a
+ * library you'd expect though
+ */
+
+#include <sys/types.h>
+#include <dev/evdev/input.h>
+
+#undef NDEBUG /* You don't get to disable asserts here */
+#include <assert.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fnmatch.h>
+#include <kenv.h>
+#include <libgen.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "quirks.h"
+#include "util.h"
+#include "util-list.h"
+
+
+/* Custom logging so we can have detailed output for the tool but minimal
+ * logging for moused itself. */
+#define qlog_debug(ctx_, ...) quirk_log_msg((ctx_), QLOG_NOISE, __VA_ARGS__)
+#define qlog_info(ctx_, ...) quirk_log_msg((ctx_), QLOG_INFO, __VA_ARGS__)
+#define qlog_error(ctx_, ...) quirk_log_msg((ctx_), QLOG_ERROR, __VA_ARGS__)
+#define qlog_parser(ctx_, ...) quirk_log_msg((ctx_), QLOG_PARSER_ERROR, __VA_ARGS__)
+
+enum property_type {
+ PT_UINT,
+ PT_INT,
+ PT_STRING,
+ PT_BOOL,
+ PT_DIMENSION,
+ PT_RANGE,
+ PT_DOUBLE,
+ PT_TUPLES,
+ PT_UINT_ARRAY,
+};
+
+struct quirk_array {
+ union {
+ uint32_t u[32];
+ } data;
+ size_t nelements;
+};
+
+/**
+ * Generic value holder for the property types we support. The type
+ * identifies which value in the union is defined and we expect callers to
+ * already know which type yields which value.
+ */
+struct property {
+ size_t refcount;
+ struct list link; /* struct sections.properties */
+
+ enum quirk id;
+ enum property_type type;
+ union {
+ bool b;
+ uint32_t u;
+ int32_t i;
+ char *s;
+ double d;
+ struct quirk_dimensions dim;
+ struct quirk_range range;
+ struct quirk_tuples tuples;
+ struct quirk_array array;
+ } value;
+};
+
+enum match_flags {
+ M_NAME = bit(0),
+ M_BUS = bit(1),
+ M_VID = bit(2),
+ M_PID = bit(3),
+ M_DMI = bit(4),
+ M_UDEV_TYPE = bit(5),
+ M_DT = bit(6),
+ M_VERSION = bit(7),
+ M_UNIQ = bit(8),
+
+ M_LAST = M_UNIQ,
+};
+
+enum bustype {
+ BT_UNKNOWN,
+ BT_USB,
+ BT_BLUETOOTH,
+ BT_PS2,
+ BT_RMI,
+ BT_I2C,
+ BT_SPI,
+};
+
+enum udev_type {
+ UDEV_MOUSE = bit(1),
+ UDEV_POINTINGSTICK = bit(2),
+ UDEV_TOUCHPAD = bit(3),
+ UDEV_TABLET = bit(4),
+ UDEV_TABLET_PAD = bit(5),
+ UDEV_JOYSTICK = bit(6),
+ UDEV_KEYBOARD = bit(7),
+};
+
+/**
+ * Contains the combined set of matches for one section or the values for
+ * one device.
+ *
+ * bits defines which fields are set, the rest is zero.
+ */
+struct match {
+ uint32_t bits;
+
+ char *name;
+ char *uniq;
+ enum bustype bus;
+ uint32_t vendor;
+ uint32_t product[64]; /* zero-terminated */
+ uint32_t version;
+
+ char *dmi; /* dmi modalias with preceding "dmi:" */
+
+ /* We can have more than one type set, so this is a bitfield */
+ uint32_t udev_type;
+
+ char *dt; /* device tree compatible (first) string */
+};
+
+/**
+ * Represents one section in the .quirks file.
+ */
+struct section {
+ struct list link;
+
+ bool has_match; /* to check for empty sections */
+ bool has_property; /* to check for empty sections */
+
+ char *name; /* the [Section Name] */
+ struct match match;
+ struct list properties;
+};
+
+/**
+ * The struct returned to the caller. It contains the
+ * properties for a given device.
+ */
+struct quirks {
+ size_t refcount;
+ struct list link; /* struct quirks_context.quirks */
+
+ /* These are not ref'd, just a collection of pointers */
+ struct property **properties;
+ size_t nproperties;
+
+ /* Special properties for AttrEventCode and AttrInputCode, these are
+ * owned by us, not the section */
+ struct list floating_properties;
+};
+
+/**
+ * Quirk matching context, initialized once with quirks_init_subsystem()
+ */
+struct quirks_context {
+ size_t refcount;
+
+ moused_log_handler *log_handler;
+ enum quirks_log_type log_type;
+
+ char *dmi;
+ char *dt;
+
+ struct list sections;
+
+ /* list of quirks handed to moused, just for bookkeeping */
+ struct list quirks;
+};
+
+MOUSED_ATTRIBUTE_PRINTF(3, 0)
+static inline void
+quirk_log_msg_va(struct quirks_context *ctx,
+ enum quirks_log_priorities priority,
+ const char *format,
+ va_list args)
+{
+ switch (priority) {
+ /* We don't use this if we're logging through syslog */
+ default:
+ case QLOG_NOISE:
+ case QLOG_PARSER_ERROR:
+ if (ctx->log_type == QLOG_MOUSED_LOGGING)
+ return;
+ break;
+ case QLOG_DEBUG: /* These map straight to syslog priorities */
+ case QLOG_INFO:
+ case QLOG_ERROR:
+ break;
+ }
+
+ ctx->log_handler(priority,
+ 0,
+ format,
+ args);
+}
+
+MOUSED_ATTRIBUTE_PRINTF(3, 4)
+static inline void
+quirk_log_msg(struct quirks_context *ctx,
+ enum quirks_log_priorities priority,
+ const char *format,
+ ...)
+{
+ va_list args;
+
+ va_start(args, format);
+ quirk_log_msg_va(ctx, priority, format, args);
+ va_end(args);
+
+}
+
+const char *
+quirk_get_name(enum quirk q)
+{
+ switch(q) {
+ case QUIRK_MODEL_ALPS_SERIAL_TOUCHPAD: return "ModelALPSSerialTouchpad";
+ case QUIRK_MODEL_APPLE_TOUCHPAD: return "ModelAppleTouchpad";
+ case QUIRK_MODEL_APPLE_TOUCHPAD_ONEBUTTON: return "ModelAppleTouchpadOneButton";
+ case QUIRK_MODEL_BOUNCING_KEYS: return "ModelBouncingKeys";
+ case QUIRK_MODEL_CHROMEBOOK: return "ModelChromebook";
+ case QUIRK_MODEL_CLEVO_W740SU: return "ModelClevoW740SU";
+ case QUIRK_MODEL_DELL_CANVAS_TOTEM: return "ModelDellCanvasTotem";
+ case QUIRK_MODEL_HP_PAVILION_DM4_TOUCHPAD: return "ModelHPPavilionDM4Touchpad";
+ case QUIRK_MODEL_HP_ZBOOK_STUDIO_G3: return "ModelHPZBookStudioG3";
+ case QUIRK_MODEL_INVERT_HORIZONTAL_SCROLLING: return "ModelInvertHorizontalScrolling";
+ case QUIRK_MODEL_LENOVO_SCROLLPOINT: return "ModelLenovoScrollPoint";
+ case QUIRK_MODEL_LENOVO_T450_TOUCHPAD: return "ModelLenovoT450Touchpad";
+ case QUIRK_MODEL_LENOVO_X1GEN6_TOUCHPAD: return "ModelLenovoX1Gen6Touchpad";
+ case QUIRK_MODEL_LENOVO_X230: return "ModelLenovoX230";
+ case QUIRK_MODEL_SYNAPTICS_SERIAL_TOUCHPAD: return "ModelSynapticsSerialTouchpad";
+ case QUIRK_MODEL_SYSTEM76_BONOBO: return "ModelSystem76Bonobo";
+ case QUIRK_MODEL_SYSTEM76_GALAGO: return "ModelSystem76Galago";
+ case QUIRK_MODEL_SYSTEM76_KUDU: return "ModelSystem76Kudu";
+ case QUIRK_MODEL_TABLET_MODE_NO_SUSPEND: return "ModelTabletModeNoSuspend";
+ case QUIRK_MODEL_TABLET_MODE_SWITCH_UNRELIABLE: return "ModelTabletModeSwitchUnreliable";
+ case QUIRK_MODEL_TOUCHPAD_VISIBLE_MARKER: return "ModelTouchpadVisibleMarker";
+ case QUIRK_MODEL_TOUCHPAD_PHANTOM_CLICKS: return "ModelTouchpadPhantomClicks";
+ case QUIRK_MODEL_TRACKBALL: return "ModelTrackball";
+ case QUIRK_MODEL_WACOM_TOUCHPAD: return "ModelWacomTouchpad";
+ case QUIRK_MODEL_PRESSURE_PAD: return "ModelPressurePad";
+
+ case QUIRK_ATTR_SIZE_HINT: return "AttrSizeHint";
+ case QUIRK_ATTR_TOUCH_SIZE_RANGE: return "AttrTouchSizeRange";
+ case QUIRK_ATTR_PALM_SIZE_THRESHOLD: return "AttrPalmSizeThreshold";
+ case QUIRK_ATTR_LID_SWITCH_RELIABILITY: return "AttrLidSwitchReliability";
+ case QUIRK_ATTR_KEYBOARD_INTEGRATION: return "AttrKeyboardIntegration";
+ case QUIRK_ATTR_TRACKPOINT_INTEGRATION: return "AttrPointingStickIntegration";
+ case QUIRK_ATTR_TPKBCOMBO_LAYOUT: return "AttrTPKComboLayout";
+ case QUIRK_ATTR_PRESSURE_RANGE: return "AttrPressureRange";
+ case QUIRK_ATTR_PALM_PRESSURE_THRESHOLD: return "AttrPalmPressureThreshold";
+ case QUIRK_ATTR_RESOLUTION_HINT: return "AttrResolutionHint";
+ case QUIRK_ATTR_TRACKPOINT_MULTIPLIER: return "AttrTrackpointMultiplier";
+ case QUIRK_ATTR_THUMB_PRESSURE_THRESHOLD: return "AttrThumbPressureThreshold";
+ case QUIRK_ATTR_USE_VELOCITY_AVERAGING: return "AttrUseVelocityAveraging";
+ case QUIRK_ATTR_TABLET_SMOOTHING: return "AttrTabletSmoothing";
+ case QUIRK_ATTR_THUMB_SIZE_THRESHOLD: return "AttrThumbSizeThreshold";
+ case QUIRK_ATTR_MSC_TIMESTAMP: return "AttrMscTimestamp";
+ case QUIRK_ATTR_EVENT_CODE: return "AttrEventCode";
+ case QUIRK_ATTR_INPUT_PROP: return "AttrInputProp";
+
+ case MOUSED_GRAB_DEVICE: return "MousedGrabDevice";
+ case MOUSED_IGNORE_DEVICE: return "MousedIgnoreDevice";
+
+ case MOUSED_CLICK_THRESHOLD: return "MousedClickThreshold";
+ case MOUSED_DRIFT_TERMINATE: return "MousedDriftTerminate";
+ case MOUSED_DRIFT_DISTANCE: return "MousedDriftDistance";
+ case MOUSED_DRIFT_TIME: return "MousedDriftTime";
+ case MOUSED_DRIFT_AFTER: return "MousedDriftAfter";
+ case MOUSED_EMULATE_THIRD_BUTTON: return "MousedEmulateThirdButton";
+ case MOUSED_EMULATE_THIRD_BUTTON_TIMEOUT: return "MousedEmulateThirdButtonTimeout";
+ case MOUSED_EXPONENTIAL_ACCEL: return "MousedExponentialAccel";
+ case MOUSED_EXPONENTIAL_OFFSET: return "MousedExponentialOffset";
+ case MOUSED_LINEAR_ACCEL_X: return "MousedLinearAccelX";
+ case MOUSED_LINEAR_ACCEL_Y: return "MousedLinearAccelY";
+ case MOUSED_LINEAR_ACCEL_Z: return "MousedLinearAccelZ";
+ case MOUSED_MAP_Z_AXIS: return "MousedMapZAxis";
+ case MOUSED_VIRTUAL_SCROLL_ENABLE: return "MousedVirtualScrollEnable";
+ case MOUSED_HOR_VIRTUAL_SCROLL_ENABLE: return "MousedHorVirtualScrollEnable";
+ case MOUSED_VIRTUAL_SCROLL_SPEED: return "MousedVirtualScrollSpeed";
+ case MOUSED_VIRTUAL_SCROLL_THRESHOLD: return "MousedVirtualScrollThreshold";
+ case MOUSED_WMODE: return "MousedWMode";
+
+ case MOUSED_TWO_FINGER_SCROLL: return "MousedTwoFingerScroll";
+ case MOUSED_NATURAL_SCROLL: return "MousedNaturalScroll";
+ case MOUSED_THREE_FINGER_DRAG: return "MousedThreeFingerDrag";
+ case MOUSED_SOFTBUTTON2_X: return "MousedSoftButton2X";
+ case MOUSED_SOFTBUTTON3_X: return "MousedSoftButton3X";
+ case MOUSED_SOFTBUTTONS_Y: return "MousedSoftButtonsY";
+ case MOUSED_TAP_TIMEOUT: return "MousedTapTimeout";
+ case MOUSED_TAP_PRESSURE_THRESHOLD: return "MousedTapPressureThreshold";
+ case MOUSED_TAP_MAX_DELTA: return "MousedTapMaxDelta";
+ case MOUSED_TAPHOLD_TIMEOUT: return "MousedTapholdTimeout";
+ case MOUSED_VSCROLL_MIN_DELTA: return "MousedVScrollMinDelta";
+ case MOUSED_VSCROLL_HOR_AREA: return "MousedVScrollHorArea";
+ case MOUSED_VSCROLL_VER_AREA: return "MousedVScrollVerArea";
+
+
+ default:
+ abort();
+ }
+}
+
+static inline const char *
+matchflagname(enum match_flags f)
+{
+ switch(f) {
+ case M_NAME: return "MatchName"; break;
+ case M_BUS: return "MatchBus"; break;
+ case M_VID: return "MatchVendor"; break;
+ case M_PID: return "MatchProduct"; break;
+ case M_VERSION: return "MatchVersion"; break;
+ case M_DMI: return "MatchDMIModalias"; break;
+ case M_UDEV_TYPE: return "MatchDevType"; break;
+ case M_DT: return "MatchDeviceTree"; break;
+ case M_UNIQ: return "MatchUniq"; break;
+ default:
+ abort();
+ }
+}
+
+static inline struct property *
+property_new(void)
+{
+ struct property *p;
+
+ p = zalloc(sizeof *p);
+ p->refcount = 1;
+ list_init(&p->link);
+
+ return p;
+}
+
+static inline struct property *
+property_ref(struct property *p)
+{
+ assert(p->refcount > 0);
+ p->refcount++;
+ return p;
+}
+
+static inline struct property *
+property_unref(struct property *p)
+{
+ /* Note: we don't cleanup here, that is a separate call so we
+ can abort if we haven't cleaned up correctly. */
+ assert(p->refcount > 0);
+ p->refcount--;
+
+ return NULL;
+}
+
+/* Separate call so we can verify that the caller unrefs the property
+ * before shutting down the subsystem.
+ */
+static inline void
+property_cleanup(struct property *p)
+{
+ /* If we get here, the quirks must've been removed already */
+ property_unref(p);
+ assert(p->refcount == 0);
+
+ list_remove(&p->link);
+ if (p->type == PT_STRING)
+ free(p->value.s);
+ free(p);
+}
+
+/**
+ * Return the system DMI info in modalias format.
+ */
+static inline char *
+init_dmi(void)
+{
+#define LEN (KENV_MVALLEN + 1)
+ char *modalias;
+ char bios_vendor[LEN], bios_version[LEN], bios_date[LEN];
+ char sys_vendor[LEN], product_name[LEN], product_version[LEN];
+ char board_vendor[LEN], board_name[LEN], board_version[LEN];
+ char chassis_vendor[LEN], chassis_type[LEN], chassis_version[LEN];
+ int chassis_type_num = 0x2;
+
+ kenv(KENV_GET, "smbios.bios.vendor", bios_vendor, LEN);
+ kenv(KENV_GET, "smbios.bios.version", bios_version, LEN);
+ kenv(KENV_GET, "smbios.bios.reldate", bios_date, LEN);
+ kenv(KENV_GET, "smbios.system.maker", sys_vendor, LEN);
+ kenv(KENV_GET, "smbios.system.product", product_name, LEN);
+ kenv(KENV_GET, "smbios.system.version", product_version, LEN);
+ kenv(KENV_GET, "smbios.planar.maker", board_vendor, LEN);
+ kenv(KENV_GET, "smbios.planar.product", board_name, LEN);
+ kenv(KENV_GET, "smbios.planar.version", board_version, LEN);
+ kenv(KENV_GET, "smbios.chassis.vendor", chassis_vendor, LEN);
+ kenv(KENV_GET, "smbios.chassis.type", chassis_type, LEN);
+ kenv(KENV_GET, "smbios.chassis.version", chassis_version, LEN);
+#undef LEN
+
+ if (strcmp(chassis_type, "Desktop") == 0)
+ chassis_type_num = 0x3;
+ else if (strcmp(chassis_type, "Portable") == 0)
+ chassis_type_num = 0x8;
+ else if (strcmp(chassis_type, "Laptop") == 0)
+ chassis_type_num = 0x9;
+ else if (strcmp(chassis_type, "Notebook") == 0)
+ chassis_type_num = 0xA;
+ else if (strcmp(chassis_type, "Tablet") == 0)
+ chassis_type_num = 0x1E;
+ else if (strcmp(chassis_type, "Convertible") == 0)
+ chassis_type_num = 0x1F;
+ else if (strcmp(chassis_type, "Detachable") == 0)
+ chassis_type_num = 0x20;
+
+ xasprintf(&modalias,
+ "dmi:bvn%s:bvr%s:bd%s:svn%s:pn%s:pvr%s:rvn%s:rn%s:rvr%s:cvn%s:ct%d:cvr%s:",
+ bios_vendor, bios_version, bios_date, sys_vendor, product_name,
+ product_version, board_vendor, board_name, board_version, chassis_vendor,
+ chassis_type_num, chassis_version);
+
+ return modalias;
+}
+
+/**
+ * Return the dt compatible string
+ */
+static inline char *
+init_dt(void)
+{
+ char compatible[1024];
+ char *copy = NULL;
+ const char *syspath = "/sys/firmware/devicetree/base/compatible";
+ FILE *fp;
+
+ if (getenv("LIBINPUT_RUNNING_TEST_SUITE"))
+ return safe_strdup("");
+
+ fp = fopen(syspath, "r");
+ if (!fp)
+ return NULL;
+
+ /* devicetree/base/compatible has multiple null-terminated entries
+ but we only care about the first one here, so strdup is enough */
+ if (fgets(compatible, sizeof(compatible), fp)) {
+ copy = safe_strdup(compatible);
+ }
+
+ fclose(fp);
+
+ return copy;
+}
+
+static inline struct section *
+section_new(const char *path, const char *name)
+{
+ struct section *s = zalloc(sizeof(*s));
+
+ char *path_dup = safe_strdup(path);
+ xasprintf(&s->name, "%s (%s)", name, basename(path_dup));
+ free(path_dup);
+ list_init(&s->link);
+ list_init(&s->properties);
+
+ return s;
+}
+
+static inline void
+section_destroy(struct section *s)
+{
+ struct property *p;
+
+ free(s->name);
+ free(s->match.name);
+ free(s->match.uniq);
+ free(s->match.dmi);
+ free(s->match.dt);
+
+ list_for_each_safe(p, &s->properties, link)
+ property_cleanup(p);
+
+ assert(list_empty(&s->properties));
+
+ list_remove(&s->link);
+ free(s);
+}
+
+static inline bool
+parse_hex(const char *value, unsigned int *parsed)
+{
+ return strstartswith(value, "0x") &&
+ safe_atou_base(value, parsed, 16) &&
+ strspn(value, "0123456789xABCDEF") == strlen(value) &&
+ *parsed <= 0xFFFF;
+}
+
+static int
+strv_parse_hex(const char *str, size_t index, void *data)
+{
+ unsigned int *product = data;
+
+ return !parse_hex(str, &product[index]); /* 0 for success */
+}
+
+/**
+ * Parse a MatchFooBar=banana line.
+ *
+ * @param section The section struct to be filled in
+ * @param key The MatchFooBar part of the line
+ * @param value The banana part of the line.
+ *
+ * @return true on success, false otherwise.
+ */
+static bool
+parse_match(struct quirks_context *ctx,
+ struct section *s,
+ const char *key,
+ const char *value)
+{
+ int rc = false;
+
+#define check_set_bit(s_, bit_) { \
+ if ((s_)->match.bits & (bit_)) goto out; \
+ (s_)->match.bits |= (bit_); \
+ }
+
+ assert(strlen(value) >= 1);
+
+ if (streq(key, "MatchName")) {
+ check_set_bit(s, M_NAME);
+ s->match.name = safe_strdup(value);
+ } else if (streq(key, "MatchUniq")) {
+ check_set_bit(s, M_UNIQ);
+ s->match.uniq = safe_strdup(value);
+ } else if (streq(key, "MatchBus")) {
+ check_set_bit(s, M_BUS);
+ if (streq(value, "usb"))
+ s->match.bus = BT_USB;
+ else if (streq(value, "bluetooth"))
+ s->match.bus = BT_BLUETOOTH;
+ else if (streq(value, "ps2"))
+ s->match.bus = BT_PS2;
+ else if (streq(value, "rmi"))
+ s->match.bus = BT_RMI;
+ else if (streq(value, "i2c"))
+ s->match.bus = BT_I2C;
+ else if (streq(value, "spi"))
+ s->match.bus = BT_SPI;
+ else
+ goto out;
+ } else if (streq(key, "MatchVendor")) {
+ unsigned int vendor;
+
+ check_set_bit(s, M_VID);
+ if (!parse_hex(value, &vendor))
+ goto out;
+
+ s->match.vendor = vendor;
+ } else if (streq(key, "MatchProduct")) {
+ unsigned int product[ARRAY_LENGTH(s->match.product)] = {0};
+ const size_t max = ARRAY_LENGTH(s->match.product) - 1;
+
+ size_t nelems = 0;
+ char **strs = strv_from_string(value, ";", &nelems);
+ int rc = strv_for_each_n((const char**)strs, max, strv_parse_hex, product);
+ strv_free(strs);
+ if (rc != 0)
+ goto out;
+
+ check_set_bit(s, M_PID);
+ memcpy(s->match.product, product, sizeof(product));
+ } else if (streq(key, "MatchVersion")) {
+ unsigned int version;
+
+ check_set_bit(s, M_VERSION);
+ if (!parse_hex(value, &version))
+ goto out;
+
+ s->match.version = version;
+ } else if (streq(key, "MatchDMIModalias")) {
+ check_set_bit(s, M_DMI);
+ if (!strstartswith(value, "dmi:")) {
+ qlog_parser(ctx,
+ "%s: MatchDMIModalias must start with 'dmi:'\n",
+ s->name);
+ goto out;
+ }
+ s->match.dmi = safe_strdup(value);
+ } else if (streq(key, "MatchUdevType") || streq(key, "MatchDevType")) {
+ check_set_bit(s, M_UDEV_TYPE);
+ if (streq(value, "touchpad"))
+ s->match.udev_type = UDEV_TOUCHPAD;
+ else if (streq(value, "mouse"))
+ s->match.udev_type = UDEV_MOUSE;
+ else if (streq(value, "pointingstick"))
+ s->match.udev_type = UDEV_POINTINGSTICK;
+ else if (streq(value, "keyboard"))
+ s->match.udev_type = UDEV_KEYBOARD;
+ else if (streq(value, "joystick"))
+ s->match.udev_type = UDEV_JOYSTICK;
+ else if (streq(value, "tablet"))
+ s->match.udev_type = UDEV_TABLET;
+ else if (streq(value, "tablet-pad"))
+ s->match.udev_type = UDEV_TABLET_PAD;
+ else
+ goto out;
+ } else if (streq(key, "MatchDeviceTree")) {
+ check_set_bit(s, M_DT);
+ s->match.dt = safe_strdup(value);
+ } else {
+ qlog_error(ctx, "Unknown match key '%s'\n", key);
+ goto out;
+ }
+
+#undef check_set_bit
+ s->has_match = true;
+ rc = true;
+out:
+ return rc;
+}
+
+/**
+ * Parse a ModelFooBar=1 line.
+ *
+ * @param section The section struct to be filled in
+ * @param key The ModelFooBar part of the line
+ * @param value The value after the =, must be 1 or 0.
+ *
+ * @return true on success, false otherwise.
+ */
+static bool
+parse_model(struct quirks_context *ctx,
+ struct section *s,
+ const char *key,
+ const char *value)
+{
+ bool b;
+ enum quirk q = QUIRK_MODEL_ALPS_SERIAL_TOUCHPAD;
+
+ assert(strstartswith(key, "Model"));
+
+ if (!parse_boolean_property(value, &b))
+ return false;
+
+ do {
+ if (streq(key, quirk_get_name(q))) {
+ struct property *p = property_new();
+ p->id = q,
+ p->type = PT_BOOL;
+ p->value.b = b;
+ list_append(&s->properties, &p->link);
+ s->has_property = true;
+ return true;
+ }
+ } while (++q < _QUIRK_LAST_MODEL_QUIRK_);
+
+ qlog_error(ctx, "Unknown key %s in %s\n", key, s->name);
+
+ return false;
+}
+
+/**
+ * Parse a AttrFooBar=banana line.
+ *
+ * @param section The section struct to be filled in
+ * @param key The AttrFooBar part of the line
+ * @param value The banana part of the line.
+ *
+ * Value parsing depends on the attribute type.
+ *
+ * @return true on success, false otherwise.
+ */
+static inline bool
+parse_attr(struct quirks_context *ctx,
+ struct section *s,
+ const char *key,
+ const char *value)
+{
+ struct property *p = property_new();
+ bool rc = false;
+ struct quirk_dimensions dim;
+ struct quirk_range range;
+ unsigned int v;
+ bool b;
+ double d;
+
+ if (streq(key, quirk_get_name(QUIRK_ATTR_SIZE_HINT))) {
+ p->id = QUIRK_ATTR_SIZE_HINT;
+ if (!parse_dimension_property(value, &dim.x, &dim.y))
+ goto out;
+ p->type = PT_DIMENSION;
+ p->value.dim = dim;
+ rc = true;
+ } else if (streq(key, quirk_get_name(QUIRK_ATTR_TOUCH_SIZE_RANGE))) {
+ p->id = QUIRK_ATTR_TOUCH_SIZE_RANGE;
+ if (!parse_range_property(value, &range.upper, &range.lower))
+ goto out;
+ p->type = PT_RANGE;
+ p->value.range = range;
+ rc = true;
+ } else if (streq(key, quirk_get_name(QUIRK_ATTR_PALM_SIZE_THRESHOLD))) {
+ p->id = QUIRK_ATTR_PALM_SIZE_THRESHOLD;
+ if (!safe_atou(value, &v))
+ goto out;
+ p->type = PT_UINT;
+ p->value.u = v;
+ rc = true;
+ } else if (streq(key, quirk_get_name(QUIRK_ATTR_LID_SWITCH_RELIABILITY))) {
+ p->id = QUIRK_ATTR_LID_SWITCH_RELIABILITY;
+ if (!streq(value, "reliable") &&
+ !streq(value, "write_open") &&
+ !streq(value, "unreliable"))
+ goto out;
+ p->type = PT_STRING;
+ p->value.s = safe_strdup(value);
+ rc = true;
+ } else if (streq(key, quirk_get_name(QUIRK_ATTR_KEYBOARD_INTEGRATION))) {
+ p->id = QUIRK_ATTR_KEYBOARD_INTEGRATION;
+ if (!streq(value, "internal") && !streq(value, "external"))
+ goto out;
+ p->type = PT_STRING;
+ p->value.s = safe_strdup(value);
+ rc = true;
+ } else if (streq(key, quirk_get_name(QUIRK_ATTR_TRACKPOINT_INTEGRATION))) {
+ p->id = QUIRK_ATTR_TRACKPOINT_INTEGRATION;
+ if (!streq(value, "internal") && !streq(value, "external"))
+ goto out;
+ p->type = PT_STRING;
+ p->value.s = safe_strdup(value);
+ rc = true;
+ } else if (streq(key, quirk_get_name(QUIRK_ATTR_TPKBCOMBO_LAYOUT))) {
+ p->id = QUIRK_ATTR_TPKBCOMBO_LAYOUT;
+ if (!streq(value, "below"))
+ goto out;
+ p->type = PT_STRING;
+ p->value.s = safe_strdup(value);
+ rc = true;
+ } else if (streq(key, quirk_get_name(QUIRK_ATTR_PRESSURE_RANGE))) {
+ p->id = QUIRK_ATTR_PRESSURE_RANGE;
+ if (!parse_range_property(value, &range.upper, &range.lower))
+ goto out;
+ p->type = PT_RANGE;
+ p->value.range = range;
+ rc = true;
+ } else if (streq(key, quirk_get_name(QUIRK_ATTR_PALM_PRESSURE_THRESHOLD))) {
+ p->id = QUIRK_ATTR_PALM_PRESSURE_THRESHOLD;
+ if (!safe_atou(value, &v))
+ goto out;
+ p->type = PT_UINT;
+ p->value.u = v;
+ rc = true;
+ } else if (streq(key, quirk_get_name(QUIRK_ATTR_RESOLUTION_HINT))) {
+ p->id = QUIRK_ATTR_RESOLUTION_HINT;
+ if (!parse_dimension_property(value, &dim.x, &dim.y))
+ goto out;
+ p->type = PT_DIMENSION;
+ p->value.dim = dim;
+ rc = true;
+ } else if (streq(key, quirk_get_name(QUIRK_ATTR_TRACKPOINT_MULTIPLIER))) {
+ p->id = QUIRK_ATTR_TRACKPOINT_MULTIPLIER;
+ if (!safe_atod(value, &d))
+ goto out;
+ p->type = PT_DOUBLE;
+ p->value.d = d;
+ rc = true;
+ } else if (streq(key, quirk_get_name(QUIRK_ATTR_USE_VELOCITY_AVERAGING))) {
+ p->id = QUIRK_ATTR_USE_VELOCITY_AVERAGING;
+ if (!parse_boolean_property(value, &b))
+ goto out;
+ p->type = PT_BOOL;
+ p->value.b = b;
+ rc = true;
+ } else if (streq(key, quirk_get_name(QUIRK_ATTR_TABLET_SMOOTHING))) {
+ p->id = QUIRK_ATTR_TABLET_SMOOTHING;
+ if (!parse_boolean_property(value, &b))
+ goto out;
+ p->type = PT_BOOL;
+ p->value.b = b;
+ rc = true;
+ } else if (streq(key, quirk_get_name(QUIRK_ATTR_THUMB_PRESSURE_THRESHOLD))) {
+ p->id = QUIRK_ATTR_THUMB_PRESSURE_THRESHOLD;
+ if (!safe_atou(value, &v))
+ goto out;
+ p->type = PT_UINT;
+ p->value.u = v;
+ rc = true;
+ } else if (streq(key, quirk_get_name(QUIRK_ATTR_THUMB_SIZE_THRESHOLD))) {
+ p->id = QUIRK_ATTR_THUMB_SIZE_THRESHOLD;
+ if (!safe_atou(value, &v))
+ goto out;
+ p->type = PT_UINT;
+ p->value.u = v;
+ rc = true;
+ } else if (streq(key, quirk_get_name(QUIRK_ATTR_MSC_TIMESTAMP))) {
+ p->id = QUIRK_ATTR_MSC_TIMESTAMP;
+ if (!streq(value, "watch"))
+ goto out;
+ p->type = PT_STRING;
+ p->value.s = safe_strdup(value);
+ rc = true;
+ } else if (streq(key, quirk_get_name(QUIRK_ATTR_EVENT_CODE))) {
+ struct input_event events[32];
+ size_t nevents = ARRAY_LENGTH(events);
+
+ p->id = QUIRK_ATTR_EVENT_CODE;
+
+ if (!parse_evcode_property(value, events, &nevents) ||
+ nevents == 0)
+ goto out;
+
+ for (size_t i = 0; i < nevents; i++) {
+ p->value.tuples.tuples[i].first = events[i].type;
+ p->value.tuples.tuples[i].second = events[i].code;
+ p->value.tuples.tuples[i].third = events[i].value;
+ }
+ p->value.tuples.ntuples = nevents;
+ p->type = PT_TUPLES;
+
+ rc = true;
+ } else if (streq(key, quirk_get_name(QUIRK_ATTR_INPUT_PROP))) {
+ struct input_prop props[INPUT_PROP_CNT];
+ size_t nprops = ARRAY_LENGTH(props);
+
+ p->id = QUIRK_ATTR_INPUT_PROP;
+
+ if (!parse_input_prop_property(value, props, &nprops) ||
+ nprops == 0)
+ goto out;
+
+ for (size_t i = 0; i < nprops; i++) {
+ p->value.tuples.tuples[i].first = props[i].prop;
+ p->value.tuples.tuples[i].second = props[i].enabled;
+ }
+
+ rc = true;
+ } else {
+ qlog_error(ctx, "Unknown key %s in %s\n", key, s->name);
+ }
+out:
+ if (rc) {
+ list_append(&s->properties, &p->link);
+ s->has_property = true;
+ } else {
+ property_cleanup(p);
+ }
+ return rc;
+}
+
+/**
+ * Parse a MousedFooBar=banana line.
+ *
+ * @param section The section struct to be filled in
+ * @param key The MousedFooBar part of the line
+ * @param value The banana part of the line.
+ *
+ * Value parsing depends on the attribute type.
+ *
+ * @return true on success, false otherwise.
+ */
+static inline bool
+parse_moused(struct quirks_context *ctx,
+ struct section *s,
+ const char *key,
+ const char *value)
+{
+ struct property *p = property_new();
+ bool rc = false;
+ struct quirk_dimensions dim;
+ struct quirk_range range;
+ unsigned int v;
+ int i;
+ bool b;
+ double d;
+
+ if (streq(key, quirk_get_name(MOUSED_GRAB_DEVICE))) {
+ p->id = MOUSED_GRAB_DEVICE;
+ if (!parse_boolean_property(value, &b))
+ goto out;
+ p->type = PT_BOOL;
+ p->value.b = b;
+ rc = true;
+ } else if (streq(key, quirk_get_name(MOUSED_IGNORE_DEVICE))) {
+ p->id = MOUSED_IGNORE_DEVICE;
+ if (!parse_boolean_property(value, &b))
+ goto out;
+ p->type = PT_BOOL;
+ p->value.b = b;
+ rc = true;
+ } else if (streq(key, quirk_get_name(MOUSED_CLICK_THRESHOLD))) {
+ p->id = MOUSED_CLICK_THRESHOLD;
+ if (!safe_atou(value, &v))
+ goto out;
+ p->type = PT_UINT;
+ p->value.u = v;
+ rc = true;
+ } else if (streq(key, quirk_get_name(MOUSED_DRIFT_TERMINATE))) {
+ p->id = MOUSED_DRIFT_TERMINATE;
+ if (!parse_boolean_property(value, &b))
+ goto out;
+ p->type = PT_BOOL;
+ p->value.b = b;
+ rc = true;
+ } else if (streq(key, quirk_get_name(MOUSED_DRIFT_DISTANCE))) {
+ p->id = MOUSED_DRIFT_DISTANCE;
+ if (!safe_atou(value, &v))
+ goto out;
+ p->type = PT_UINT;
+ p->value.u = v;
+ rc = true;
+ } else if (streq(key, quirk_get_name(MOUSED_DRIFT_TIME))) {
+ p->id = MOUSED_DRIFT_TIME;
+ if (!safe_atou(value, &v))
+ goto out;
+ p->type = PT_UINT;
+ p->value.u = v;
+ rc = true;
+ } else if (streq(key, quirk_get_name(MOUSED_DRIFT_AFTER))) {
+ p->id = MOUSED_DRIFT_AFTER;
+ if (!safe_atou(value, &v))
+ goto out;
+ p->type = PT_UINT;
+ p->value.u = v;
+ rc = true;
+ } else if (streq(key, quirk_get_name(MOUSED_EMULATE_THIRD_BUTTON))) {
+ p->id = MOUSED_EMULATE_THIRD_BUTTON;
+ if (!parse_boolean_property(value, &b))
+ goto out;
+ p->type = PT_BOOL;
+ p->value.b = b;
+ rc = true;
+ } else if (streq(key, quirk_get_name(MOUSED_EMULATE_THIRD_BUTTON_TIMEOUT))) {
+ p->id = MOUSED_EMULATE_THIRD_BUTTON_TIMEOUT;
+ if (!safe_atou(value, &v))
+ goto out;
+ p->type = PT_UINT;
+ p->value.u = v;
+ rc = true;
+ } else if (streq(key, quirk_get_name(MOUSED_EXPONENTIAL_ACCEL))) {
+ p->id = MOUSED_EXPONENTIAL_ACCEL;
+ if (!safe_atod(value, &d))
+ goto out;
+ p->type = PT_DOUBLE;
+ p->value.d = d;
+ rc = true;
+ } else if (streq(key, quirk_get_name(MOUSED_EXPONENTIAL_OFFSET))) {
+ p->id = MOUSED_EXPONENTIAL_OFFSET;
+ if (!safe_atod(value, &d))
+ goto out;
+ p->type = PT_DOUBLE;
+ p->value.d = d;
+ rc = true;
+ } else if (streq(key, quirk_get_name(MOUSED_LINEAR_ACCEL_X))) {
+ p->id = MOUSED_LINEAR_ACCEL_X;
+ if (!safe_atod(value, &d))
+ goto out;
+ p->type = PT_DOUBLE;
+ p->value.d = d;
+ rc = true;
+ } else if (streq(key, quirk_get_name(MOUSED_LINEAR_ACCEL_Y))) {
+ p->id = MOUSED_LINEAR_ACCEL_Y;
+ if (!safe_atod(value, &d))
+ goto out;
+ p->type = PT_DOUBLE;
+ p->value.d = d;
+ rc = true;
+ } else if (streq(key, quirk_get_name(MOUSED_LINEAR_ACCEL_Z))) {
+ p->id = MOUSED_LINEAR_ACCEL_Z;
+ if (!safe_atod(value, &d))
+ goto out;
+ p->type = PT_DOUBLE;
+ p->value.d = d;
+ rc = true;
+ } else if (streq(key, quirk_get_name(MOUSED_MAP_Z_AXIS))) {
+ } else if (streq(key, quirk_get_name(MOUSED_VIRTUAL_SCROLL_ENABLE))) {
+ p->id = MOUSED_VIRTUAL_SCROLL_ENABLE;
+ if (!parse_boolean_property(value, &b))
+ goto out;
+ p->type = PT_BOOL;
+ p->value.b = b;
+ rc = true;
+ } else if (streq(key, quirk_get_name(MOUSED_HOR_VIRTUAL_SCROLL_ENABLE))) {
+ p->id = MOUSED_HOR_VIRTUAL_SCROLL_ENABLE;
+ if (!parse_boolean_property(value, &b))
+ goto out;
+ p->type = PT_BOOL;
+ p->value.b = b;
+ rc = true;
+ } else if (streq(key, quirk_get_name(MOUSED_VIRTUAL_SCROLL_SPEED))) {
+ p->id = MOUSED_VIRTUAL_SCROLL_SPEED;
+ if (!safe_atou(value, &v))
+ goto out;
+ p->type = PT_UINT;
+ p->value.u = v;
+ rc = true;
+ } else if (streq(key, quirk_get_name(MOUSED_VIRTUAL_SCROLL_THRESHOLD))) {
+ p->id = MOUSED_VIRTUAL_SCROLL_THRESHOLD;
+ if (!safe_atou(value, &v))
+ goto out;
+ p->type = PT_UINT;
+ p->value.u = v;
+ rc = true;
+ } else if (streq(key, quirk_get_name(MOUSED_WMODE))) {
+ p->id = MOUSED_WMODE;
+ if (!safe_atou(value, &v))
+ goto out;
+ p->type = PT_UINT;
+ p->value.u = v;
+ rc = true;
+ } else if (streq(key, quirk_get_name(MOUSED_TWO_FINGER_SCROLL))) {
+ p->id = MOUSED_TWO_FINGER_SCROLL;
+ if (!parse_boolean_property(value, &b))
+ goto out;
+ p->type = PT_BOOL;
+ p->value.b = b;
+ rc = true;
+ } else if (streq(key, quirk_get_name(MOUSED_NATURAL_SCROLL))) {
+ p->id = MOUSED_NATURAL_SCROLL;
+ if (!parse_boolean_property(value, &b))
+ goto out;
+ p->type = PT_BOOL;
+ p->value.b = b;
+ rc = true;
+ } else if (streq(key, quirk_get_name(MOUSED_THREE_FINGER_DRAG))) {
+ p->id = MOUSED_THREE_FINGER_DRAG;
+ if (!parse_boolean_property(value, &b))
+ goto out;
+ p->type = PT_BOOL;
+ p->value.b = b;
+ rc = true;
+ } else if (streq(key, quirk_get_name(MOUSED_SOFTBUTTON2_X))) {
+ p->id = MOUSED_SOFTBUTTON2_X;
+ if (!safe_atou(value, &v))
+ goto out;
+ p->type = PT_UINT;
+ p->value.u = v;
+ rc = true;
+ } else if (streq(key, quirk_get_name(MOUSED_SOFTBUTTON3_X))) {
+ p->id = MOUSED_SOFTBUTTON3_X;
+ if (!safe_atou(value, &v))
+ goto out;
+ p->type = PT_UINT;
+ p->value.u = v;
+ rc = true;
+ } else if (streq(key, quirk_get_name(MOUSED_SOFTBUTTONS_Y))) {
+ p->id = MOUSED_SOFTBUTTONS_Y;
+ if (!safe_atoi(value, &i))
+ goto out;
+ p->type = PT_INT;
+ p->value.i = i;
+ rc = true;
+ } else if (streq(key, quirk_get_name(MOUSED_TAP_TIMEOUT))) {
+ p->id = MOUSED_TAP_TIMEOUT;
+ if (!safe_atou(value, &v))
+ goto out;
+ p->type = PT_UINT;
+ p->value.u = v;
+ rc = true;
+ } else if (streq(key, quirk_get_name(MOUSED_TAP_PRESSURE_THRESHOLD))) {
+ p->id = MOUSED_TAP_PRESSURE_THRESHOLD;
+ if (!safe_atou(value, &v))
+ goto out;
+ p->type = PT_UINT;
+ p->value.u = v;
+ rc = true;
+ } else if (streq(key, quirk_get_name(MOUSED_TAP_MAX_DELTA))) {
+ p->id = MOUSED_TAP_MAX_DELTA;
+ if (!safe_atod(value, &d))
+ goto out;
+ p->type = PT_DOUBLE;
+ p->value.d = d;
+ rc = true;
+ } else if (streq(key, quirk_get_name(MOUSED_TAPHOLD_TIMEOUT))) {
+ p->id = MOUSED_TAPHOLD_TIMEOUT;
+ if (!safe_atou(value, &v))
+ goto out;
+ p->type = PT_UINT;
+ p->value.u = v;
+ rc = true;
+ } else if (streq(key, quirk_get_name(MOUSED_VSCROLL_MIN_DELTA))) {
+ p->id = MOUSED_VSCROLL_MIN_DELTA;
+ if (!safe_atod(value, &d))
+ goto out;
+ p->type = PT_DOUBLE;
+ p->value.d = d;
+ rc = true;
+ } else if (streq(key, quirk_get_name(MOUSED_VSCROLL_HOR_AREA))) {
+ p->id = MOUSED_VSCROLL_HOR_AREA;
+ if (!safe_atod(value, &d))
+ goto out;
+ p->type = PT_DOUBLE;
+ p->value.d = d;
+ rc = true;
+ } else if (streq(key, quirk_get_name(MOUSED_VSCROLL_VER_AREA))) {
+ p->id = MOUSED_VSCROLL_VER_AREA;
+ if (!safe_atod(value, &d))
+ goto out;
+ p->type = PT_DOUBLE;
+ p->value.d = d;
+ rc = true;
+ } else {
+ qlog_error(ctx, "Unknown key %s in %s\n", key, s->name);
+ }
+out:
+ if (rc) {
+ list_append(&s->properties, &p->link);
+ s->has_property = true;
+ } else {
+ property_cleanup(p);
+ }
+ return rc;
+}
+
+/**
+ * Parse a single line, expected to be in the format Key=value. Anything
+ * else will be rejected with a failure.
+ *
+ * Our data files can only have Match, Model and Attr, so let's check for
+ * those too.
+ */
+static bool
+parse_value_line(struct quirks_context *ctx, struct section *s, const char *line)
+{
+ bool rc = false;
+
+ size_t nelem;
+ char **strv = strv_from_string(line, "=", &nelem);
+ if (!strv || nelem != 2)
+ goto out;
+
+ const char *key = strv[0];
+ const char *value = strv[1];
+ if (strlen(key) == 0 || strlen(value) == 0)
+ goto out;
+
+ /* Whatever the value is, it's not supposed to be in quotes */
+ if (value[0] == '"' || value[0] == '\'')
+ goto out;
+
+ if (strstartswith(key, "Match"))
+ rc = parse_match(ctx, s, key, value);
+ else if (strstartswith(key, "Model"))
+ rc = parse_model(ctx, s, key, value);
+ else if (strstartswith(key, "Attr"))
+ rc = parse_attr(ctx, s, key, value);
+ else if (strstartswith(key, "Moused"))
+ rc = parse_moused(ctx, s, key, value);
+ else
+ qlog_error(ctx, "Unknown value prefix %s\n", line);
+out:
+ strv_free(strv);
+ return rc;
+}
+
+static inline bool
+parse_file(struct quirks_context *ctx, const char *path)
+{
+ enum state {
+ STATE_SECTION,
+ STATE_MATCH,
+ STATE_MATCH_OR_VALUE,
+ STATE_VALUE_OR_SECTION,
+ STATE_ANY,
+ };
+ FILE *fp;
+ char line[512];
+ bool rc = false;
+ enum state state = STATE_SECTION;
+ struct section *section = NULL;
+ int lineno = -1;
+
+ qlog_debug(ctx, "%s\n", path);
+
+ /* Not using open_restricted here, if we can't access
+ * our own data files, our installation is screwed up.
+ */
+ fp = fopen(path, "r");
+ if (!fp) {
+ /* If the file doesn't exist that's fine. Only way this can
+ * happen is for the custom override file, all others are
+ * provided by scandir so they do exist. Short of races we
+ * don't care about. */
+ if (errno == ENOENT)
+ return true;
+
+ qlog_error(ctx, "%s: failed to open file\n", path);
+ goto out;
+ }
+
+ while (fgets(line, sizeof(line), fp)) {
+ char *comment;
+
+ lineno++;
+
+ comment = strstr(line, "#");
+ if (comment) {
+ /* comment points to # but we need to remove the
+ * preceding whitespaces too */
+ comment--;
+ while (comment >= line) {
+ if (*comment != ' ' && *comment != '\t')
+ break;
+ comment--;
+ }
+ *(comment + 1) = '\0';
+ } else { /* strip the trailing newline */
+ comment = strstr(line, "\n");
+ if (comment)
+ *comment = '\0';
+ }
+ if (strlen(line) == 0)
+ continue;
+
+ /* We don't use quotes for strings, so we really don't want
+ * erroneous trailing whitespaces */
+ switch (line[strlen(line) - 1]) {
+ case ' ':
+ case '\t':
+ qlog_parser(ctx,
+ "%s:%d: Trailing whitespace '%s'\n",
+ path, lineno, line);
+ goto out;
+ }
+
+ switch (line[0]) {
+ case '\0':
+ case '\n':
+ case '#':
+ break;
+ /* white space not allowed */
+ case ' ':
+ case '\t':
+ qlog_parser(ctx, "%s:%d: Preceding whitespace '%s'\n",
+ path, lineno, line);
+ goto out;
+ /* section title */
+ case '[':
+ if (line[strlen(line) - 1] != ']') {
+ qlog_parser(ctx, "%s:%d: Closing ] missing '%s'\n",
+ path, lineno, line);
+ goto out;
+ }
+
+ if (state != STATE_SECTION &&
+ state != STATE_VALUE_OR_SECTION) {
+ qlog_parser(ctx, "%s:%d: expected section before %s\n",
+ path, lineno, line);
+ goto out;
+ }
+ if (section &&
+ (!section->has_match || !section->has_property)) {
+ qlog_parser(ctx, "%s:%d: previous section %s was empty\n",
+ path, lineno, section->name);
+ goto out; /* Previous section was empty */
+ }
+
+ state = STATE_MATCH;
+ section = section_new(path, line);
+ list_append(&ctx->sections, &section->link);
+ break;
+ default:
+ /* entries must start with A-Z */
+ if (line[0] < 'A' || line[0] > 'Z') {
+ qlog_parser(ctx, "%s:%d: Unexpected line %s\n",
+ path, lineno, line);
+ goto out;
+ }
+ switch (state) {
+ case STATE_SECTION:
+ qlog_parser(ctx, "%s:%d: expected [Section], got %s\n",
+ path, lineno, line);
+ goto out;
+ case STATE_MATCH:
+ if (!strstartswith(line, "Match")) {
+ qlog_parser(ctx, "%s:%d: expected MatchFoo=bar, have %s\n",
+ path, lineno, line);
+ goto out;
+ }
+ state = STATE_MATCH_OR_VALUE;
+ break;
+ case STATE_MATCH_OR_VALUE:
+ if (!strstartswith(line, "Match"))
+ state = STATE_VALUE_OR_SECTION;
+ break;
+ case STATE_VALUE_OR_SECTION:
+ if (strstartswith(line, "Match")) {
+ qlog_parser(ctx, "%s:%d: expected value or [Section], have %s\n",
+ path, lineno, line);
+ goto out;
+ }
+ break;
+ case STATE_ANY:
+ break;
+ }
+
+ if (!parse_value_line(ctx, section, line)) {
+ qlog_parser(ctx, "%s:%d: failed to parse %s\n",
+ path, lineno, line);
+ goto out;
+ }
+ break;
+ }
+ }
+
+ if (!section) {
+ qlog_parser(ctx, "%s: is an empty file\n", path);
+ goto out;
+ }
+
+ if ((!section->has_match || !section->has_property)) {
+ qlog_parser(ctx, "%s:%d: previous section %s was empty\n",
+ path, lineno, section->name);
+ goto out; /* Previous section was empty */
+ }
+
+ rc = true;
+out:
+ if (fp)
+ fclose(fp);
+
+ return rc;
+}
+
+static int
+is_data_file(const struct dirent *dir) {
+ return strendswith(dir->d_name, ".quirks");
+}
+
+static inline bool
+parse_files(struct quirks_context *ctx, const char *data_path)
+{
+ struct dirent **namelist;
+ int ndev = -1;
+ int idx = 0;
+
+ ndev = scandir(data_path, &namelist, is_data_file, versionsort);
+ if (ndev <= 0) {
+ qlog_error(ctx,
+ "%s: failed to find data files\n",
+ data_path);
+ return false;
+ }
+
+ for (idx = 0; idx < ndev; idx++) {
+ char path[PATH_MAX];
+
+ snprintf(path,
+ sizeof(path),
+ "%s/%s",
+ data_path,
+ namelist[idx]->d_name);
+
+ if (!parse_file(ctx, path))
+ break;
+ }
+
+ for (int i = 0; i < ndev; i++)
+ free(namelist[i]);
+ free(namelist);
+
+ return idx == ndev;
+}
+
+struct quirks_context *
+quirks_init_subsystem(const char *data_path,
+ const char *override_file,
+ moused_log_handler log_handler,
+ enum quirks_log_type log_type)
+{
+ _unref_(quirks_context) *ctx = zalloc(sizeof *ctx);
+
+ assert(data_path);
+
+ ctx->refcount = 1;
+ ctx->log_handler = log_handler;
+ ctx->log_type = log_type;
+ list_init(&ctx->quirks);
+ list_init(&ctx->sections);
+
+ qlog_debug(ctx, "%s is data root\n", data_path);
+
+ ctx->dmi = init_dmi();
+ ctx->dt = init_dt();
+ if (!ctx->dmi && !ctx->dt)
+ return NULL;
+
+ if (!parse_files(ctx, data_path))
+ return NULL;
+
+ if (override_file && !parse_file(ctx, override_file))
+ return NULL;
+
+ return steal(&ctx);
+}
+
+struct quirks_context *
+quirks_context_ref(struct quirks_context *ctx)
+{
+ assert(ctx->refcount > 0);
+ ctx->refcount++;
+
+ return ctx;
+}
+
+struct quirks_context *
+quirks_context_unref(struct quirks_context *ctx)
+{
+ struct section *s;
+
+ if (!ctx)
+ return NULL;
+
+ assert(ctx->refcount >= 1);
+ ctx->refcount--;
+
+ if (ctx->refcount > 0)
+ return NULL;
+
+ /* Caller needs to clean up before calling this */
+ assert(list_empty(&ctx->quirks));
+
+ list_for_each_safe(s, &ctx->sections, link) {
+ section_destroy(s);
+ }
+
+ free(ctx->dmi);
+ free(ctx->dt);
+ free(ctx);
+
+ return NULL;
+}
+
+static struct quirks *
+quirks_new(void)
+{
+ struct quirks *q;
+
+ q = zalloc(sizeof *q);
+ q->refcount = 1;
+ q->nproperties = 0;
+ list_init(&q->link);
+ list_init(&q->floating_properties);
+
+ return q;
+}
+
+struct quirks *
+quirks_unref(struct quirks *q)
+{
+ if (!q)
+ return NULL;
+
+ /* We don't really refcount, but might
+ * as well have the API in place */
+ assert(q->refcount == 1);
+
+ for (size_t i = 0; i < q->nproperties; i++) {
+ property_unref(q->properties[i]);
+ }
+
+ /* Floating properties are owned by our quirks context, need to be
+ * cleaned up here */
+ struct property *p;
+ list_for_each_safe(p, &q->floating_properties, link) {
+ property_cleanup(p);
+ }
+
+ list_remove(&q->link);
+ free(q->properties);
+ free(q);
+
+ return NULL;
+}
+
+static inline void
+match_fill_name(struct match *m,
+ struct device *device)
+{
+ if (device->name[0] == 0)
+ return;
+
+ m->name = safe_strdup(device->name);
+
+ m->bits |= M_NAME;
+}
+
+static inline void
+match_fill_uniq(struct match *m,
+ struct device *device)
+{
+ if (device->uniq[0] == 0)
+ return;
+
+ m->uniq = safe_strdup(device->uniq);
+
+ m->bits |= M_UNIQ;
+}
+
+static inline void
+match_fill_bus_vid_pid(struct match *m,
+ struct device *device)
+{
+ m->product[0] = device->id.product;
+ m->product[1] = 0;
+ m->vendor = device->id.vendor;
+ m->version = device->id.version;
+ m->bits |= M_PID|M_VID|M_VERSION;
+ switch (device->id.bustype) {
+ case BUS_USB:
+ m->bus = BT_USB;
+ m->bits |= M_BUS;
+ break;
+ case BUS_BLUETOOTH:
+ m->bus = BT_BLUETOOTH;
+ m->bits |= M_BUS;
+ break;
+ case BUS_I8042:
+ m->bus = BT_PS2;
+ m->bits |= M_BUS;
+ break;
+ case BUS_RMI:
+ m->bus = BT_RMI;
+ m->bits |= M_BUS;
+ break;
+ case BUS_I2C:
+ m->bus = BT_I2C;
+ m->bits |= M_BUS;
+ break;
+ case BUS_SPI:
+ m->bus = BT_SPI;
+ m->bits |= M_BUS;
+ break;
+ default:
+ break;
+ }
+}
+
+static inline void
+match_fill_udev_type(struct match *m,
+ struct device *device)
+{
+ switch (device->type) {
+ case DEVICE_TYPE_MOUSE:
+ m->udev_type |= UDEV_MOUSE;
+ break;
+ case DEVICE_TYPE_POINTINGSTICK:
+ m->udev_type |= UDEV_MOUSE | UDEV_POINTINGSTICK;
+ break;
+ case DEVICE_TYPE_TOUCHPAD:
+ m->udev_type |= UDEV_TOUCHPAD;
+ break;
+ case DEVICE_TYPE_TABLET:
+ m->udev_type |= UDEV_TABLET;
+ break;
+ case DEVICE_TYPE_TABLET_PAD:
+ m->udev_type |= UDEV_TABLET_PAD;
+ break;
+ case DEVICE_TYPE_KEYBOARD:
+ m->udev_type |= UDEV_KEYBOARD;
+ break;
+ case DEVICE_TYPE_JOYSTICK:
+ m->udev_type |= UDEV_JOYSTICK;
+ break;
+ default:
+ break;
+ }
+ m->bits |= M_UDEV_TYPE;
+}
+
+static inline void
+match_fill_dmi_dt(struct match *m, char *dmi, char *dt)
+{
+ if (dmi) {
+ m->dmi = dmi;
+ m->bits |= M_DMI;
+ }
+
+ if (dt) {
+ m->dt = dt;
+ m->bits |= M_DT;
+ }
+}
+
+static struct match *
+match_new(struct device *device,
+ char *dmi, char *dt)
+{
+ struct match *m = zalloc(sizeof *m);
+
+ match_fill_name(m, device);
+ match_fill_uniq(m, device);
+ match_fill_bus_vid_pid(m, device);
+ match_fill_dmi_dt(m, dmi, dt);
+ match_fill_udev_type(m, device);
+ return m;
+}
+
+static void
+match_free(struct match *m)
+{
+ /* dmi and dt are global */
+ free(m->name);
+ free(m->uniq);
+ free(m);
+}
+
+static void
+quirk_merge_event_codes(struct quirks_context *ctx,
+ struct quirks *q,
+ const struct property *property)
+{
+ for (size_t i = 0; i < q->nproperties; i++) {
+ struct property *p = q->properties[i];
+
+ if (p->id != property->id)
+ continue;
+
+ /* We have a duplicated property, merge in with ours */
+ size_t offset = p->value.tuples.ntuples;
+ size_t max = ARRAY_LENGTH(p->value.tuples.tuples);
+ for (size_t j = 0; j < property->value.tuples.ntuples; j++) {
+ if (offset + j >= max)
+ break;
+ p->value.tuples.tuples[offset + j] = property->value.tuples.tuples[j];
+ p->value.tuples.ntuples++;
+ }
+ return;
+ }
+
+ /* First time we add AttrEventCode: create a new property.
+ * Unlike the other properties, this one isn't part of a section, it belongs
+ * to the quirks */
+ struct property *newprop = property_new();
+ newprop->id = property->id;
+ newprop->type = property->type;
+ newprop->value.tuples = property->value.tuples;
+ /* Caller responsible for pre-allocating space */
+ q->properties[q->nproperties++] = property_ref(newprop);
+ list_append(&q->floating_properties, &newprop->link);
+}
+
+static void
+quirk_apply_section(struct quirks_context *ctx,
+ struct quirks *q,
+ const struct section *s)
+{
+ struct property *p;
+ size_t nprops = 0;
+ void *tmp;
+
+ list_for_each(p, &s->properties, link) {
+ nprops++;
+ }
+
+ nprops += q->nproperties;
+ tmp = realloc(q->properties, nprops * sizeof(p));
+ if (!tmp)
+ return;
+
+ q->properties = tmp;
+ list_for_each(p, &s->properties, link) {
+ qlog_debug(ctx, "property added: %s from %s\n",
+ quirk_get_name(p->id), s->name);
+
+ /* All quirks but AttrEventCode and AttrInputProp
+ * simply overwrite each other, so we can just append the
+ * matching property and, later when checking the quirk, pick
+ * the last one in the array.
+ *
+ * The event codes/input props are special because they're lists
+ * that may *partially* override each other, e.g. a section may
+ * enable BTN_LEFT and BTN_RIGHT but a later section may disable
+ * only BTN_RIGHT. This should result in BTN_LEFT force-enabled
+ * and BTN_RIGHT force-disabled.
+ *
+ * To hack around this, those are the only ones where only ever
+ * have one struct property in the list (not owned by a section)
+ * and we simply merge any extra sections onto that.
+ */
+ if (p->id == QUIRK_ATTR_EVENT_CODE ||
+ p->id == QUIRK_ATTR_INPUT_PROP)
+ quirk_merge_event_codes(ctx, q, p);
+ else
+ q->properties[q->nproperties++] = property_ref(p);
+ }
+}
+
+static bool
+quirk_match_section(struct quirks_context *ctx,
+ struct quirks *q,
+ struct section *s,
+ struct match *m,
+ struct device *device)
+{
+ uint32_t matched_flags = 0x0;
+
+ for (uint32_t flag = 0x1; flag <= M_LAST; flag <<= 1) {
+ uint32_t prev_matched_flags = matched_flags;
+ /* section doesn't have this bit set, continue */
+ if ((s->match.bits & flag) == 0)
+ continue;
+
+ /* Couldn't fill in this bit for the match, so we
+ * do not match on it */
+ if ((m->bits & flag) == 0) {
+ qlog_debug(ctx,
+ "%s wants %s but we don't have that\n",
+ s->name, matchflagname(flag));
+ continue;
+ }
+
+ /* now check the actual matching bit */
+ switch (flag) {
+ case M_NAME:
+ if (fnmatch(s->match.name, m->name, 0) == 0)
+ matched_flags |= flag;
+ break;
+ case M_UNIQ:
+ if (fnmatch(s->match.uniq, m->uniq, 0) == 0)
+ matched_flags |= flag;
+ break;
+ case M_BUS:
+ if (m->bus == s->match.bus)
+ matched_flags |= flag;
+ break;
+ case M_VID:
+ if (m->vendor == s->match.vendor)
+ matched_flags |= flag;
+ break;
+ case M_PID:
+ ARRAY_FOR_EACH(m->product, mi) {
+ if (*mi == 0 || matched_flags & flag)
+ break;
+
+ ARRAY_FOR_EACH(s->match.product, si) {
+ if (*si == 0)
+ break;
+ if (*mi == *si) {
+ matched_flags |= flag;
+ break;
+ }
+ }
+ }
+ break;
+ case M_VERSION:
+ if (m->version == s->match.version)
+ matched_flags |= flag;
+ break;
+ case M_DMI:
+ if (fnmatch(s->match.dmi, m->dmi, 0) == 0)
+ matched_flags |= flag;
+ break;
+ case M_DT:
+ if (fnmatch(s->match.dt, m->dt, 0) == 0)
+ matched_flags |= flag;
+ break;
+ case M_UDEV_TYPE:
+ if (s->match.udev_type & m->udev_type)
+ matched_flags |= flag;
+ break;
+ default:
+ abort();
+ }
+
+ if (prev_matched_flags != matched_flags) {
+ qlog_debug(ctx,
+ "%s matches for %s\n",
+ s->name,
+ matchflagname(flag));
+ }
+ }
+
+ if (s->match.bits == matched_flags) {
+ qlog_debug(ctx, "%s is full match\n", s->name);
+ quirk_apply_section(ctx, q, s);
+ }
+
+ return true;
+}
+
+struct quirks *
+quirks_fetch_for_device(struct quirks_context *ctx,
+ struct device *device)
+{
+ struct section *s;
+ struct match *m;
+
+ if (!ctx)
+ return NULL;
+
+ qlog_debug(ctx, "%s: fetching quirks\n", device->path);
+
+ _unref_(quirks) *q = quirks_new();
+
+ m = match_new(device, ctx->dmi, ctx->dt);
+
+ list_for_each(s, &ctx->sections, link) {
+ quirk_match_section(ctx, q, s, m, device);
+ }
+
+ match_free(m);
+
+ if (q->nproperties == 0) {
+ return NULL;
+ }
+
+ list_insert(&ctx->quirks, &q->link);
+
+ return steal(&q);
+}
+
+static inline struct property *
+quirk_find_prop(struct quirks *q, enum quirk which)
+{
+ /* Run backwards to only handle the last one assigned */
+ for (ssize_t i = q->nproperties - 1; i >= 0; i--) {
+ struct property *p = q->properties[i];
+ if (p->id == which)
+ return p;
+ }
+
+ return NULL;
+}
+
+bool
+quirks_has_quirk(struct quirks *q, enum quirk which)
+{
+ return quirk_find_prop(q, which) != NULL;
+}
+
+bool
+quirks_get_int32(struct quirks *q, enum quirk which, int32_t *val)
+{
+ struct property *p;
+
+ if (!q)
+ return false;
+
+ p = quirk_find_prop(q, which);
+ if (!p)
+ return false;
+
+ assert(p->type == PT_INT);
+ *val = p->value.i;
+
+ return true;
+}
+
+bool
+quirks_get_uint32(struct quirks *q, enum quirk which, uint32_t *val)
+{
+ struct property *p;
+
+ if (!q)
+ return false;
+
+ p = quirk_find_prop(q, which);
+ if (!p)
+ return false;
+
+ assert(p->type == PT_UINT);
+ *val = p->value.u;
+
+ return true;
+}
+
+bool
+quirks_get_double(struct quirks *q, enum quirk which, double *val)
+{
+ struct property *p;
+
+ if (!q)
+ return false;
+
+ p = quirk_find_prop(q, which);
+ if (!p)
+ return false;
+
+ assert(p->type == PT_DOUBLE);
+ *val = p->value.d;
+
+ return true;
+}
+
+bool
+quirks_get_string(struct quirks *q, enum quirk which, char **val)
+{
+ struct property *p;
+
+ if (!q)
+ return false;
+
+ p = quirk_find_prop(q, which);
+ if (!p)
+ return false;
+
+ assert(p->type == PT_STRING);
+ *val = p->value.s;
+
+ return true;
+}
+
+bool
+quirks_get_bool(struct quirks *q, enum quirk which, bool *val)
+{
+ struct property *p;
+
+ if (!q)
+ return false;
+
+ p = quirk_find_prop(q, which);
+ if (!p)
+ return false;
+
+ assert(p->type == PT_BOOL);
+ *val = p->value.b;
+
+ return true;
+}
+
+bool
+quirks_get_dimensions(struct quirks *q,
+ enum quirk which,
+ struct quirk_dimensions *val)
+{
+ struct property *p;
+
+ if (!q)
+ return false;
+
+ p = quirk_find_prop(q, which);
+ if (!p)
+ return false;
+
+ assert(p->type == PT_DIMENSION);
+ *val = p->value.dim;
+
+ return true;
+}
+
+bool
+quirks_get_range(struct quirks *q,
+ enum quirk which,
+ struct quirk_range *val)
+{
+ struct property *p;
+
+ if (!q)
+ return false;
+
+ p = quirk_find_prop(q, which);
+ if (!p)
+ return false;
+
+ assert(p->type == PT_RANGE);
+ *val = p->value.range;
+
+ return true;
+}
+
+bool
+quirks_get_tuples(struct quirks *q,
+ enum quirk which,
+ const struct quirk_tuples **tuples)
+{
+ struct property *p;
+
+ if (!q)
+ return false;
+
+ p = quirk_find_prop(q, which);
+ if (!p)
+ return false;
+
+ assert(p->type == PT_TUPLES);
+ *tuples = &p->value.tuples;
+
+ return true;
+}
+
+bool
+quirks_get_uint32_array(struct quirks *q,
+ enum quirk which,
+ const uint32_t **array,
+ size_t *nelements)
+{
+ struct property *p;
+
+ if (!q)
+ return false;
+
+ p = quirk_find_prop(q, which);
+ if (!p)
+ return false;
+
+ assert(p->type == PT_UINT_ARRAY);
+ *array = p->value.array.data.u;
+ *nelements = p->value.array.nelements;
+
+ return true;
+}
diff --git a/usr.sbin/moused/moused/quirks.h b/usr.sbin/moused/moused/quirks.h
new file mode 100644
index 000000000000..6a34d17be83c
--- /dev/null
+++ b/usr.sbin/moused/moused/quirks.h
@@ -0,0 +1,369 @@
+/*
+ * Copyright © 2018 Red Hat, Inc.
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include "util.h"
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <syslog.h>
+
+/**
+ * Handle to the quirks context.
+ */
+struct quirks_context;
+
+/**
+ * Contains all quirks set for a single device.
+ */
+struct quirks;
+
+struct quirk_dimensions {
+ size_t x, y;
+};
+
+struct quirk_range {
+ int lower, upper;
+};
+
+struct quirk_tuples {
+ struct {
+ int first;
+ int second;
+ int third;
+ } tuples[32];
+ size_t ntuples;
+};
+
+/**
+ * Quirks known to libinput. Moused does not support all of them.
+ */
+enum quirk {
+ QUIRK_MODEL_ALPS_SERIAL_TOUCHPAD = 100,
+ QUIRK_MODEL_APPLE_TOUCHPAD,
+ QUIRK_MODEL_APPLE_TOUCHPAD_ONEBUTTON,
+ QUIRK_MODEL_BOUNCING_KEYS,
+ QUIRK_MODEL_CHROMEBOOK,
+ QUIRK_MODEL_CLEVO_W740SU,
+ QUIRK_MODEL_DELL_CANVAS_TOTEM,
+ QUIRK_MODEL_HP_PAVILION_DM4_TOUCHPAD,
+ QUIRK_MODEL_HP_ZBOOK_STUDIO_G3,
+ QUIRK_MODEL_INVERT_HORIZONTAL_SCROLLING,
+ QUIRK_MODEL_LENOVO_SCROLLPOINT,
+ QUIRK_MODEL_LENOVO_T450_TOUCHPAD,
+ QUIRK_MODEL_LENOVO_X1GEN6_TOUCHPAD,
+ QUIRK_MODEL_LENOVO_X230,
+ QUIRK_MODEL_SYNAPTICS_SERIAL_TOUCHPAD,
+ QUIRK_MODEL_SYSTEM76_BONOBO,
+ QUIRK_MODEL_SYSTEM76_GALAGO,
+ QUIRK_MODEL_SYSTEM76_KUDU,
+ QUIRK_MODEL_TABLET_MODE_NO_SUSPEND,
+ QUIRK_MODEL_TABLET_MODE_SWITCH_UNRELIABLE,
+ QUIRK_MODEL_TOUCHPAD_VISIBLE_MARKER,
+ QUIRK_MODEL_TRACKBALL,
+ QUIRK_MODEL_WACOM_TOUCHPAD,
+ QUIRK_MODEL_PRESSURE_PAD,
+ QUIRK_MODEL_TOUCHPAD_PHANTOM_CLICKS,
+
+ _QUIRK_LAST_MODEL_QUIRK_, /* Guard: do not modify */
+
+ QUIRK_ATTR_SIZE_HINT = 300,
+ QUIRK_ATTR_TOUCH_SIZE_RANGE,
+ QUIRK_ATTR_PALM_SIZE_THRESHOLD,
+ QUIRK_ATTR_LID_SWITCH_RELIABILITY,
+ QUIRK_ATTR_KEYBOARD_INTEGRATION,
+ QUIRK_ATTR_TRACKPOINT_INTEGRATION,
+ QUIRK_ATTR_TPKBCOMBO_LAYOUT,
+ QUIRK_ATTR_PRESSURE_RANGE,
+ QUIRK_ATTR_PALM_PRESSURE_THRESHOLD,
+ QUIRK_ATTR_RESOLUTION_HINT,
+ QUIRK_ATTR_TRACKPOINT_MULTIPLIER,
+ QUIRK_ATTR_THUMB_PRESSURE_THRESHOLD,
+ QUIRK_ATTR_USE_VELOCITY_AVERAGING,
+ QUIRK_ATTR_TABLET_SMOOTHING,
+ QUIRK_ATTR_THUMB_SIZE_THRESHOLD,
+ QUIRK_ATTR_MSC_TIMESTAMP,
+ QUIRK_ATTR_EVENT_CODE,
+ QUIRK_ATTR_INPUT_PROP,
+
+ _QUIRK_LAST_ATTR_QUIRK_, /* Guard: do not modify */
+
+
+ /* Daemon parameters */
+ MOUSED_GRAB_DEVICE = 1000,
+ MOUSED_IGNORE_DEVICE,
+
+ /* Standard moused parameters */
+ MOUSED_CLICK_THRESHOLD,
+ MOUSED_DRIFT_TERMINATE,
+ MOUSED_DRIFT_DISTANCE,
+ MOUSED_DRIFT_TIME,
+ MOUSED_DRIFT_AFTER,
+ MOUSED_EMULATE_THIRD_BUTTON,
+ MOUSED_EMULATE_THIRD_BUTTON_TIMEOUT,
+ MOUSED_EXPONENTIAL_ACCEL,
+ MOUSED_EXPONENTIAL_OFFSET,
+ MOUSED_LINEAR_ACCEL_X,
+ MOUSED_LINEAR_ACCEL_Y,
+ MOUSED_LINEAR_ACCEL_Z,
+ MOUSED_MAP_Z_AXIS,
+ MOUSED_VIRTUAL_SCROLL_ENABLE,
+ MOUSED_HOR_VIRTUAL_SCROLL_ENABLE,
+ MOUSED_VIRTUAL_SCROLL_SPEED,
+ MOUSED_VIRTUAL_SCROLL_THRESHOLD,
+ MOUSED_WMODE,
+
+ /* Touchpad parameters from psm(4) driver */
+ MOUSED_TWO_FINGER_SCROLL,
+ MOUSED_NATURAL_SCROLL,
+ MOUSED_THREE_FINGER_DRAG,
+ MOUSED_SOFTBUTTON2_X,
+ MOUSED_SOFTBUTTON3_X,
+ MOUSED_SOFTBUTTONS_Y,
+ MOUSED_TAP_TIMEOUT,
+ MOUSED_TAP_PRESSURE_THRESHOLD,
+ MOUSED_TAP_MAX_DELTA,
+ MOUSED_TAPHOLD_TIMEOUT,
+ MOUSED_VSCROLL_MIN_DELTA,
+ MOUSED_VSCROLL_HOR_AREA,
+ MOUSED_VSCROLL_VER_AREA,
+
+ _MOUSED_LAST_OPTION_ /* Guard: do not modify */
+};
+
+/**
+ * Returns a printable name for the quirk. This name is for developer
+ * tools, not user consumption. Do not display this in a GUI.
+ */
+const char*
+quirk_get_name(enum quirk q);
+
+/**
+ * Log priorities used if custom logging is enabled.
+ */
+enum quirks_log_priorities {
+ QLOG_NOISE = LOG_DEBUG + 1,
+ QLOG_DEBUG = LOG_DEBUG,
+ QLOG_INFO = LOG_INFO,
+ QLOG_ERROR = LOG_ERR,
+ QLOG_PARSER_ERROR = LOG_CRIT,
+};
+
+/**
+ * Log type to be used for logging. Use the moused logging to hook up a
+ * moused log handler. This will cause the quirks to reduce the noise and
+ * only provide useful messages.
+ *
+ * QLOG_CUSTOM_LOG_PRIORITIES enables more fine-grained and verbose logging,
+ * allowing debugging tools to be more useful.
+ */
+enum quirks_log_type {
+ QLOG_MOUSED_LOGGING,
+ QLOG_CUSTOM_LOG_PRIORITIES,
+};
+
+/**
+ * Initialize the quirks subsystem. This function must be called
+ * before anything else.
+ *
+ * If log_type is QLOG_CUSTOM_LOG_PRIORITIES, the log handler is called with
+ * the custom QLOG_* log priorities. Otherwise, the log handler only uses
+ * the moused (syslog) log priorities.
+ *
+ * @param config_file A file path to main configuration file
+ * @param quirks_path The directory containing the various quirk files
+ * @param log_handler The moused log handler called for debugging output
+ *
+ * @return an opaque handle to the context
+ */
+struct quirks_context *
+quirks_init_subsystem(const char *config_file,
+ const char *quirks_path,
+ moused_log_handler log_handler,
+ enum quirks_log_type log_type);
+
+/**
+ * Clean up after ourselves. This function must be called
+ * as the last call to the quirks subsystem.
+ *
+ * All quirks returned to the caller in quirks_fetch_for_device() must be
+ * unref'd before this call.
+ *
+ * @return Always NULL
+ */
+struct quirks_context *
+quirks_context_unref(struct quirks_context *ctx);
+
+DEFINE_UNREF_CLEANUP_FUNC(quirks_context);
+
+struct quirks_context *
+quirks_context_ref(struct quirks_context *ctx);
+
+/**
+ * Fetch the quirks for a given device. If no quirks are defined, this
+ * function returns NULL.
+ *
+ * @return A new quirks struct, use quirks_unref() to release
+ */
+struct quirks *
+quirks_fetch_for_device(struct quirks_context *ctx,
+ struct device *device);
+
+/**
+ * Reduce the refcount by one. When the refcount reaches zero, the
+ * associated struct is released.
+ *
+ * @return Always NULL
+ */
+struct quirks *
+quirks_unref(struct quirks *q);
+
+DEFINE_UNREF_CLEANUP_FUNC(quirks);
+
+/**
+ * Returns true if the given quirk applies is in this quirk list.
+ */
+bool
+quirks_has_quirk(struct quirks *q, enum quirk which);
+
+/**
+ * Get the value of the given quirk, as unsigned integer.
+ * This function will assert if the quirk type does not match the
+ * requested type. If the quirk is not set for this device, val is
+ * unchanged.
+ *
+ * @return true if the quirk value is valid, false otherwise.
+ */
+bool
+quirks_get_uint32(struct quirks *q,
+ enum quirk which,
+ uint32_t *val);
+
+/**
+ * Get the value of the given quirk, as signed integer.
+ * This function will assert if the quirk type does not match the
+ * requested type. If the quirk is not set for this device, val is
+ * unchanged.
+ *
+ * @return true if the quirk value is valid, false otherwise.
+ */
+bool
+quirks_get_int32(struct quirks *q,
+ enum quirk which,
+ int32_t *val);
+
+/**
+ * Get the value of the given quirk, as double.
+ * This function will assert if the quirk type does not match the
+ * requested type. If the quirk is not set for this device, val is
+ * unchanged.
+ *
+ * @return true if the quirk value is valid, false otherwise.
+ */
+bool
+quirks_get_double(struct quirks *q,
+ enum quirk which,
+ double *val);
+
+/**
+ * Get the value of the given quirk, as string.
+ * This function will assert if the quirk type does not match the
+ * requested type. If the quirk is not set for this device, val is
+ * unchanged.
+ *
+ * val is set to the string, do not modify or free it. The lifetime of the
+ * returned string is bound to the lifetime of the quirk.
+ *
+ * @return true if the quirk value is valid, false otherwise.
+ */
+bool
+quirks_get_string(struct quirks *q,
+ enum quirk which,
+ char **val);
+
+/**
+ * Get the value of the given quirk, as bool.
+ * This function will assert if the quirk type does not match the
+ * requested type. If the quirk is not set for this device, val is
+ * unchanged.
+ *
+ * @return true if the quirk value is valid, false otherwise.
+ */
+bool
+quirks_get_bool(struct quirks *q,
+ enum quirk which,
+ bool *val);
+
+/**
+ * Get the value of the given quirk, as dimension.
+ * This function will assert if the quirk type does not match the
+ * requested type. If the quirk is not set for this device, val is
+ * unchanged.
+ *
+ * @return true if the quirk value is valid, false otherwise.
+ */
+bool
+quirks_get_dimensions(struct quirks *q,
+ enum quirk which,
+ struct quirk_dimensions *val);
+
+/**
+ * Get the value of the given quirk, as range.
+ * This function will assert if the quirk type does not match the
+ * requested type. If the quirk is not set for this device, val is
+ * unchanged.
+ *
+ * @return true if the quirk value is valid, false otherwise.
+ */
+bool
+quirks_get_range(struct quirks *q,
+ enum quirk which,
+ struct quirk_range *val);
+
+/**
+ * Get the tuples of the given quirk.
+ * This function will assert if the quirk type does not match the
+ * requested type. If the quirk is not set for this device, tuples is
+ * unchanged.
+ *
+ * @return true if the quirk value is valid, false otherwise.
+ */
+bool
+quirks_get_tuples(struct quirks *q,
+ enum quirk which,
+ const struct quirk_tuples **tuples);
+
+/**
+ * Get the uint32 array of the given quirk.
+ * This function will assert if the quirk type does not match the
+ * requested type. If the quirk is not set for this device, tuples is
+ * unchanged.
+ *
+ * @return true if the quirk value is valid, false otherwise.
+ */
+bool
+quirks_get_uint32_array(struct quirks *q,
+ enum quirk which,
+ const uint32_t **array,
+ size_t *nelements);
diff --git a/usr.sbin/moused/moused/quirks/5-generic-touchpad.quirks b/usr.sbin/moused/moused/quirks/5-generic-touchpad.quirks
new file mode 100644
index 000000000000..c741ffc80bd6
--- /dev/null
+++ b/usr.sbin/moused/moused/quirks/5-generic-touchpad.quirks
@@ -0,0 +1,9 @@
+# Do not edit this file, it will be overwritten on update
+
+[SynPS/2 Synaptics TouchPad]
+MatchDevType=touchpad
+MatchName=SynPS/2 Synaptics TouchPad
+AttrPressureRange=35:30
+MousedTapPressureThreshold=44
+#AttrThumbPressureThreshold=45
+AttrPalmPressureThreshold=220
diff --git a/usr.sbin/moused/moused/util-evdev.c b/usr.sbin/moused/moused/util-evdev.c
new file mode 100644
index 000000000000..925979fdc02c
--- /dev/null
+++ b/usr.sbin/moused/moused/util-evdev.c
@@ -0,0 +1,173 @@
+/*
+ * Copyright © 2013 David Herrmann <dh.herrmann@gmail.com>
+ * Copyright © 2013 Red Hat, Inc.
+ *
+ * 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 <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <dev/evdev/input.h>
+
+#include "event-names.h"
+#include "util-evdev.h"
+
+#define ARRAY_LENGTH(a) (sizeof(a) / (sizeof((a)[0])))
+
+struct name_lookup {
+ const char *name;
+ size_t len;
+};
+
+static inline bool
+startswith(const char *str, size_t len, const char *prefix, size_t plen)
+{
+ return len >= plen && !strncmp(str, prefix, plen);
+}
+
+static int type_from_prefix(const char *name, ssize_t len)
+{
+ const char *e;
+ size_t i;
+ ssize_t l;
+
+ /* MAX_ is not allowed, even though EV_MAX exists */
+ if (startswith(name, len, "MAX_", 4))
+ return -1;
+ /* BTN_ is special as there is no EV_BTN type */
+ if (startswith(name, len, "BTN_", 4))
+ return EV_KEY;
+ /* FF_STATUS_ is special as FF_ is a prefix of it, so test it first */
+ if (startswith(name, len, "FF_STATUS_", 10))
+ return EV_FF_STATUS;
+
+ for (i = 0; i < ARRAY_LENGTH(ev_names); ++i) {
+ /* skip EV_ prefix so @e is suffix of [EV_]XYZ */
+ e = &ev_names[i].name[3];
+ l = strlen(e);
+
+ /* compare prefix and test for trailing _ */
+ if (len > l && startswith(name, len, e, l) && name[l] == '_')
+ return ev_names[i].value;
+ }
+
+ return -1;
+}
+
+static int cmp_entry(const void *vlookup, const void *ventry)
+{
+ const struct name_lookup *lookup = vlookup;
+ const struct name_entry *entry = ventry;
+ int r;
+
+ r = strncmp(lookup->name, entry->name, lookup->len);
+ if (!r) {
+ if (entry->name[lookup->len])
+ r = -1;
+ else
+ r = 0;
+ }
+
+ return r;
+}
+
+static const struct name_entry*
+lookup_name(const struct name_entry *array, size_t asize,
+ struct name_lookup *lookup)
+{
+ const struct name_entry *entry;
+
+ entry = bsearch(lookup, array, asize, sizeof(*array), cmp_entry);
+ if (!entry)
+ return NULL;
+
+ return entry;
+}
+
+int
+libevdev_event_type_get_max(unsigned int type)
+{
+ if (type > EV_MAX)
+ return -1;
+
+ return ev_max[type];
+}
+
+int
+libevdev_event_code_from_name(unsigned int type, const char *name)
+{
+ struct name_lookup lookup;
+ const struct name_entry *entry;
+ int real_type;
+ size_t len = strlen(name);
+
+ real_type = type_from_prefix(name, len);
+ if (real_type < 0 || (unsigned int)real_type != type)
+ return -1;
+
+ lookup.name = name;
+ lookup.len = len;
+
+ entry = lookup_name(code_names, ARRAY_LENGTH(code_names), &lookup);
+
+ return entry ? (int)entry->value : -1;
+}
+
+static int
+libevdev_event_type_from_name_n(const char *name, size_t len)
+{
+ struct name_lookup lookup;
+ const struct name_entry *entry;
+
+ lookup.name = name;
+ lookup.len = len;
+
+ entry = lookup_name(ev_names, ARRAY_LENGTH(ev_names), &lookup);
+
+ return entry ? (int)entry->value : -1;
+}
+
+int
+libevdev_event_type_from_name(const char *name)
+{
+ return libevdev_event_type_from_name_n(name, strlen(name));
+}
+
+static int
+libevdev_property_from_name_n(const char *name, size_t len)
+{
+ struct name_lookup lookup;
+ const struct name_entry *entry;
+
+ lookup.name = name;
+ lookup.len = len;
+
+ entry = lookup_name(prop_names, ARRAY_LENGTH(prop_names), &lookup);
+
+ return entry ? (int)entry->value : -1;
+}
+
+int
+libevdev_property_from_name(const char *name)
+{
+ return libevdev_property_from_name_n(name, strlen(name));
+}
diff --git a/usr.sbin/moused/moused/util-evdev.h b/usr.sbin/moused/moused/util-evdev.h
new file mode 100644
index 000000000000..cb2e3f1fb935
--- /dev/null
+++ b/usr.sbin/moused/moused/util-evdev.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright © 2013 Red Hat, Inc.
+ *
+ * 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 UTIL_EVDEV_H
+#define UTIL_EVDEV_H
+
+#include <dev/evdev/input.h>
+
+int libevdev_event_code_from_name(unsigned int type, const char *name);
+int libevdev_event_type_get_max(unsigned int type);
+int libevdev_event_type_from_name(const char *name);
+int libevdev_property_from_name(const char *name);
+
+#endif
diff --git a/usr.sbin/moused/moused/util-list.c b/usr.sbin/moused/moused/util-list.c
new file mode 100644
index 000000000000..7f85b368076c
--- /dev/null
+++ b/usr.sbin/moused/moused/util-list.c
@@ -0,0 +1,86 @@
+/*
+ * Copyright © 2008-2011 Kristian Høgsberg
+ * Copyright © 2011 Intel Corporation
+ * Copyright © 2013-2015 Red Hat, Inc.
+ *
+ * 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 <assert.h>
+#include <stddef.h>
+#include <stdbool.h>
+
+#include "util-list.h"
+
+void
+list_init(struct list *list)
+{
+ list->prev = list;
+ list->next = list;
+}
+
+void
+list_insert(struct list *list, struct list *elm)
+{
+ assert((list->next != NULL && list->prev != NULL) ||
+ !"list->next|prev is NULL, possibly missing list_init()");
+ assert(((elm->next == NULL && elm->prev == NULL) || list_empty(elm)) ||
+ !"elm->next|prev is not NULL, list node used twice?");
+
+ elm->prev = list;
+ elm->next = list->next;
+ list->next = elm;
+ elm->next->prev = elm;
+}
+
+void
+list_append(struct list *list, struct list *elm)
+{
+ assert((list->next != NULL && list->prev != NULL) ||
+ !"list->next|prev is NULL, possibly missing list_init()");
+ assert(((elm->next == NULL && elm->prev == NULL) || list_empty(elm)) ||
+ !"elm->next|prev is not NULL, list node used twice?");
+
+ elm->next = list;
+ elm->prev = list->prev;
+ list->prev = elm;
+ elm->prev->next = elm;
+}
+
+void
+list_remove(struct list *elm)
+{
+ assert((elm->next != NULL && elm->prev != NULL) ||
+ !"list->next|prev is NULL, possibly missing list_init()");
+
+ elm->prev->next = elm->next;
+ elm->next->prev = elm->prev;
+ elm->next = NULL;
+ elm->prev = NULL;
+}
+
+bool
+list_empty(const struct list *list)
+{
+ assert((list->next != NULL && list->prev != NULL) ||
+ !"list->next|prev is NULL, possibly missing list_init()");
+
+ return list->next == list;
+}
diff --git a/usr.sbin/moused/moused/util-list.h b/usr.sbin/moused/moused/util-list.h
new file mode 100644
index 000000000000..d7a8ce724d22
--- /dev/null
+++ b/usr.sbin/moused/moused/util-list.h
@@ -0,0 +1,194 @@
+/*
+ * Copyright © 2008-2011 Kristian Høgsberg
+ * Copyright © 2011 Intel Corporation
+ * Copyright © 2013-2015 Red Hat, Inc.
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include <stdbool.h>
+#include <stddef.h>
+
+/*
+ * This list data structure is a verbatim copy from wayland-util.h from the
+ * Wayland project; except that wl_ prefix has been removed.
+ */
+
+
+/**
+ * Doubly linked list implementation. This struct is used for both the list
+ * nodes and the list head. Use like this:
+ *
+ * @code
+ *
+ * struct foo {
+ * struct list list_of_bars; // the list head
+ * };
+ *
+ * struct bar {
+ * struct list link; // links between the bars
+ * };
+ *
+ * struct foo *f = zalloc(sizeof *f);
+ * struct bar *b = make_some_bar();
+ *
+ * list_init(&f->list_of_bars);
+ * list_append(&f->list_of_bars, &b->link);
+ * list_remove(&b->link);
+ * @endcode
+ */
+struct list {
+ struct list *prev;
+ struct list *next;
+};
+
+/**
+ * Initialize a list head. This function *must* be called once for each list
+ * head. This function *must not* be called for a node to be added to a
+ * list.
+ */
+void list_init(struct list *list);
+
+/**
+ * Insert an element at the front of the list
+ */
+void list_insert(struct list *list, struct list *elm);
+/**
+ * Append an element to the back of the list
+ */
+void list_append(struct list *list, struct list *elm);
+
+/**
+ * Remove an element from list.
+ *
+ * Removing a list element is only possible once, the caller must track
+ * whether the list node has already been removed.
+ *
+ */
+void list_remove(struct list *elm);
+/**
+ * Returns true if the given list head is an empty list.
+ */
+bool list_empty(const struct list *list);
+
+/**
+ * Return the 'type' parent container struct of 'ptr' of which
+ * 'member' is our 'ptr' field. For example:
+ *
+ * @code
+ * struct foo { // the parent container struct
+ * uint32_t a;
+ * struct bar bar_member; // the member field
+ * };
+ *
+ * struct foo *f = zalloc(sizeof *f);
+ * struct bar *b = &f->bar_member;
+ * struct foo *f2 = container_of(b, struct foo, bar_member);
+ *
+ * assert(f == f2);
+ * @endcode
+ */
+#define container_of(ptr, type, member) \
+ (__typeof__(type) *)((char *)(ptr) - \
+ offsetof(__typeof__(type), member))
+
+/**
+ * Given a list 'head', return the first entry of type 'pos' that has a
+ * member 'link'.
+ *
+ * The 'pos' argument is solely used to determine the type be returned and
+ * not modified otherwise. It is common to use the same pointer that the
+ * return value of list_first_entry() is assigned to, for example:
+ *
+ * @code
+ * struct foo {
+ * struct list list_of_bars;
+ * };
+ *
+ * struct bar {
+ * struct list link;
+ * }
+ *
+ * struct foo *f = get_a_foo();
+ * struct bar *b = 0; // initialize to avoid static analysis errors
+ * b = list_first_entry(&f->list_of_bars, b, link);
+ * @endcode
+ */
+#define list_first_entry(head, pointer_of_type, member) \
+ container_of((head)->next, __typeof__(*pointer_of_type), member)
+
+/**
+ * Given a list 'head', return the first entry of type 'container_type' that
+ * has a member 'link'.
+ *
+ * @code
+ * struct foo {
+ * struct list list_of_bars;
+ * };
+ *
+ * struct bar {
+ * struct list link;
+ * }
+ *
+ * struct foo *f = get_a_foo();
+ * struct bar *b = list_first_entry(&f->list_of_bars, struct bar, link);
+ * @endcode
+ */
+#define list_first_entry_by_type(head, container_type, member) \
+ container_of((head)->next, container_type, member)
+
+/**
+ * Iterate through the list.
+ *
+ * @code
+ * struct foo *f = get_a_foo();
+ * struct bar *element;
+ * list_for_each(element, &f->list_of_bars, link) {
+ * }
+ * @endcode
+ *
+ * If a list node needs to be removed during iteration, use
+ * list_for_each_safe().
+ */
+#define list_for_each(pos, head, member) \
+ for (pos = list_first_entry_by_type(head, __typeof__(*pos), member); \
+ &pos->member != (head); \
+ pos = list_first_entry_by_type(&pos->member, __typeof__(*pos), member))
+
+/**
+ * Iterate through the list. Equivalent to list_for_each() but allows
+ * calling list_remove() on the element.
+ *
+ * @code
+ * struct foo *f = get_a_foo();
+ * struct bar *element;
+ * list_for_each(element, tmp, &f->list_of_bars, link) {
+ * list_remove(&element->link);
+ * }
+ * @endcode
+ */
+#define list_for_each_safe(pos, head, member) \
+ pos = list_first_entry_by_type(head, __typeof__(*pos), member); \
+ for (__typeof__(pos) _tmp = list_first_entry_by_type(&pos->member, __typeof__(*_tmp), member); \
+ &pos->member != (head); \
+ pos = _tmp, \
+ _tmp = list_first_entry_by_type(&pos->member, __typeof__(*_tmp), member))
diff --git a/usr.sbin/moused/moused/util.c b/usr.sbin/moused/moused/util.c
new file mode 100644
index 000000000000..09bcc42b0f19
--- /dev/null
+++ b/usr.sbin/moused/moused/util.c
@@ -0,0 +1,423 @@
+/*
+ * Copyright © 2008 Kristian Høgsberg
+ * Copyright © 2013-2019 Red Hat, Inc.
+ *
+ * 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 <sys/types.h>
+#include <dev/evdev/input.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <limits.h>
+#include <math.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <xlocale.h>
+
+#include "util.h"
+#include "util-evdev.h"
+#include "util-list.h"
+
+/* util-strings.c */
+
+/**
+ * Return the next word in a string pointed to by state before the first
+ * separator character. Call repeatedly to tokenize a whole string.
+ *
+ * @param state Current state
+ * @param len String length of the word returned
+ * @param separators List of separator characters
+ *
+ * @return The first word in *state, NOT null-terminated
+ */
+static const char *
+next_word(const char **state, size_t *len, const char *separators)
+{
+ const char *next = *state;
+ size_t l;
+
+ if (!*next)
+ return NULL;
+
+ next += strspn(next, separators);
+ if (!*next) {
+ *state = next;
+ return NULL;
+ }
+
+ l = strcspn(next, separators);
+ *state = next + l;
+ *len = l;
+
+ return next;
+}
+
+/**
+ * Return a null-terminated string array with the tokens in the input
+ * string, e.g. "one two\tthree" with a separator list of " \t" will return
+ * an array [ "one", "two", "three", NULL ] and num elements 3.
+ *
+ * Use strv_free() to free the array.
+ *
+ * Another example:
+ * result = strv_from_string("+1-2++3--4++-+5-+-", "+-", &nelem)
+ * result == [ "1", "2", "3", "4", "5", NULL ] and nelem == 5
+ *
+ * @param in Input string
+ * @param separators List of separator characters
+ * @param num_elements Number of elements found in the input string
+ *
+ * @return A null-terminated string array or NULL on errors
+ */
+char **
+strv_from_string(const char *in, const char *separators, size_t *num_elements)
+{
+ assert(in != NULL);
+ assert(separators != NULL);
+ assert(num_elements != NULL);
+
+ const char *s = in;
+ size_t l, nelems = 0;
+ while (next_word(&s, &l, separators) != NULL)
+ nelems++;
+
+ if (nelems == 0) {
+ *num_elements = 0;
+ return NULL;
+ }
+
+ size_t strv_len = nelems + 1; /* NULL-terminated */
+ char **strv = zalloc(strv_len * sizeof *strv);
+
+ size_t idx = 0;
+ const char *word;
+ s = in;
+ while ((word = next_word(&s, &l, separators)) != NULL) {
+ char *copy = strndup(word, l);
+ if (!copy) {
+ strv_free(strv);
+ *num_elements = 0;
+ return NULL;
+ }
+
+ strv[idx++] = copy;
+ }
+
+ *num_elements = nelems;
+
+ return strv;
+}
+
+/**
+ * Iterate through strv, calling func with each string and its respective index.
+ * Iteration stops successfully after max elements or at the last element,
+ * whichever occurs first.
+ *
+ * If func returns non-zero, iteration stops and strv_for_each returns
+ * that value.
+ *
+ * @return zero on success, otherwise the error returned by the callback
+ */
+int strv_for_each_n(const char **strv, size_t max, strv_foreach_callback_t func, void *data)
+{
+ for (size_t i = 0; i < max && strv && strv[i]; i++) {
+ int ret = func(strv[i], i, data);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+/* !util-strings.c */
+
+/* util-prop-parsers.c */
+
+/**
+ * Parses a simple dimension string in the form of "10x40". The two
+ * numbers must be positive integers in decimal notation.
+ * On success, the two numbers are stored in w and h. On failure, w and h
+ * are unmodified.
+ *
+ * @param prop The value of the property
+ * @param w Returns the first component of the dimension
+ * @param h Returns the second component of the dimension
+ * @return true on success, false otherwise
+ */
+bool
+parse_dimension_property(const char *prop, size_t *w, size_t *h)
+{
+ int x, y;
+
+ if (!prop)
+ return false;
+
+ if (sscanf(prop, "%dx%d", &x, &y) != 2)
+ return false;
+
+ if (x <= 0 || y <= 0)
+ return false;
+
+ *w = (size_t)x;
+ *h = (size_t)y;
+ return true;
+}
+
+/**
+ * Parses a string of the format "a:b" where both a and b must be integer
+ * numbers and a > b. Also allowed is the special string value "none" which
+ * amounts to unsetting the property.
+ *
+ * @param prop The value of the property
+ * @param hi Set to the first digit or 0 in case of 'none'
+ * @param lo Set to the second digit or 0 in case of 'none'
+ * @return true on success, false otherwise
+ */
+bool
+parse_range_property(const char *prop, int *hi, int *lo)
+{
+ int first, second;
+
+ if (!prop)
+ return false;
+
+ if (streq(prop, "none")) {
+ *hi = 0;
+ *lo = 0;
+ return true;
+ }
+
+ if (sscanf(prop, "%d:%d", &first, &second) != 2)
+ return false;
+
+ if (second >= first)
+ return false;
+
+ *hi = first;
+ *lo = second;
+
+ return true;
+}
+
+bool
+parse_boolean_property(const char *prop, bool *b)
+{
+ if (!prop)
+ return false;
+
+ if (streq(prop, "1"))
+ *b = true;
+ else if (streq(prop, "0"))
+ *b = false;
+ else
+ return false;
+
+ return true;
+}
+
+static bool
+parse_evcode_string(const char *s, int *type_out, int *code_out)
+{
+ int type, code;
+
+ if (strstartswith(s, "EV_")) {
+ type = libevdev_event_type_from_name(s);
+ if (type == -1)
+ return false;
+
+ code = EVENT_CODE_UNDEFINED;
+ } else {
+ struct map {
+ const char *str;
+ int type;
+ } map[] = {
+ { "KEY_", EV_KEY },
+ { "BTN_", EV_KEY },
+ { "ABS_", EV_ABS },
+ { "REL_", EV_REL },
+ { "SW_", EV_SW },
+ };
+ bool found = false;
+
+ ARRAY_FOR_EACH(map, m) {
+ if (!strstartswith(s, m->str))
+ continue;
+
+ type = m->type;
+ code = libevdev_event_code_from_name(type, s);
+ if (code == -1)
+ return false;
+
+ found = true;
+ break;
+ }
+ if (!found)
+ return false;
+ }
+
+ *type_out = type;
+ *code_out = code;
+
+ return true;
+}
+
+/**
+ * Parses a string of the format "+EV_ABS;+KEY_A;-BTN_TOOL_DOUBLETAP;-ABS_X;"
+ * where each element must be + or - (enable/disable) followed by a named event
+ * type OR a named event code OR a tuple in the form of EV_KEY:0x123, i.e. a
+ * named event type followed by a hex event code.
+ *
+ * events must point to an existing array of size nevents.
+ * nevents specifies the size of the array in events and returns the number
+ * of items, elements exceeding nevents are simply ignored, just make sure
+ * events is large enough for your use-case.
+ *
+ * The results are returned as input events with type and code set, all
+ * other fields undefined. Where only the event type is specified, the code
+ * is set to EVENT_CODE_UNDEFINED.
+ *
+ * On success, events contains nevents events with each event's value set to 1
+ * or 0 depending on the + or - prefix.
+ */
+bool
+parse_evcode_property(const char *prop, struct input_event *events, size_t *nevents)
+{
+ bool rc = false;
+ /* A randomly chosen max so we avoid crazy quirks */
+ struct input_event evs[32];
+
+ memset(evs, 0, sizeof evs);
+
+ size_t ncodes;
+ char **strv = strv_from_string(prop, ";", &ncodes);
+ if (!strv || ncodes == 0 || ncodes > ARRAY_LENGTH(evs))
+ goto out;
+
+ ncodes = min(*nevents, ncodes);
+ for (size_t idx = 0; strv[idx]; idx++) {
+ char *s = strv[idx];
+ bool enable;
+
+ switch (*s) {
+ case '+': enable = true; break;
+ case '-': enable = false; break;
+ default:
+ goto out;
+ }
+
+ s++;
+
+ int type, code;
+
+ if (strstr(s, ":") == NULL) {
+ if (!parse_evcode_string(s, &type, &code))
+ goto out;
+ } else {
+ int consumed;
+ char stype[13] = {0}; /* EV_FF_STATUS + '\0' */
+
+ if (sscanf(s, "%12[A-Z_]:%x%n", stype, &code, &consumed) != 2 ||
+ strlen(s) != (size_t)consumed ||
+ (type = libevdev_event_type_from_name(stype)) == -1 ||
+ code < 0 || code > libevdev_event_type_get_max(type))
+ goto out;
+ }
+
+ evs[idx].type = type;
+ evs[idx].code = code;
+ evs[idx].value = enable;
+ }
+
+ memcpy(events, evs, ncodes * sizeof *events);
+ *nevents = ncodes;
+ rc = true;
+
+out:
+ strv_free(strv);
+ return rc;
+}
+
+/**
+ * Parses a string of the format "+INPUT_PROP_BUTTONPAD;-INPUT_PROP_POINTER;+0x123;"
+ * where each element must be a named input prop OR a hexcode in the form
+ * 0x1234. The prefix for each element must be either '+' (enable) or '-' (disable).
+ *
+ * props must point to an existing array of size nprops.
+ * nprops specifies the size of the array in props and returns the number
+ * of elements, elements exceeding nprops are simply ignored, just make sure
+ * props is large enough for your use-case.
+ *
+ * On success, props contains nprops elements.
+ */
+bool
+parse_input_prop_property(const char *prop, struct input_prop *props_out, size_t *nprops)
+{
+ bool rc = false;
+ struct input_prop props[INPUT_PROP_CNT]; /* doubling up on quirks is a bug */
+
+ size_t count;
+ char **strv = strv_from_string(prop, ";", &count);
+ if (!strv || count == 0 || count > ARRAY_LENGTH(props))
+ goto out;
+
+ count = min(*nprops, count);
+ for (size_t idx = 0; strv[idx]; idx++) {
+ char *s = strv[idx];
+ unsigned int prop;
+ bool enable;
+
+ switch (*s) {
+ case '+': enable = true; break;
+ case '-': enable = false; break;
+ default:
+ goto out;
+ }
+
+ s++;
+
+ if (safe_atou_base(s, &prop, 16)) {
+ if (prop > INPUT_PROP_MAX)
+ goto out;
+ } else {
+ int val = libevdev_property_from_name(s);
+ if (val == -1)
+ goto out;
+ prop = (unsigned int)val;
+ }
+ props[idx].prop = prop;
+ props[idx].enabled = enable;
+ }
+
+ memcpy(props_out, props, count * sizeof *props);
+ *nprops = count;
+ rc = true;
+
+out:
+ strv_free(strv);
+ return rc;
+}
+
+/* !util-prop-parsers.c */
diff --git a/usr.sbin/moused/moused/util.h b/usr.sbin/moused/moused/util.h
new file mode 100644
index 000000000000..a359cbc1079a
--- /dev/null
+++ b/usr.sbin/moused/moused/util.h
@@ -0,0 +1,413 @@
+/*
+ * Copyright © 2008-2011 Kristian Høgsberg
+ * Copyright © 2011 Intel Corporation
+ * Copyright © 2013-2015 Red Hat, Inc.
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include <sys/types.h>
+#include <sys/mouse.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+#include <xlocale.h>
+
+#define HAVE_LOCALE_H 1
+
+#define MOUSED_ATTRIBUTE_PRINTF(_format, _args) \
+ __attribute__ ((format (printf, _format, _args)))
+
+#define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0])
+/**
+ * Iterate through the array _arr, assigning the variable elem to each
+ * element. elem only exists within the loop.
+ */
+#define ARRAY_FOR_EACH(_arr, _elem) \
+ for (__typeof__((_arr)[0]) *_elem = _arr; \
+ _elem < (_arr) + ARRAY_LENGTH(_arr); \
+ _elem++)
+
+#define versionsort(...) alphasort(__VA_ARGS__)
+#define bit(x_) (1UL << (x_))
+#define min(a, b) (((a) < (b)) ? (a) : (b))
+
+/* Supported device interfaces */
+enum device_if {
+ DEVICE_IF_UNKNOWN = -1,
+ DEVICE_IF_EVDEV = 0,
+ DEVICE_IF_SYSMOUSE,
+};
+
+/* Recognized device types */
+enum device_type {
+ DEVICE_TYPE_UNKNOWN = -1,
+ DEVICE_TYPE_MOUSE = 0,
+ DEVICE_TYPE_POINTINGSTICK,
+ DEVICE_TYPE_TOUCHPAD,
+ DEVICE_TYPE_TOUCHSCREEN,
+ DEVICE_TYPE_TABLET,
+ DEVICE_TYPE_TABLET_PAD,
+ DEVICE_TYPE_KEYBOARD,
+ DEVICE_TYPE_JOYSTICK,
+};
+
+struct device {
+ char path[80];
+ enum device_if iftype;
+ enum device_type type;
+ char name[80];
+ char uniq[80];
+ struct input_id id;
+ mousemode_t mode;
+};
+
+/**
+ * @ingroup base
+ *
+ * Log handler type for custom logging.
+ *
+ * @param priority The priority of the current message
+ * @param format Message format in printf-style
+ * @param args Message arguments
+ */
+typedef void moused_log_handler(int priority, int errnum,
+ const char *format, va_list args);
+
+/* util-mem.h */
+
+/**
+ * Use: _unref_(foo) struct foo *bar;
+ *
+ * This requires foo_unrefp() to be present, use DEFINE_UNREF_CLEANUP_FUNC.
+ */
+#define _unref_(_type) __attribute__((cleanup(_type##_unrefp))) struct _type
+
+/**
+ * Define a cleanup function for the struct type foo with a matching
+ * foo_unref(). Use:
+ * DEFINE_UNREF_CLEANUP_FUNC(foo)
+ * _unref_(foo) struct foo *bar;
+ */
+#define DEFINE_UNREF_CLEANUP_FUNC(_type) \
+ static inline void _type##_unrefp(struct _type **_p) { \
+ if (*_p) \
+ _type##_unref(*_p); \
+ } \
+ struct __useless_struct_to_allow_trailing_semicolon__
+
+static inline void*
+_steal(void *ptr) {
+ void **original = (void**)ptr;
+ void *swapped = *original;
+ *original = NULL;
+ return swapped;
+}
+
+/**
+ * Resets the pointer content and resets the data to NULL.
+ * This circumvents _cleanup_ handling for that pointer.
+ * Use:
+ * _cleanup_free_ char *data = malloc();
+ * return steal(&data);
+ *
+ */
+#define steal(ptr_) \
+ (typeof(*ptr_))_steal(ptr_)
+
+/* ! util-mem.h */
+
+/* util-strings.h */
+
+static inline bool
+streq(const char *str1, const char *str2)
+{
+ /* one NULL, one not NULL is always false */
+ if (str1 && str2)
+ return strcmp(str1, str2) == 0;
+ return str1 == str2;
+}
+
+static inline bool
+strneq(const char *str1, const char *str2, int n)
+{
+ /* one NULL, one not NULL is always false */
+ if (str1 && str2)
+ return strncmp(str1, str2, n) == 0;
+ return str1 == str2;
+}
+
+static inline void *
+zalloc(size_t size)
+{
+ void *p;
+
+ /* We never need to alloc anything more than 1,5 MB so we can assume
+ * if we ever get above that something's going wrong */
+ if (size > 1536 * 1024)
+ assert(!"bug: internal malloc size limit exceeded");
+
+ p = calloc(1, size);
+ if (!p)
+ abort();
+
+ return p;
+}
+
+/**
+ * strdup guaranteed to succeed. If the input string is NULL, the output
+ * string is NULL. If the input string is a string pointer, we strdup or
+ * abort on failure.
+ */
+static inline char*
+safe_strdup(const char *str)
+{
+ char *s;
+
+ if (!str)
+ return NULL;
+
+ s = strdup(str);
+ if (!s)
+ abort();
+ return s;
+}
+
+/**
+ * Simple wrapper for asprintf that ensures the passed in-pointer is set
+ * to NULL upon error.
+ * The standard asprintf() call does not guarantee the passed in pointer
+ * will be NULL'ed upon failure, whereas this wrapper does.
+ *
+ * @param strp pointer to set to newly allocated string.
+ * This pointer should be passed to free() to release when done.
+ * @param fmt the format string to use for printing.
+ * @return The number of bytes printed (excluding the null byte terminator)
+ * upon success or -1 upon failure. In the case of failure the pointer is set
+ * to NULL.
+ */
+__attribute__ ((format (printf, 2, 3)))
+static inline int
+xasprintf(char **strp, const char *fmt, ...)
+{
+ int rc = 0;
+ va_list args;
+
+ va_start(args, fmt);
+ rc = vasprintf(strp, fmt, args);
+ va_end(args);
+ if ((rc == -1) && strp)
+ *strp = NULL;
+
+ return rc;
+}
+
+static inline bool
+safe_atoi_base(const char *str, int *val, int base)
+{
+ assert(str != NULL);
+
+ char *endptr;
+ long v;
+
+ assert(base == 10 || base == 16 || base == 8);
+
+ errno = 0;
+ v = strtol(str, &endptr, base);
+ if (errno > 0)
+ return false;
+ if (str == endptr)
+ return false;
+ if (*str != '\0' && *endptr != '\0')
+ return false;
+
+ if (v > INT_MAX || v < INT_MIN)
+ return false;
+
+ *val = v;
+ return true;
+}
+
+static inline bool
+safe_atoi(const char *str, int *val)
+{
+ assert(str != NULL);
+ return safe_atoi_base(str, val, 10);
+}
+
+static inline bool
+safe_atou_base(const char *str, unsigned int *val, int base)
+{
+ assert(str != NULL);
+
+ char *endptr;
+ unsigned long v;
+
+ assert(base == 10 || base == 16 || base == 8);
+
+ errno = 0;
+ v = strtoul(str, &endptr, base);
+ if (errno > 0)
+ return false;
+ if (str == endptr)
+ return false;
+ if (*str != '\0' && *endptr != '\0')
+ return false;
+
+ if ((long)v < 0)
+ return false;
+
+ *val = v;
+ return true;
+}
+
+static inline bool
+safe_atou(const char *str, unsigned int *val)
+{
+ assert(str != NULL);
+ return safe_atou_base(str, val, 10);
+}
+
+static inline bool
+safe_atod(const char *str, double *val)
+{
+ assert(str != NULL);
+
+ char *endptr;
+ double v;
+ size_t slen = strlen(str);
+
+ /* We don't have a use-case where we want to accept hex for a double
+ * or any of the other values strtod can parse */
+ for (size_t i = 0; i < slen; i++) {
+ char c = str[i];
+
+ if (isdigit(c))
+ continue;
+ switch(c) {
+ case '+':
+ case '-':
+ case '.':
+ break;
+ default:
+ return false;
+ }
+ }
+
+#ifdef HAVE_LOCALE_H
+ /* Create a "C" locale to force strtod to use '.' as separator */
+ locale_t c_locale = newlocale(LC_NUMERIC_MASK, "C", (locale_t)0);
+ if (c_locale == (locale_t)0)
+ return false;
+
+ errno = 0;
+ v = strtod_l(str, &endptr, c_locale);
+ freelocale(c_locale);
+#else
+ /* No locale support in provided libc, assume it already uses '.' */
+ errno = 0;
+ v = strtod(str, &endptr);
+#endif
+ if (errno > 0)
+ return false;
+ if (str == endptr)
+ return false;
+ if (*str != '\0' && *endptr != '\0')
+ return false;
+ if (v != 0.0 && !isnormal(v))
+ return false;
+
+ *val = v;
+ return true;
+}
+
+char **strv_from_string(const char *in, const char *separator, size_t *num_elements);
+
+typedef int (*strv_foreach_callback_t)(const char *str, size_t index, void *data);
+int strv_for_each_n(const char **strv, size_t max, strv_foreach_callback_t func, void *data);
+
+static inline void
+strv_free(char **strv) {
+ char **s = strv;
+
+ if (!strv)
+ return;
+
+ while (*s != NULL) {
+ free(*s);
+ *s = (char*)0x1; /* detect use-after-free */
+ s++;
+ }
+
+ free (strv);
+}
+
+/**
+ * Return true if str ends in suffix, false otherwise. If the suffix is the
+ * empty string, strendswith() always returns false.
+ */
+static inline bool
+strendswith(const char *str, const char *suffix)
+{
+ if (str == NULL)
+ return false;
+
+ size_t slen = strlen(str);
+ size_t suffixlen = strlen(suffix);
+ size_t offset;
+
+ if (slen == 0 || suffixlen == 0 || suffixlen > slen)
+ return false;
+
+ offset = slen - suffixlen;
+ return strneq(&str[offset], suffix, suffixlen);
+}
+
+static inline bool
+strstartswith(const char *str, const char *prefix)
+{
+ if (str == NULL)
+ return false;
+
+ size_t prefixlen = strlen(prefix);
+
+ return prefixlen > 0 ? strneq(str, prefix, strlen(prefix)) : false;
+}
+
+/* !util-strings.h */
+
+/* util-prop-parsers.h */
+
+struct input_prop {
+ unsigned int prop;
+ bool enabled;
+};
+
+bool parse_dimension_property(const char *prop, size_t *w, size_t *h);
+bool parse_range_property(const char *prop, int *hi, int *lo);
+bool parse_boolean_property(const char *prop, bool *b);
+#define EVENT_CODE_UNDEFINED 0xffff
+bool parse_evcode_property(const char *prop, struct input_event *events, size_t *nevents);
+bool parse_input_prop_property(const char *prop, struct input_prop *props_out, size_t *nprops);
+
+/* !util-prop-parsers.h */
diff --git a/usr.sbin/moused/msconvd/Makefile b/usr.sbin/moused/msconvd/Makefile
new file mode 100644
index 000000000000..6ea5eee7db3b
--- /dev/null
+++ b/usr.sbin/moused/msconvd/Makefile
@@ -0,0 +1,8 @@
+PACKAGE= console-tools
+PROG= msconvd
+SRCS= ${PROG}.c
+LIBADD= util
+BINDIR= /usr/sbin
+MAN= ${PROG}.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/moused/moused.8 b/usr.sbin/moused/msconvd/msconvd.8
index cd5d8ddde339..17434ecb9b60 100644
--- a/usr.sbin/moused/moused.8
+++ b/usr.sbin/moused/msconvd/msconvd.8
@@ -31,30 +31,21 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd July 7, 2024
-.Dt MOUSED 8
+.Dd May 18, 2025
+.Dt MSCONVD 8
.Os
.Sh NAME
-.Nm moused
-.Nd pass mouse data to the system video console driver
+.Nm msconvd
+.Nd mouse protocol conversion daemon
.Sh SYNOPSIS
.Nm
-.Op Fl DPRacdfs
+.Op Fl DPRcdfs
.Op Fl I Ar file
.Op Fl F Ar rate
.Op Fl r Ar resolution
.Op Fl S Ar baudrate
-.Op Fl VH Op Fl U Ar distance Fl L Ar distance
-.Op Fl A Ar exp Ns Op , Ns Ar offset
-.Op Fl a Ar X Ns Op , Ns Ar Y
-.Op Fl C Ar threshold
-.Op Fl m Ar N=M
-.Op Fl w Ar N
-.Op Fl z Ar target
.Op Fl t Ar mousetype
.Op Fl l Ar level
-.Op Fl 3 Op Fl E Ar timeout
-.Op Fl T Ar distance Ns Op , Ns Ar time Ns Op , Ns Ar after
.Fl p Ar port
.Pp
.Nm
@@ -64,38 +55,41 @@
.Sh DESCRIPTION
The
.Nm
-utility and the console driver work together to support
-mouse operation in the text console and user programs.
-They virtualize the mouse and provide user programs with mouse data
-in the standard format
-(see
-.Xr sysmouse 4 ) .
-.Pp
-The mouse daemon listens to the specified port for mouse data,
-interprets and then passes it via ioctls to the console driver.
-The mouse daemon
-reports translation movement, button press/release
+utility and the
+.Xr moused 8
+driver work together to support legacy devices like COM,
+.Xr ams 4
+mices and X10 remotes as well.
+.Xr psm 4 ,
+.Xr ums 4
+and some other devices are supported too but not recomended to use with
+.Nm
+unless kernel is compiled without
+.Dq option EVDEV_SUPPORT .
+The
+.Nm
+listens to the specified port for mouse data, decodes and then passes
+it via input event device a.k.a evdev to consumer aplications like
+.Xr moused 8
+or
+.Xr libinput 1 .
+It does not display the mouse pointer on the screen or provide cut and
+paste functions.
+The msconv daemon converts translation movement, button press/release
events and movement of the roller or the wheel if available.
-The roller/wheel movement is reported as
-.Dq Z
-axis movement.
-.Pp
-The console driver will display the mouse pointer on the screen
-and provide cut and paste functions if the mouse pointer is enabled
-in the virtual console via
-.Xr vidcontrol 1 .
-If
-.Xr sysmouse 4
-is opened by the user program, the console driver also passes the mouse
-data to the device so that the user program will see it.
-.Pp
-If the mouse daemon receives the signal
+.Pp
+.Pp
+If the
+.Nm
+receives the signal
.Dv SIGHUP ,
it will reopen the mouse port and reinitialize itself.
Useful if
the mouse is attached/detached while the system is suspended.
.Pp
-If the mouse daemon receives the signal
+If the
+.Nm
+receives the signal
.Dv SIGUSR1 ,
it will stop passing mouse events.
Sending the signal
@@ -106,19 +100,6 @@ interrupted by accidentally touching the mouse pad.
.Pp
The following options are available:
.Bl -tag -width indent
-.It Fl 3
-Emulate the third (middle) button for 2-button mice.
-It is emulated
-by pressing the left and right physical buttons simultaneously.
-.It Fl C Ar threshold
-Set double click speed as the maximum interval in msec between button clicks.
-Without this option, the default value of 500 msec will be assumed.
-This option will have effect only on the cut and paste operations
-in the text mode console.
-The user program which is reading mouse data
-via
-.Xr sysmouse 4
-will not be affected.
.It Fl D
Lower DTR on the serial port.
This option is valid only if
@@ -128,46 +109,8 @@ The DTR line may need to be dropped for a 3-button mouse
to operate in the
.Ar mousesystems
mode.
-.It Fl E Ar timeout
-When the third button emulation is enabled
-(see above),
-the
-.Nm
-utility waits
-.Ar timeout
-msec at most before deciding whether two buttons are being pressed
-simultaneously.
-The default timeout is 100 msec.
.It Fl F Ar rate
Set the report rate (reports/sec) of the device if supported.
-.It Fl L Ar distance
-When
-.Dq Virtual Scrolling
-is enabled, the
-.Fl L
-option can be used to set the
-.Ar distance
-(in pixels) that the mouse must move before a scroll event
-is generated.
-This effectively controls the scrolling speed.
-The default
-.Ar distance
-is 2 pixels.
-.It Fl H
-Enable
-.Dq Horizontal Virtual Scrolling .
-With this option set, holding the middle mouse
-button down will cause motion to be interpreted as
-horizontal scrolling.
-Use the
-.Fl U
-option to set the distance the mouse must move before the scrolling mode is
-activated and the
-.Fl L
-option to set the scrolling speed.
-This option may be used with or without the
-.Fl V
-option.
.It Fl I Ar file
Write the process id of the
.Nm
@@ -199,89 +142,6 @@ mode.
.It Fl S Ar baudrate
Select the baudrate for the serial port (1200 to 9600).
Not all serial mice support this option.
-.It Fl T Ar distance Ns Op , Ns Ar time Ns Op , Ns Ar after
-Terminate drift.
-Use this option if mouse pointer slowly wanders when mouse is not moved.
-Movements up to
-.Ar distance
-(for example 4) pixels (X+Y) in
-.Ar time
-msec (default 500) are ignored, except during
-.Ar after
-msec (default 4000) since last real mouse movement.
-.It Fl V
-Enable
-.Dq Virtual Scrolling .
-With this option set, holding the middle mouse
-button down will cause motion to be interpreted as scrolling.
-Use the
-.Fl U
-option to set the distance the mouse must move before the scrolling mode is
-activated and the
-.Fl L
-option to set the scrolling speed.
-.It Fl U Ar distance
-When
-.Dq Virtual Scrolling
-is enabled, the
-.Fl U
-option can be used to set the
-.Ar distance
-(in pixels) that the mouse must move before the scrolling
-mode is activated.
-The default
-.Ar distance
-is 3 pixels.
-.It Fl A Ar exp Ns Op , Ns Ar offset
-Apply exponential (dynamic) acceleration to mouse movements:
-the faster you move the mouse, the more it will be accelerated.
-That means that small mouse movements are not accelerated,
-so they are still very accurate, while a faster movement will
-drive the pointer quickly across the screen.
-.Pp
-The
-.Ar exp
-value specifies the exponent, which is basically
-the amount of acceleration.
-Useful values are in the range 1.1 to 2.0, but it depends on
-your mouse hardware and your personal preference.
-A value of 1.0 means no exponential acceleration.
-A value of 2.0 means squared acceleration (i.e. if
-you move the mouse twice as fast, the pointer will move
-four times as fast on the screen).
-Values beyond 2.0 are possible but not recommended.
-A good value to start is probably 1.5.
-.Pp
-The optional
-.Ar offset
-value specifies the distance at which the acceleration begins.
-The default is 1.0, which means that the acceleration is applied
-to movements larger than one unit.
-If you specify a larger value, it takes more speed for
-the acceleration to kick in, i.e. the speed range for
-small and accurate movements is wider.
-Usually the default should be sufficient, but if you're
-not satisfied with the behaviour, try a value of 2.0.
-.Pp
-Note that the
-.Fl A
-option interacts badly with the X server's own acceleration,
-which doesn't work very well anyway.
-Therefore it is recommended to switch it off if necessary:
-.Dq xset m 1 .
-.It Fl a Ar X Ns Op , Ns Ar Y
-Accelerate or decelerate the mouse input.
-This is a linear acceleration only.
-Values less than 1.0 slow down movement, values greater than 1.0 speed it
-up.
-Specifying only one value sets the acceleration for both axes.
-.Pp
-You can use the
-.Fl a
-and
-.Fl A
-options at the same time to have the combined effect
-of linear and exponential acceleration.
.It Fl c
Some mice report middle button down events
as if the left and right buttons are being pressed.
@@ -339,18 +199,6 @@ Refer to
in
.Xr psm 4
for more information on this.
-.It Fl m Ar N=M
-Assign the physical button
-.Ar M
-to the logical button
-.Ar N .
-You may specify as many instances of this option as you like.
-More than one physical button may be assigned to a logical button at the
-same time.
-In this case the logical button will be down,
-if either of the assigned physical buttons is held down.
-Do not put space around
-.Ql = .
.It Fl p Ar port
Use
.Ar port
@@ -382,7 +230,7 @@ you need to use this option only if the
.Nm
utility is not able to detect the protocol automatically
(see
-.Sx "Configuring Mouse Daemon" ) .
+.Sx "Configuring Mouse Protocol Conversion Daemon" ) .
.Pp
Note that if a protocol type is specified with this option, the
.Fl P
@@ -475,73 +323,9 @@ For the USB mouse,
.Ar auto
is the only protocol type available for the USB mouse
and should be specified for any USB mice, regardless of the brand.
-.It Fl w Ar N
-Make the physical button
-.Ar N
-act as the wheel mode button.
-While this button is pressed, X and Y axis movement is reported to be zero
-and the Y axis movement is mapped to Z axis.
-You may further map the Z axis movement to virtual buttons by the
-.Fl z
-option below.
-.It Fl z Ar target
-Map Z axis (roller/wheel) movement to another axis or to virtual buttons.
-Valid
-.Ar target
-maybe:
-.Bl -tag -compact -width x__
-.It Ar x
-.It Ar y
-X or Y axis movement will be reported when the Z axis movement is detected.
-.It Ar N
-Report down events for the virtual buttons
-.Ar N
-and
-.Ar N+1
-respectively when negative and positive Z axis movement
-is detected.
-There do not need to be physical buttons
-.Ar N
-and
-.Ar N+1 .
-Note that mapping to logical buttons is carried out after mapping
-from the Z axis movement to the virtual buttons is done.
-.It Ar N1 N2
-Report down events for the virtual buttons
-.Ar N1
-and
-.Ar N2
-respectively when negative and positive Z axis movement
-is detected.
-.It Ar N1 N2 N3 N4
-This is useful for the mouse with two wheels of which
-the second wheel is used to generate horizontal scroll action,
-and for the mouse which has a knob or a stick which can detect
-the horizontal force applied by the user.
-.Pp
-The motion of the second wheel will be mapped to the buttons
-.Ar N3 ,
-for the negative direction, and
-.Ar N4 ,
-for the positive direction.
-If the buttons
-.Ar N3
-and
-.Ar N4
-actually exist in this mouse, their actions will not be detected.
-.Pp
-Note that horizontal movement or second roller/wheel movement may not
-always be detected,
-because there appears to be no accepted standard as to how it is encoded.
-.Pp
-Note also that some mice think left is the negative horizontal direction;
-others may think otherwise.
-Moreover, there are some mice whose two wheels are both mounted vertically,
-and the direction of the second vertical wheel does not match the
-first one.
.El
.El
-.Ss Configuring Mouse Daemon
+.Ss Configuring Mouse Protocol Conversion Daemon
The first thing you need to know is the interface type
of the mouse you are going to use.
It can be determined by looking at the connector of the mouse.
@@ -673,13 +457,17 @@ protocol.
.El
.Pp
To test if the selected protocol type is correct for the given mouse,
-enable the mouse pointer in the current virtual console,
+ensure the
+.Xr moused 8
+is running in auto port mode,
.Pp
-.Dl "vidcontrol -m on"
+.Dl "moused -p auto"
.Pp
-start the mouse daemon in the foreground mode,
+start the
+.Nm
+in the foreground mode,
.Pp
-.Dl "moused -f -p <selected_port> -t <selected_protocol>"
+.Dl "msconvd -f -p <selected_port> -t <selected_protocol>"
.Pp
and see if the mouse pointer travels correctly
according to the mouse movement.
@@ -688,19 +476,20 @@ clicking the left, right and middle buttons.
Type ^C to stop
the command.
.Ss Multiple Mice
-As many instances of the mouse daemon as the number of mice attached to
-the system may be run simultaneously; one
-instance for each mouse.
+As many instances of the
+.Nm
+as the number of mice attached to the system may be run simultaneously;
+one instance for each mouse.
This is useful if the user wants to use the built-in PS/2 pointing device
of a laptop computer while on the road, but wants to use a serial
mouse when s/he attaches the system to the docking station in the office.
-Run two mouse daemons and tell the application program
-(such as the
-.Tn "X\ Window System" )
-to use
-.Xr sysmouse 4 ,
-then the application program will always see mouse data from either mouse.
-When the serial mouse is not attached, the corresponding mouse daemon
+Run two
+.Nm
+and then the application program e.g.
+.Xr moused 8
+will always see mouse data from either mouse.
+When the serial mouse is not attached, the corresponding
+.Nm
will not detect any movement or button state change and the application
program will only see mouse data coming from the daemon for the
PS/2 mouse.
@@ -709,18 +498,18 @@ are moved at the same time in this configuration,
the mouse pointer will travel across the screen just as if movement of
the mice is combined all together.
.Sh FILES
-.Bl -tag -width /dev/consolectl -compact
-.It Pa /dev/consolectl
-device to control the console
+.Bl -tag -width /dev/input/event%d -compact
+.It Pa /dev/input/event%d
+input event device
.It Pa /dev/psm%d
PS/2 mouse driver
-.It Pa /dev/sysmouse
-virtualized mouse driver
+.It Pa /dev/cuau%d
+serial port
.It Pa /dev/ttyv%d
virtual consoles
.It Pa /dev/ums%d
USB mouse driver
-.It Pa /var/run/moused.pid
+.It Pa /var/run/msconvd.pid
process id of the currently running
.Nm
utility
@@ -728,7 +517,7 @@ utility
UNIX-domain stream socket for X10 MouseRemote events
.El
.Sh EXAMPLES
-.Dl "moused -p /dev/cuau0 -i type"
+.Dl "msconvd -p /dev/cuau0 -i type"
.Pp
Let the
.Nm
@@ -737,8 +526,7 @@ utility determine the protocol type of the mouse at the serial port
If successful, the command will print the type, otherwise it will say
.Dq Li unknown .
.Bd -literal -offset indent
-moused -p /dev/cuau0
-vidcontrol -m on
+msconvd -p /dev/cuau0
.Ed
.Pp
If the
@@ -746,10 +534,9 @@ If the
utility is able to identify the protocol type of the mouse at the specified
port automatically, you can start the daemon without the
.Fl t
-option and enable the mouse pointer in the text console as above.
+option.
.Bd -literal -offset indent
-moused -p /dev/mouse -t microsoft
-vidcontrol -m on
+msconvd -p /dev/mouse -t microsoft
.Ed
.Pp
Start the mouse daemon on the serial port
@@ -760,39 +547,26 @@ is explicitly specified by the
.Fl t
option.
.Pp
-.Dl "moused -p /dev/mouse -m 1=3 -m 3=1"
-.Pp
-Assign the physical button 3 (right button) to the logical button 1
-(logical left) and the physical button 1 (left) to the logical
-button 3 (logical right).
-This will effectively swap the left and right buttons.
-.Pp
-.Dl "moused -p /dev/mouse -t intellimouse -z 4"
-.Pp
-Report negative Z axis movement (i.e., mouse wheel) as the button 4 pressed
-and positive Z axis movement (i.e., mouse wheel) as the button 5 pressed.
-.Pp
If you add
.Pp
-.Dl "ALL ALL = NOPASSWD: /usr/bin/killall -USR1 moused"
+.Dl "ALL ALL = NOPASSWD: /usr/bin/killall -USR1 msconvd"
.Pp
to your
.Pa /usr/local/etc/sudoers
file, and bind
.Pp
-.Dl "killall -USR1 moused"
+.Dl "killall -USR1 msconvd"
.Pp
to a key in your window manager, you can suspend mouse events on your laptop if
you keep brushing over the mouse pad while typing.
.Sh SEE ALSO
+.Xr moused 8 ,
.Xr kill 1 ,
-.Xr vidcontrol 1 ,
.Xr xset 1 ,
-.Xr keyboard 4 ,
.Xr psm 4 ,
.Xr screen 4 ,
.Xr sysmouse 4 ,
-.Xr ums 4
+.Xr uart 4
.Sh STANDARDS
The
.Nm
@@ -809,14 +583,23 @@ for the given serial mouse.
The
.Nm
utility first appeared in
+.Fx 15.0 .
+It is a cropped-down version of
+.Fx 14.0
+.Xr moused 8
+utility originated back in
.Fx 2.2 .
.Sh AUTHORS
.An -nosplit
The
.Nm
-utility was written by
+utility is based on
+.Xr moused 8
+written by
.An Michael Smith Aq Mt msmith@FreeBSD.org .
-This manual page was written by
+This manual page is extracted from
+.Xr moused 8
+page written by
.An Mike Pritchard Aq Mt mpp@FreeBSD.org .
The command and manual page have since been updated by
.An Kazutaka Yokota Aq Mt yokota@FreeBSD.org .
@@ -830,21 +613,7 @@ treat the tapping action
as fourth button events.
Use the option
.Dq Fl m Li 1=4
+of
+.Xr moused 8
for these models
to obtain the same effect as the other pad devices.
-.Pp
-Cut and paste functions in the virtual console assume that there
-are three buttons on the mouse.
-The logical button 1 (logical left) selects a region of text in the
-console and copies it to the cut buffer.
-The logical button 3 (logical right) extends the selected region.
-The logical button 2 (logical middle) pastes the selected text
-at the text cursor position.
-If the mouse has only two buttons, the middle, `paste' button
-is not available.
-To obtain the paste function, use the
-.Fl 3
-option to emulate the middle button, or use the
-.Fl m
-option to assign the physical right button to the logical middle button:
-.Dq Fl m Li 2=3 .
diff --git a/usr.sbin/moused/moused.c b/usr.sbin/moused/msconvd/msconvd.c
index 068919f2e941..7b06d92019aa 100644
--- a/usr.sbin/moused/moused.c
+++ b/usr.sbin/moused/msconvd/msconvd.c
@@ -34,11 +34,11 @@
**/
/**
- ** MOUSED.C
+ ** MSCONVD.C
**
- ** Mouse daemon : listens to a serial port, the bus mouse interface, or
- ** the PS/2 mouse port for mouse data stream, interprets data and passes
- ** ioctls off to the console driver.
+ ** Mouse protocol conversion daemon : listens to a serial port or
+ ** the PS/2 mouse port for mouse data stream, decodes data and passes
+ ** writes off to the uinput driver.
**
** The mouse interface functions are derived closely from the mouse
** handler in the XFree86 X server. Many thanks to the XFree86 people
@@ -54,15 +54,20 @@
#include <sys/time.h>
#include <sys/un.h>
+#include <dev/evdev/input.h>
+#include <dev/evdev/uinput.h>
+
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
+#include <libgen.h>
#include <libutil.h>
#include <limits.h>
#include <setjmp.h>
#include <signal.h>
#include <stdarg.h>
+#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
@@ -70,23 +75,6 @@
#include <syslog.h>
#include <termios.h>
#include <unistd.h>
-#include <math.h>
-
-#define MAX_CLICKTHRESHOLD 2000 /* 2 seconds */
-#define MAX_BUTTON2TIMEOUT 2000 /* 2 seconds */
-#define DFLT_CLICKTHRESHOLD 500 /* 0.5 second */
-#define DFLT_BUTTON2TIMEOUT 100 /* 0.1 second */
-#define DFLT_SCROLLTHRESHOLD 3 /* 3 pixels */
-#define DFLT_SCROLLSPEED 2 /* 2 pixels */
-
-/* Abort 3-button emulation delay after this many movement events. */
-#define BUTTON2_MAXMOVE 3
-
-#define TRUE 1
-#define FALSE 0
-
-#define MOUSE_XAXIS (-1)
-#define MOUSE_YAXIS (-2)
/* Logitech PS2++ protocol */
#define MOUSE_PS2PLUS_CHECKBITS(b) \
@@ -95,13 +83,9 @@
(((b[0] & 0x30) >> 2) | ((b[1] & 0x30) >> 4))
#define ChordMiddle 0x0001
-#define Emulate3Button 0x0002
#define ClearDTR 0x0004
#define ClearRTS 0x0008
#define NoPnP 0x0010
-#define VirtualScroll 0x0020
-#define HVirtualScroll 0x0040
-#define ExponentialAcc 0x0080
#define ID_NONE 0
#define ID_PORT 1
@@ -110,22 +94,6 @@
#define ID_MODEL 8
#define ID_ALL (ID_PORT | ID_IF | ID_TYPE | ID_MODEL)
-/* Operations on timespecs */
-#define tsclr(tvp) ((tvp)->tv_sec = (tvp)->tv_nsec = 0)
-#define tscmp(tvp, uvp, cmp) \
- (((tvp)->tv_sec == (uvp)->tv_sec) ? \
- ((tvp)->tv_nsec cmp (uvp)->tv_nsec) : \
- ((tvp)->tv_sec cmp (uvp)->tv_sec))
-#define tssub(tvp, uvp, vvp) \
- do { \
- (vvp)->tv_sec = (tvp)->tv_sec - (uvp)->tv_sec; \
- (vvp)->tv_nsec = (tvp)->tv_nsec - (uvp)->tv_nsec; \
- if ((vvp)->tv_nsec < 0) { \
- (vvp)->tv_sec--; \
- (vvp)->tv_nsec += 1000000000; \
- } \
- } while (0)
-
#define debug(...) do { \
if (debug && nodaemon) \
warnx(__VA_ARGS__); \
@@ -174,30 +142,21 @@ typedef struct {
/* global variables */
static int debug = 0;
-static int nodaemon = FALSE;
-static int background = FALSE;
-static int paused = FALSE;
+static bool nodaemon = false;
+static bool background = false;
+static bool paused = false;
static int identify = ID_NONE;
-static int extioctl = FALSE;
-static const char *pidfile = "/var/run/moused.pid";
+static const char *pidfile = "/var/run/msconvd.pid";
static struct pidfh *pfh;
-#define SCROLL_NOTSCROLLING 0
-#define SCROLL_PREPARE 1
-#define SCROLL_SCROLLING 2
-
-static int scroll_state;
-static int scroll_movement;
-static int hscroll_movement;
-
/* local variables */
/* interface (the table must be ordered by MOUSE_IF_XXX in mouse.h) */
static symtab_t rifs[] = {
- { "serial", MOUSE_IF_SERIAL, 0 },
- { "ps/2", MOUSE_IF_PS2, 0 },
- { "sysmouse", MOUSE_IF_SYSMOUSE, 0 },
- { "usb", MOUSE_IF_USB, 0 },
+ { "serial", MOUSE_IF_SERIAL, BUS_RS232 },
+ { "ps/2", MOUSE_IF_PS2, BUS_I8042 },
+ { "sysmouse", MOUSE_IF_SYSMOUSE, BUS_VIRTUAL },
+ { "usb", MOUSE_IF_USB, BUS_USB },
{ NULL, MOUSE_IF_UNKNOWN, 0 },
};
@@ -381,6 +340,18 @@ static unsigned short rodentcflags[] =
(CS8 | CREAD | HUPCL ), /* GTCO Digi-Pad */
};
+/* evdev button codes */
+static const int16_t evdev_buttons[8] = {
+ BTN_LEFT,
+ BTN_MIDDLE,
+ BTN_RIGHT,
+ BTN_SIDE,
+ BTN_EXTRA,
+ BTN_FORWARD,
+ BTN_BACK,
+ BTN_TASK
+};
+
static struct rodentparam {
int flags;
const char *portname; /* /dev/XXX */
@@ -389,25 +360,13 @@ static struct rodentparam {
int baudrate;
int rate; /* report rate */
int resolution; /* MOUSE_RES_XXX or a positive number */
- int zmap[4]; /* MOUSE_{X|Y}AXIS or a button number */
- int wmode; /* wheel mode button number */
int mfd; /* mouse file descriptor */
- int cfd; /* /dev/consolectl file descriptor */
+ int ufd; /* /dev/uinput file descriptor */
int mremsfd; /* mouse remote server file descriptor */
int mremcfd; /* mouse remote client file descriptor */
int is_removable; /* set if device is removable, like USB */
- long clickthreshold; /* double click speed in msec */
- long button2timeout; /* 3 button emulation timeout */
mousehw_t hw; /* mouse device hardware information */
mousemode_t mode; /* protocol information */
- float accelx; /* Acceleration in the X axis */
- float accely; /* Acceleration in the Y axis */
- float expoaccel; /* Exponential acceleration */
- float expoffset; /* Movement offset for exponential accel. */
- float remainx; /* Remainder on X and Y axis, respectively... */
- float remainy; /* ... to compensate for rounding errors. */
- int scrollthreshold; /* Movement distance before virtual scrolling */
- int scrollspeed; /* Movement distance to rate of scrolling */
} rodent = {
.flags = 0,
.portname = NULL,
@@ -416,107 +375,18 @@ static struct rodentparam {
.baudrate = 1200,
.rate = 0,
.resolution = MOUSE_RES_UNKNOWN,
- .zmap = { 0, 0, 0, 0 },
- .wmode = 0,
.mfd = -1,
- .cfd = -1,
+ .ufd = -1,
.mremsfd = -1,
.mremcfd = -1,
.is_removable = 0,
- .clickthreshold = DFLT_CLICKTHRESHOLD,
- .button2timeout = DFLT_BUTTON2TIMEOUT,
- .accelx = 1.0,
- .accely = 1.0,
- .expoaccel = 1.0,
- .expoffset = 1.0,
- .remainx = 0.0,
- .remainy = 0.0,
- .scrollthreshold = DFLT_SCROLLTHRESHOLD,
- .scrollspeed = DFLT_SCROLLSPEED,
-};
-
-/* button status */
-struct button_state {
- int count; /* 0: up, 1: single click, 2: double click,... */
- struct timespec ts; /* timestamp on the last button event */
-};
-static struct button_state bstate[MOUSE_MAXBUTTON]; /* button state */
-static struct button_state *mstate[MOUSE_MAXBUTTON];/* mapped button st.*/
-static struct button_state zstate[4]; /* Z/W axis state */
-
-/* state machine for 3 button emulation */
-
-#define S0 0 /* start */
-#define S1 1 /* button 1 delayed down */
-#define S2 2 /* button 3 delayed down */
-#define S3 3 /* both buttons down -> button 2 down */
-#define S4 4 /* button 1 delayed up */
-#define S5 5 /* button 1 down */
-#define S6 6 /* button 3 down */
-#define S7 7 /* both buttons down */
-#define S8 8 /* button 3 delayed up */
-#define S9 9 /* button 1 or 3 up after S3 */
-
-#define A(b1, b3) (((b1) ? 2 : 0) | ((b3) ? 1 : 0))
-#define A_TIMEOUT 4
-#define S_DELAYED(st) (states[st].s[A_TIMEOUT] != (st))
-
-static struct {
- int s[A_TIMEOUT + 1];
- int buttons;
- int mask;
- int timeout;
-} states[10] = {
- /* S0 */
- { { S0, S2, S1, S3, S0 }, 0, ~(MOUSE_BUTTON1DOWN | MOUSE_BUTTON3DOWN), FALSE },
- /* S1 */
- { { S4, S2, S1, S3, S5 }, 0, ~MOUSE_BUTTON1DOWN, FALSE },
- /* S2 */
- { { S8, S2, S1, S3, S6 }, 0, ~MOUSE_BUTTON3DOWN, FALSE },
- /* S3 */
- { { S0, S9, S9, S3, S3 }, MOUSE_BUTTON2DOWN, ~0, FALSE },
- /* S4 */
- { { S0, S2, S1, S3, S0 }, MOUSE_BUTTON1DOWN, ~0, TRUE },
- /* S5 */
- { { S0, S2, S5, S7, S5 }, MOUSE_BUTTON1DOWN, ~0, FALSE },
- /* S6 */
- { { S0, S6, S1, S7, S6 }, MOUSE_BUTTON3DOWN, ~0, FALSE },
- /* S7 */
- { { S0, S6, S5, S7, S7 }, MOUSE_BUTTON1DOWN | MOUSE_BUTTON3DOWN, ~0, FALSE },
- /* S8 */
- { { S0, S2, S1, S3, S0 }, MOUSE_BUTTON3DOWN, ~0, TRUE },
- /* S9 */
- { { S0, S9, S9, S3, S9 }, 0, ~(MOUSE_BUTTON1DOWN | MOUSE_BUTTON3DOWN), FALSE },
};
-static int mouse_button_state;
-static struct timespec mouse_button_state_ts;
-static int mouse_move_delayed;
static jmp_buf env;
-struct drift_xy {
- int x;
- int y;
-};
-static int drift_distance = 4; /* max steps X+Y */
-static int drift_time = 500; /* in 0.5 sec */
-static struct timespec drift_time_ts;
-static struct timespec drift_2time_ts; /* 2*drift_time */
-static int drift_after = 4000; /* 4 sec */
-static struct timespec drift_after_ts;
-static int drift_terminate = FALSE;
-static struct timespec drift_current_ts;
-static struct timespec drift_tmp;
-static struct timespec drift_last_activity = {0, 0};
-static struct timespec drift_since = {0, 0};
-static struct drift_xy drift_last = {0, 0}; /* steps in last drift_time */
-static struct drift_xy drift_previous = {0, 0}; /* steps in prev. drift_time */
-
/* function prototypes */
-static void linacc(int, int, int*, int*);
-static void expoacc(int, int, int*, int*);
-static void moused(void);
+static void msconvd(void);
static void hup(int sig);
static void cleanup(int sig);
static void pause_mouse(int sig);
@@ -524,31 +394,29 @@ static void usage(void);
static void log_or_warn(int log_pri, int errnum, const char *fmt, ...)
__printflike(3, 4);
+static int r_uinput_register(void);
+static int r_uinput_report(int fd, mousestatus_t *act);
static int r_identify(void);
static const char *r_if(int type);
+static uint16_t r_bustype(int type);
static const char *r_name(int type);
static const char *r_model(int model);
static void r_init(void);
static int r_protocol(u_char b, mousestatus_t *act);
-static int r_statetrans(mousestatus_t *a1, mousestatus_t *a2, int trans);
-static int r_installmap(char *arg);
-static void r_map(mousestatus_t *act1, mousestatus_t *act2);
-static void r_timestamp(mousestatus_t *act);
-static int r_timeout(void);
-static void r_click(mousestatus_t *act);
static void setmousespeed(int old, int new, unsigned cflag);
-static int pnpwakeup1(void);
-static int pnpwakeup2(void);
+static bool pnpwakeup1(void);
+static bool pnpwakeup2(void);
static int pnpgets(char *buf);
-static int pnpparse(pnpid_t *id, char *buf, int len);
+static bool pnpparse(pnpid_t *id, char *buf, int len);
static symtab_t *pnpproto(pnpid_t *id);
static symtab_t *gettoken(symtab_t *tab, const char *s, int len);
static const char *gettokenname(symtab_t *tab, int val);
+static int gettokenval2(symtab_t *tab, int val);
static void mremote_serversetup(void);
-static void mremote_clientchg(int add);
+static void mremote_clientchg(bool add);
static int kidspad(u_char rxc, mousestatus_t *act);
static int gtco_digipad(u_char, mousestatus_t *);
@@ -558,52 +426,10 @@ main(int argc, char *argv[])
{
int c;
int i;
- int j;
- for (i = 0; i < MOUSE_MAXBUTTON; ++i)
- mstate[i] = &bstate[i];
-
- while ((c = getopt(argc, argv, "3A:C:DE:F:HI:L:PRS:T:VU:a:cdfhi:l:m:p:r:st:w:z:")) != -1)
+ while ((c = getopt(argc, argv, "DF:I:PRS:cdfhi:l:p:r:st:")) != -1)
switch(c) {
- case '3':
- rodent.flags |= Emulate3Button;
- break;
-
- case 'E':
- rodent.button2timeout = atoi(optarg);
- if ((rodent.button2timeout < 0) ||
- (rodent.button2timeout > MAX_BUTTON2TIMEOUT)) {
- warnx("invalid argument `%s'", optarg);
- usage();
- }
- break;
-
- case 'a':
- i = sscanf(optarg, "%f,%f", &rodent.accelx, &rodent.accely);
- if (i == 0) {
- warnx("invalid linear acceleration argument '%s'", optarg);
- usage();
- }
-
- if (i == 1)
- rodent.accely = rodent.accelx;
-
- break;
-
- case 'A':
- rodent.flags |= ExponentialAcc;
- i = sscanf(optarg, "%f,%f", &rodent.expoaccel, &rodent.expoffset);
- if (i == 0) {
- warnx("invalid exponential acceleration argument '%s'", optarg);
- usage();
- }
-
- if (i == 1)
- rodent.expoffset = 1.0;
-
- break;
-
case 'c':
rodent.flags |= ChordMiddle;
break;
@@ -613,7 +439,7 @@ main(int argc, char *argv[])
break;
case 'f':
- nodaemon = TRUE;
+ nodaemon = true;
break;
case 'i':
@@ -631,7 +457,7 @@ main(int argc, char *argv[])
warnx("invalid argument `%s'", optarg);
usage();
}
- nodaemon = TRUE;
+ nodaemon = true;
break;
case 'l':
@@ -642,13 +468,6 @@ main(int argc, char *argv[])
}
break;
- case 'm':
- if (!r_installmap(optarg)) {
- warnx("invalid argument `%s'", optarg);
- usage();
- }
- break;
-
case 'p':
rodent.portname = optarg;
break;
@@ -677,58 +496,6 @@ main(int argc, char *argv[])
rodent.baudrate = 9600;
break;
- case 'w':
- i = atoi(optarg);
- if ((i <= 0) || (i > MOUSE_MAXBUTTON)) {
- warnx("invalid argument `%s'", optarg);
- usage();
- }
- rodent.wmode = 1 << (i - 1);
- break;
-
- case 'z':
- if (strcmp(optarg, "x") == 0)
- rodent.zmap[0] = MOUSE_XAXIS;
- else if (strcmp(optarg, "y") == 0)
- rodent.zmap[0] = MOUSE_YAXIS;
- else {
- i = atoi(optarg);
- /*
- * Use button i for negative Z axis movement and
- * button (i + 1) for positive Z axis movement.
- */
- if ((i <= 0) || (i > MOUSE_MAXBUTTON - 1)) {
- warnx("invalid argument `%s'", optarg);
- usage();
- }
- rodent.zmap[0] = i;
- rodent.zmap[1] = i + 1;
- debug("optind: %d, optarg: '%s'", optind, optarg);
- for (j = 1; j < 4; ++j) {
- if ((optind >= argc) || !isdigit(*argv[optind]))
- break;
- i = atoi(argv[optind]);
- if ((i <= 0) || (i > MOUSE_MAXBUTTON - 1)) {
- warnx("invalid argument `%s'", argv[optind]);
- usage();
- }
- rodent.zmap[j] = i;
- ++optind;
- }
- if ((rodent.zmap[2] != 0) && (rodent.zmap[3] == 0))
- rodent.zmap[3] = rodent.zmap[2] + 1;
- }
- break;
-
- case 'C':
- rodent.clickthreshold = atoi(optarg);
- if ((rodent.clickthreshold < 0) ||
- (rodent.clickthreshold > MAX_CLICKTHRESHOLD)) {
- warnx("invalid argument `%s'", optarg);
- usage();
- }
- break;
-
case 'D':
rodent.flags |= ClearDTR;
break;
@@ -741,22 +508,10 @@ main(int argc, char *argv[])
}
break;
- case 'H':
- rodent.flags |= HVirtualScroll;
- break;
-
case 'I':
pidfile = optarg;
break;
- case 'L':
- rodent.scrollspeed = atoi(optarg);
- if (rodent.scrollspeed < 0) {
- warnx("invalid argument `%s'", optarg);
- usage();
- }
- break;
-
case 'P':
rodent.flags |= NoPnP;
break;
@@ -774,24 +529,6 @@ main(int argc, char *argv[])
debug("rodent baudrate %d", rodent.baudrate);
break;
- case 'T':
- drift_terminate = TRUE;
- sscanf(optarg, "%d,%d,%d", &drift_distance, &drift_time,
- &drift_after);
- if (drift_distance <= 0 || drift_time <= 0 || drift_after <= 0) {
- warnx("invalid argument `%s'", optarg);
- usage();
- }
- debug("terminate drift: distance %d, time %d, after %d",
- drift_distance, drift_time, drift_after);
- drift_time_ts.tv_sec = drift_time / 1000;
- drift_time_ts.tv_nsec = (drift_time % 1000) * 1000000;
- drift_2time_ts.tv_sec = (drift_time *= 2) / 1000;
- drift_2time_ts.tv_nsec = (drift_time % 1000) * 1000000;
- drift_after_ts.tv_sec = drift_after / 1000;
- drift_after_ts.tv_nsec = (drift_after % 1000) * 1000000;
- break;
-
case 't':
if (strcmp(optarg, "auto") == 0) {
rodent.rtype = MOUSE_PROTO_UNKNOWN;
@@ -812,34 +549,12 @@ main(int argc, char *argv[])
}
break;
- case 'V':
- rodent.flags |= VirtualScroll;
- break;
- case 'U':
- rodent.scrollthreshold = atoi(optarg);
- if (rodent.scrollthreshold < 0) {
- warnx("invalid argument `%s'", optarg);
- usage();
- }
- break;
-
case 'h':
case '?':
default:
usage();
}
- /* fix Z axis mapping */
- for (i = 0; i < 4; ++i) {
- if (rodent.zmap[i] > 0) {
- for (j = 0; j < MOUSE_MAXBUTTON; ++j) {
- if (mstate[j] == &bstate[rodent.zmap[i] - 1])
- mstate[j] = &zstate[i];
- }
- rodent.zmap[i] = 1 << (rodent.zmap[i] - 1);
- }
- }
-
/* the default port name */
switch(rodent.rtype) {
@@ -908,14 +623,14 @@ main(int argc, char *argv[])
}
r_init(); /* call init function */
- moused();
+ msconvd();
}
if (rodent.mfd != -1)
close(rodent.mfd);
- if (rodent.cfd != -1)
- close(rodent.cfd);
- rodent.mfd = rodent.cfd = -1;
+ if (rodent.ufd != -1)
+ close(rodent.ufd);
+ rodent.mfd = rodent.ufd = -1;
if (rodent.is_removable)
exit(0);
}
@@ -924,90 +639,24 @@ main(int argc, char *argv[])
exit(0);
}
-/*
- * Function to calculate linear acceleration.
- *
- * If there are any rounding errors, the remainder
- * is stored in the remainx and remainy variables
- * and taken into account upon the next movement.
- */
-
-static void
-linacc(int dx, int dy, int *movex, int *movey)
-{
- float fdx, fdy;
-
- if (dx == 0 && dy == 0) {
- *movex = *movey = 0;
- return;
- }
- fdx = dx * rodent.accelx + rodent.remainx;
- fdy = dy * rodent.accely + rodent.remainy;
- *movex = lround(fdx);
- *movey = lround(fdy);
- rodent.remainx = fdx - *movex;
- rodent.remainy = fdy - *movey;
-}
-
-/*
- * Function to calculate exponential acceleration.
- * (Also includes linear acceleration if enabled.)
- *
- * In order to give a smoother behaviour, we record the four
- * most recent non-zero movements and use their average value
- * to calculate the acceleration.
- */
-
-static void
-expoacc(int dx, int dy, int *movex, int *movey)
-{
- static float lastlength[3] = {0.0, 0.0, 0.0};
- float fdx, fdy, length, lbase, accel;
-
- if (dx == 0 && dy == 0) {
- *movex = *movey = 0;
- return;
- }
- fdx = dx * rodent.accelx;
- fdy = dy * rodent.accely;
- length = sqrtf((fdx * fdx) + (fdy * fdy)); /* Pythagoras */
- length = (length + lastlength[0] + lastlength[1] + lastlength[2]) / 4;
- lbase = length / rodent.expoffset;
- accel = powf(lbase, rodent.expoaccel) / lbase;
- fdx = fdx * accel + rodent.remainx;
- fdy = fdy * accel + rodent.remainy;
- *movex = lroundf(fdx);
- *movey = lroundf(fdy);
- rodent.remainx = fdx - *movex;
- rodent.remainy = fdy - *movey;
- lastlength[2] = lastlength[1];
- lastlength[1] = lastlength[0];
- lastlength[0] = length; /* Insert new average, not original length! */
-}
-
static void
-moused(void)
+msconvd(void)
{
- struct mouse_info mouse;
- mousestatus_t action0; /* original mouse action */
- mousestatus_t action; /* interim buffer */
- mousestatus_t action2; /* mapped action */
- struct timeval timeout;
+ mousestatus_t action; /* mouse action */
fd_set fds;
u_char b;
pid_t mpid;
int flags;
int c;
- int i;
- if ((rodent.cfd = open("/dev/consolectl", O_RDWR, 0)) == -1)
- logerr(1, "cannot open /dev/consolectl");
+ if ((rodent.ufd = r_uinput_register()) == -1)
+ logerr(1, "cannot register uinput device");
if (!nodaemon && !background) {
pfh = pidfile_open(pidfile, 0600, &mpid);
if (pfh == NULL) {
if (errno == EEXIST)
- logerrx(1, "moused already running, pid: %d", mpid);
+ logerrx(1, "msconvd already running, pid: %d", mpid);
logwarn("cannot open pid file");
}
if (daemon(0, 0)) {
@@ -1016,35 +665,15 @@ moused(void)
errno = saved_errno;
logerr(1, "failed to become a daemon");
} else {
- background = TRUE;
+ background = true;
pidfile_write(pfh);
}
}
/* clear mouse data */
- bzero(&action0, sizeof(action0));
bzero(&action, sizeof(action));
- bzero(&action2, sizeof(action2));
- bzero(&mouse, sizeof(mouse));
- mouse_button_state = S0;
- clock_gettime(CLOCK_MONOTONIC_FAST, &mouse_button_state_ts);
- mouse_move_delayed = 0;
- for (i = 0; i < MOUSE_MAXBUTTON; ++i) {
- bstate[i].count = 0;
- bstate[i].ts = mouse_button_state_ts;
- }
- for (i = 0; i < (int)nitems(zstate); ++i) {
- zstate[i].count = 0;
- zstate[i].ts = mouse_button_state_ts;
- }
-
- /* choose which ioctl command to use */
- mouse.operation = MOUSE_MOTION_EVENT;
- extioctl = (ioctl(rodent.cfd, CONS_MOUSECTL, &mouse) == 0);
/* process mouse data */
- timeout.tv_sec = 0;
- timeout.tv_usec = 20000; /* 20 msec */
for (;;) {
FD_ZERO(&fds);
@@ -1054,265 +683,37 @@ moused(void)
if (rodent.mremcfd >= 0)
FD_SET(rodent.mremcfd, &fds);
- c = select(FD_SETSIZE, &fds, NULL, NULL,
- ((rodent.flags & Emulate3Button) &&
- S_DELAYED(mouse_button_state)) ? &timeout : NULL);
+ c = select(FD_SETSIZE, &fds, NULL, NULL, NULL);
if (c < 0) { /* error */
logwarn("failed to read from mouse");
continue;
- } else if (c == 0) { /* timeout */
- /* assert(rodent.flags & Emulate3Button) */
- action0.button = action0.obutton;
- action0.dx = action0.dy = action0.dz = 0;
- action0.flags = flags = 0;
- if (r_timeout() && r_statetrans(&action0, &action, A_TIMEOUT)) {
- if (debug > 2)
- debug("flags:%08x buttons:%08x obuttons:%08x",
- action.flags, action.button, action.obutton);
- } else {
- action0.obutton = action0.button;
- continue;
- }
- } else {
- /* MouseRemote client connect/disconnect */
- if ((rodent.mremsfd >= 0) && FD_ISSET(rodent.mremsfd, &fds)) {
- mremote_clientchg(TRUE);
- continue;
- }
- if ((rodent.mremcfd >= 0) && FD_ISSET(rodent.mremcfd, &fds)) {
- mremote_clientchg(FALSE);
- continue;
- }
- /* mouse movement */
- if (read(rodent.mfd, &b, 1) == -1) {
- if (errno == EWOULDBLOCK)
- continue;
- else
- return;
- }
- if ((flags = r_protocol(b, &action0)) == 0)
+ }
+ /* MouseRemote client connect/disconnect */
+ if ((rodent.mremsfd >= 0) && FD_ISSET(rodent.mremsfd, &fds)) {
+ mremote_clientchg(true);
+ continue;
+ }
+ if ((rodent.mremcfd >= 0) && FD_ISSET(rodent.mremcfd, &fds)) {
+ mremote_clientchg(false);
+ continue;
+ }
+ /* mouse movement */
+ if (read(rodent.mfd, &b, 1) == -1) {
+ if (errno == EWOULDBLOCK)
continue;
-
- if ((rodent.flags & VirtualScroll) || (rodent.flags & HVirtualScroll)) {
- /* Allow middle button drags to scroll up and down */
- if (action0.button == MOUSE_BUTTON2DOWN) {
- if (scroll_state == SCROLL_NOTSCROLLING) {
- scroll_state = SCROLL_PREPARE;
- scroll_movement = hscroll_movement = 0;
- debug("PREPARING TO SCROLL");
- }
- debug("[BUTTON2] flags:%08x buttons:%08x obuttons:%08x",
- action.flags, action.button, action.obutton);
- } else {
- debug("[NOTBUTTON2] flags:%08x buttons:%08x obuttons:%08x",
- action.flags, action.button, action.obutton);
-
- /* This isn't a middle button down... move along... */
- if (scroll_state == SCROLL_SCROLLING) {
- /*
- * We were scrolling, someone let go of button 2.
- * Now turn autoscroll off.
- */
- scroll_state = SCROLL_NOTSCROLLING;
- debug("DONE WITH SCROLLING / %d", scroll_state);
- } else if (scroll_state == SCROLL_PREPARE) {
- mousestatus_t newaction = action0;
-
- /* We were preparing to scroll, but we never moved... */
- r_timestamp(&action0);
- r_statetrans(&action0, &newaction,
- A(newaction.button & MOUSE_BUTTON1DOWN,
- action0.button & MOUSE_BUTTON3DOWN));
-
- /* Send middle down */
- newaction.button = MOUSE_BUTTON2DOWN;
- r_click(&newaction);
-
- /* Send middle up */
- r_timestamp(&newaction);
- newaction.obutton = newaction.button;
- newaction.button = action0.button;
- r_click(&newaction);
- }
- }
- }
-
- r_timestamp(&action0);
- r_statetrans(&action0, &action,
- A(action0.button & MOUSE_BUTTON1DOWN,
- action0.button & MOUSE_BUTTON3DOWN));
- debug("flags:%08x buttons:%08x obuttons:%08x", action.flags,
- action.button, action.obutton);
+ else
+ return;
}
- action0.obutton = action0.button;
- flags &= MOUSE_POSCHANGED;
- flags |= action.obutton ^ action.button;
- action.flags = flags;
-
- if (flags) { /* handler detected action */
- r_map(&action, &action2);
- debug("activity : buttons 0x%08x dx %d dy %d dz %d",
- action2.button, action2.dx, action2.dy, action2.dz);
+ if ((flags = r_protocol(b, &action)) == 0)
+ continue;
- if ((rodent.flags & VirtualScroll) || (rodent.flags & HVirtualScroll)) {
- /*
- * If *only* the middle button is pressed AND we are moving
- * the stick/trackpoint/nipple, scroll!
- */
- if (scroll_state == SCROLL_PREPARE) {
- /* Middle button down, waiting for movement threshold */
- if (action2.dy || action2.dx) {
- if (rodent.flags & VirtualScroll) {
- scroll_movement += action2.dy;
- if (scroll_movement < -rodent.scrollthreshold) {
- scroll_state = SCROLL_SCROLLING;
- } else if (scroll_movement > rodent.scrollthreshold) {
- scroll_state = SCROLL_SCROLLING;
- }
- }
- if (rodent.flags & HVirtualScroll) {
- hscroll_movement += action2.dx;
- if (hscroll_movement < -rodent.scrollthreshold) {
- scroll_state = SCROLL_SCROLLING;
- } else if (hscroll_movement > rodent.scrollthreshold) {
- scroll_state = SCROLL_SCROLLING;
- }
- }
- if (scroll_state == SCROLL_SCROLLING) scroll_movement = hscroll_movement = 0;
- }
- } else if (scroll_state == SCROLL_SCROLLING) {
- if (rodent.flags & VirtualScroll) {
- scroll_movement += action2.dy;
- debug("SCROLL: %d", scroll_movement);
- if (scroll_movement < -rodent.scrollspeed) {
- /* Scroll down */
- action2.dz = -1;
- scroll_movement = 0;
- }
- else if (scroll_movement > rodent.scrollspeed) {
- /* Scroll up */
- action2.dz = 1;
- scroll_movement = 0;
- }
- }
- if (rodent.flags & HVirtualScroll) {
- hscroll_movement += action2.dx;
- debug("HORIZONTAL SCROLL: %d", hscroll_movement);
-
- if (hscroll_movement < -rodent.scrollspeed) {
- action2.dz = -2;
- hscroll_movement = 0;
- }
- else if (hscroll_movement > rodent.scrollspeed) {
- action2.dz = 2;
- hscroll_movement = 0;
- }
- }
-
- /* Don't move while scrolling */
- action2.dx = action2.dy = 0;
- }
- }
+ debug("flags:%08x buttons:%08x obuttons:%08x", action.flags,
+ action.button, action.obutton);
- if (drift_terminate) {
- if ((flags & MOUSE_POSCHANGED) == 0 || action.dz || action2.dz)
- drift_last_activity = drift_current_ts;
- else {
- /* X or/and Y movement only - possibly drift */
- tssub(&drift_current_ts, &drift_last_activity, &drift_tmp);
- if (tscmp(&drift_tmp, &drift_after_ts, >)) {
- tssub(&drift_current_ts, &drift_since, &drift_tmp);
- if (tscmp(&drift_tmp, &drift_time_ts, <)) {
- drift_last.x += action2.dx;
- drift_last.y += action2.dy;
- } else {
- /* discard old accumulated steps (drift) */
- if (tscmp(&drift_tmp, &drift_2time_ts, >))
- drift_previous.x = drift_previous.y = 0;
- else
- drift_previous = drift_last;
- drift_last.x = action2.dx;
- drift_last.y = action2.dy;
- drift_since = drift_current_ts;
- }
- if (abs(drift_last.x) + abs(drift_last.y)
- > drift_distance) {
- /* real movement, pass all accumulated steps */
- action2.dx = drift_previous.x + drift_last.x;
- action2.dy = drift_previous.y + drift_last.y;
- /* and reset accumulators */
- tsclr(&drift_since);
- drift_last.x = drift_last.y = 0;
- /* drift_previous will be cleared at next movement*/
- drift_last_activity = drift_current_ts;
- } else {
- continue; /* don't pass current movement to
- * console driver */
- }
- }
- }
- }
-
- if (extioctl) {
- /* Defer clicks until we aren't VirtualScroll'ing. */
- if (scroll_state == SCROLL_NOTSCROLLING)
- r_click(&action2);
-
- if (action2.flags & MOUSE_POSCHANGED) {
- mouse.operation = MOUSE_MOTION_EVENT;
- mouse.u.data.buttons = action2.button;
- if (rodent.flags & ExponentialAcc) {
- expoacc(action2.dx, action2.dy,
- &mouse.u.data.x, &mouse.u.data.y);
- }
- else {
- linacc(action2.dx, action2.dy,
- &mouse.u.data.x, &mouse.u.data.y);
- }
- mouse.u.data.z = action2.dz;
- if (debug < 2)
- if (!paused)
- ioctl(rodent.cfd, CONS_MOUSECTL, &mouse);
- }
- } else {
- mouse.operation = MOUSE_ACTION;
- mouse.u.data.buttons = action2.button;
- if (rodent.flags & ExponentialAcc) {
- expoacc(action2.dx, action2.dy,
- &mouse.u.data.x, &mouse.u.data.y);
- }
- else {
- linacc(action2.dx, action2.dy,
- &mouse.u.data.x, &mouse.u.data.y);
- }
- mouse.u.data.z = action2.dz;
- if (debug < 2)
- if (!paused)
- ioctl(rodent.cfd, CONS_MOUSECTL, &mouse);
- }
-
- /*
- * If the Z axis movement is mapped to an imaginary physical
- * button, we need to cook up a corresponding button `up' event
- * after sending a button `down' event.
- */
- if ((rodent.zmap[0] > 0) && (action.dz != 0)) {
- action.obutton = action.button;
- action.dx = action.dy = action.dz = 0;
- r_map(&action, &action2);
- debug("activity : buttons 0x%08x dx %d dy %d dz %d",
- action2.button, action2.dx, action2.dy, action2.dz);
-
- if (extioctl) {
- r_click(&action2);
- } else {
- mouse.operation = MOUSE_ACTION;
- mouse.u.data.buttons = action2.button;
- mouse.u.data.x = mouse.u.data.y = mouse.u.data.z = 0;
- if (debug < 2)
- if (!paused)
- ioctl(rodent.cfd, CONS_MOUSECTL, &mouse);
- }
+ if (flags) {
+ if (r_uinput_report(rodent.ufd, &action) == -1) {
+ logwarn("failed to write to uinput");
+ return;
}
}
}
@@ -1347,12 +748,10 @@ pause_mouse(__unused int sig)
static void
usage(void)
{
- fprintf(stderr, "%s\n%s\n%s\n%s\n%s\n",
- "usage: moused [-DRcdfs] [-I file] [-F rate] [-r resolution] [-S baudrate]",
- " [-VH [-U threshold]] [-a X[,Y]] [-C threshold] [-m N=M] [-w N]",
- " [-z N] [-t <mousetype>] [-l level] [-3 [-E timeout]]",
- " [-T distance[,time[,after]]] -p <port>",
- " moused [-d] -i <port|if|type|model|all> -p <port>");
+ fprintf(stderr, "%s\n%s\n%s\n",
+ "usage: msconvd [-DPRcdfs] [-I file] [-F rate] [-r resolution] [-S baudrate]",
+ " [-t <mousetype>] [-l level] -p <port>",
+ " msconvd [-Pd] -i <port|if|type|model|all> -p <port>");
exit(1);
}
@@ -1380,6 +779,98 @@ log_or_warn(int log_pri, int errnum, const char *fmt, ...)
warnx("%s", buf);
}
+/*
+ * Setup uinput device as 8button mouse with wheel
+ */
+static int
+r_uinput_register(void)
+{
+ struct uinput_setup uisetup;
+ char *phys;
+ int fd;
+ size_t i;
+
+ fd = open("/dev/uinput", O_RDWR | O_NONBLOCK);
+ if (fd < 0)
+ return (-1);
+
+ /* Set device name and bus/vendor information */
+ memset(&uisetup, 0, sizeof(uisetup));
+ snprintf(uisetup.name, UINPUT_MAX_NAME_SIZE,
+ "%s mouse on %s", r_model(rodent.hw.model), rodent.portname);
+ uisetup.id.bustype = r_bustype(rodent.hw.iftype);
+ uisetup.id.vendor = 0;
+ uisetup.id.product = 0;
+ uisetup.id.version = 0;
+ phys = basename(__DECONST(char *, rodent.portname));
+ if (ioctl(fd, UI_SET_PHYS, phys) < 0 ||
+ ioctl(fd, UI_DEV_SETUP, &uisetup) < 0)
+ goto bail_out;
+
+ /* Advertise events and axes */
+ if (ioctl(fd, UI_SET_EVBIT, EV_SYN) < 0 ||
+ ioctl(fd, UI_SET_EVBIT, EV_KEY) < 0 ||
+ ioctl(fd, UI_SET_EVBIT, EV_REL) < 0 ||
+ ioctl(fd, UI_SET_RELBIT, REL_X) < 0 ||
+ ioctl(fd, UI_SET_RELBIT, REL_Y) < 0 ||
+ ioctl(fd, UI_SET_RELBIT, REL_WHEEL) < 0 ||
+ ioctl(fd, UI_SET_PROPBIT, INPUT_PROP_POINTER) < 0)
+ goto bail_out;
+
+ /* Advertise mouse buttons */
+ for (i = 0; i < nitems(evdev_buttons); i++)
+ if (ioctl(fd, UI_SET_KEYBIT, evdev_buttons[i]) < 0)
+ goto bail_out;
+
+ if (ioctl(fd, UI_DEV_CREATE) >= 0)
+ return (fd); /* SUCCESS */
+
+bail_out:
+ close (fd);
+ return (-1);
+}
+
+static int
+uinput_event(int fd, uint16_t type, uint16_t code, int32_t value)
+{
+ struct input_event ie;
+
+ if (debug >= 2 || paused)
+ return (0);
+
+ memset(&ie, 0, sizeof(ie));
+ ie.type = type;
+ ie.code = code;
+ ie.value = value;
+ return (write(fd, &ie, sizeof(ie)));
+}
+
+static int
+r_uinput_report(int fd, mousestatus_t *act)
+{
+ size_t i;
+ int32_t mask;
+
+ if ((act->dx != 0 && uinput_event(fd, EV_REL, REL_X, act->dx) < 0) ||
+ (act->dy != 0 && uinput_event(fd, EV_REL, REL_Y, act->dy) < 0) ||
+ (act->dz != 0 && uinput_event(fd, EV_REL, REL_WHEEL, -act->dz) < 0))
+ return (-1);
+
+ for (i = 0; i < nitems(evdev_buttons); i++) {
+ mask = 1 << i;
+ if ((act->button & mask) == (act->obutton & mask))
+ continue;
+ if (uinput_event(fd, EV_KEY, evdev_buttons[i],
+ (act->button & mask) != 0) < 0)
+ return (-1);
+ }
+
+ if (uinput_event(fd, EV_SYN, SYN_REPORT, 0) < 0)
+ return (-1);
+
+ return (0);
+}
+
/**
** Mouse interface code, courtesy of XFree86 3.1.2.
**
@@ -1559,6 +1050,12 @@ r_if(int iftype)
return (gettokenname(rifs, iftype));
}
+static uint16_t
+r_bustype(int iftype)
+{
+ return (gettokenval2(rifs, iftype));
+}
+
static const char *
r_name(int type)
{
@@ -1875,7 +1372,7 @@ r_protocol(u_char rBuf, mousestatus_t *act)
static int pBufP = 0;
static unsigned char pBuf[8];
static int prev_x, prev_y;
- static int on = FALSE;
+ static bool on = false;
int x, y;
debug("received char 0x%x",(int)rBuf);
@@ -2109,7 +1606,7 @@ r_protocol(u_char rBuf, mousestatus_t *act)
act->button |= (pBuf[0] & MOUSE_VERSA_TAP) ? MOUSE_BUTTON4DOWN : 0;
act->dx = act->dy = 0;
if (!(pBuf[0] & MOUSE_VERSA_IN_USE)) {
- on = FALSE;
+ on = false;
break;
}
x = (pBuf[2] << 6) | pBuf[1];
@@ -2122,7 +1619,7 @@ r_protocol(u_char rBuf, mousestatus_t *act)
act->dx = prev_x - x;
act->dy = prev_y - y;
} else {
- on = TRUE;
+ on = true;
}
prev_x = x;
prev_y = y;
@@ -2245,7 +1742,7 @@ r_protocol(u_char rBuf, mousestatus_t *act)
(pBuf[0] & MOUSE_PS2VERSA_TAP) ? MOUSE_BUTTON4DOWN : 0;
act->dx = act->dy = 0;
if (!(pBuf[0] & MOUSE_PS2VERSA_IN_USE)) {
- on = FALSE;
+ on = false;
break;
}
x = ((pBuf[4] << 8) & 0xf00) | pBuf[1];
@@ -2258,7 +1755,7 @@ r_protocol(u_char rBuf, mousestatus_t *act)
act->dx = prev_x - x;
act->dy = prev_y - y;
} else {
- on = TRUE;
+ on = true;
}
prev_x = x;
prev_y = y;
@@ -2324,301 +1821,6 @@ r_protocol(u_char rBuf, mousestatus_t *act)
return (act->flags);
}
-static int
-r_statetrans(mousestatus_t *a1, mousestatus_t *a2, int trans)
-{
- int changed;
- int flags;
-
- a2->dx = a1->dx;
- a2->dy = a1->dy;
- a2->dz = a1->dz;
- a2->obutton = a2->button;
- a2->button = a1->button;
- a2->flags = a1->flags;
- changed = FALSE;
-
- if (rodent.flags & Emulate3Button) {
- if (debug > 2)
- debug("state:%d, trans:%d -> state:%d",
- mouse_button_state, trans,
- states[mouse_button_state].s[trans]);
- /*
- * Avoid re-ordering button and movement events. While a button
- * event is deferred, throw away up to BUTTON2_MAXMOVE movement
- * events to allow for mouse jitter. If more movement events
- * occur, then complete the deferred button events immediately.
- */
- if ((a2->dx != 0 || a2->dy != 0) &&
- S_DELAYED(states[mouse_button_state].s[trans])) {
- if (++mouse_move_delayed > BUTTON2_MAXMOVE) {
- mouse_move_delayed = 0;
- mouse_button_state =
- states[mouse_button_state].s[A_TIMEOUT];
- changed = TRUE;
- } else
- a2->dx = a2->dy = 0;
- } else
- mouse_move_delayed = 0;
- if (mouse_button_state != states[mouse_button_state].s[trans])
- changed = TRUE;
- if (changed)
- clock_gettime(CLOCK_MONOTONIC_FAST, &mouse_button_state_ts);
- mouse_button_state = states[mouse_button_state].s[trans];
- a2->button &=
- ~(MOUSE_BUTTON1DOWN | MOUSE_BUTTON2DOWN | MOUSE_BUTTON3DOWN);
- a2->button &= states[mouse_button_state].mask;
- a2->button |= states[mouse_button_state].buttons;
- flags = a2->flags & MOUSE_POSCHANGED;
- flags |= a2->obutton ^ a2->button;
- if (flags & MOUSE_BUTTON2DOWN) {
- a2->flags = flags & MOUSE_BUTTON2DOWN;
- r_timestamp(a2);
- }
- a2->flags = flags;
- }
- return (changed);
-}
-
-/* phisical to logical button mapping */
-static int p2l[MOUSE_MAXBUTTON] = {
- MOUSE_BUTTON1DOWN, MOUSE_BUTTON2DOWN, MOUSE_BUTTON3DOWN, MOUSE_BUTTON4DOWN,
- MOUSE_BUTTON5DOWN, MOUSE_BUTTON6DOWN, MOUSE_BUTTON7DOWN, MOUSE_BUTTON8DOWN,
- 0x00000100, 0x00000200, 0x00000400, 0x00000800,
- 0x00001000, 0x00002000, 0x00004000, 0x00008000,
- 0x00010000, 0x00020000, 0x00040000, 0x00080000,
- 0x00100000, 0x00200000, 0x00400000, 0x00800000,
- 0x01000000, 0x02000000, 0x04000000, 0x08000000,
- 0x10000000, 0x20000000, 0x40000000,
-};
-
-static char *
-skipspace(char *s)
-{
- while(isspace(*s))
- ++s;
- return (s);
-}
-
-static int
-r_installmap(char *arg)
-{
- int pbutton;
- int lbutton;
- char *s;
-
- while (*arg) {
- arg = skipspace(arg);
- s = arg;
- while (isdigit(*arg))
- ++arg;
- arg = skipspace(arg);
- if ((arg <= s) || (*arg != '='))
- return (FALSE);
- lbutton = atoi(s);
-
- arg = skipspace(++arg);
- s = arg;
- while (isdigit(*arg))
- ++arg;
- if ((arg <= s) || (!isspace(*arg) && (*arg != '\0')))
- return (FALSE);
- pbutton = atoi(s);
-
- if ((lbutton <= 0) || (lbutton > MOUSE_MAXBUTTON))
- return (FALSE);
- if ((pbutton <= 0) || (pbutton > MOUSE_MAXBUTTON))
- return (FALSE);
- p2l[pbutton - 1] = 1 << (lbutton - 1);
- mstate[lbutton - 1] = &bstate[pbutton - 1];
- }
-
- return (TRUE);
-}
-
-static void
-r_map(mousestatus_t *act1, mousestatus_t *act2)
-{
- register int pb;
- register int pbuttons;
- int lbuttons;
-
- pbuttons = act1->button;
- lbuttons = 0;
-
- act2->obutton = act2->button;
- if (pbuttons & rodent.wmode) {
- pbuttons &= ~rodent.wmode;
- act1->dz = act1->dy;
- act1->dx = 0;
- act1->dy = 0;
- }
- act2->dx = act1->dx;
- act2->dy = act1->dy;
- act2->dz = act1->dz;
-
- switch (rodent.zmap[0]) {
- case 0: /* do nothing */
- break;
- case MOUSE_XAXIS:
- if (act1->dz != 0) {
- act2->dx = act1->dz;
- act2->dz = 0;
- }
- break;
- case MOUSE_YAXIS:
- if (act1->dz != 0) {
- act2->dy = act1->dz;
- act2->dz = 0;
- }
- break;
- default: /* buttons */
- pbuttons &= ~(rodent.zmap[0] | rodent.zmap[1]
- | rodent.zmap[2] | rodent.zmap[3]);
- if ((act1->dz < -1) && rodent.zmap[2]) {
- pbuttons |= rodent.zmap[2];
- zstate[2].count = 1;
- } else if (act1->dz < 0) {
- pbuttons |= rodent.zmap[0];
- zstate[0].count = 1;
- } else if ((act1->dz > 1) && rodent.zmap[3]) {
- pbuttons |= rodent.zmap[3];
- zstate[3].count = 1;
- } else if (act1->dz > 0) {
- pbuttons |= rodent.zmap[1];
- zstate[1].count = 1;
- }
- act2->dz = 0;
- break;
- }
-
- for (pb = 0; (pb < MOUSE_MAXBUTTON) && (pbuttons != 0); ++pb) {
- lbuttons |= (pbuttons & 1) ? p2l[pb] : 0;
- pbuttons >>= 1;
- }
- act2->button = lbuttons;
-
- act2->flags = ((act2->dx || act2->dy || act2->dz) ? MOUSE_POSCHANGED : 0)
- | (act2->obutton ^ act2->button);
-}
-
-static void
-r_timestamp(mousestatus_t *act)
-{
- struct timespec ts;
- struct timespec ts1;
- struct timespec ts2;
- struct timespec ts3;
- int button;
- int mask;
- int i;
-
- mask = act->flags & MOUSE_BUTTONS;
-#if 0
- if (mask == 0)
- return;
-#endif
-
- clock_gettime(CLOCK_MONOTONIC_FAST, &ts1);
- drift_current_ts = ts1;
-
- /* double click threshold */
- ts2.tv_sec = rodent.clickthreshold / 1000;
- ts2.tv_nsec = (rodent.clickthreshold % 1000) * 1000000;
- tssub(&ts1, &ts2, &ts);
- debug("ts: %jd %ld", (intmax_t)ts.tv_sec, ts.tv_nsec);
-
- /* 3 button emulation timeout */
- ts2.tv_sec = rodent.button2timeout / 1000;
- ts2.tv_nsec = (rodent.button2timeout % 1000) * 1000000;
- tssub(&ts1, &ts2, &ts3);
-
- button = MOUSE_BUTTON1DOWN;
- for (i = 0; (i < MOUSE_MAXBUTTON) && (mask != 0); ++i) {
- if (mask & 1) {
- if (act->button & button) {
- /* the button is down */
- debug(" : %jd %ld",
- (intmax_t)bstate[i].ts.tv_sec, bstate[i].ts.tv_nsec);
- if (tscmp(&ts, &bstate[i].ts, >)) {
- bstate[i].count = 1;
- } else {
- ++bstate[i].count;
- }
- bstate[i].ts = ts1;
- } else {
- /* the button is up */
- bstate[i].ts = ts1;
- }
- } else {
- if (act->button & button) {
- /* the button has been down */
- if (tscmp(&ts3, &bstate[i].ts, >)) {
- bstate[i].count = 1;
- bstate[i].ts = ts1;
- act->flags |= button;
- debug("button %d timeout", i + 1);
- }
- } else {
- /* the button has been up */
- }
- }
- button <<= 1;
- mask >>= 1;
- }
-}
-
-static int
-r_timeout(void)
-{
- struct timespec ts;
- struct timespec ts1;
- struct timespec ts2;
-
- if (states[mouse_button_state].timeout)
- return (TRUE);
- clock_gettime(CLOCK_MONOTONIC_FAST, &ts1);
- ts2.tv_sec = rodent.button2timeout / 1000;
- ts2.tv_nsec = (rodent.button2timeout % 1000) * 1000000;
- tssub(&ts1, &ts2, &ts);
- return (tscmp(&ts, &mouse_button_state_ts, >));
-}
-
-static void
-r_click(mousestatus_t *act)
-{
- struct mouse_info mouse;
- int button;
- int mask;
- int i;
-
- mask = act->flags & MOUSE_BUTTONS;
- if (mask == 0)
- return;
-
- button = MOUSE_BUTTON1DOWN;
- for (i = 0; (i < MOUSE_MAXBUTTON) && (mask != 0); ++i) {
- if (mask & 1) {
- debug("mstate[%d]->count:%d", i, mstate[i]->count);
- if (act->button & button) {
- /* the button is down */
- mouse.u.event.value = mstate[i]->count;
- } else {
- /* the button is up */
- mouse.u.event.value = 0;
- }
- mouse.operation = MOUSE_BUTTON_EVENT;
- mouse.u.event.id = button;
- if (debug < 2)
- if (!paused)
- ioctl(rodent.cfd, CONS_MOUSECTL, &mouse);
- debug("button %d count %d", i + 1, mouse.u.event.value);
- }
- button <<= 1;
- mask >>= 1;
- }
-}
-
/* $XConsortium: posix_tty.c,v 1.3 95/01/05 20:42:55 kaleb Exp $ */
/* $XFree86: xc/programs/Xserver/hw/xfree86/os-support/shared/posix_tty.c,v 3.4 1995/01/28 17:05:03 dawes Exp $ */
/*
@@ -2745,7 +1947,7 @@ setmousespeed(int old, int new, unsigned cflag)
* The routine does not fully implement the COM Enumerator as par Section
* 2.1 of the document. In particular, we don't have idle state in which
* the driver software monitors the com port for dynamic connection or
- * removal of a device at the port, because `moused' simply quits if no
+ * removal of a device at the port, because `msconvd' simply quits if no
* device is found.
*
* In addition, as PnP COM device enumeration procedure slightly has
@@ -2753,7 +1955,7 @@ setmousespeed(int old, int new, unsigned cflag)
* revisions of the above spec. may fail to respond if the rev 1.0
* procedure is used. XXX
*/
-static int
+static bool
pnpwakeup1(void)
{
struct timeval timeout;
@@ -2783,7 +1985,7 @@ pnpwakeup1(void)
ioctl(rodent.mfd, TIOCMGET, &i);
debug("modem status 0%o", i);
if ((i & TIOCM_DSR) == 0)
- return (FALSE);
+ return (false);
/* port setup, 1st phase (2.1.3) */
setmousespeed(1200, 1200, (CS7 | CREAD | CLOCAL | HUPCL));
@@ -2807,7 +2009,7 @@ pnpwakeup1(void)
timeout.tv_usec = 240000;
if (select(FD_SETSIZE, &fds, NULL, NULL, &timeout) > 0) {
debug("pnpwakeup1(): valid response in first phase.");
- return (TRUE);
+ return (true);
}
/* port setup, 2nd phase (2.1.5) */
@@ -2828,13 +2030,13 @@ pnpwakeup1(void)
timeout.tv_usec = 240000;
if (select(FD_SETSIZE, &fds, NULL, NULL, &timeout) > 0) {
debug("pnpwakeup1(): valid response in second phase.");
- return (TRUE);
+ return (true);
}
- return (FALSE);
+ return (false);
}
-static int
+static bool
pnpwakeup2(void)
{
struct timeval timeout;
@@ -2867,10 +2069,10 @@ pnpwakeup2(void)
timeout.tv_usec = 240000;
if (select(FD_SETSIZE, &fds, NULL, NULL, &timeout) > 0) {
debug("pnpwakeup2(): valid response.");
- return (TRUE);
+ return (true);
}
- return (FALSE);
+ return (false);
}
static int
@@ -2885,7 +2087,7 @@ pnpgets(char *buf)
if (!pnpwakeup1() && !pnpwakeup2()) {
/*
* According to PnP spec, we should set DTR = 1 and RTS = 0 while
- * in idle state. But, `moused' shall set DTR = RTS = 1 and proceed,
+ * in idle state. But, `msconvd' shall set DTR = RTS = 1 and proceed,
* assuming there is something at the port even if it didn't
* respond to the PnP enumeration procedure.
*/
@@ -2942,7 +2144,7 @@ pnpgets(char *buf)
/*
* According to PnP spec, we should set DTR = 1 and RTS = 0 while
- * in idle state. But, `moused' shall leave the modem control lines
+ * in idle state. But, `msconvd' shall leave the modem control lines
* as they are. See above.
*/
connect_idle:
@@ -2951,7 +2153,7 @@ connect_idle:
return (MAX(i, 0));
}
-static int
+static bool
pnpparse(pnpid_t *id, char *buf, int len)
{
char s[3];
@@ -2975,7 +2177,7 @@ pnpparse(pnpid_t *id, char *buf, int len)
/* non-PnP mice */
switch(buf[0]) {
default:
- return (FALSE);
+ return (false);
case 'M': /* Microsoft */
id->eisaid = "PNP0F01";
break;
@@ -2987,7 +2189,7 @@ pnpparse(pnpid_t *id, char *buf, int len)
id->class = "MOUSE";
id->nclass = strlen(id->class);
debug("non-PnP mouse '%c'", buf[0]);
- return (TRUE);
+ return (true);
}
/* PnP mice */
@@ -3086,12 +2288,12 @@ pnpparse(pnpid_t *id, char *buf, int len)
* spec regarding checksum... XXX
*/
logwarnx("PnP checksum error", 0);
- return (FALSE);
+ return (false);
#endif
}
}
- return (TRUE);
+ return (true);
}
static symtab_t *
@@ -3159,6 +2361,18 @@ gettokenname(symtab_t *tab, int val)
return (unknown);
}
+static int
+gettokenval2(symtab_t *tab, int val)
+{
+ int i;
+
+ for (i = 0; tab[i].name != NULL; ++i) {
+ if (tab[i].val == val)
+ return (tab[i].val2);
+ }
+ return (0);
+}
+
/*
* code to read from the Genius Kidspad tablet.
@@ -3355,7 +2569,7 @@ mremote_serversetup(void)
}
static void
-mremote_clientchg(int add)
+mremote_clientchg(bool add)
{
struct sockaddr_un ad;
socklen_t ad_len;
diff --git a/usr.sbin/vidcontrol/vidcontrol.1 b/usr.sbin/vidcontrol/vidcontrol.1
index 09855df6b60f..91804facce8e 100644
--- a/usr.sbin/vidcontrol/vidcontrol.1
+++ b/usr.sbin/vidcontrol/vidcontrol.1
@@ -282,11 +282,17 @@ Show the current changes.
.El
.It Fl d
Print out current output screen map.
+Supported only with
+.Xr syscons 4 .
.It Fl E Ar emulator
Set the terminal emulator to
.Ar emulator .
+Supported only with
+.Xr syscons 4 .
.It Fl e
Show the active and available terminal emulators.
+Supported only with
+.Xr syscons 4 .
.It Xo
.Fl f
.Oo
@@ -358,13 +364,12 @@ Shows the possible video modes with the current video hardware.
.It Fl l Ar screen_map
Install screen output map file from
.Ar screen_map .
-See also
-.Xr syscons 4
-or
-.Xr vt 4
-(depending on which driver you use).
+Supported only with
+.Xr syscons 4 .
.It Fl L
Install default screen output map.
+Supported only with
+.Xr syscons 4 .
.It Fl M Ar char
Sets the base character used to render the mouse pointer to
.Ar char .