aboutsummaryrefslogtreecommitdiff
path: root/usr.sbin
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin')
-rw-r--r--usr.sbin/Makefile4
-rw-r--r--usr.sbin/autofs/common.c3
-rw-r--r--usr.sbin/bhyve/pci_xhci.c35
-rw-r--r--usr.sbin/bhyve/rfb.c19
-rw-r--r--usr.sbin/bhyve/usb_emul.h4
-rw-r--r--usr.sbin/bhyve/usb_mouse.c13
-rw-r--r--usr.sbin/bsdinstall/bsdinstall.84
-rwxr-xr-xusr.sbin/bsdinstall/scripts/jail2
-rwxr-xr-xusr.sbin/bsdinstall/scripts/zfsboot4
-rw-r--r--usr.sbin/certctl/Makefile11
-rw-r--r--usr.sbin/certctl/certctl.896
-rw-r--r--usr.sbin/certctl/certctl.c1139
-rwxr-xr-xusr.sbin/certctl/certctl.sh366
-rw-r--r--usr.sbin/certctl/tests/Makefile5
-rw-r--r--usr.sbin/certctl/tests/certctl.subr44
-rw-r--r--usr.sbin/certctl/tests/certctl_test.sh332
-rw-r--r--usr.sbin/chroot/chroot.c38
-rw-r--r--usr.sbin/ctld/ctld.hh6
-rw-r--r--usr.sbin/ctld/iscsi.hh2
-rw-r--r--usr.sbin/devinfo/devinfo.843
-rw-r--r--usr.sbin/freebsd-update/freebsd-update.sh2
-rw-r--r--usr.sbin/fwget/pci/pci6
-rw-r--r--usr.sbin/fwget/pci/pci_video_amd11
-rw-r--r--usr.sbin/jail/config.c2
-rwxr-xr-xusr.sbin/jail/tests/jail_basic_test.sh15
-rw-r--r--usr.sbin/lpr/lpc/lpc.c2
-rw-r--r--usr.sbin/makefs/ffs.c94
-rw-r--r--usr.sbin/makefs/zfs.c2
-rw-r--r--usr.sbin/makefs/zfs/vdev.c2
-rw-r--r--usr.sbin/mountd/exports.515
-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/newsyslog/newsyslog.822
-rw-r--r--usr.sbin/newsyslog/newsyslog.c29
-rw-r--r--usr.sbin/newsyslog/newsyslog.conf.584
-rw-r--r--usr.sbin/ngctl/Makefile5
-rw-r--r--usr.sbin/ngctl/main.c41
-rw-r--r--usr.sbin/ngctl/ngctl.825
-rw-r--r--usr.sbin/ntp/ntpd/leap-seconds8
-rw-r--r--usr.sbin/pkg/FreeBSD.conf.latest8
-rw-r--r--usr.sbin/pkg/FreeBSD.conf.quarterly8
-rw-r--r--usr.sbin/pkg/FreeBSD.conf.quarterly-release8
-rw-r--r--usr.sbin/pw/pw.89
-rw-r--r--usr.sbin/pw/pw.c1
-rw-r--r--usr.sbin/pw/pw_user.c18
-rw-r--r--usr.sbin/pw/pwupd.h1
-rw-r--r--usr.sbin/services_mkdb/services2
-rw-r--r--usr.sbin/syslogd/syslogd.82
-rw-r--r--usr.sbin/syslogd/syslogd.c5
-rw-r--r--usr.sbin/tcpdump/Makefile.inc4
-rw-r--r--usr.sbin/tcpdump/tcpdump/Makefile14
-rwxr-xr-xusr.sbin/unbound/setup/local-unbound-setup.sh2
-rw-r--r--usr.sbin/vidcontrol/vidcontrol.115
-rw-r--r--usr.sbin/watch/watch.82
-rw-r--r--usr.sbin/watchdogd/watchdogd.c112
73 files changed, 11984 insertions, 2033 deletions
diff --git a/usr.sbin/Makefile b/usr.sbin/Makefile
index dcfe2037f8ce..b97c22ffeb08 100644
--- a/usr.sbin/Makefile
+++ b/usr.sbin/Makefile
@@ -139,9 +139,7 @@ SUBDIR.${MK_FLOPPY}+= fdformat
SUBDIR.${MK_FLOPPY}+= fdread
SUBDIR.${MK_FLOPPY}+= fdwrite
SUBDIR.${MK_FREEBSD_UPDATE}+= freebsd-update
-.if ${MK_KERBEROS_SUPPORT} != "no"
-SUBDIR.${MK_GSSAPI}+= gssd
-.endif
+SUBDIR.${MK_KERBEROS_SUPPORT}+= gssd
SUBDIR.${MK_GPIO}+= gpioctl
SUBDIR.${MK_HYPERV}+= hyperv
SUBDIR.${MK_INET6}+= ip6addrctl
diff --git a/usr.sbin/autofs/common.c b/usr.sbin/autofs/common.c
index 18756752876c..2dd7c290cebc 100644
--- a/usr.sbin/autofs/common.c
+++ b/usr.sbin/autofs/common.c
@@ -149,10 +149,11 @@ create_directory(const char *path)
error = mkdir(partial, 0755);
if (error != 0 && errno != EEXIST) {
log_warn("cannot create %s", partial);
- return;
+ break;
}
}
+ free(partial);
free(tofree);
}
diff --git a/usr.sbin/bhyve/pci_xhci.c b/usr.sbin/bhyve/pci_xhci.c
index 0871bbb87fe5..ff12e40359e2 100644
--- a/usr.sbin/bhyve/pci_xhci.c
+++ b/usr.sbin/bhyve/pci_xhci.c
@@ -406,7 +406,7 @@ pci_xhci_usbcmd_write(struct pci_xhci_softc *sc, uint32_t cmd)
* XHCI 4.19.3 USB2 RxDetect->Polling,
* USB3 Polling->U0
*/
- if (dev->dev_ue->ue_usbver == 2)
+ if (dev->hci.hci_usbver == 2)
port->portsc |=
XHCI_PS_PLS_SET(UPS_PORT_LS_POLL);
else
@@ -2590,7 +2590,7 @@ pci_xhci_reset_port(struct pci_xhci_softc *sc, int portn, int warm)
port->portsc |= XHCI_PS_PED |
XHCI_PS_SPEED_SET(dev->hci.hci_speed);
- if (warm && dev->dev_ue->ue_usbver == 3) {
+ if (warm && dev->hci.hci_usbver == 3) {
port->portsc |= XHCI_PS_WRC;
}
@@ -2620,7 +2620,7 @@ pci_xhci_init_port(struct pci_xhci_softc *sc, int portn)
port->portsc = XHCI_PS_CCS | /* connected */
XHCI_PS_PP; /* port power */
- if (dev->dev_ue->ue_usbver == 2) {
+ if (dev->hci.hci_usbver == 2) {
port->portsc |= XHCI_PS_PLS_SET(UPS_PORT_LS_POLL) |
XHCI_PS_SPEED_SET(dev->hci.hci_speed);
} else {
@@ -2785,8 +2785,8 @@ pci_xhci_parse_devices(struct pci_xhci_softc *sc, nvlist_t *nvl)
cookie = NULL;
while ((name = nvlist_next(slots_nvl, &type, &cookie)) != NULL) {
- if (usb2_port == ((sc->usb2_port_start) + XHCI_MAX_DEVS/2) ||
- usb3_port == ((sc->usb3_port_start) + XHCI_MAX_DEVS/2)) {
+ if (usb2_port == ((sc->usb2_port_start) + XHCI_MAX_DEVS / 2) ||
+ usb3_port == ((sc->usb3_port_start) + XHCI_MAX_DEVS / 2)) {
WPRINTF(("pci_xhci max number of USB 2 or 3 "
"devices reached, max %d", XHCI_MAX_DEVS/2));
goto bad;
@@ -2834,12 +2834,25 @@ pci_xhci_parse_devices(struct pci_xhci_softc *sc, nvlist_t *nvl)
dev->hci.hci_intr = pci_xhci_dev_intr;
dev->hci.hci_event = pci_xhci_dev_event;
dev->hci.hci_speed = USB_SPEED_MAX;
+ dev->hci.hci_usbver = -1;
- if (ue->ue_usbver == 2) {
+ devsc = ue->ue_probe(&dev->hci, nvl);
+ if (devsc == NULL) {
+ free(dev);
+ goto bad;
+ }
+ dev->dev_sc = devsc;
+
+ if (dev->hci.hci_usbver == -1)
+ dev->hci.hci_usbver = ue->ue_usbver;
+
+ if (dev->hci.hci_usbver == 2) {
if (usb2_port == sc->usb2_port_start +
XHCI_MAX_DEVS / 2) {
WPRINTF(("pci_xhci max number of USB 2 devices "
"reached, max %d", XHCI_MAX_DEVS / 2));
+ free(dev->dev_sc);
+ free(dev);
goto bad;
}
dev->hci.hci_port = usb2_port;
@@ -2849,6 +2862,8 @@ pci_xhci_parse_devices(struct pci_xhci_softc *sc, nvlist_t *nvl)
XHCI_MAX_DEVS / 2) {
WPRINTF(("pci_xhci max number of USB 3 devices "
"reached, max %d", XHCI_MAX_DEVS / 2));
+ free(dev->dev_sc);
+ free(dev);
goto bad;
}
dev->hci.hci_port = usb3_port;
@@ -2857,13 +2872,10 @@ pci_xhci_parse_devices(struct pci_xhci_softc *sc, nvlist_t *nvl)
XHCI_DEVINST_PTR(sc, dev->hci.hci_port) = dev;
dev->hci.hci_address = 0;
- devsc = ue->ue_init(&dev->hci, nvl);
- if (devsc == NULL) {
+ if (ue->ue_init(dev->dev_sc))
goto bad;
- }
dev->dev_ue = ue;
- dev->dev_sc = devsc;
if (dev->hci.hci_speed == USB_SPEED_MAX)
dev->hci.hci_speed = ue->ue_usbspeed;
@@ -2885,6 +2897,8 @@ portsfinal:
bad:
for (i = 1; i <= XHCI_MAX_DEVS; i++) {
+ if (XHCI_DEVINST_PTR(sc, i) != NULL)
+ free(XHCI_DEVINST_PTR(sc, i)->dev_sc);
free(XHCI_DEVINST_PTR(sc, i));
}
@@ -3232,6 +3246,7 @@ pci_xhci_snapshot(struct vm_snapshot_meta *meta)
SNAPSHOT_VAR_OR_LEAVE(dev->hci.hci_address, meta, ret, done);
SNAPSHOT_VAR_OR_LEAVE(dev->hci.hci_port, meta, ret, done);
SNAPSHOT_VAR_OR_LEAVE(dev->hci.hci_speed, meta, ret, done);
+ SNAPSHOT_VAR_OR_LEAVE(dev->hci.hci_usbver, meta, ret, done);
}
SNAPSHOT_VAR_OR_LEAVE(sc->usb2_port_start, meta, ret, done);
diff --git a/usr.sbin/bhyve/rfb.c b/usr.sbin/bhyve/rfb.c
index 6af374a83bbe..716e191e2fc0 100644
--- a/usr.sbin/bhyve/rfb.c
+++ b/usr.sbin/bhyve/rfb.c
@@ -63,6 +63,7 @@
#include "bhyvegc.h"
#include "debug.h"
#include "console.h"
+#include "config.h"
#include "rfb.h"
#include "sockstream.h"
@@ -152,6 +153,8 @@ struct rfb_softc {
struct pixfmt pixfmt; /* owned by the write thread */
struct pixfmt new_pixfmt; /* managed with pixfmt_mtx */
uint32_t *pixrow;
+ char *fbname;
+ int fbnamelen;
};
struct rfb_pixfmt {
@@ -262,7 +265,7 @@ struct rfb_cuttext_msg {
};
static void
-rfb_send_server_init_msg(int cfd)
+rfb_send_server_init_msg(struct rfb_softc *rc, int cfd)
{
struct bhyvegc_image *gc_image;
struct rfb_srvr_info sinfo;
@@ -284,9 +287,9 @@ rfb_send_server_init_msg(int cfd)
sinfo.pixfmt.pad[0] = 0;
sinfo.pixfmt.pad[1] = 0;
sinfo.pixfmt.pad[2] = 0;
- sinfo.namelen = htonl(strlen("bhyve"));
+ sinfo.namelen = htonl(rc->fbnamelen);
(void)stream_write(cfd, &sinfo, sizeof(sinfo));
- (void)stream_write(cfd, "bhyve", strlen("bhyve"));
+ (void)stream_write(cfd, rc->fbname, rc->fbnamelen);
}
static void
@@ -1144,7 +1147,7 @@ report_and_done:
len = stream_read(cfd, buf, 1);
/* 4a. Write server-init info */
- rfb_send_server_init_msg(cfd);
+ rfb_send_server_init_msg(rc, cfd);
if (!rc->zbuf) {
rc->zbuf = malloc(RFB_ZLIB_BUFSZ + 16);
@@ -1276,6 +1279,13 @@ rfb_init(const char *hostname, int port, int wait, const char *password)
rc->password = password;
+ rc->fbnamelen = asprintf(&rc->fbname, "bhyve:%s",
+ get_config_value("name"));
+ if (rc->fbnamelen < 0) {
+ EPRINTLN("rfb: failed to allocate memory for VNC title");
+ goto error;
+ }
+
rc->pixrow = malloc(RFB_MAX_WIDTH * sizeof(uint32_t));
if (rc->pixrow == NULL) {
EPRINTLN("rfb: failed to allocate memory for pixrow buffer");
@@ -1358,6 +1368,7 @@ rfb_init(const char *hostname, int port, int wait, const char *password)
free(rc->crc);
free(rc->crc_tmp);
free(rc->pixrow);
+ free(rc->fbname);
free(rc);
return (-1);
}
diff --git a/usr.sbin/bhyve/usb_emul.h b/usr.sbin/bhyve/usb_emul.h
index 85dedfeacd3b..43b6b53b5205 100644
--- a/usr.sbin/bhyve/usb_emul.h
+++ b/usr.sbin/bhyve/usb_emul.h
@@ -52,7 +52,8 @@ struct usb_devemu {
int ue_usbspeed; /* usb device speed */
/* instance creation */
- void *(*ue_init)(struct usb_hci *hci, nvlist_t *nvl);
+ void *(*ue_probe)(struct usb_hci *hci, nvlist_t *nvl);
+ int (*ue_init)(void *sc);
/* handlers */
int (*ue_request)(void *sc, struct usb_data_xfer *xfer);
@@ -86,6 +87,7 @@ struct usb_hci {
int hci_address;
int hci_port;
int hci_speed;
+ int hci_usbver;
};
/*
diff --git a/usr.sbin/bhyve/usb_mouse.c b/usr.sbin/bhyve/usb_mouse.c
index a37941c0cd9d..82b1159d5f61 100644
--- a/usr.sbin/bhyve/usb_mouse.c
+++ b/usr.sbin/bhyve/usb_mouse.c
@@ -295,20 +295,28 @@ umouse_event(uint8_t button, int x, int y, void *arg)
}
static void *
-umouse_init(struct usb_hci *hci, nvlist_t *nvl __unused)
+umouse_probe(struct usb_hci *hci, nvlist_t *nvl __unused)
{
struct umouse_softc *sc;
sc = calloc(1, sizeof(struct umouse_softc));
sc->hci = hci;
+ return (sc);
+}
+
+static int
+umouse_init(void *scarg)
+{
+ struct umouse_softc *sc = (struct umouse_softc *)scarg;
+
sc->hid.protocol = 1; /* REPORT protocol */
pthread_mutex_init(&sc->mtx, NULL);
pthread_mutex_init(&sc->ev_mtx, NULL);
console_ptr_register(umouse_event, sc, 10);
- return (sc);
+ return (0);
}
#define UREQ(x,y) ((x) | ((y) << 8))
@@ -811,6 +819,7 @@ static struct usb_devemu ue_mouse = {
.ue_emu = "tablet",
.ue_usbver = 3,
.ue_usbspeed = USB_SPEED_HIGH,
+ .ue_probe = umouse_probe,
.ue_init = umouse_init,
.ue_request = umouse_request,
.ue_data = umouse_data_handler,
diff --git a/usr.sbin/bsdinstall/bsdinstall.8 b/usr.sbin/bsdinstall/bsdinstall.8
index 181abdcf9d05..5ccbaef87835 100644
--- a/usr.sbin/bsdinstall/bsdinstall.8
+++ b/usr.sbin/bsdinstall/bsdinstall.8
@@ -693,7 +693,7 @@ is:
# Home directories separated so they are common to all BEs
/home mountpoint=/home
-# Compress /tmp, allow exec but not setuid
+# Create /tmp and allow exec but not setuid
/tmp mountpoint=/tmp,exec=on,setuid=off
# Do not mount /usr so that 'base' files go to the BEROOT
@@ -702,7 +702,7 @@ is:
# Ports tree
/usr/ports setuid=off
-# Source tree (compressed)
+# Source tree
/usr/src
# Create /var and friends
diff --git a/usr.sbin/bsdinstall/scripts/jail b/usr.sbin/bsdinstall/scripts/jail
index 4b2882dad477..0c3c7e125fdd 100755
--- a/usr.sbin/bsdinstall/scripts/jail
+++ b/usr.sbin/bsdinstall/scripts/jail
@@ -207,7 +207,7 @@ fi
trap error SIGINT # SIGINT is bad again
bsdinstall config || error "Failed to save config"
cp /etc/resolv.conf $1/etc
-cp /etc/localtime $1/etc
+cp -P /etc/localtime $1/etc
cp /var/db/zoneinfo $1/var/db
# Run post-install script
diff --git a/usr.sbin/bsdinstall/scripts/zfsboot b/usr.sbin/bsdinstall/scripts/zfsboot
index a3c1e2ddb89f..aa05faa7d3dd 100755
--- a/usr.sbin/bsdinstall/scripts/zfsboot
+++ b/usr.sbin/bsdinstall/scripts/zfsboot
@@ -152,7 +152,7 @@ f_isset ZFSBOOT_DATASETS || ZFSBOOT_DATASETS="
# Home directories separated so they are common to all BEs
/home mountpoint=/home
- # Compress /tmp, allow exec but not setuid
+ # Create /tmp and allow exec but not setuid
/tmp mountpoint=/tmp,exec=on,setuid=off
# Don't mount /usr so that 'base' files go to the BEROOT
@@ -161,7 +161,7 @@ f_isset ZFSBOOT_DATASETS || ZFSBOOT_DATASETS="
# Ports tree
/usr/ports setuid=off
- # Source tree (compressed)
+ # Source tree
/usr/src
# Create /var and friends
diff --git a/usr.sbin/certctl/Makefile b/usr.sbin/certctl/Makefile
index 88c024daf7e6..6900f0ce3b65 100644
--- a/usr.sbin/certctl/Makefile
+++ b/usr.sbin/certctl/Makefile
@@ -1,5 +1,14 @@
+.include <src.opts.mk>
+
PACKAGE= certctl
-SCRIPTS=certctl.sh
+PROG= certctl
MAN= certctl.8
+LIBADD= crypto
+HAS_TESTS=
+SUBDIR.${MK_TESTS}= tests
+
+.ifdef BOOTSTRAPPING
+CFLAGS+=-DBOOTSTRAPPING
+.endif
.include <bsd.prog.mk>
diff --git a/usr.sbin/certctl/certctl.8 b/usr.sbin/certctl/certctl.8
index 7e49bb89e2ac..edf993e1361a 100644
--- a/usr.sbin/certctl/certctl.8
+++ b/usr.sbin/certctl/certctl.8
@@ -24,7 +24,7 @@
.\" IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
.\" POSSIBILITY OF SUCH DAMAGE.
.\"
-.Dd July 17, 2025
+.Dd August 18, 2025
.Dt CERTCTL 8
.Os
.Sh NAME
@@ -32,63 +32,85 @@
.Nd "tool for managing trusted and untrusted TLS certificates"
.Sh SYNOPSIS
.Nm
-.Op Fl v
+.Op Fl lv
.Ic list
.Nm
-.Op Fl v
+.Op Fl lv
.Ic untrusted
.Nm
-.Op Fl cnUv
+.Op Fl BnUv
.Op Fl D Ar destdir
.Op Fl M Ar metalog
.Ic rehash
.Nm
-.Op Fl cnv
-.Ic untrust Ar file
+.Op Fl nv
+.Ic untrust Ar
.Nm
-.Op Fl cnv
-.Ic trust Ar file
+.Op Fl nv
+.Ic trust Ar
.Sh DESCRIPTION
The
.Nm
utility manages the list of TLS Certificate Authorities that are trusted by
applications that use OpenSSL.
.Pp
-Flags:
+The following options are available:
.Bl -tag -width 4n
-.It Fl c
-Copy certificates instead of linking to them.
+.It Fl B
+Do not generate a bundle.
+This option is only valid in conjunction with the
+.Ic rehash
+command.
.It Fl D Ar destdir
Specify the DESTDIR (overriding values from the environment).
.It Fl d Ar distbase
Specify the DISTBASE (overriding values from the environment).
+.It Fl l
+When listing installed (trusted or untrusted) certificates, show the
+full path and distinguished name for each certificate.
.It Fl M Ar metalog
-Specify the path of the METALOG file (default: $DESTDIR/METALOG).
+Specify the path of the METALOG file
+.Po
+default:
+.Pa ${DESTDIR}/METALOG
+.Pc .
+This option is only valid in conjunction with the
+.Ic rehash
+command.
.It Fl n
-No-Op mode, do not actually perform any actions.
+Dry-run mode.
+Do not actually perform any actions except write the metalog.
.It Fl v
-Be verbose, print details about actions before performing them.
+Verbose mode.
+Print detailed information about each action taken.
.It Fl U
-Unprivileged mode, do not change the ownership of created links.
-Do record the ownership in the METALOG file.
+Unprivileged mode.
+Do not attempt to set the ownership of created files.
+This option is only valid in conjunction with the
+.Fl M
+option and the
+.Ic rehash
+command.
.El
.Pp
Primary command functions:
.Bl -tag -width untrusted
.It Ic list
-List all currently trusted certificate authorities.
+List all currently trusted certificates.
.It Ic untrusted
List all currently untrusted certificates.
.It Ic rehash
-Rebuild the list of trusted certificate authorities by scanning all directories
+Rebuild the list of trusted certificates by scanning all directories
in
.Ev TRUSTPATH
and all untrusted certificates in
.Ev UNTRUSTPATH .
-A symbolic link to each trusted certificate is placed in
+A copy of each trusted certificate is placed in
.Ev CERTDESTDIR
and each untrusted certificate in
.Ev UNTRUSTDESTDIR .
+In addition, a bundle containing the trusted certificates is placed in
+.Ev BUNDLEFILE .
.It Ic untrust
Add the specified file to the untrusted list.
.It Ic trust
@@ -97,9 +119,13 @@ Remove the specified file from the untrusted list.
.Sh ENVIRONMENT
.Bl -tag -width UNTRUSTDESTDIR
.It Ev DESTDIR
-Alternate destination directory to operate on.
+Absolute path to an alternate destination directory to operate on
+instead of the file system root, e.g.
+.Dq Li /tmp/install .
.It Ev DISTBASE
Additional path component to include when operating on certificate directories.
+This must start with a slash, e.g.
+.Dq Li /base .
.It Ev LOCALBASE
Location for local programs.
Defaults to the value of the user.localbase sysctl which is usually
@@ -107,32 +133,34 @@ Defaults to the value of the user.localbase sysctl which is usually
.It Ev TRUSTPATH
List of paths to search for trusted certificates.
Default:
-.Pa <DESTDIR><DISTBASE>/usr/share/certs/trusted
-.Pa <DESTDIR><DISTBASE>/usr/local/share/certs
-.Pa <DESTDIR><DISTBASE><LOCALBASE>/etc/ssl/certs
+.Pa ${DESTDIR}${DISTBASE}/usr/share/certs/trusted
+.Pa ${DESTDIR}${LOCALBASE}/share/certs/trusted
+.Pa ${DESTDIR}${LOCALBASE}/share/certs
.It Ev UNTRUSTPATH
List of paths to search for untrusted certificates.
Default:
-.Pa <DESTDIR><DISTBASE>/usr/share/certs/untrusted
-.Pa <DESTDIR><DISTBASE><LOCALBASE>/etc/ssl/untrusted
-.Pa <DESTDIR><DISTBASE><LOCALBASE>/etc/ssl/blacklisted
-.It Ev CERTDESTDIR
+.Pa ${DESTDIR}${DISTBASE}/usr/share/certs/untrusted
+.Pa ${DESTDIR}${LOCALBASE}/share/certs/untrusted
+.It Ev TRUSTDESTDIR
Destination directory for symbolic links to trusted certificates.
Default:
-.Pa <DESTDIR><DISTBASE>/etc/ssl/certs
+.Pa ${DESTDIR}${DISTBASE}/etc/ssl/certs
.It Ev UNTRUSTDESTDIR
Destination directory for symbolic links to untrusted certificates.
Default:
-.Pa <DESTDIR><DISTBASE>/etc/ssl/untrusted
-.It Ev EXTENSIONS
-List of file extensions to read as certificate files.
-Default: *.pem *.crt *.cer *.crl *.0
+.Pa ${DESTDIR}${DISTBASE}/etc/ssl/untrusted
+.It Ev BUNDLE
+File name of bundle to produce.
.El
.Sh SEE ALSO
.Xr openssl 1
.Sh HISTORY
.Nm
first appeared in
-.Fx 12.2
+.Fx 12.2 .
.Sh AUTHORS
-.An Allan Jude Aq Mt allanjude@freebsd.org
+.An -nosplit
+The original shell implementation was written by
+.An Allan Jude Aq Mt allanjude@FreeBSD.org .
+The current C implementation was written by
+.An Dag-Erling Sm\(/orgrav Aq Mt des@FreeBSD.org .
diff --git a/usr.sbin/certctl/certctl.c b/usr.sbin/certctl/certctl.c
new file mode 100644
index 000000000000..a53ed7b2b4b2
--- /dev/null
+++ b/usr.sbin/certctl/certctl.c
@@ -0,0 +1,1139 @@
+/*-
+ * Copyright (c) 2023-2025 Dag-Erling Smørgrav <des@FreeBSD.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <sys/types.h>
+#include <sys/sysctl.h>
+#include <sys/stat.h>
+#include <sys/tree.h>
+
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fts.h>
+#include <paths.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <openssl/ssl.h>
+
+#define info(fmt, ...) \
+ do { \
+ if (verbose) \
+ fprintf(stderr, fmt "\n", ##__VA_ARGS__); \
+ } while (0)
+
+static char *
+xasprintf(const char *fmt, ...)
+{
+ va_list ap;
+ char *str;
+ int ret;
+
+ va_start(ap, fmt);
+ ret = vasprintf(&str, fmt, ap);
+ va_end(ap);
+ if (ret < 0 || str == NULL)
+ err(1, NULL);
+ return (str);
+}
+
+static char *
+xstrdup(const char *str)
+{
+ char *dup;
+
+ if ((dup = strdup(str)) == NULL)
+ err(1, NULL);
+ return (dup);
+}
+
+static void usage(void);
+
+static bool dryrun;
+static bool longnames;
+static bool nobundle;
+static bool unprivileged;
+static bool verbose;
+
+static const char *localbase;
+static const char *destdir;
+static const char *distbase;
+static const char *metalog;
+
+static const char *uname = "root";
+static const char *gname = "wheel";
+
+static const char *const default_trusted_paths[] = {
+ "/usr/share/certs/trusted",
+ "%L/share/certs/trusted",
+ "%L/share/certs",
+ NULL
+};
+static char **trusted_paths;
+
+static const char *const default_untrusted_paths[] = {
+ "/usr/share/certs/untrusted",
+ "%L/share/certs/untrusted",
+ NULL
+};
+static char **untrusted_paths;
+
+static char *trusted_dest;
+static char *untrusted_dest;
+static char *bundle_dest;
+
+#define SSL_PATH "/etc/ssl"
+#define TRUSTED_DIR "certs"
+#define TRUSTED_PATH SSL_PATH "/" TRUSTED_DIR
+#define UNTRUSTED_DIR "untrusted"
+#define UNTRUSTED_PATH SSL_PATH "/" UNTRUSTED_DIR
+#define LEGACY_DIR "blacklisted"
+#define LEGACY_PATH SSL_PATH "/" LEGACY_DIR
+#define BUNDLE_FILE "cert.pem"
+#define BUNDLE_PATH SSL_PATH "/" BUNDLE_FILE
+
+static FILE *mlf;
+
+/*
+ * Create a directory and its parents as needed.
+ */
+static void
+mkdirp(const char *dir)
+{
+ struct stat sb;
+ const char *sep;
+ char *parent;
+
+ if (stat(dir, &sb) == 0)
+ return;
+ if ((sep = strrchr(dir, '/')) != NULL) {
+ parent = xasprintf("%.*s", (int)(sep - dir), dir);
+ mkdirp(parent);
+ free(parent);
+ }
+ info("creating %s", dir);
+ if (mkdir(dir, 0755) != 0)
+ err(1, "mkdir %s", dir);
+}
+
+/*
+ * Remove duplicate and trailing slashes from a path.
+ */
+static char *
+normalize_path(const char *str)
+{
+ char *buf, *dst;
+
+ if ((buf = malloc(strlen(str) + 1)) == NULL)
+ err(1, NULL);
+ for (dst = buf; *str != '\0'; dst++) {
+ if ((*dst = *str++) == '/') {
+ while (*str == '/')
+ str++;
+ if (*str == '\0')
+ break;
+ }
+ }
+ *dst = '\0';
+ return (buf);
+}
+
+/*
+ * Split a colon-separated list into a NULL-terminated array.
+ */
+static char **
+split_paths(const char *str)
+{
+ char **paths;
+ const char *p, *q;
+ unsigned int i, n;
+
+ for (p = str, n = 1; *p; p++) {
+ if (*p == ':')
+ n++;
+ }
+ if ((paths = calloc(n + 1, sizeof(*paths))) == NULL)
+ err(1, NULL);
+ for (p = q = str, i = 0; i < n; i++, p = q + 1) {
+ q = strchrnul(p, ':');
+ if ((paths[i] = strndup(p, q - p)) == NULL)
+ err(1, NULL);
+ }
+ return (paths);
+}
+
+/*
+ * Expand %L into LOCALBASE and prefix DESTDIR and DISTBASE as needed.
+ */
+static char *
+expand_path(const char *template)
+{
+ if (template[0] == '%' && template[1] == 'L')
+ return (xasprintf("%s%s%s", destdir, localbase, template + 2));
+ return (xasprintf("%s%s%s", destdir, distbase, template));
+}
+
+/*
+ * Expand an array of paths.
+ */
+static char **
+expand_paths(const char *const *templates)
+{
+ char **paths;
+ unsigned int i, n;
+
+ for (n = 0; templates[n] != NULL; n++)
+ continue;
+ if ((paths = calloc(n + 1, sizeof(*paths))) == NULL)
+ err(1, NULL);
+ for (i = 0; i < n; i++)
+ paths[i] = expand_path(templates[i]);
+ return (paths);
+}
+
+/*
+ * If destdir is a prefix of path, returns a pointer to the rest of path,
+ * otherwise returns path.
+ *
+ * Note that this intentionally does not strip distbase from the path!
+ * Unlike destdir, distbase is expected to be included in the metalog.
+ */
+static const char *
+unexpand_path(const char *path)
+{
+ const char *p = path;
+ const char *q = destdir;
+
+ while (*p && *p == *q) {
+ p++;
+ q++;
+ }
+ return (*q == '\0' && *p == '/' ? p : path);
+}
+
+/*
+ * X509 certificate in a rank-balanced tree.
+ */
+struct cert {
+ RB_ENTRY(cert) entry;
+ unsigned long hash;
+ char *name;
+ X509 *x509;
+ char *path;
+};
+
+static void
+free_cert(struct cert *cert)
+{
+ free(cert->name);
+ X509_free(cert->x509);
+ free(cert->path);
+ free(cert);
+}
+
+static int
+certcmp(const struct cert *a, const struct cert *b)
+{
+ return (X509_cmp(a->x509, b->x509));
+}
+
+RB_HEAD(cert_tree, cert);
+static struct cert_tree trusted = RB_INITIALIZER(&trusted);
+static struct cert_tree untrusted = RB_INITIALIZER(&untrusted);
+RB_GENERATE_STATIC(cert_tree, cert, entry, certcmp);
+
+static void
+free_certs(struct cert_tree *tree)
+{
+ struct cert *cert, *tmp;
+
+ RB_FOREACH_SAFE(cert, cert_tree, tree, tmp) {
+ RB_REMOVE(cert_tree, tree, cert);
+ free_cert(cert);
+ }
+}
+
+static struct cert *
+find_cert(struct cert_tree *haystack, X509 *x509)
+{
+ struct cert needle = { .x509 = x509 };
+
+ return (RB_FIND(cert_tree, haystack, &needle));
+}
+
+/*
+ * File containing a certificate in a rank-balanced tree sorted by
+ * certificate hash and disambiguating counter. This is needed because
+ * the certificate hash function is prone to collisions, necessitating a
+ * counter to distinguish certificates that hash to the same value.
+ */
+struct file {
+ RB_ENTRY(file) entry;
+ const struct cert *cert;
+ unsigned int c;
+};
+
+static int
+filecmp(const struct file *a, const struct file *b)
+{
+ if (a->cert->hash > b->cert->hash)
+ return (1);
+ if (a->cert->hash < b->cert->hash)
+ return (-1);
+ return (a->c - b->c);
+}
+
+RB_HEAD(file_tree, file);
+RB_GENERATE_STATIC(file_tree, file, entry, filecmp);
+
+/*
+ * Lexicographical sort for scandir().
+ */
+static int
+lexisort(const struct dirent **d1, const struct dirent **d2)
+{
+ return (strcmp((*d1)->d_name, (*d2)->d_name));
+}
+
+/*
+ * Read certificate(s) from a single file and insert them into a tree.
+ * Ignore certificates that already exist in the tree. If exclude is not
+ * null, also ignore certificates that exist in exclude.
+ *
+ * Returns the number certificates added to the tree, or -1 on failure.
+ */
+static int
+read_cert(const char *path, struct cert_tree *tree, struct cert_tree *exclude)
+{
+ FILE *f;
+ X509 *x509;
+ X509_NAME *name;
+ struct cert *cert;
+ unsigned long hash;
+ int len, ni, no;
+
+ if ((f = fopen(path, "r")) == NULL) {
+ warn("%s", path);
+ return (-1);
+ }
+ for (ni = no = 0;
+ (x509 = PEM_read_X509(f, NULL, NULL, NULL)) != NULL;
+ ni++) {
+ hash = X509_subject_name_hash(x509);
+ if (exclude && find_cert(exclude, x509)) {
+ info("%08lx: excluded", hash);
+ X509_free(x509);
+ continue;
+ }
+ if (find_cert(tree, x509)) {
+ info("%08lx: duplicate", hash);
+ X509_free(x509);
+ continue;
+ }
+ if ((cert = calloc(1, sizeof(*cert))) == NULL)
+ err(1, NULL);
+ cert->x509 = x509;
+ name = X509_get_subject_name(x509);
+ cert->hash = X509_NAME_hash_ex(name, NULL, NULL, NULL);
+ len = X509_NAME_get_text_by_NID(name, NID_commonName,
+ NULL, 0);
+ if (len > 0) {
+ if ((cert->name = malloc(len + 1)) == NULL)
+ err(1, NULL);
+ X509_NAME_get_text_by_NID(name, NID_commonName,
+ cert->name, len + 1);
+ } else {
+ /* fallback for certificates without CN */
+ cert->name = X509_NAME_oneline(name, NULL, 0);
+ }
+ cert->path = xstrdup(unexpand_path(path));
+ if (RB_INSERT(cert_tree, tree, cert) != NULL)
+ errx(1, "unexpected duplicate");
+ info("%08lx: %s", cert->hash, cert->name);
+ no++;
+ }
+ /*
+ * ni is the number of certificates we found in the file.
+ * no is the number of certificates that weren't already in our
+ * tree or on the exclusion list.
+ */
+ if (ni == 0)
+ warnx("%s: no valid certificates found", path);
+ fclose(f);
+ return (no);
+}
+
+/*
+ * Load all certificates found in the specified path into a tree,
+ * optionally excluding those that already exist in a different tree.
+ *
+ * Returns the number of certificates added to the tree, or -1 on failure.
+ */
+static int
+read_certs(const char *path, struct cert_tree *tree, struct cert_tree *exclude)
+{
+ struct stat sb;
+ char *paths[] = { __DECONST(char *, path), NULL };
+ FTS *fts;
+ FTSENT *ent;
+ int fts_options = FTS_LOGICAL | FTS_NOCHDIR;
+ int ret, total = 0;
+
+ if (stat(path, &sb) != 0) {
+ return (-1);
+ } else if (!S_ISDIR(sb.st_mode)) {
+ errno = ENOTDIR;
+ return (-1);
+ }
+ if ((fts = fts_open(paths, fts_options, NULL)) == NULL)
+ err(1, "fts_open()");
+ while ((ent = fts_read(fts)) != NULL) {
+ if (ent->fts_info != FTS_F) {
+ if (ent->fts_info == FTS_ERR)
+ warnc(ent->fts_errno, "fts_read()");
+ continue;
+ }
+ info("found %s", ent->fts_path);
+ ret = read_cert(ent->fts_path, tree, exclude);
+ if (ret > 0)
+ total += ret;
+ }
+ fts_close(fts);
+ return (total);
+}
+
+/*
+ * Save the contents of a cert tree to disk.
+ *
+ * Returns 0 on success and -1 on failure.
+ */
+static int
+write_certs(const char *dir, struct cert_tree *tree)
+{
+ struct file_tree files = RB_INITIALIZER(&files);
+ struct cert *cert;
+ struct file *file, *tmp;
+ struct dirent **dents, **ent;
+ char *path, *tmppath = NULL;
+ FILE *f;
+ mode_t mode = 0444;
+ int cmp, d, fd, ndents, ret = 0;
+
+ /*
+ * Start by generating unambiguous file names for each certificate
+ * and storing them in lexicographical order
+ */
+ RB_FOREACH(cert, cert_tree, tree) {
+ if ((file = calloc(1, sizeof(*file))) == NULL)
+ err(1, NULL);
+ file->cert = cert;
+ for (file->c = 0; file->c < INT_MAX; file->c++)
+ if (RB_INSERT(file_tree, &files, file) == NULL)
+ break;
+ if (file->c == INT_MAX)
+ errx(1, "unable to disambiguate %08lx", cert->hash);
+ free(cert->path);
+ cert->path = xasprintf("%08lx.%d", cert->hash, file->c);
+ }
+ /*
+ * Open and scan the directory.
+ */
+ if ((d = open(dir, O_DIRECTORY | O_RDONLY)) < 0 ||
+#ifdef BOOTSTRAPPING
+ (ndents = scandir(dir, &dents, NULL, lexisort))
+#else
+ (ndents = fdscandir(d, &dents, NULL, lexisort))
+#endif
+ < 0)
+ err(1, "%s", dir);
+ /*
+ * Iterate over the directory listing and the certificate listing
+ * in parallel. If the directory listing gets ahead of the
+ * certificate listing, we need to write the current certificate
+ * and advance the certificate listing. If the certificate
+ * listing is ahead of the directory listing, we need to delete
+ * the current file and advance the directory listing. If they
+ * are neck and neck, we have a match and could in theory compare
+ * the two, but in practice it's faster to just replace the
+ * current file with the current certificate (and advance both).
+ */
+ ent = dents;
+ file = RB_MIN(file_tree, &files);
+ for (;;) {
+ if (ent < dents + ndents) {
+ /* skip directories */
+ if ((*ent)->d_type == DT_DIR) {
+ free(*ent++);
+ continue;
+ }
+ if (file != NULL) {
+ /* compare current dirent to current cert */
+ path = file->cert->path;
+ cmp = strcmp((*ent)->d_name, path);
+ } else {
+ /* trailing files in directory */
+ path = NULL;
+ cmp = -1;
+ }
+ } else {
+ if (file != NULL) {
+ /* trailing certificates */
+ path = file->cert->path;
+ cmp = 1;
+ } else {
+ /* end of both lists */
+ path = NULL;
+ break;
+ }
+ }
+ if (cmp < 0) {
+ /* a file on disk with no matching certificate */
+ info("removing %s/%s", dir, (*ent)->d_name);
+ if (!dryrun)
+ (void)unlinkat(d, (*ent)->d_name, 0);
+ free(*ent++);
+ continue;
+ }
+ if (cmp == 0) {
+ /* a file on disk with a matching certificate */
+ info("replacing %s/%s", dir, (*ent)->d_name);
+ if (dryrun) {
+ fd = open(_PATH_DEVNULL, O_WRONLY);
+ } else {
+ tmppath = xasprintf(".%s", path);
+ fd = openat(d, tmppath,
+ O_CREAT | O_WRONLY | O_TRUNC, mode);
+ if (!unprivileged && fd >= 0)
+ (void)fchmod(fd, mode);
+ }
+ free(*ent++);
+ } else {
+ /* a certificate with no matching file */
+ info("writing %s/%s", dir, path);
+ if (dryrun) {
+ fd = open(_PATH_DEVNULL, O_WRONLY);
+ } else {
+ tmppath = xasprintf(".%s", path);
+ fd = openat(d, tmppath,
+ O_CREAT | O_WRONLY | O_EXCL, mode);
+ }
+ }
+ /* write the certificate */
+ if (fd < 0 ||
+ (f = fdopen(fd, "w")) == NULL ||
+ !PEM_write_X509(f, file->cert->x509)) {
+ if (tmppath != NULL && fd >= 0) {
+ int serrno = errno;
+ (void)unlinkat(d, tmppath, 0);
+ errno = serrno;
+ }
+ err(1, "%s/%s", dir, tmppath ? tmppath : path);
+ }
+ /* rename temp file if applicable */
+ if (tmppath != NULL) {
+ if (ret == 0 && renameat(d, tmppath, d, path) != 0) {
+ warn("%s/%s", dir, path);
+ ret = -1;
+ }
+ if (ret != 0)
+ (void)unlinkat(d, tmppath, 0);
+ free(tmppath);
+ tmppath = NULL;
+ }
+ fflush(f);
+ /* emit metalog */
+ if (mlf != NULL) {
+ fprintf(mlf, ".%s/%s type=file "
+ "uname=%s gname=%s mode=%#o size=%ld\n",
+ unexpand_path(dir), path,
+ uname, gname, mode, ftell(f));
+ }
+ fclose(f);
+ /* advance certificate listing */
+ tmp = RB_NEXT(file_tree, &files, file);
+ RB_REMOVE(file_tree, &files, file);
+ free(file);
+ file = tmp;
+ }
+ free(dents);
+ close(d);
+ return (ret);
+}
+
+/*
+ * Save all certs in a tree to a single file (bundle).
+ *
+ * Returns 0 on success and -1 on failure.
+ */
+static int
+write_bundle(const char *dir, const char *file, struct cert_tree *tree)
+{
+ struct cert *cert;
+ char *tmpfile = NULL;
+ FILE *f;
+ int d, fd, ret = 0;
+ mode_t mode = 0444;
+
+ if (dir != NULL) {
+ if ((d = open(dir, O_DIRECTORY | O_RDONLY)) < 0)
+ err(1, "%s", dir);
+ } else {
+ dir = ".";
+ d = AT_FDCWD;
+ }
+ info("writing %s/%s", dir, file);
+ if (dryrun) {
+ fd = open(_PATH_DEVNULL, O_WRONLY);
+ } else {
+ tmpfile = xasprintf(".%s", file);
+ fd = openat(d, tmpfile, O_WRONLY | O_CREAT | O_EXCL, mode);
+ }
+ if (fd < 0 || (f = fdopen(fd, "w")) == NULL) {
+ if (tmpfile != NULL && fd >= 0) {
+ int serrno = errno;
+ (void)unlinkat(d, tmpfile, 0);
+ errno = serrno;
+ }
+ err(1, "%s/%s", dir, tmpfile ? tmpfile : file);
+ }
+ RB_FOREACH(cert, cert_tree, tree) {
+ if (!PEM_write_X509(f, cert->x509)) {
+ warn("%s/%s", dir, tmpfile ? tmpfile : file);
+ ret = -1;
+ break;
+ }
+ }
+ if (tmpfile != NULL) {
+ if (ret == 0 && renameat(d, tmpfile, d, file) != 0) {
+ warn("%s/%s", dir, file);
+ ret = -1;
+ }
+ if (ret != 0)
+ (void)unlinkat(d, tmpfile, 0);
+ free(tmpfile);
+ }
+ if (ret == 0 && mlf != NULL) {
+ fprintf(mlf,
+ ".%s/%s type=file uname=%s gname=%s mode=%#o size=%ld\n",
+ unexpand_path(dir), file, uname, gname, mode, ftell(f));
+ }
+ fclose(f);
+ if (d != AT_FDCWD)
+ close(d);
+ return (ret);
+}
+
+/*
+ * Load trusted certificates.
+ *
+ * Returns the number of certificates loaded.
+ */
+static unsigned int
+load_trusted(bool all, struct cert_tree *exclude)
+{
+ unsigned int i, n;
+ int ret;
+
+ /* load external trusted certs */
+ for (i = n = 0; all && trusted_paths[i] != NULL; i++) {
+ ret = read_certs(trusted_paths[i], &trusted, exclude);
+ if (ret > 0)
+ n += ret;
+ }
+
+ /* load installed trusted certs */
+ ret = read_certs(trusted_dest, &trusted, exclude);
+ if (ret > 0)
+ n += ret;
+
+ info("%d trusted certificates found", n);
+ return (n);
+}
+
+/*
+ * Load untrusted certificates.
+ *
+ * Returns the number of certificates loaded.
+ */
+static unsigned int
+load_untrusted(bool all)
+{
+ char *path;
+ unsigned int i, n;
+ int ret;
+
+ /* load external untrusted certs */
+ for (i = n = 0; all && untrusted_paths[i] != NULL; i++) {
+ ret = read_certs(untrusted_paths[i], &untrusted, NULL);
+ if (ret > 0)
+ n += ret;
+ }
+
+ /* load installed untrusted certs */
+ ret = read_certs(untrusted_dest, &untrusted, NULL);
+ if (ret > 0)
+ n += ret;
+
+ /* load legacy untrusted certs */
+ path = expand_path(LEGACY_PATH);
+ ret = read_certs(path, &untrusted, NULL);
+ if (ret > 0) {
+ warnx("certificates found in legacy directory %s",
+ path);
+ n += ret;
+ } else if (ret == 0) {
+ warnx("legacy directory %s can safely be deleted",
+ path);
+ }
+ free(path);
+
+ info("%d untrusted certificates found", n);
+ return (n);
+}
+
+/*
+ * Save trusted certificates.
+ *
+ * Returns 0 on success and -1 on failure.
+ */
+static int
+save_trusted(void)
+{
+ int ret;
+
+ mkdirp(trusted_dest);
+ ret = write_certs(trusted_dest, &trusted);
+ return (ret);
+}
+
+/*
+ * Save untrusted certificates.
+ *
+ * Returns 0 on success and -1 on failure.
+ */
+static int
+save_untrusted(void)
+{
+ int ret;
+
+ mkdirp(untrusted_dest);
+ ret = write_certs(untrusted_dest, &untrusted);
+ return (ret);
+}
+
+/*
+ * Save certificate bundle.
+ *
+ * Returns 0 on success and -1 on failure.
+ */
+static int
+save_bundle(void)
+{
+ char *dir, *file, *sep;
+ int ret;
+
+ if ((sep = strrchr(bundle_dest, '/')) == NULL) {
+ dir = NULL;
+ file = bundle_dest;
+ } else {
+ dir = xasprintf("%.*s", (int)(sep - bundle_dest), bundle_dest);
+ file = sep + 1;
+ mkdirp(dir);
+ }
+ ret = write_bundle(dir, file, &trusted);
+ free(dir);
+ return (ret);
+}
+
+/*
+ * Save everything.
+ *
+ * Returns 0 on success and -1 on failure.
+ */
+static int
+save_all(void)
+{
+ int ret = 0;
+
+ ret |= save_untrusted();
+ ret |= save_trusted();
+ if (!nobundle)
+ ret |= save_bundle();
+ return (ret);
+}
+
+/*
+ * List the contents of a certificate tree.
+ */
+static void
+list_certs(struct cert_tree *tree)
+{
+ struct cert *cert;
+ char *path, *name;
+
+ RB_FOREACH(cert, cert_tree, tree) {
+ path = longnames ? NULL : strrchr(cert->path, '/');
+ name = longnames ? NULL : strrchr(cert->name, '=');
+ printf("%s\t%s\n", path ? path + 1 : cert->path,
+ name ? name + 1 : cert->name);
+ }
+}
+
+/*
+ * Load installed trusted certificates, then list them.
+ *
+ * Returns 0 on success and -1 on failure.
+ */
+static int
+certctl_list(int argc, char **argv __unused)
+{
+ if (argc > 1)
+ usage();
+ /* load trusted certificates */
+ load_trusted(false, NULL);
+ /* list them */
+ list_certs(&trusted);
+ free_certs(&trusted);
+ return (0);
+}
+
+/*
+ * Load installed untrusted certificates, then list them.
+ *
+ * Returns 0 on success and -1 on failure.
+ */
+static int
+certctl_untrusted(int argc, char **argv __unused)
+{
+ if (argc > 1)
+ usage();
+ /* load untrusted certificates */
+ load_untrusted(false);
+ /* list them */
+ list_certs(&untrusted);
+ free_certs(&untrusted);
+ return (0);
+}
+
+/*
+ * Load trusted and untrusted certificates from all sources, then
+ * regenerate both the hashed directories and the bundle.
+ *
+ * Returns 0 on success and -1 on failure.
+ */
+static int
+certctl_rehash(int argc, char **argv __unused)
+{
+ int ret;
+
+ if (argc > 1)
+ usage();
+
+ if (unprivileged && (mlf = fopen(metalog, "a")) == NULL) {
+ warn("%s", metalog);
+ return (-1);
+ }
+
+ /* load untrusted certs first */
+ load_untrusted(true);
+
+ /* load trusted certs, excluding any that are already untrusted */
+ load_trusted(true, &untrusted);
+
+ /* save everything */
+ ret = save_all();
+
+ /* clean up */
+ free_certs(&untrusted);
+ free_certs(&trusted);
+ if (mlf != NULL)
+ fclose(mlf);
+ return (ret);
+}
+
+/*
+ * Manually add one or more certificates to the list of trusted certificates.
+ *
+ * Returns 0 on success and -1 on failure.
+ */
+static int
+certctl_trust(int argc, char **argv)
+{
+ struct cert_tree extra = RB_INITIALIZER(&extra);
+ struct cert *cert, *other, *tmp;
+ unsigned int n;
+ int i, ret;
+
+ if (argc < 2)
+ usage();
+
+ /* load untrusted certs first */
+ load_untrusted(true);
+
+ /* load trusted certs, excluding any that are already untrusted */
+ load_trusted(true, &untrusted);
+
+ /* now load the additional trusted certificates */
+ n = 0;
+ for (i = 1; i < argc; i++) {
+ ret = read_cert(argv[i], &extra, &trusted);
+ if (ret > 0)
+ n += ret;
+ }
+ if (n == 0) {
+ warnx("no new trusted certificates found");
+ free_certs(&untrusted);
+ free_certs(&trusted);
+ free_certs(&extra);
+ return (0);
+ }
+
+ /*
+ * For each new trusted cert, move it from the extra list to the
+ * trusted list, then check if a matching certificate exists on
+ * the untrusted list. If that is the case, warn the user, then
+ * remove the matching certificate from the untrusted list.
+ */
+ RB_FOREACH_SAFE(cert, cert_tree, &extra, tmp) {
+ RB_REMOVE(cert_tree, &extra, cert);
+ RB_INSERT(cert_tree, &trusted, cert);
+ if ((other = RB_FIND(cert_tree, &untrusted, cert)) != NULL) {
+ warnx("%s was previously untrusted", cert->name);
+ RB_REMOVE(cert_tree, &untrusted, other);
+ free_cert(other);
+ }
+ }
+
+ /* save everything */
+ ret = save_all();
+
+ /* clean up */
+ free_certs(&untrusted);
+ free_certs(&trusted);
+ return (ret);
+}
+
+/*
+ * Manually add one or more certificates to the list of untrusted
+ * certificates.
+ *
+ * Returns 0 on success and -1 on failure.
+ */
+static int
+certctl_untrust(int argc, char **argv)
+{
+ unsigned int n;
+ int i, ret;
+
+ if (argc < 2)
+ usage();
+
+ /* load untrusted certs first */
+ load_untrusted(true);
+
+ /* now load the additional untrusted certificates */
+ n = 0;
+ for (i = 1; i < argc; i++) {
+ ret = read_cert(argv[i], &untrusted, NULL);
+ if (ret > 0)
+ n += ret;
+ }
+ if (n == 0) {
+ warnx("no new untrusted certificates found");
+ free_certs(&untrusted);
+ return (0);
+ }
+
+ /* load trusted certs, excluding any that are already untrusted */
+ load_trusted(true, &untrusted);
+
+ /* save everything */
+ ret = save_all();
+
+ /* clean up */
+ free_certs(&untrusted);
+ free_certs(&trusted);
+ return (ret);
+}
+
+static void
+set_defaults(void)
+{
+ const char *value;
+ char *str;
+ size_t len;
+
+ if (localbase == NULL &&
+ (localbase = getenv("LOCALBASE")) == NULL) {
+ if ((str = malloc((len = PATH_MAX) + 1)) == NULL)
+ err(1, NULL);
+ while (sysctlbyname("user.localbase", str, &len, NULL, 0) < 0) {
+ if (errno != ENOMEM)
+ err(1, "sysctl(user.localbase)");
+ if ((str = realloc(str, len + 1)) == NULL)
+ err(1, NULL);
+ }
+ str[len] = '\0';
+ localbase = str;
+ }
+
+ if (destdir == NULL &&
+ (destdir = getenv("DESTDIR")) == NULL)
+ destdir = "";
+ destdir = normalize_path(destdir);
+
+ if (distbase == NULL &&
+ (distbase = getenv("DISTBASE")) == NULL)
+ distbase = "";
+ if (*distbase != '\0' && *distbase != '/')
+ errx(1, "DISTBASE=%s does not begin with a slash", distbase);
+ distbase = normalize_path(distbase);
+
+ if (unprivileged && metalog == NULL &&
+ (metalog = getenv("METALOG")) == NULL)
+ metalog = xasprintf("%s/METALOG", destdir);
+
+ if (!verbose) {
+ if ((value = getenv("CERTCTL_VERBOSE")) != NULL) {
+ if (value[0] != '\0') {
+ verbose = true;
+ }
+ }
+ }
+
+ if ((value = getenv("TRUSTPATH")) != NULL)
+ trusted_paths = split_paths(value);
+ else
+ trusted_paths = expand_paths(default_trusted_paths);
+
+ if ((value = getenv("UNTRUSTPATH")) != NULL)
+ untrusted_paths = split_paths(value);
+ else
+ untrusted_paths = expand_paths(default_untrusted_paths);
+
+ if ((value = getenv("TRUSTDESTDIR")) != NULL ||
+ (value = getenv("CERTDESTDIR")) != NULL)
+ trusted_dest = normalize_path(value);
+ else
+ trusted_dest = expand_path(TRUSTED_PATH);
+
+ if ((value = getenv("UNTRUSTDESTDIR")) != NULL)
+ untrusted_dest = normalize_path(value);
+ else
+ untrusted_dest = expand_path(UNTRUSTED_PATH);
+
+ if ((value = getenv("BUNDLE")) != NULL)
+ bundle_dest = normalize_path(value);
+ else
+ bundle_dest = expand_path(BUNDLE_PATH);
+
+ info("localbase:\t%s", localbase);
+ info("destdir:\t%s", destdir);
+ info("distbase:\t%s", distbase);
+ info("unprivileged:\t%s", unprivileged ? "true" : "false");
+ info("verbose:\t%s", verbose ? "true" : "false");
+}
+
+typedef int (*main_t)(int, char **);
+
+static struct {
+ const char *name;
+ main_t func;
+} commands[] = {
+ { "list", certctl_list },
+ { "untrusted", certctl_untrusted },
+ { "rehash", certctl_rehash },
+ { "untrust", certctl_untrust },
+ { "trust", certctl_trust },
+ { 0 },
+};
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: certctl [-lv] [-D destdir] [-d distbase] list\n"
+ " certctl [-lv] [-D destdir] [-d distbase] untrusted\n"
+ " certctl [-BnUv] [-D destdir] [-d distbase] [-M metalog] rehash\n"
+ " certctl [-nv] [-D destdir] [-d distbase] untrust <file>\n"
+ " certctl [-nv] [-D destdir] [-d distbase] trust <file>\n");
+ exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+ const char *command;
+ int opt;
+
+ while ((opt = getopt(argc, argv, "BcD:d:g:lL:M:no:Uv")) != -1)
+ switch (opt) {
+ case 'B':
+ nobundle = true;
+ break;
+ case 'c':
+ /* ignored for compatibility */
+ break;
+ case 'D':
+ destdir = optarg;
+ break;
+ case 'd':
+ distbase = optarg;
+ break;
+ case 'g':
+ gname = optarg;
+ break;
+ case 'l':
+ longnames = true;
+ break;
+ case 'L':
+ localbase = optarg;
+ break;
+ case 'M':
+ metalog = optarg;
+ break;
+ case 'n':
+ dryrun = true;
+ break;
+ case 'o':
+ uname = optarg;
+ break;
+ case 'U':
+ unprivileged = true;
+ break;
+ case 'v':
+ verbose = true;
+ break;
+ default:
+ usage();
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1)
+ usage();
+
+ command = *argv;
+
+ if ((nobundle || unprivileged || metalog != NULL) &&
+ strcmp(command, "rehash") != 0)
+ usage();
+ if (!unprivileged && metalog != NULL) {
+ warnx("-M may only be used in conjunction with -U");
+ usage();
+ }
+
+ set_defaults();
+
+ for (unsigned i = 0; commands[i].name != NULL; i++)
+ if (strcmp(command, commands[i].name) == 0)
+ exit(!!commands[i].func(argc, argv));
+ usage();
+}
diff --git a/usr.sbin/certctl/certctl.sh b/usr.sbin/certctl/certctl.sh
deleted file mode 100755
index 2bde651de126..000000000000
--- a/usr.sbin/certctl/certctl.sh
+++ /dev/null
@@ -1,366 +0,0 @@
-#!/bin/sh
-#-
-# SPDX-License-Identifier: BSD-2-Clause
-#
-# Copyright 2018 Allan Jude <allanjude@freebsd.org>
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted providing 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 ``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 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.
-#
-
-set -u
-
-############################################################ CONFIGURATION
-
-: ${DESTDIR:=}
-: ${DISTBASE:=}
-
-############################################################ GLOBALS
-
-SCRIPTNAME="${0##*/}"
-LINK=-lrs
-ERRORS=0
-NOOP=false
-UNPRIV=false
-VERBOSE=false
-
-############################################################ FUNCTIONS
-
-info()
-{
- echo "${0##*/}: $@" >&2
-}
-
-verbose()
-{
- if "${VERBOSE}" ; then
- info "$@"
- fi
-}
-
-perform()
-{
- if ! "${NOOP}" ; then
- "$@"
- fi
-}
-
-cert_files_in()
-{
- find -L "$@" -type f \( \
- -name '*.pem' -or \
- -name '*.crt' -or \
- -name '*.cer' \
- \) 2>/dev/null
-}
-
-eolcvt()
-{
- cat "$@" | tr -s '\r' '\n'
-}
-
-do_hash()
-{
- local hash
-
- if hash=$(openssl x509 -noout -subject_hash -in "$1") ; then
- echo "$hash"
- return 0
- else
- info "Error: $1"
- ERRORS=$((ERRORS + 1))
- return 1
- fi
-}
-
-get_decimal()
-{
- local checkdir hash decimal
-
- checkdir=$1
- hash=$2
- decimal=0
-
- while [ -e "$checkdir/$hash.$decimal" ] ; do
- decimal=$((decimal + 1))
- done
-
- echo ${decimal}
- return 0
-}
-
-create_trusted()
-{
- local hash certhash otherfile otherhash
- local suffix
-
- hash=$(do_hash "$1") || return
- certhash=$(openssl x509 -sha1 -in "$1" -noout -fingerprint)
- for otherfile in $(find $UNTRUSTDESTDIR -name "$hash.*") ; do
- otherhash=$(openssl x509 -sha1 -in "$otherfile" -noout -fingerprint)
- if [ "$certhash" = "$otherhash" ] ; then
- info "Skipping untrusted certificate $hash ($otherfile)"
- return 0
- fi
- done
- for otherfile in $(find $CERTDESTDIR -name "$hash.*") ; do
- otherhash=$(openssl x509 -sha1 -in "$otherfile" -noout -fingerprint)
- if [ "$certhash" = "$otherhash" ] ; then
- verbose "Skipping duplicate entry for certificate $hash"
- return 0
- fi
- done
- suffix=$(get_decimal "$CERTDESTDIR" "$hash")
- verbose "Adding $hash.$suffix to trust store"
- perform install ${INSTALLFLAGS} -m 0444 ${LINK} \
- "$(realpath "$1")" "$CERTDESTDIR/$hash.$suffix"
-}
-
-# Accepts either dot-hash form from `certctl list` or a path to a valid cert.
-resolve_certname()
-{
- local hash srcfile filename
- local suffix
-
- # If it exists as a file, we'll try that; otherwise, we'll scan
- if [ -e "$1" ] ; then
- hash=$(do_hash "$1") || return
- srcfile=$(realpath "$1")
- suffix=$(get_decimal "$UNTRUSTDESTDIR" "$hash")
- filename="$hash.$suffix"
- echo "$srcfile" "$hash.$suffix"
- elif [ -e "${CERTDESTDIR}/$1" ] ; then
- srcfile=$(realpath "${CERTDESTDIR}/$1")
- hash=$(echo "$1" | sed -Ee 's/\.([0-9])+$//')
- suffix=$(get_decimal "$UNTRUSTDESTDIR" "$hash")
- filename="$hash.$suffix"
- echo "$srcfile" "$hash.$suffix"
- fi
-}
-
-create_untrusted()
-{
- local srcfile filename
-
- set -- $(resolve_certname "$1")
- srcfile=$1
- filename=$2
-
- if [ -z "$srcfile" -o -z "$filename" ] ; then
- return
- fi
-
- verbose "Adding $filename to untrusted list"
- perform install ${INSTALLFLAGS} -m 0444 ${LINK} \
- "$srcfile" "$UNTRUSTDESTDIR/$filename"
-}
-
-do_scan()
-{
- local CFUNC CSEARCH CPATH CFILE CERT SPLITDIR
- local oldIFS="$IFS"
- CFUNC="$1"
- CSEARCH="$2"
-
- IFS=:
- set -- $CSEARCH
- IFS="$oldIFS"
- for CFILE in $(cert_files_in "$@") ; do
- verbose "Reading $CFILE"
- case $(eolcvt "$CFILE" | egrep -c '^-+BEGIN CERTIFICATE-+$') in
- 0)
- ;;
- 1)
- "$CFUNC" "$CFILE"
- ;;
- *)
- verbose "Multiple certificates found, splitting..."
- SPLITDIR=$(mktemp -d)
- eolcvt "$CFILE" | egrep '^(---|[0-9A-Za-z/+=]+$)' | \
- split -p '^-+BEGIN CERTIFICATE-+$' - "$SPLITDIR/x"
- for CERT in $(find "$SPLITDIR" -type f) ; do
- "$CFUNC" "$CERT"
- done
- rm -rf "$SPLITDIR"
- ;;
- esac
- done
-}
-
-do_list()
-{
- local CFILE subject
-
- for CFILE in $(find "$@" \( -type f -or -type l \) -name '*.[0-9]') ; do
- if [ ! -s "$CFILE" ] ; then
- info "Unable to read $CFILE"
- ERRORS=$((ERRORS + 1))
- continue
- fi
- subject=
- if ! "$VERBOSE" ; then
- subject=$(openssl x509 -noout -subject -nameopt multiline -in "$CFILE" | sed -n '/commonName/s/.*= //p')
- fi
- if [ -z "$subject" ] ; then
- subject=$(openssl x509 -noout -subject -in "$CFILE")
- fi
- printf "%s\t%s\n" "${CFILE##*/}" "$subject"
- done
-}
-
-cmd_rehash()
-{
-
- if [ -e "$CERTDESTDIR" ] ; then
- perform find "$CERTDESTDIR" \( -type f -or -type l \) -delete
- else
- perform install -d -m 0755 "$CERTDESTDIR"
- fi
- if [ -e "$UNTRUSTDESTDIR" ] ; then
- perform find "$UNTRUSTDESTDIR" \( -type f -or -type l \) -delete
- else
- perform install -d -m 0755 "$UNTRUSTDESTDIR"
- fi
-
- do_scan create_untrusted "$UNTRUSTPATH"
- do_scan create_trusted "$TRUSTPATH"
-}
-
-cmd_list()
-{
- info "Listing Trusted Certificates:"
- do_list "$CERTDESTDIR"
-}
-
-cmd_untrust()
-{
- local UTFILE
-
- shift # verb
- perform install -d -m 0755 "$UNTRUSTDESTDIR"
- for UTFILE in "$@"; do
- info "Adding $UTFILE to untrusted list"
- create_untrusted "$UTFILE"
- done
-}
-
-cmd_trust()
-{
- local UTFILE untrustedhash certhash hash
-
- shift # verb
- for UTFILE in "$@"; do
- if [ -s "$UTFILE" ] ; then
- hash=$(do_hash "$UTFILE")
- certhash=$(openssl x509 -sha1 -in "$UTFILE" -noout -fingerprint)
- for UNTRUSTEDFILE in $(find $UNTRUSTDESTDIR -name "$hash.*") ; do
- untrustedhash=$(openssl x509 -sha1 -in "$UNTRUSTEDFILE" -noout -fingerprint)
- if [ "$certhash" = "$untrustedhash" ] ; then
- info "Removing $(basename "$UNTRUSTEDFILE") from untrusted list"
- perform rm -f $UNTRUSTEDFILE
- fi
- done
- elif [ -e "$UNTRUSTDESTDIR/$UTFILE" ] ; then
- info "Removing $UTFILE from untrusted list"
- perform rm -f "$UNTRUSTDESTDIR/$UTFILE"
- else
- info "Cannot find $UTFILE"
- ERRORS=$((ERRORS + 1))
- fi
- done
-}
-
-cmd_untrusted()
-{
- info "Listing Untrusted Certificates:"
- do_list "$UNTRUSTDESTDIR"
-}
-
-usage()
-{
- exec >&2
- echo "Manage the TLS trusted certificates on the system"
- echo " $SCRIPTNAME [-v] list"
- echo " List trusted certificates"
- echo " $SCRIPTNAME [-v] untrusted"
- echo " List untrusted certificates"
- echo " $SCRIPTNAME [-cnUv] [-D <destdir>] [-d <distbase>] [-M <metalog>] rehash"
- echo " Rehash all trusted and untrusted certificates"
- echo " $SCRIPTNAME [-cnv] untrust <file>"
- echo " Add <file> to the list of untrusted certificates"
- echo " $SCRIPTNAME [-cnv] trust <file>"
- echo " Remove <file> from the list of untrusted certificates"
- exit 64
-}
-
-############################################################ MAIN
-
-while getopts cD:d:M:nUv flag; do
- case "$flag" in
- c) LINK=-c ;;
- D) DESTDIR=${OPTARG} ;;
- d) DISTBASE=${OPTARG} ;;
- M) METALOG=${OPTARG} ;;
- n) NOOP=true ;;
- U) UNPRIV=true ;;
- v) VERBOSE=true ;;
- esac
-done
-shift $((OPTIND - 1))
-
-DESTDIR=${DESTDIR%/}
-
-if ! [ -z "${CERTCTL_VERBOSE:-}" ] ; then
- VERBOSE=true
-fi
-: ${METALOG:=${DESTDIR}/METALOG}
-INSTALLFLAGS=
-if "$UNPRIV" ; then
- INSTALLFLAGS="-U -M ${METALOG} -D ${DESTDIR:-/} -o root -g wheel"
-fi
-: ${LOCALBASE:=$(sysctl -n user.localbase)}
-: ${TRUSTPATH:=${DESTDIR}${DISTBASE}/usr/share/certs/trusted:${DESTDIR}${LOCALBASE}/share/certs:${DESTDIR}${LOCALBASE}/etc/ssl/certs}
-: ${UNTRUSTPATH:=${DESTDIR}${DISTBASE}/usr/share/certs/untrusted:${DESTDIR}${LOCALBASE}/etc/ssl/untrusted:${DESTDIR}${LOCALBASE}/etc/ssl/blacklisted}
-: ${CERTDESTDIR:=${DESTDIR}${DISTBASE}/etc/ssl/certs}
-: ${UNTRUSTDESTDIR:=${DESTDIR}${DISTBASE}/etc/ssl/untrusted}
-
-[ $# -gt 0 ] || usage
-case "$1" in
-list) cmd_list ;;
-rehash) cmd_rehash ;;
-blacklist) cmd_untrust "$@" ;;
-untrust) cmd_untrust "$@" ;;
-trust) cmd_trust "$@" ;;
-unblacklist) cmd_trust "$@" ;;
-untrusted) cmd_untrusted ;;
-blacklisted) cmd_untrusted ;;
-*) usage # NOTREACHED
-esac
-
-retval=$?
-if [ $ERRORS -gt 0 ] ; then
- info "Encountered $ERRORS errors"
-fi
-exit $retval
-
-################################################################################
-# END
-################################################################################
diff --git a/usr.sbin/certctl/tests/Makefile b/usr.sbin/certctl/tests/Makefile
new file mode 100644
index 000000000000..da301c3ded03
--- /dev/null
+++ b/usr.sbin/certctl/tests/Makefile
@@ -0,0 +1,5 @@
+PACKAGE= tests
+ATF_TESTS_SH= certctl_test
+${PACKAGE}FILES+= certctl.subr
+
+.include <bsd.test.mk>
diff --git a/usr.sbin/certctl/tests/certctl.subr b/usr.sbin/certctl/tests/certctl.subr
new file mode 100644
index 000000000000..841cc1781e69
--- /dev/null
+++ b/usr.sbin/certctl/tests/certctl.subr
@@ -0,0 +1,44 @@
+#
+# Copyright (c) 2025 Dag-Erling Smørgrav <des@FreeBSD.org>
+#
+# SPDX-License-Identifier: BSD-2-Clause
+#
+
+# Generate a random name
+rand_name() {
+ local length=${1:-32}
+
+ jot -r -c -s '' ${length} A Z
+}
+
+# Generate a subject for a given name
+subject() {
+ local crtname=$1
+
+ echo "/CN=${crtname}/O=FreeBSD/OU=Test/"
+}
+
+# Generate a key
+gen_key() {
+ local keyname=$1
+
+ env -i PATH="${PATH}" OPENSSL_CONF=/dev/null \
+ openssl genrsa -out ${keyname}.key
+}
+
+# Generate a certificate for a given name, key, and serial number
+gen_crt() {
+ local crtname=$1
+ local keyname=${2:-${crtname}}
+ local serial=${3:-1}
+
+ if ! [ -f "${keyname}".key ]; then
+ gen_key "${keyname}"
+ fi
+ env -i PATH="${PATH}" OPENSSL_CONF=/dev/null \
+ openssl req -x509 -new \
+ -subj="$(subject ${crtname})" \
+ -set_serial ${serial} \
+ -key ${keyname}.key \
+ -out ${crtname}.crt
+}
diff --git a/usr.sbin/certctl/tests/certctl_test.sh b/usr.sbin/certctl/tests/certctl_test.sh
new file mode 100644
index 000000000000..74749db0b3f5
--- /dev/null
+++ b/usr.sbin/certctl/tests/certctl_test.sh
@@ -0,0 +1,332 @@
+#
+# Copyright (c) 2025 Dag-Erling Smørgrav <des@FreeBSD.org>
+#
+# SPDX-License-Identifier: BSD-2-Clause
+#
+
+. $(atf_get_srcdir)/certctl.subr
+
+# Random sets of eight non-colliding names
+set1()
+{
+ cat <<EOF
+AVOYKJHSLFHWPVQMKBHENUAHJTEGMCCB 0ca83bbe
+UYSYXKDNNJTYOQPBGIKQDHRJYZHTDPKK 0d9a6512
+LODHGFXMZYKGOKAYGWTMMYQJYHDATDDM 4e6219f5
+NBBTQHJLHKBFFFWJTHHSNKOQYMGLHLPW 5dd76abc
+BJFAQZXZHYQLIDDPCAQFPDMNXICUXBXW ad68573d
+IOKNTHVEVVIJMNMYAVILMEMQQWLVRESN b577803d
+BHGMAJJGNJPIVMHMFCUTJLGFROJICEKN c98a6338
+HCRFQMGDQJALMLUQNXMPGLXFLLJRODJW f50c6379
+EOF
+}
+
+set2()
+{
+ cat <<EOF
+GOHKZTSKIPDSYNLMGYXGLROPTATELXIU 30789c88
+YOOTYHEGHZIYFXOBLNKENPSJUDGOPJJU 7fadbc13
+ETRINNYBGKIENAVGOKVJYFSSHFZIJZRH 8ed664af
+DBFGMFFMRNLPQLQPOLXOEUVLCRXLRSWT 8f34355e
+WFOPBQPLQFHDHZOUQFEIDGSYDUOTSNDQ ac0471df
+HMNETZMGNIWRGXQCVZXVZGWSGFBRRDQC b32f1472
+SHFYBXDVAUACBFPPAIGDAQIAGYOYGMQE baca75fa
+PCBGDNVPYCDGNRQSGRSLXFHYKXLAVLHW ddeeae01
+EOF
+}
+
+set3()
+{
+ cat <<EOF
+NJWIRLPWAIICVJBKXXHFHLCPAERZATRL 000aa2e5
+RJAENDPOCZQEVCPFUWOWDXPCSMYJPVYC 021b95a3
+PQUQDSWHBNVLBTNBGONYRLGZZVEFXVLO 071e8c50
+VZEXRKJUPZSFBDWBOLUZXOGLNTEAPCZM 3af7bb9b
+ZXOWOXQTXNZMAMZIWVFDZDJEWOOAGAOH 48d5c7cc
+KQSFQYVJMFTMADIHJIWGSQISWKSHRYQO 509f5ba1
+AIECYSLWZOIEPJWWUTWSQXCNCIHHZHYI 8cb0c503
+RFHWDJZEPOFLMPGXAHVEJFHCDODAPVEV 9ae4e049
+EOF
+}
+
+# Random set of three colliding names
+collhash=f2888ce3
+coll()
+{
+ cat <<EOF
+EJFTZEOANQLOYPEHWWXBWEWEFVKHMSNA $collhash
+LEMRWZAZLKZLPPSFLNLQZVGKKBEOFYWG $collhash
+ZWUPHYWKKTVEFBJOLLPDAIKGRDFVXZID $collhash
+EOF
+}
+
+sortfile() {
+ for filename; do
+ sort "${filename}" >"${filename}"-
+ mv "${filename}"- "${filename}"
+ done
+}
+
+certctl_setup()
+{
+ export DESTDIR="$PWD"
+
+ # Create input directories
+ mkdir -p ${DESTDIR}${DISTBASE}/usr/share/certs/trusted
+ mkdir -p ${DESTDIR}${DISTBASE}/usr/share/certs/untrusted
+ mkdir -p ${DESTDIR}/usr/local/share/certs
+
+ # Do not create output directories; certctl will take care of it
+ #mkdir -p ${DESTDIR}${DISTBASE}/etc/ssl/certs
+ #mkdir -p ${DESTDIR}${DISTBASE}/etc/ssl/untrusted
+
+ # Generate a random key
+ keyname="testkey"
+ gen_key ${keyname}
+
+ # Generate certificates
+ :>metalog.expect
+ :>trusted.expect
+ :>untrusted.expect
+ metalog() {
+ echo ".${DISTBASE}$@ type=file" >>metalog.expect
+ }
+ trusted() {
+ local crtname=$1
+ local filename=$2
+ printf "%s\t%s\n" "${filename}" "${crtname}" >>trusted.expect
+ metalog "/etc/ssl/certs/${filename}"
+ }
+ untrusted() {
+ local crtname=$1
+ local filename=$2
+ printf "%s\t%s\n" "${filename}" "${crtname}" >>untrusted.expect
+ metalog "/etc/ssl/untrusted/${filename}"
+ }
+ set1 | while read crtname hash ; do
+ gen_crt ${crtname} ${keyname}
+ mv ${crtname}.crt ${DESTDIR}${DISTBASE}/usr/share/certs/trusted
+ trusted "${crtname}" "${hash}.0"
+ done
+ local c=0
+ coll | while read crtname hash ; do
+ gen_crt ${crtname} ${keyname}
+ mv ${crtname}.crt ${DESTDIR}${DISTBASE}/usr/share/certs/trusted
+ trusted "${crtname}" "${hash}.${c}"
+ c=$((c+1))
+ done
+ set2 | while read crtname hash ; do
+ gen_crt ${crtname} ${keyname}
+ openssl x509 -in ${crtname}.crt
+ rm ${crtname}.crt
+ trusted "${crtname}" "${hash}.0"
+ done >usr/local/share/certs/bundle.crt
+ set3 | while read crtname hash ; do
+ gen_crt ${crtname} ${keyname}
+ mv ${crtname}.crt ${DESTDIR}${DISTBASE}/usr/share/certs/untrusted
+ untrusted "${crtname}" "${hash}.0"
+ done
+ metalog "/etc/ssl/cert.pem"
+ unset -f untrusted
+ unset -f trusted
+ unset -f metalog
+ sortfile *.expect
+}
+
+check_trusted() {
+ local crtname=$1
+ local subject="$(subject ${crtname})"
+ local c=${2:-1}
+
+ atf_check -e ignore -o match:"found: ${c}\$" \
+ openssl storeutl -noout -subject "${subject}" \
+ ${DESTDIR}${DISTBASE}/etc/ssl/certs
+ atf_check -e ignore -o not-match:"found: [1-9]" \
+ openssl storeutl -noout -subject "${subject}" \
+ ${DESTDIR}${DISTBASE}/etc/ssl/untrusted
+}
+
+check_untrusted() {
+ local crtname=$1
+ local subject="$(subject ${crtname})"
+ local c=${2:-1}
+
+ atf_check -e ignore -o not-match:"found: [1-9]" \
+ openssl storeutl -noout -subject "${subject}" \
+ ${DESTDIR}/${DISTBASE}/etc/ssl/certs
+ atf_check -e ignore -o match:"found: ${c}\$" \
+ openssl storeutl -noout -subject "${subject}" \
+ ${DESTDIR}/${DISTBASE}/etc/ssl/untrusted
+}
+
+check_in_bundle() {
+ local b=${DISTBASE}${DISTBASE+/}
+ local crtfile=$1
+ local line
+
+ line=$(tail +5 "${crtfile}" | head -1)
+ atf_check grep -q "${line}" ${DESTDIR}${DISTBASE}/etc/ssl/cert.pem
+}
+
+check_not_in_bundle() {
+ local b=${DISTBASE}${DISTBASE+/}
+ local crtfile=$1
+ local line
+
+ line=$(tail +5 "${crtfile}" | head -1)
+ atf_check -s exit:1 grep -q "${line}" etc/ssl/cert.pem
+}
+
+atf_test_case rehash
+rehash_head()
+{
+ atf_set "descr" "Test the rehash command"
+}
+rehash_body()
+{
+ certctl_setup
+ atf_check certctl rehash
+
+ # Verify non-colliding trusted certificates
+ (set1; set2) >trusted
+ while read crtname hash ; do
+ check_trusted "${crtname}"
+ done <trusted
+
+ # Verify colliding trusted certificates
+ coll >coll
+ while read crtname hash ; do
+ check_trusted "${crtname}" $(wc -l <coll)
+ done <coll
+
+ # Verify untrusted certificates
+ set3 >untrusted
+ while read crtname hash ; do
+ check_untrusted "${crtname}"
+ done <untrusted
+
+ # Verify bundle
+ for f in etc/ssl/certs/*.? ; do
+ check_in_bundle "${f}"
+ done
+ for f in etc/ssl/untrusted/*.? ; do
+ check_not_in_bundle "${f}"
+ done
+}
+
+atf_test_case list
+list_head()
+{
+ atf_set "descr" "Test the list and untrusted commands"
+}
+list_body()
+{
+ certctl_setup
+ atf_check certctl rehash
+
+ atf_check -o save:trusted.out certctl list
+ sortfile trusted.out
+ # the ordering of the colliding certificates is partly
+ # determined by fields that change every time we regenerate
+ # them, so ignore them in the diff
+ atf_check diff -u \
+ --ignore-matching-lines $collhash \
+ trusted.expect trusted.out
+
+ atf_check -o save:untrusted.out certctl untrusted
+ sortfile untrusted.out
+ atf_check diff -u \
+ untrusted.expect untrusted.out
+}
+
+atf_test_case trust
+trust_head()
+{
+ atf_set "descr" "Test the trust command"
+}
+trust_body()
+{
+ certctl_setup
+ atf_check certctl rehash
+ crtname=$(set3 | (read crtname hash ; echo ${crtname}))
+ crtfile=usr/share/certs/untrusted/${crtname}.crt
+ check_untrusted ${crtname}
+ check_not_in_bundle ${crtfile}
+ atf_check -e match:"was previously untrusted" \
+ certctl trust ${crtfile}
+ check_trusted ${crtname}
+ check_in_bundle ${crtfile}
+}
+
+atf_test_case untrust
+untrust_head()
+{
+ atf_set "descr" "Test the untrust command"
+}
+untrust_body()
+{
+ certctl_setup
+ atf_check certctl rehash
+ crtname=$(set1 | (read crtname hash ; echo ${crtname}))
+ crtfile=usr/share/certs/trusted/${crtname}.crt
+ check_trusted "${crtname}"
+ check_in_bundle ${crtfile}
+ atf_check certctl untrust "${crtfile}"
+ check_untrusted "${crtname}"
+ check_not_in_bundle ${crtfile}
+}
+
+atf_test_case metalog
+metalog_head()
+{
+ atf_set "descr" "Verify the metalog"
+}
+metalog_body()
+{
+ export DISTBASE=/base
+ certctl_setup
+
+ # certctl gets DESTDIR and DISTBASE from environment
+ rm -f metalog.orig
+ atf_check certctl -U -M metalog.orig rehash
+ sed -E 's/(type=file) .*/\1/' metalog.orig | sort >metalog.short
+ atf_check diff -u metalog.expect metalog.short
+
+ # certctl gets DESTDIR and DISTBASE from command line
+ rm -f metalog.orig
+ atf_check env -uDESTDIR -uDISTBASE \
+ certctl -D ${DESTDIR} -d ${DISTBASE} -U -M metalog.orig rehash
+ sed -E 's/(type=file) .*/\1/' metalog.orig | sort >metalog.short
+ atf_check diff -u metalog.expect metalog.short
+
+ # as above, but intentionally add trailing slashes
+ rm -f metalog.orig
+ atf_check env -uDESTDIR -uDISTBASE \
+ certctl -D ${DESTDIR}// -d ${DISTBASE}/ -U -M metalog.orig rehash
+ sed -E 's/(type=file) .*/\1/' metalog.orig | sort >metalog.short
+ atf_check diff -u metalog.expect metalog.short
+}
+
+atf_test_case misc
+misc_head()
+{
+ atf_set "descr" "Test miscellaneous edge cases"
+}
+misc_body()
+{
+ # certctl rejects DISTBASE that does not begin with a slash
+ atf_check -s exit:1 -e match:"begin with a slash" \
+ certctl -d base -n rehash
+ atf_check -s exit:1 -e match:"begin with a slash" \
+ env DISTBASE=base certctl -n rehash
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case rehash
+ atf_add_test_case list
+ atf_add_test_case trust
+ atf_add_test_case untrust
+ atf_add_test_case metalog
+ atf_add_test_case misc
+}
diff --git a/usr.sbin/chroot/chroot.c b/usr.sbin/chroot/chroot.c
index d9fb29474d87..e1af0a4131d3 100644
--- a/usr.sbin/chroot/chroot.c
+++ b/usr.sbin/chroot/chroot.c
@@ -103,7 +103,9 @@ main(int argc, char *argv[])
gid = 0;
uid = 0;
+ gids = 0;
user = group = grouplist = NULL;
+ gidlist = NULL;
nonprivileged = false;
while ((ch = getopt(argc, argv, "G:g:u:n")) != -1) {
switch(ch) {
@@ -119,6 +121,11 @@ main(int argc, char *argv[])
break;
case 'G':
grouplist = optarg;
+
+ /*
+ * XXX Why not allow us to drop all of our supplementary
+ * groups?
+ */
if (*grouplist == '\0')
usage();
break;
@@ -139,23 +146,20 @@ main(int argc, char *argv[])
if (group != NULL)
gid = resolve_group(group);
- ngroups_max = sysconf(_SC_NGROUPS_MAX) + 1;
- if ((gidlist = malloc(sizeof(gid_t) * ngroups_max)) == NULL)
- err(1, "malloc");
- /* Populate the egid slot in our groups to avoid accidents. */
- if (gid == 0)
- gidlist[0] = getegid();
- else
- gidlist[0] = gid;
- for (gids = 1;
- (p = strsep(&grouplist, ",")) != NULL && gids < ngroups_max; ) {
- if (*p == '\0')
- continue;
-
- gidlist[gids++] = resolve_group(p);
+ if (grouplist != NULL) {
+ ngroups_max = sysconf(_SC_NGROUPS_MAX);
+ if ((gidlist = malloc(sizeof(gid_t) * ngroups_max)) == NULL)
+ err(1, "malloc");
+ for (gids = 0; (p = strsep(&grouplist, ",")) != NULL &&
+ gids < ngroups_max; ) {
+ if (*p == '\0')
+ continue;
+
+ gidlist[gids++] = resolve_group(p);
+ }
+ if (p != NULL && gids == ngroups_max)
+ errx(1, "too many supplementary groups provided");
}
- if (p != NULL && gids == ngroups_max)
- errx(1, "too many supplementary groups provided");
if (user != NULL)
uid = resolve_user(user);
@@ -175,7 +179,7 @@ main(int argc, char *argv[])
err(1, "%s", argv[0]);
}
- if (gids && setgroups(gids, gidlist) == -1)
+ if (gidlist != NULL && setgroups(gids, gidlist) == -1)
err(1, "setgroups");
if (group && setgid(gid) == -1)
err(1, "setgid");
diff --git a/usr.sbin/ctld/ctld.hh b/usr.sbin/ctld/ctld.hh
index bfe4507bb3e6..cc88e6eb590e 100644
--- a/usr.sbin/ctld/ctld.hh
+++ b/usr.sbin/ctld/ctld.hh
@@ -425,7 +425,7 @@ protected:
virtual struct portal_group *default_portal_group() = 0;
struct conf *t_conf;
- std::array<struct lun *, MAX_LUNS> t_luns;
+ std::array<struct lun *, MAX_LUNS> t_luns = {};
auth_group_sp t_auth_group;
std::list<port *> t_ports;
std::string t_name;
@@ -434,7 +434,7 @@ protected:
std::string t_redirection;
/* Name of this target's physical port, if any, i.e. "isp0" */
std::string t_pport;
- bool t_private_auth;
+ bool t_private_auth = false;
};
using target_up = std::unique_ptr<target>;
@@ -575,7 +575,7 @@ struct pport {
private:
std::string pp_name;
uint32_t pp_ctl_port;
- bool pp_linked;
+ bool pp_linked = false;
};
struct kports {
diff --git a/usr.sbin/ctld/iscsi.hh b/usr.sbin/ctld/iscsi.hh
index d510e8c6731b..66bfecd62692 100644
--- a/usr.sbin/ctld/iscsi.hh
+++ b/usr.sbin/ctld/iscsi.hh
@@ -66,7 +66,7 @@ private:
std::string conn_initiator_name;
std::string conn_initiator_addr;
std::string conn_initiator_alias;
- uint8_t conn_initiator_isid[6];
+ uint8_t conn_initiator_isid[6] = {};
const struct sockaddr *conn_initiator_sa = nullptr;
int conn_max_recv_data_segment_limit = 0;
int conn_max_send_data_segment_limit = 0;
diff --git a/usr.sbin/devinfo/devinfo.8 b/usr.sbin/devinfo/devinfo.8
index f782b919056c..c34713d367ff 100644
--- a/usr.sbin/devinfo/devinfo.8
+++ b/usr.sbin/devinfo/devinfo.8
@@ -1,4 +1,5 @@
-.\" -*- nroff -*-
+.\"
+.\" SPDX-License-Identifer: BSD-2-Clause
.\"
.\" Copyright (c) 2002 Hiten Pandya
.\" Copyright (c) 2002 Robert N. M. Watson
@@ -25,7 +26,7 @@
.\" (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 January 29, 2025
+.Dd August 28, 2025
.Dt DEVINFO 8
.Os
.Sh NAME
@@ -33,11 +34,14 @@
.Nd print information about system device configuration
.Sh SYNOPSIS
.Nm
+.Op Fl -libxo
.Op Fl rv
.Nm
-.Fl u Op Fl v
-.Nm
+.Op Fl -libxo
.Fl p Ar dev Op Fl v
+.Nm
+.Op Fl -libxo
+.Fl u Op Fl v
.Sh DESCRIPTION
The
.Nm
@@ -46,11 +50,23 @@ in the system, starting from the
.Dq nexus
device.
.Pp
-The following options are accepted.
-.Bl -tag -width indent
+The following options are accepted:
+.Bl -tag -width "--libxo"
+.It Fl -libxo
+Generate output via
+.Xr libxo 3
+in a selection of different human and machine readable formats.
+See
+.Xr xo_options 7
+for details on command line arguments.
+.It Fl p Ar dev
+Display the path of
+.Ar dev
+back to the root of the device tree.
.It Fl r
-Causes hardware resource information (such as IRQ, I/O ports, I/O memory
-addresses) to be also listed, under each device that has reserved those resources.
+Causes hardware resource information
+.Pq such as IRQ, I/O ports, I/O memory addresses
+to be also listed, under each device that has reserved those resources.
.It Fl u
Displays the same information as with
.Fl r
@@ -63,19 +79,22 @@ Display all devices in the driver tree, not just those that are attached or
busy.
Without this flag, only those devices that have attached are reported.
This flag also displays verbose information about each device.
-.It Fl p Ar dev
-Display the path of
-.Ar dev
-back to the root of the device tree.
.El
.Sh SEE ALSO
.Xr systat 1 ,
.Xr devinfo 3 ,
+.Xr libxo 3 ,
+.Xr xo_options 7 ,
.Xr devctl 8 ,
.Xr iostat 8 ,
.Xr pciconf 8 ,
.Xr vmstat 8 ,
.Xr devclass 9 ,
.Xr device 9
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Fx 5.0 .
.Sh AUTHORS
.An Mike Smith Aq Mt msmith@FreeBSD.org
diff --git a/usr.sbin/freebsd-update/freebsd-update.sh b/usr.sbin/freebsd-update/freebsd-update.sh
index c388e76644dc..81040431ea79 100644
--- a/usr.sbin/freebsd-update/freebsd-update.sh
+++ b/usr.sbin/freebsd-update/freebsd-update.sh
@@ -3000,7 +3000,7 @@ install_from_index () {
if [ -z "${LINK}" ]; then
# Create a file, without setting flags.
gunzip < files/${HASH}.gz > ${HASH}
- install -S -o ${OWNER} -g ${GROUP} \
+ install -o ${OWNER} -g ${GROUP} \
-m ${PERM} ${HASH} ${BASEDIR}/${FPATH}
rm ${HASH}
else
diff --git a/usr.sbin/fwget/pci/pci b/usr.sbin/fwget/pci/pci
index fbdfa0001c5c..de8b7c8bb2b3 100644
--- a/usr.sbin/fwget/pci/pci
+++ b/usr.sbin/fwget/pci/pci
@@ -27,7 +27,7 @@
pci_get_class()
{
- local hexclass=$(echo $1 | sed 's/.*class=\(0x[0-9a-z]\{2\}\).*/\1/')
+ local hexclass=$(echo $1 | sed 's/.*class=\(0x[0-9a-f]\{2\}\).*/\1/')
case "${hexclass}" in
0x00) echo "old" ;; # built before class codes were finalized
0x02) echo "network" ;;
@@ -38,7 +38,7 @@ pci_get_class()
pci_get_vendor()
{
- local hexvendor=$(echo $1 | sed 's/.*\ vendor=\(0x[0-9a-z]*\).*/\1/')
+ local hexvendor=$(echo $1 | sed 's/.*\ vendor=\(0x[0-9a-f]*\).*/\1/')
case "${hexvendor}" in
0x1002) echo "amd" ;;
@@ -52,7 +52,7 @@ pci_get_vendor()
pci_get_device()
{
- local hexdevice=$(echo $1 | sed 's/.*\ device=\(0x[0-9a-z]*\).*/\1/')
+ local hexdevice=$(echo $1 | sed 's/.*\ device=\(0x[0-9a-f]*\).*/\1/')
echo ${hexdevice}
}
diff --git a/usr.sbin/fwget/pci/pci_video_amd b/usr.sbin/fwget/pci/pci_video_amd
index 5017789b9f28..7e50454d3944 100644
--- a/usr.sbin/fwget/pci/pci_video_amd
+++ b/usr.sbin/fwget/pci/pci_video_amd
@@ -48,7 +48,7 @@ pci_video_amd()
0x13*)
addpkg "gpu-firmware-amd-kmod-kaveri"
;;
- 0x664*|0x664*)
+ 0x664*|0x665*)
addpkg "gpu-firmware-amd-kmod-bonaire"
;;
0x67a*|0x67b*)
@@ -72,7 +72,7 @@ pci_video_amd()
0x987*)
addpkg "gpu-firmware-amd-kmod-carrizo"
;;
- 0x98e4*)
+ 0x98e4)
addpkg "gpu-firmware-amd-kmod-stoney"
;;
0x67e*|0x67ff)
@@ -111,7 +111,10 @@ pci_video_amd()
0x734*)
addpkg "gpu-firmware-amd-kmod-navi14"
;;
- 0x15e7|0x1636|0x1638|0x164c)
+ 0x15e7|0x1638)
+ addpkg "gpu-firmware-amd-kmod-renoir gpu-firmware-amd-kmod-green-sardine"
+ ;;
+ 0x1636|0x164c)
addpkg "gpu-firmware-amd-kmod-renoir"
;;
0x736*)
@@ -135,7 +138,7 @@ pci_video_amd()
0x740*|0x741*)
addpkg "gpu-firmware-amd-kmod-aldebaran"
;;
- 0x13fe)
+ 0x13fe|0x143f)
addpkg "gpu-firmware-amd-kmod-cyan-skillfish2"
;;
0x742*|0x743*)
diff --git a/usr.sbin/jail/config.c b/usr.sbin/jail/config.c
index 70de82e662e7..1bad04ccde68 100644
--- a/usr.sbin/jail/config.c
+++ b/usr.sbin/jail/config.c
@@ -189,7 +189,7 @@ load_config(const char *cfname)
* jail is created or found.
*/
if (j->intparams[KP_NAME] == NULL)
- add_param(j, j->intparams[KP_JID], KP_NAME, NULL);
+ add_param(j, NULL, KP_NAME, j->name);
/* Resolve any variable substitutions. */
pgen = 0;
diff --git a/usr.sbin/jail/tests/jail_basic_test.sh b/usr.sbin/jail/tests/jail_basic_test.sh
index 509900e8569c..6802da7b049a 100755
--- a/usr.sbin/jail/tests/jail_basic_test.sh
+++ b/usr.sbin/jail/tests/jail_basic_test.sh
@@ -198,7 +198,7 @@ clean_jails()
fi
while read jail; do
- if jls -e -j "$jail"; then
+ if jls -c -j "$jail"; then
jail -r "$jail"
fi
done < jails.lst
@@ -211,10 +211,23 @@ jid_name_set_body()
echo "basejail" >> jails.lst
echo "$jid { name = basejail; persist; }" > jail.conf
atf_check -o match:"$jid: created" jail -f jail.conf -c "$jid"
+ # Confirm that we didn't override the explicitly-set name with the jid
+ # as the name.
+ atf_check -o match:"basejail" jls -j "$jid" name
+ atf_check -o match:"$jid: removed" jail -f jail.conf -r "$jid"
+
+ echo "$jid { host.hostname = \"\${name}\"; persist; }" > jail.conf
+ atf_check -o match:"$jid: created" jail -f jail.conf -c "$jid"
+ # Confirm that ${name} expanded and expanded correctly to the
+ # jid-implied name.
+ atf_check -o match:"$jid" jls -j "$jid" host.hostname
atf_check -o match:"$jid: removed" jail -f jail.conf -r "$jid"
echo "basejail { jid = $jid; persist; }" > jail.conf
atf_check -o match:"basejail: created" jail -f jail.conf -c basejail
+ # Confirm that our jid assigment in the definition worked out and we
+ # did in-fact create the jail there.
+ atf_check -o match:"$jid" jls -j "basejail" jid
atf_check -o match:"basejail: removed" jail -f jail.conf -r basejail
}
diff --git a/usr.sbin/lpr/lpc/lpc.c b/usr.sbin/lpr/lpc/lpc.c
index a3da852de46e..b4db5bb2e29f 100644
--- a/usr.sbin/lpr/lpc/lpc.c
+++ b/usr.sbin/lpr/lpc/lpc.c
@@ -358,6 +358,8 @@ ingroup(const char *grname)
err(1, "getgroups");
}
gid = gptr->gr_gid;
+ if (gid == getegid())
+ return(1);
for (i = 0; i < ngroups; i++)
if (gid == groups[i])
return(1);
diff --git a/usr.sbin/makefs/ffs.c b/usr.sbin/makefs/ffs.c
index c0fcadf11fba..ed94abb7504f 100644
--- a/usr.sbin/makefs/ffs.c
+++ b/usr.sbin/makefs/ffs.c
@@ -591,6 +591,75 @@ ffs_create_image(const char *image, fsinfo_t *fsopts)
return (fsopts->fd);
}
+static void
+ffs_add_size(fsinfo_t *fsopts, size_t file_len)
+{
+ ffs_opt_t *ffs_opts = fsopts->fs_specific;
+ size_t blocks, fs_nindir, overhead;
+ int indir_level;
+
+ blocks = howmany(file_len, ffs_opts->bsize);
+
+ if (blocks <= UFS_NDADDR) {
+ /* Count full blocks. */
+ fsopts->size += rounddown2(file_len, ffs_opts->bsize);
+ /* Calculate fragment size needed. */
+ overhead = howmany(file_len -
+ rounddown2(file_len, ffs_opts->bsize), ffs_opts->fsize);
+
+ /*
+ * A file could have just 1 fragment with size 1/8, 1/4 or 1/2
+ * of bsize.
+ */
+ switch (overhead) {
+ case 0:
+ break;
+ case 1:
+ fsopts->size += ffs_opts->fsize;
+ break;
+ case 2:
+ fsopts->size += 2 * ffs_opts->fsize;
+ break;
+ case 3:
+ case 4:
+ fsopts->size += 4 * ffs_opts->fsize;
+ break;
+ default:
+ fsopts->size += ffs_opts->bsize;
+ break;
+ }
+ return;
+ }
+
+ /* File does not fit into direct blocks, count indirect blocks. */
+ blocks = howmany(file_len - UFS_NDADDR * (size_t)ffs_opts->bsize,
+ ffs_opts->bsize);
+ fs_nindir = (size_t)ffs_opts->bsize / ((ffs_opts->version == 1) ?
+ sizeof(ufs1_daddr_t) : sizeof(ufs2_daddr_t));
+
+ indir_level = overhead = 0;
+ while (blocks > 0 && indir_level < 3) {
+ /* One indirect block is stored in di_ib[] */
+ blocks = howmany(blocks, fs_nindir) - 1;
+ fsopts->size += ffs_opts->bsize * blocks;
+ overhead += blocks + 1;
+ indir_level++;
+ }
+
+ assert(blocks == 0);
+
+ if ((debug & DEBUG_FS_SIZE_DIR_NODE) != 0) {
+ printf("ffs_size_dir: size %jd, using %d levels of indirect "
+ "blocks, overhead %jd blocks\n", (uintmax_t)file_len,
+ indir_level, (uintmax_t)overhead);
+ }
+
+ /*
+ * If the file is big enough to use indirect blocks,
+ * we allocate bsize block for trailing data.
+ */
+ fsopts->size += roundup2(file_len, ffs_opts->bsize);
+}
static void
ffs_size_dir(fsnode *root, fsinfo_t *fsopts)
@@ -622,20 +691,6 @@ ffs_size_dir(fsnode *root, fsinfo_t *fsopts)
e, tmpdir.d_namlen, this, curdirsize); \
} while (0);
-#define ADDSIZE(x) do { \
- if ((size_t)(x) < UFS_NDADDR * (size_t)ffs_opts->bsize) { \
- fsopts->size += roundup((x), ffs_opts->fsize); \
- } else { \
- /* Count space consumed by indirecttion blocks. */ \
- fsopts->size += ffs_opts->bsize * \
- (howmany((x), UFS_NDADDR * ffs_opts->bsize) - 1); \
- /* \
- * If the file is big enough to use indirect blocks, \
- * we allocate bsize block for trailing data. \
- */ \
- fsopts->size += roundup((x), ffs_opts->bsize); \
- } \
-} while (0);
curdirsize = 0;
for (node = root; node != NULL; node = node->next) {
@@ -646,13 +701,13 @@ ffs_size_dir(fsnode *root, fsinfo_t *fsopts)
} else if ((node->inode->flags & FI_SIZED) == 0) {
/* don't count duplicate names */
node->inode->flags |= FI_SIZED;
- if (debug & DEBUG_FS_SIZE_DIR_NODE)
+ if ((debug & DEBUG_FS_SIZE_DIR_NODE) != 0)
printf("ffs_size_dir: `%s' size %lld\n",
node->name,
(long long)node->inode->st.st_size);
fsopts->inodes++;
if (node->type == S_IFREG)
- ADDSIZE(node->inode->st.st_size);
+ ffs_add_size(fsopts, node->inode->st.st_size);
if (node->type == S_IFLNK) {
size_t slen;
@@ -660,13 +715,16 @@ ffs_size_dir(fsnode *root, fsinfo_t *fsopts)
if (slen >= (ffs_opts->version == 1 ?
UFS1_MAXSYMLINKLEN :
UFS2_MAXSYMLINKLEN))
- ADDSIZE(slen);
+ ffs_add_size(fsopts, slen);
}
}
if (node->type == S_IFDIR)
ffs_size_dir(node->child, fsopts);
}
- ADDSIZE(curdirsize);
+ ffs_add_size(fsopts, curdirsize);
+
+ /* Round up to full block to account fragment scattering. */
+ fsopts->size = roundup2(fsopts->size, ffs_opts->bsize);
if (debug & DEBUG_FS_SIZE_DIR)
printf("ffs_size_dir: exit: size %lld inodes %lld\n",
diff --git a/usr.sbin/makefs/zfs.c b/usr.sbin/makefs/zfs.c
index 8d50c450541b..e33a182e5c8f 100644
--- a/usr.sbin/makefs/zfs.c
+++ b/usr.sbin/makefs/zfs.c
@@ -596,7 +596,7 @@ pool_labels_write(zfs_opt_t *zfs)
* checksum is calculated in vdev_label_write().
*/
for (size_t uoff = 0; uoff < sizeof(label->vl_uberblock);
- uoff += (1 << zfs->ashift)) {
+ uoff += ASHIFT_UBERBLOCK_SIZE(zfs->ashift)) {
ub = (uberblock_t *)(&label->vl_uberblock[0] + uoff);
ub->ub_magic = UBERBLOCK_MAGIC;
ub->ub_version = SPA_VERSION;
diff --git a/usr.sbin/makefs/zfs/vdev.c b/usr.sbin/makefs/zfs/vdev.c
index afcce402cb13..a2423e180ca3 100644
--- a/usr.sbin/makefs/zfs/vdev.c
+++ b/usr.sbin/makefs/zfs/vdev.c
@@ -200,7 +200,7 @@ vdev_label_write(zfs_opt_t *zfs, int ind, const vdev_label_t *labelp)
* per sector; for example, with an ashift of 12 we end up with
* 128KB/4KB=32 copies of the uberblock in the ring.
*/
- blksz = 1 << zfs->ashift;
+ blksz = ASHIFT_UBERBLOCK_SIZE(zfs->ashift);
assert(sizeof(label->vl_uberblock) % blksz == 0);
for (size_t roff = 0; roff < sizeof(label->vl_uberblock);
roff += blksz) {
diff --git a/usr.sbin/mountd/exports.5 b/usr.sbin/mountd/exports.5
index a90d81c4db99..786411fbf6d8 100644
--- a/usr.sbin/mountd/exports.5
+++ b/usr.sbin/mountd/exports.5
@@ -25,7 +25,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd December 16, 2024
+.Dd August 24, 2025
.Dt EXPORTS 5
.Os
.Sh NAME
@@ -148,6 +148,19 @@ characters.
Mount points for a file system may appear on multiple lines each with
different sets of hosts and export options.
.Pp
+Note that, for NFSv4 exporting, there must be both one or more ``V4:'' line(s)
+and one or more line(s) exporting the file systems that are to be
+exported to NFSv4 clients.
+If there are multiple ``V4:'' lines, these lines must all specify the
+same root directory path, but with different options for different
+clients.
+These line(s) do not export any file system, but simply define the
+location of the ``root'' of the NFSv4 export subtree.
+The line(s) exporting the file systems should always
+specify the pathname of the root of a server file system
+and must include at least one line exporting the file system
+which is specified as the ``root'' by the ``V4:'' line(s).
+.Pp
The second component of a line specifies how the file system is to be
exported to the host set.
The option flags specify whether the file system
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..acd6e5223685
--- /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)
+{
+ static const 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/newsyslog/newsyslog.8 b/usr.sbin/newsyslog/newsyslog.8
index 6d4fc378e790..7429e3b8eb01 100644
--- a/usr.sbin/newsyslog/newsyslog.8
+++ b/usr.sbin/newsyslog/newsyslog.8
@@ -14,7 +14,7 @@
.\" the suitability of this software for any purpose. It is
.\" provided "as is" without express or implied warranty.
.\"
-.Dd December 22, 2023
+.Dd September 1, 2025
.Dt NEWSYSLOG 8
.Os
.Sh NAME
@@ -24,7 +24,6 @@
.Nm
.Op Fl CFNPnrsv
.Op Fl a Ar directory
-.Op Fl c Ar none Ns | Ns Ar legacy Ns | Ns Ar bzip2 Ns | Ns Ar gzip Ns | Ns Ar xz Ns | Ns Ar zstd
.Op Fl d Ar directory
.Op Fl f Ar config_file
.Op Fl S Ar pidfile
@@ -79,25 +78,6 @@ and mode three (above) assumes that this is so.
The following options can be used with
.Nm :
.Bl -tag -width indent
-.It Fl c Ar none Ns | Ns Ar legacy Ns | Ns Ar bzip2 Ns | Ns Ar gzip Ns | Ns Ar xz Ns | Ns Ar zstd
-Instructs
-.Nm
-to use the specified compression method when a file is flagged for compression.
-The default method is
-.Dq legacy ,
-which interprets the
-.Sy J, X, Y, Z
-flags in the configuration file according to their historical meanings.
-This default setting can be overridden by specifying
-.Fl c Ar none ,
-which causes
-.Nm
-to ignore all compression flags.
-Alternatively, specifying one of the compression methods:
-.Sy bzip2 , gzip , xz ,
-or
-.Sy zstd ,
-will apply the chosen method to all files flagged for compression.
.It Fl f Ar config_file
Instruct
.Nm
diff --git a/usr.sbin/newsyslog/newsyslog.c b/usr.sbin/newsyslog/newsyslog.c
index d07f302fd24f..7ebdd7cbc0dd 100644
--- a/usr.sbin/newsyslog/newsyslog.c
+++ b/usr.sbin/newsyslog/newsyslog.c
@@ -701,19 +701,12 @@ parse_args(int argc, char **argv)
hostname_shortlen = strcspn(hostname, ".");
/* Parse command line options. */
- while ((ch = getopt(argc, argv, "a:c:d:f:nrst:vCD:FNPR:S:")) != -1)
+ while ((ch = getopt(argc, argv, "a:d:f:nrst:vCD:FNPR:S:")) != -1)
switch (ch) {
case 'a':
archtodir++;
archdirname = optarg;
break;
- case 'c':
- if (!parse_compression_type(optarg, &compress_type_override)) {
- warnx("Unrecognized compression method '%s'.", optarg);
- usage();
- }
- compress_type_set = true;
- break;
case 'd':
destdir = optarg;
break;
@@ -858,26 +851,10 @@ parse_doption(const char *doption)
static void
usage(void)
{
- int i;
- char *alltypes = NULL, *tmp = NULL;
-
- for (i = 0; i < COMPRESS_TYPES; i++) {
- if (i == COMPRESS_NONE) {
- (void)asprintf(&tmp, "%s|legacy", compress_type[i].name);
- } else {
- (void)asprintf(&tmp, "%s|%s", alltypes, compress_type[i].name);
- }
- if (alltypes)
- free(alltypes);
- alltypes = tmp;
- tmp = NULL;
- }
fprintf(stderr,
- "usage: newsyslog [-CFNPnrsv] [-a directory] [-c %s]\n"
- " [-d directory] [-f config_file]\n"
- " [-S pidfile] [-t timefmt] [[-R tagname] file ...]\n",
- alltypes);
+ "usage: newsyslog [-CFNPnrsv] [-a directory] [-d directory] [-f config_file]\n"
+ " [-S pidfile] [-t timefmt] [[-R tagname] file ...]\n");
exit(1);
}
diff --git a/usr.sbin/newsyslog/newsyslog.conf.5 b/usr.sbin/newsyslog/newsyslog.conf.5
index 2887ecb226aa..0a47af7285c6 100644
--- a/usr.sbin/newsyslog/newsyslog.conf.5
+++ b/usr.sbin/newsyslog/newsyslog.conf.5
@@ -18,7 +18,7 @@
.\" the suitability of this software for any purpose. It is
.\" provided "as is" without express or implied warranty.
.\"
-.Dd November 11, 2024
+.Dd September 1, 2025
.Dt NEWSYSLOG.CONF 5
.Os
.Sh NAME
@@ -44,8 +44,7 @@ reads a configuration file,
normally
.Pa /etc/newsyslog.conf ,
to determine which logs may potentially be rotated and archived.
-Each line has five mandatory fields and four optional fields,
-separated with whitespace.
+.Pp
Blank lines or lines beginning with
.Ql #
are ignored.
@@ -63,34 +62,73 @@ in this case preceding
is removed and
.Ql #
is treated as an ordinary character.
+.Pp
+The special
+.Dq Ar <compress>
+and
+.Dq Ar <include>
+lines are defined as follows:
+.Bl -tag -width indent
+.It Ar <compress> Ar none Ns | Ns Ar legacy Ns | Ns Ar bzip2 Ns | Ns Ar gzip Ns | Ns Ar xz Ns | Ns Ar zstd
+This special option sets the global compress method,
+it should be placed before all log file entries in
+.Nm
+configuration file.
+The global compress method applies to all log files flagged as
+compressible
+.Dq Sy J ,
+.Dq Sy X ,
+.Dq Sy Y ,
+.Dq Sy Z
+.Ar flags
+below.
+.Pp
+The following compression methods are available:
+.Bl -tag -width indent
+.It Cm none
+No compression is performed, even when a log file is marked as
+compressible. This is useful for filesystems that have native
+compression support.
+.It Cm legacy
+Interprets the
+.Sy J, X, Y, Z
+flags in the configuration file according to their historical meanings.
+This is the default method.
+.It Cm bzip2
+Use
+.Xr bzip2 1
+for all compressible log files.
+.It Cm gzip
+Use
+.Xr gzip 1
+for all compressible log files.
+.It Cm xz
+Use
+.Xr xz 1
+for all compressible log files.
+.It Cm zstd
+Use
+.Xr zstd 1
+for all compressible log files.
+.El
+.It Ar <include>
+The special <include> entry is used to include other configuration
+files and supports globbing.
+.El
+.Pp
+Each other line has five mandatory fields and four optional fields,
+separated with whitespace.
The fields of the configuration file are as follows:
.Bl -tag -width indent
.It Ar logfile_name
Name of the system log file to be archived,
-or one of the special strings
-.Dq Li <compress> ,
-.Dq Li <default> ,
-or
-.Dq Li <include> .
-The <compress> entry,
-which should be placed at the beginning of the
-.Nm
-configuration file,
-sets the global compress method.
-This method is applied when a log file is flagged as
-compressible,
-which has the same effect of passing a compress method to the
-.Fl c
-option on the
-.Xr newsyslog 8
-command line.
+or the special string
+.Dq Ar <default> .
The special <default> entry will only be used if a log file
name is given as a command line argument to
.Xr newsyslog 8 ,
and if that log file name is not matched by any other
line in the configuration file.
-The include entry is used to include other configuration
-files and supports globbing.
.It Ar owner : Ns Ar group
This optional field specifies the owner and group for the archive file.
The
@@ -432,7 +470,7 @@ can be the signal number, e.g., 30 for
.El
.Sh EXAMPLES
The following is an example of the
-.Dq Aq Li include
+.Dq <include>
entry:
.Dl "<include> /etc/newsyslog-local.conf"
.Sh SEE ALSO
diff --git a/usr.sbin/ngctl/Makefile b/usr.sbin/ngctl/Makefile
index 72a5ccaa96d7..997841272376 100644
--- a/usr.sbin/ngctl/Makefile
+++ b/usr.sbin/ngctl/Makefile
@@ -13,4 +13,9 @@ LIBADD= netgraph
CFLAGS+= -DEDITLINE
LIBADD+= edit pthread
+.if ${MK_JAIL} != "no"
+CFLAGS+= -DJAIL
+LIBADD+= jail
+.endif
+
.include <bsd.prog.mk>
diff --git a/usr.sbin/ngctl/main.c b/usr.sbin/ngctl/main.c
index 7c79e67d8275..b32e4f878b6e 100644
--- a/usr.sbin/ngctl/main.c
+++ b/usr.sbin/ngctl/main.c
@@ -55,6 +55,10 @@
#include <histedit.h>
#include <pthread.h>
#endif
+#ifdef JAIL
+#include <sys/jail.h>
+#include <jail.h>
+#endif
#include <netgraph.h>
@@ -137,16 +141,17 @@ int csock, dsock;
int
main(int ac, char *av[])
{
- char name[NG_NODESIZ];
- int interactive = isatty(0) && isatty(1);
- FILE *fp = NULL;
- int ch, rtn = 0;
+ char name[NG_NODESIZ];
+ int interactive = isatty(0) && isatty(1);
+ FILE *fp = NULL;
+ const char *jail_name = NULL;
+ int ch, rtn = 0;
/* Set default node name */
snprintf(name, sizeof(name), "ngctl%d", getpid());
/* Parse command line */
- while ((ch = getopt(ac, av, "df:n:")) != -1) {
+ while ((ch = getopt(ac, av, "df:j:n:")) != -1) {
switch (ch) {
case 'd':
NgSetDebug(NgSetDebug(-1) + 1);
@@ -157,6 +162,13 @@ main(int ac, char *av[])
else if ((fp = fopen(optarg, "r")) == NULL)
err(EX_NOINPUT, "%s", optarg);
break;
+ case 'j':
+#ifdef JAIL
+ jail_name = optarg;
+#else
+ errx(EX_UNAVAILABLE, "not built with jail support");
+#endif
+ break;
case 'n':
snprintf(name, sizeof(name), "%s", optarg);
break;
@@ -169,6 +181,22 @@ main(int ac, char *av[])
ac -= optind;
av += optind;
+ if (jail_name != NULL) {
+ int jid;
+
+ if (jail_name[0] == '\0')
+ Usage("invalid jail name");
+
+ jid = jail_getid(jail_name);
+
+ if (jid == -1)
+ errx((errno == EPERM) ? EX_NOPERM : EX_NOHOST,
+ "%s", jail_errmsg);
+ if (jail_attach(jid) != 0)
+ errx((errno == EPERM) ? EX_NOPERM : EX_OSERR,
+ "cannot attach to jail");
+ }
+
/* Create a new socket node */
if (NgMkSockNode(name, &csock, &dsock) < 0)
err(EX_OSERR, "can't create node");
@@ -657,6 +685,7 @@ Usage(const char *msg)
if (msg)
warnx("%s", msg);
fprintf(stderr,
- "usage: ngctl [-d] [-f file] [-n name] [command ...]\n");
+ "usage: ngctl [-j jail] [-d] [-f filename] [-n nodename] "
+ "[command [argument ...]]\n");
exit(EX_USAGE);
}
diff --git a/usr.sbin/ngctl/ngctl.8 b/usr.sbin/ngctl/ngctl.8
index 2225c836674a..63b8f58ed3df 100644
--- a/usr.sbin/ngctl/ngctl.8
+++ b/usr.sbin/ngctl/ngctl.8
@@ -31,7 +31,7 @@
.\" OF SUCH DAMAGE.
.\" $Whistle: ngctl.8,v 1.6 1999/01/20 03:19:44 archie Exp $
.\"
-.Dd January 19, 1999
+.Dd August 29, 2025
.Dt NGCTL 8
.Os
.Sh NAME
@@ -39,9 +39,11 @@
.Nd netgraph control utility
.Sh SYNOPSIS
.Nm
+.Op Fl j Ar jail
.Op Fl d
.Op Fl f Ar filename
.Op Fl n Ar nodename
+.Op Ar command Op Ns Ar argument ...
.Op Ar command ...
.Sh DESCRIPTION
The
@@ -73,12 +75,31 @@ form if the originating node supports conversion.
.Pp
The options are as follows:
.Bl -tag -width indent
-.It Fl f Ar nodeinfo
+.It Fl f Ar filename
Read commands from the named file.
A single dash represents the standard input.
Blank lines and lines starting with a
.Dq #
are ignored.
+Note that when the
+.Fl j Ar jail
+option is specified, the file will be opened before attaching to the jail and
+then be processed inside the jail.
+.It Fl j Ar jail
+Perform the actions inside the
+.Ar jail .
+.Pp
+.Nm
+will first attach to the
+.Ar jail
+(by jail id or jail name) before performing the effects.
+.Pp
+This allows netgraph nodes of
+.Ar jail
+to be created, modified, and destroyed even if the
+.Nm
+binary is not available in
+.Ar jail .
.It Fl n Ar nodename
Assign
.Em nodename
diff --git a/usr.sbin/ntp/ntpd/leap-seconds b/usr.sbin/ntp/ntpd/leap-seconds
index da0efc8c8566..649a22c6278f 100644
--- a/usr.sbin/ntp/ntpd/leap-seconds
+++ b/usr.sbin/ntp/ntpd/leap-seconds
@@ -60,15 +60,15 @@
#
# The following line shows the last update of this file in NTP timestamp:
#
-#$ 3929093563
+#$ 3960835200
#
# 2) Expiration date of the file given on a semi-annual basis: last June or last December
#
-# File expires on 28 June 2025
+# File expires on 28 June 2026
#
# Expire date in NTP timestamp:
#
-#@ 3960057600
+#@ 3991593600
#
#
# LIST OF LEAP SECONDS
@@ -117,4 +117,4 @@
# please see the readme file in the 'source' directory :
# https://hpiers.obspm.fr/iers/bul/bulc/ntp/sources/README
#
-#h be738595 57b0cf1b b0218343 fb77062f 5a775e7
+#h 49db2447 571e5e1b 2f002a53 9c8da8e4 39b8e49e
diff --git a/usr.sbin/pkg/FreeBSD.conf.latest b/usr.sbin/pkg/FreeBSD.conf.latest
index 8c68118c4938..ac1636386942 100644
--- a/usr.sbin/pkg/FreeBSD.conf.latest
+++ b/usr.sbin/pkg/FreeBSD.conf.latest
@@ -3,18 +3,18 @@
# create a /usr/local/etc/pkg/repos/FreeBSD.conf file, e.g.:
#
# mkdir -p /usr/local/etc/pkg/repos
-# echo "FreeBSD: { enabled: no }" > /usr/local/etc/pkg/repos/FreeBSD.conf
-# echo "FreeBSD-kmods: { enabled: no }" >> /usr/local/etc/pkg/repos/FreeBSD.conf
+# echo "FreeBSD-ports: { enabled: no }" > /usr/local/etc/pkg/repos/FreeBSD.conf
+# echo "FreeBSD-ports-kmods: { enabled: no }" >> /usr/local/etc/pkg/repos/FreeBSD.conf
#
-FreeBSD: {
+FreeBSD-ports: {
url: "pkg+https://pkg.FreeBSD.org/${ABI}/latest",
mirror_type: "srv",
signature_type: "fingerprints",
fingerprints: "/usr/share/keys/pkg",
enabled: yes
}
-FreeBSD-kmods: {
+FreeBSD-ports-kmods: {
url: "pkg+https://pkg.FreeBSD.org/${ABI}/kmods_latest",
mirror_type: "srv",
signature_type: "fingerprints",
diff --git a/usr.sbin/pkg/FreeBSD.conf.quarterly b/usr.sbin/pkg/FreeBSD.conf.quarterly
index 884226fc02d1..4e26582c6981 100644
--- a/usr.sbin/pkg/FreeBSD.conf.quarterly
+++ b/usr.sbin/pkg/FreeBSD.conf.quarterly
@@ -3,18 +3,18 @@
# create a /usr/local/etc/pkg/repos/FreeBSD.conf file, e.g.:
#
# mkdir -p /usr/local/etc/pkg/repos
-# echo "FreeBSD: { enabled: no }" > /usr/local/etc/pkg/repos/FreeBSD.conf
-# echo "FreeBSD-kmods: { enabled: no }" >> /usr/local/etc/pkg/repos/FreeBSD.conf
+# echo "FreeBSD-ports: { enabled: no }" > /usr/local/etc/pkg/repos/FreeBSD.conf
+# echo "FreeBSD-ports-kmods: { enabled: no }" >> /usr/local/etc/pkg/repos/FreeBSD.conf
#
-FreeBSD: {
+FreeBSD-ports: {
url: "pkg+https://pkg.FreeBSD.org/${ABI}/quarterly",
mirror_type: "srv",
signature_type: "fingerprints",
fingerprints: "/usr/share/keys/pkg",
enabled: yes
}
-FreeBSD-kmods: {
+FreeBSD-ports-kmods: {
url: "pkg+https://pkg.FreeBSD.org/${ABI}/kmods_quarterly",
mirror_type: "srv",
signature_type: "fingerprints",
diff --git a/usr.sbin/pkg/FreeBSD.conf.quarterly-release b/usr.sbin/pkg/FreeBSD.conf.quarterly-release
index bd0ee13b9b43..b4a78009f7d2 100644
--- a/usr.sbin/pkg/FreeBSD.conf.quarterly-release
+++ b/usr.sbin/pkg/FreeBSD.conf.quarterly-release
@@ -3,18 +3,18 @@
# create a /usr/local/etc/pkg/repos/FreeBSD.conf file, e.g.:
#
# mkdir -p /usr/local/etc/pkg/repos
-# echo "FreeBSD: { enabled: no }" > /usr/local/etc/pkg/repos/FreeBSD.conf
-# echo "FreeBSD-kmods: { enabled: no }" >> /usr/local/etc/pkg/repos/FreeBSD.conf
+# echo "FreeBSD-ports: { enabled: no }" > /usr/local/etc/pkg/repos/FreeBSD.conf
+# echo "FreeBSD-ports-kmods: { enabled: no }" >> /usr/local/etc/pkg/repos/FreeBSD.conf
#
-FreeBSD: {
+FreeBSD-ports: {
url: "pkg+https://pkg.FreeBSD.org/${ABI}/quarterly",
mirror_type: "srv",
signature_type: "fingerprints",
fingerprints: "/usr/share/keys/pkg",
enabled: yes
}
-FreeBSD-kmods: {
+FreeBSD-ports-kmods: {
url: "pkg+https://pkg.FreeBSD.org/${ABI}/kmods_quarterly_${VERSION_MINOR}",
mirror_type: "srv",
signature_type: "fingerprints",
diff --git a/usr.sbin/pw/pw.8 b/usr.sbin/pw/pw.8
index c72623ee05b3..5eae810b6732 100644
--- a/usr.sbin/pw/pw.8
+++ b/usr.sbin/pw/pw.8
@@ -22,7 +22,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd July 29, 2024
+.Dd August 19, 2025
.Dt PW 8
.Os
.Sh NAME
@@ -191,7 +191,12 @@ utility handles updating the
.Xr master.passwd 5 ,
.Xr group 5
and the secure and insecure
-password database files, and must be run as root.
+password database files, and must be run as root
+.Po except when using
+.Fl R
+or
+.Fl V
+.Pc .
.Pp
The first one or two keywords provided to
.Nm
diff --git a/usr.sbin/pw/pw.c b/usr.sbin/pw/pw.c
index fc17f6dba022..a4c95258f3bb 100644
--- a/usr.sbin/pw/pw.c
+++ b/usr.sbin/pw/pw.c
@@ -162,6 +162,7 @@ main(int argc, char *argv[])
snprintf(conf.etcpath, sizeof(conf.etcpath),
"%s%s", optarg, arg == 'R' ?
_PATH_PWD : "");
+ conf.altroot = true;
} else
break;
}
diff --git a/usr.sbin/pw/pw_user.c b/usr.sbin/pw/pw_user.c
index d9fd8c77c13e..8a9a4342f5ef 100644
--- a/usr.sbin/pw/pw_user.c
+++ b/usr.sbin/pw/pw_user.c
@@ -238,6 +238,13 @@ perform_chgpwent(const char *name, struct passwd *pwd, char *nispasswd)
}
}
+static void
+pw_check_root(void)
+{
+ if (!conf.altroot && geteuid() != 0)
+ errx(EX_NOPERM, "you must be root");
+}
+
/*
* The M_LOCK and M_UNLOCK functions simply add or remove
* a "*LOCKED*" prefix from in front of the password to
@@ -256,8 +263,7 @@ pw_userlock(char *arg1, int mode)
bool locked = false;
uid_t id = (uid_t)-1;
- if (geteuid() != 0)
- errx(EX_NOPERM, "you must be root");
+ pw_check_root();
if (arg1 == NULL)
errx(EX_DATAERR, "username or id required");
@@ -1324,8 +1330,8 @@ pw_user_add(int argc, char **argv, char *arg1)
if (argc > 0)
usage();
- if (geteuid() != 0 && ! dryrun)
- errx(EX_NOPERM, "you must be root");
+ if (!dryrun)
+ pw_check_root();
if (quiet)
freopen(_PATH_DEVNULL, "w", stderr);
@@ -1641,8 +1647,8 @@ pw_user_mod(int argc, char **argv, char *arg1)
if (argc > 0)
usage();
- if (geteuid() != 0 && ! dryrun)
- errx(EX_NOPERM, "you must be root");
+ if (!dryrun)
+ pw_check_root();
if (quiet)
freopen(_PATH_DEVNULL, "w", stderr);
diff --git a/usr.sbin/pw/pwupd.h b/usr.sbin/pw/pwupd.h
index 262b044e07fc..a39a022ca309 100644
--- a/usr.sbin/pw/pwupd.h
+++ b/usr.sbin/pw/pwupd.h
@@ -78,6 +78,7 @@ struct pwconf {
char etcpath[MAXPATHLEN];
int fd;
int rootfd;
+ bool altroot;
bool checkduplicate;
};
diff --git a/usr.sbin/services_mkdb/services b/usr.sbin/services_mkdb/services
index 4a5b6863d92d..c5f950831767 100644
--- a/usr.sbin/services_mkdb/services
+++ b/usr.sbin/services_mkdb/services
@@ -893,7 +893,7 @@ biff 512/udp comsat #used by mail system to notify users
# processes on the same machine
login 513/tcp #remote login a la telnet;
# automatic authentication performed
-# based on priviledged port numbers
+# based on privileged port numbers
# and distributed data bases which
# identify "authentication domains"
who 513/udp whod #maintains data bases showing who's
diff --git a/usr.sbin/syslogd/syslogd.8 b/usr.sbin/syslogd/syslogd.8
index fa61e78eaf3e..d39d9fdc8f5a 100644
--- a/usr.sbin/syslogd/syslogd.8
+++ b/usr.sbin/syslogd/syslogd.8
@@ -403,7 +403,7 @@ The message can contain a priority code,
which should be a preceding
decimal number in angle braces,
for example,
-.Sq Aq 5 .
+.Sq <5> .
This priority code should map into the priorities defined in the
include file
.In sys/syslog.h .
diff --git a/usr.sbin/syslogd/syslogd.c b/usr.sbin/syslogd/syslogd.c
index fe7427130b78..81bbbbe66be8 100644
--- a/usr.sbin/syslogd/syslogd.c
+++ b/usr.sbin/syslogd/syslogd.c
@@ -1830,15 +1830,14 @@ fprintlog_write(struct filed *f, struct iovlist *il, int flags)
case EHOSTUNREACH:
case EHOSTDOWN:
case EADDRNOTAVAIL:
+ case EAGAIN:
+ case ECONNREFUSED:
break;
/* case EBADF: */
/* case EACCES: */
/* case ENOTSOCK: */
/* case EFAULT: */
/* case EMSGSIZE: */
- /* case EAGAIN: */
- /* case ENOBUFS: */
- /* case ECONNREFUSED: */
default:
dprintf("removing entry: errno=%d\n", e);
f->f_type = F_UNUSED;
diff --git a/usr.sbin/tcpdump/Makefile.inc b/usr.sbin/tcpdump/Makefile.inc
index abbdc366c6a0..454f1869508d 100644
--- a/usr.sbin/tcpdump/Makefile.inc
+++ b/usr.sbin/tcpdump/Makefile.inc
@@ -1,3 +1,3 @@
-BINDIR?= /usr/sbin
-
WARNS?= 3
+
+.include "../Makefile.inc"
diff --git a/usr.sbin/tcpdump/tcpdump/Makefile b/usr.sbin/tcpdump/tcpdump/Makefile
index bfbe750c25be..21c5f9ac7fdf 100644
--- a/usr.sbin/tcpdump/tcpdump/Makefile
+++ b/usr.sbin/tcpdump/tcpdump/Makefile
@@ -186,9 +186,9 @@ SRCS= addrtoname.c \
print-unsupported.c \
print-vsock.c \
print-whois.c \
- print-zep.c \
- version.c
-CLEANFILES+= version.c ${MAN}
+ print-zep.c
+
+CLEANFILES+= ${MAN}
CFLAGS+= -I${.CURDIR} -I${TCPDUMP_DISTDIR}
CFLAGS+= -DHAVE_CONFIG_H
@@ -197,9 +197,6 @@ CFLAGS+= -D_U_="__attribute__((unused))"
.if ${MK_INET6_SUPPORT} != "no"
CFLAGS+= -DINET6 -DHAVE_OS_IPV6_SUPPORT
.endif
-.if ${MACHINE_CPUARCH} != "i386"
-CFLAGS+= -DLBL_ALIGN
-.endif
LIBADD= pcap
.if ${MK_CASPER} != "no"
@@ -220,11 +217,6 @@ SRCS+= print-pflog.c \
CFLAGS+= -DHAVE_NET_PFVAR_H -DHAVE_NET_IF_PFLOG_H
.endif
-version.c: ${TCPDUMP_DISTDIR}/VERSION
- rm -f version.c ; \
- sed 's/.*/char version[] = "&";/' ${TCPDUMP_DISTDIR}/VERSION \
- > version.c
-
.include <bsd.prog.mk>
.for mp in ${MAN}
diff --git a/usr.sbin/unbound/setup/local-unbound-setup.sh b/usr.sbin/unbound/setup/local-unbound-setup.sh
index d52534b46fa3..d57d74952fc7 100755
--- a/usr.sbin/unbound/setup/local-unbound-setup.sh
+++ b/usr.sbin/unbound/setup/local-unbound-setup.sh
@@ -259,7 +259,7 @@ gen_unbound_conf() {
echo " pidfile: ${pidfile}"
echo " auto-trust-anchor-file: ${anchor}"
if [ "${use_tls}" = "yes" ] ; then
- echo " tls-system-cert: yes"
+ echo " tls-cert-bundle: /etc/ssl/cert.pem"
fi
echo ""
if [ -f "${forward_conf}" ] ; then
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 .
diff --git a/usr.sbin/watch/watch.8 b/usr.sbin/watch/watch.8
index 7acd79df8710..3cc72267f207 100644
--- a/usr.sbin/watch/watch.8
+++ b/usr.sbin/watch/watch.8
@@ -28,7 +28,7 @@ The
utility writes to standard output.
.Pp
The options are as follows:
-.Bl -tag -width indent
+.Bl -tag -width "-f snpdev"
.It Fl c
Reconnect on close.
If the tty observed by
diff --git a/usr.sbin/watchdogd/watchdogd.c b/usr.sbin/watchdogd/watchdogd.c
index 88b467486da1..27123f2143d0 100644
--- a/usr.sbin/watchdogd/watchdogd.c
+++ b/usr.sbin/watchdogd/watchdogd.c
@@ -63,25 +63,25 @@
static long fetchtimeout(int opt,
const char *longopt, const char *myoptarg, int zero_ok);
static void parseargs(int, char *[]);
-static int seconds_to_pow2ns(int);
static void sighandler(int);
static void watchdog_loop(void);
static int watchdog_init(void);
static int watchdog_onoff(int onoff);
-static int watchdog_patpat(u_int timeout);
+static int watchdog_patpat(sbintime_t);
static void usage(void);
-static int tstotv(struct timeval *tv, struct timespec *ts);
static int tvtohz(struct timeval *tv);
static int debugging = 0;
static int end_program = 0;
static const char *pidfile = _PATH_VARRUN "watchdogd.pid";
-static u_int timeout = WD_TO_128SEC;
+static sbintime_t timeout = 128 * SBT_1S;
static u_int exit_timeout = WD_TO_NEVER;
static u_int pretimeout = 0;
static u_int timeout_sec;
static u_int nap = 10;
+#ifdef notyet
static int passive = 0;
+#endif
static int is_daemon = 0;
static int is_dry_run = 0; /* do not arm the watchdog, only
report on timing of the watch
@@ -174,38 +174,23 @@ main(int argc, char *argv[])
pidfile_remove(pfh);
return (EX_OK);
} else {
- if (passive)
- timeout |= WD_PASSIVE;
- else
- timeout |= WD_ACTIVE;
if (watchdog_patpat(timeout) < 0)
err(EX_OSERR, "patting the dog");
return (EX_OK);
}
}
-static void
-pow2ns_to_ts(int pow2ns, struct timespec *ts)
-{
- uint64_t ns;
-
- ns = 1ULL << pow2ns;
- ts->tv_sec = ns / 1000000000ULL;
- ts->tv_nsec = ns % 1000000000ULL;
-}
-
/*
* Convert a timeout in seconds to N where 2^N nanoseconds is close to
* "seconds".
*
* The kernel expects the timeouts for watchdogs in "2^N nanosecond format".
*/
-static u_int
-parse_timeout_to_pow2ns(char opt, const char *longopt, const char *myoptarg)
+static sbintime_t
+parse_timeout_to_sbt(char opt, const char *longopt, const char *myoptarg)
{
- double a;
- u_int rv;
- struct timespec ts;
+ long a;
+ sbintime_t rv;
struct timeval tv;
int ticks;
char shortopt[] = "- ";
@@ -216,19 +201,17 @@ parse_timeout_to_pow2ns(char opt, const char *longopt, const char *myoptarg)
a = fetchtimeout(opt, longopt, myoptarg, 1);
if (a == 0)
- rv = WD_TO_NEVER;
+ rv = 0;
else
- rv = seconds_to_pow2ns(a);
- pow2ns_to_ts(rv, &ts);
- tstotv(&tv, &ts);
+ rv = a * SBT_1S;
+ tv = sbttotv(rv);
ticks = tvtohz(&tv);
if (debugging) {
printf("Timeout for %s%s "
- "is 2^%d nanoseconds "
- "(in: %s sec -> out: %jd sec %ld ns -> %d ticks)\n",
+ "is "
+ "(in: %s sec -> out: %jd sec %ld us -> %d ticks)\n",
longopt ? "-" : "", longopt ? longopt : shortopt,
- rv,
- myoptarg, (intmax_t)ts.tv_sec, ts.tv_nsec, ticks);
+ myoptarg, (intmax_t)tv.tv_sec, tv.tv_usec, ticks);
}
if (ticks <= 0) {
errx(1, "Timeout for %s%s is too small, please choose a higher timeout.", longopt ? "-" : "", longopt ? longopt : shortopt);
@@ -364,7 +347,7 @@ watchdog_loop(void)
}
if (failed == 0)
- watchdog_patpat(timeout|WD_ACTIVE);
+ watchdog_patpat(timeout);
waited = watchdog_check_dogfunction_time(&ts_start, &ts_end);
if (nap - waited > 0)
@@ -387,13 +370,22 @@ try_end:
* to keep the watchdog from firing.
*/
static int
-watchdog_patpat(u_int t)
+watchdog_patpat(sbintime_t sbt)
{
if (is_dry_run)
return 0;
- return ioctl(fd, WDIOCPATPAT, &t);
+ return ioctl(fd, WDIOC_SETTIMEOUT, &sbt);
+}
+
+static int
+watchdog_control(u_int control)
+{
+ if (is_dry_run)
+ return (0);
+
+ return ioctl(fd, WDIOC_CONTROL, &control);
}
/*
@@ -420,7 +412,7 @@ watchdog_onoff(int onoff)
warn("setting WDIOC_SETSOFT %d", softtimeout_set);
return (error);
}
- error = watchdog_patpat((timeout|WD_ACTIVE));
+ error = watchdog_patpat(timeout);
if (error) {
warn("watchdog_patpat failed");
goto failsafe;
@@ -452,12 +444,12 @@ watchdog_onoff(int onoff)
}
}
/* pat one more time for good measure */
- return watchdog_patpat((timeout|WD_ACTIVE));
+ return watchdog_patpat(timeout);
} else {
- return watchdog_patpat(exit_timeout);
+ return watchdog_control(WD_CTRL_DISABLE);
}
failsafe:
- watchdog_patpat(exit_timeout);
+ watchdog_control(WD_CTRL_DISABLE);
return (error);
}
@@ -567,15 +559,6 @@ timeout_act_str2int(const char *lopt, const char *acts)
return rv;
}
-int
-tstotv(struct timeval *tv, struct timespec *ts)
-{
-
- tv->tv_sec = ts->tv_sec;
- tv->tv_usec = ts->tv_nsec / 1000;
- return 0;
-}
-
/*
* Convert a timeval to a number of ticks.
* Mostly copied from the kernel.
@@ -647,30 +630,6 @@ tvtohz(struct timeval *tv)
return ((int)ticks);
}
-static int
-seconds_to_pow2ns(int seconds)
-{
- uint64_t power;
- uint64_t ns;
- uint64_t shifted;
-
- if (seconds <= 0)
- errx(1, "seconds %d < 0", seconds);
- ns = ((uint64_t)seconds) * 1000000000ULL;
- power = flsll(ns);
- shifted = 1ULL << power;
- if (shifted <= ns) {
- power++;
- }
- if (debugging) {
- printf("shifted %lld\n", (long long)shifted);
- printf("seconds_to_pow2ns: seconds: %d, ns %lld, power %d\n",
- seconds, (long long)ns, (int)power);
- }
- return (power);
-}
-
-
/*
* Handle the few command line arguments supported.
*/
@@ -683,8 +642,7 @@ parseargs(int argc, char *argv[])
const char *lopt;
/* Get the default value of timeout_sec from the default timeout. */
- pow2ns_to_ts(timeout, &ts);
- timeout_sec = ts.tv_sec;
+ timeout_sec = sbintime_getsec(timeout);
/*
* if we end with a 'd' aka 'watchdogd' then we are the daemon program,
@@ -727,10 +685,10 @@ parseargs(int argc, char *argv[])
break;
case 't':
timeout_sec = atoi(optarg);
- timeout = parse_timeout_to_pow2ns(c, NULL, optarg);
+ timeout = parse_timeout_to_sbt(c, NULL, optarg);
if (debugging)
- printf("Timeout is 2^%d nanoseconds\n",
- timeout);
+ printf("Timeout is %d\n",
+ (int)(timeout / SBT_1S));
break;
case 'T':
carp_thresh_seconds =
@@ -740,7 +698,7 @@ parseargs(int argc, char *argv[])
do_timedog = 1;
break;
case 'x':
- exit_timeout = parse_timeout_to_pow2ns(c, NULL, optarg);
+ exit_timeout = parse_timeout_to_sbt(c, NULL, optarg);
if (exit_timeout != 0)
exit_timeout |= WD_ACTIVE;
break;