Author: wulf
Date: Mon Apr 30 12:16:54 2018
New Revision: 333113
URL: https://svnweb.freebsd.org/changeset/base/333113

Log:
  bthidd(8): Add evdev protocol support for bluetooth keyboards and mouses
  
  User-visible changes:
  
  "-u" is added to to list of command line options supported by bthidd.
  Use it to enable evdev support. uinput and evdev modules should be
  kld-loaded or compiled into the kernel in that case.
  
  bthidd_evdev_support rc.conf variable is added to control enabling of
  evdev support in bthidd startup script. Possible values are: "YES", "NO",
  "AUTO"(default). Setting bthidd_evdev_support to "AUTO" inserts "-u" option
  if kernel is compiled with EVDEV_SUPPORT option enabled.
  
  Support for consumer HID usage page keyboard events is implemented. Most of
  them are available only through evdev protocol.
  
  kern.evdev.rcpt_mask sysctl is checked, so "sysctl kern.evdev.rcpt_mask=12"
  should be executed if EVDEV_SUPPORT is compiled into kernel.
  
  It is recommended to regenerate bthidd.conf entries with bthidcontrol(8)
  "Query" command to set user-friendly names of bluetooth devices.
  
  Reviewed by:  emax, gonzo, wblock (docs), bcr (docs, early version)
  Differential Revision:        https://reviews.freebsd.org/D13456

Added:
  head/usr.sbin/bluetooth/bthidd/btuinput.c   (contents, props changed)
  head/usr.sbin/bluetooth/bthidd/btuinput.h   (contents, props changed)
Modified:
  head/etc/defaults/rc.conf
  head/etc/rc.d/bthidd
  head/usr.sbin/bluetooth/bthidd/Makefile
  head/usr.sbin/bluetooth/bthidd/bthid_config.h
  head/usr.sbin/bluetooth/bthidd/bthidd.8
  head/usr.sbin/bluetooth/bthidd/bthidd.c
  head/usr.sbin/bluetooth/bthidd/bthidd.h
  head/usr.sbin/bluetooth/bthidd/client.c
  head/usr.sbin/bluetooth/bthidd/hid.c
  head/usr.sbin/bluetooth/bthidd/kbd.c
  head/usr.sbin/bluetooth/bthidd/parser.y
  head/usr.sbin/bluetooth/bthidd/server.c
  head/usr.sbin/bluetooth/bthidd/session.c

Modified: head/etc/defaults/rc.conf
==============================================================================
--- head/etc/defaults/rc.conf   Mon Apr 30 10:49:29 2018        (r333112)
+++ head/etc/defaults/rc.conf   Mon Apr 30 12:16:54 2018        (r333113)
@@ -433,6 +433,7 @@ sdpd_username="nobody"              # it initializes
 bthidd_enable="NO"             # Enable bthidd(8) (or NO)
 bthidd_config="/etc/bluetooth/bthidd.conf" # bthidd(8) configuration file
 bthidd_hids="/var/db/bthidd.hids" # bthidd(8) known HID devices file
+bthidd_evdev_support="AUTO"    # AUTO depends on EVDEV_SUPPORT kernel option
 
 rfcomm_pppd_server_enable="NO" # Enable rfcomm_pppd(8) in server mode (or NO)
 rfcomm_pppd_server_profile="one two"   # Profile to use from /etc/ppp/ppp.conf

Modified: head/etc/rc.d/bthidd
==============================================================================
--- head/etc/rc.d/bthidd        Mon Apr 30 10:49:29 2018        (r333112)
+++ head/etc/rc.d/bthidd        Mon Apr 30 12:16:54 2018        (r333113)
@@ -17,8 +17,25 @@ command="/usr/sbin/${name}"
 pidfile="/var/run/${name}.pid"
 start_precmd="bthidd_prestart"
 
+evdev_enabled()
+{
+       case ${bthidd_evdev_support} in
+       [Aa][Uu][Tt][Oo])
+               check_kern_features evdev_support
+               return $?
+               ;;
+       *)
+               checkyesno bthidd_evdev_support
+               return $?
+               ;;
+       esac
+}
+
 bthidd_prestart()
 {
+       if evdev_enabled; then
+               load_kld -m uinput uinput
+       fi
        load_kld -m kbdmux kbdmux
        load_kld -m vkbd vkbd
        load_kld -m ng_btsocket ng_btsocket
@@ -29,6 +46,9 @@ load_rc_config $name
 config="${bthidd_config:-/etc/bluetooth/${name}.conf}"
 hids="${bthidd_hids:-/var/db/${name}.hids}"
 command_args="-c ${config} -H ${hids} -p ${pidfile}"
+if evdev_enabled; then
+       command_args="$command_args -u"
+fi
 required_files="${config}"
 
 run_rc_command "$1"

Modified: head/usr.sbin/bluetooth/bthidd/Makefile
==============================================================================
--- head/usr.sbin/bluetooth/bthidd/Makefile     Mon Apr 30 10:49:29 2018        
(r333112)
+++ head/usr.sbin/bluetooth/bthidd/Makefile     Mon Apr 30 12:16:54 2018        
(r333113)
@@ -4,8 +4,8 @@
 PROG=          bthidd
 MAN=           bthidd.8
 #              bthidd.conf.5
-SRCS=          bthidd.c client.c hid.c kbd.c lexer.l parser.y server.c \
-               session.c
+SRCS=          bthidd.c btuinput.c client.c hid.c kbd.c lexer.l parser.y \
+               server.c session.c
 
 CFLAGS+=       -I${.CURDIR}
 

Modified: head/usr.sbin/bluetooth/bthidd/bthid_config.h
==============================================================================
--- head/usr.sbin/bluetooth/bthidd/bthid_config.h       Mon Apr 30 10:49:29 
2018        (r333112)
+++ head/usr.sbin/bluetooth/bthidd/bthid_config.h       Mon Apr 30 12:16:54 
2018        (r333113)
@@ -53,7 +53,11 @@ struct hid_device
        unsigned                battery_power        : 1;
        unsigned                normally_connectable : 1;
        unsigned                keyboard             : 1;
-       unsigned                reserved             : 11;
+       unsigned                mouse                : 1;
+       unsigned                has_wheel            : 1;
+       unsigned                has_hwheel           : 1;
+       unsigned                has_cons             : 1;
+       unsigned                reserved             : 7;
        report_desc_t           desc;           /* HID report descriptor */
        LIST_ENTRY(hid_device)  next;           /* link to the next */
 };

Modified: head/usr.sbin/bluetooth/bthidd/bthidd.8
==============================================================================
--- head/usr.sbin/bluetooth/bthidd/bthidd.8     Mon Apr 30 10:49:29 2018        
(r333112)
+++ head/usr.sbin/bluetooth/bthidd/bthidd.8     Mon Apr 30 12:16:54 2018        
(r333113)
@@ -25,7 +25,7 @@
 .\" $Id: bthidd.8,v 1.1 2006/09/07 21:36:55 max Exp $
 .\" $FreeBSD$
 .\"
-.Dd September 7, 2006
+.Dd April 30, 2018
 .Dt BTHIDD 8
 .Os
 .Sh NAME
@@ -40,6 +40,7 @@
 .Op Fl H Ar file
 .Op Fl p Ar file
 .Op Fl t Ar val
+.Op Fl u
 .Sh DESCRIPTION
 The
 .Nm
@@ -82,6 +83,11 @@ disconnected
 .Dq passive
 Bluetooth HID devices and will attempt to establish an outgoing connection.
 The default rescan interval is 10 seconds.
+.It Fl u
+Enable support for input event device protocol.
+Requires evdev and uinput drivers to be loaded with
+.Xr kldload 8
+or compiled into the kernel.
 .El
 .Sh KNOWN LIMITATIONS
 The

Modified: head/usr.sbin/bluetooth/bthidd/bthidd.c
==============================================================================
--- head/usr.sbin/bluetooth/bthidd/bthidd.c     Mon Apr 30 10:49:29 2018        
(r333112)
+++ head/usr.sbin/bluetooth/bthidd/bthidd.c     Mon Apr 30 12:16:54 2018        
(r333113)
@@ -69,14 +69,15 @@ main(int32_t argc, char *argv[])
        struct sigaction         sa;
        char const              *pid_file = BTHIDD_PIDFILE;
        char                    *ep;
-       int32_t                  opt, detach, tval;
+       int32_t                  opt, detach, tval, uinput;
 
        memset(&srv, 0, sizeof(srv));
        memset(&srv.bdaddr, 0, sizeof(srv.bdaddr));
        detach = 1;
        tval = 10; /* sec */
+       uinput = 0;
 
-       while ((opt = getopt(argc, argv, "a:c:dH:hp:t:")) != -1) {
+       while ((opt = getopt(argc, argv, "a:c:dH:hp:t:u")) != -1) {
                switch (opt) {
                case 'a': /* BDADDR */
                        if (!bt_aton(optarg, &srv.bdaddr)) {
@@ -111,6 +112,10 @@ main(int32_t argc, char *argv[])
                                usage();
                        break;
 
+               case 'u': /* enable evdev support */
+                       uinput = 1;
+                       break;
+
                case 'h':
                default:
                        usage();
@@ -158,6 +163,8 @@ main(int32_t argc, char *argv[])
            server_init(&srv) < 0 || write_pid_file(pid_file) < 0)
                exit(1);
 
+       srv.uinput = uinput;
+
        for (done = 0; !done; ) {
                if (elapsed(tval))
                        client_rescan(&srv);
@@ -263,6 +270,7 @@ usage(void)
 "      -h              display this message\n" \
 "      -p file         specify PID file name\n" \
 "      -t tval         specify client rescan interval (sec)\n" \
+"      -u              enable evdev protocol support\n" \
 "", BTHIDD_IDENT);
        exit(255);
 }

Modified: head/usr.sbin/bluetooth/bthidd/bthidd.h
==============================================================================
--- head/usr.sbin/bluetooth/bthidd/bthidd.h     Mon Apr 30 10:49:29 2018        
(r333112)
+++ head/usr.sbin/bluetooth/bthidd/bthidd.h     Mon Apr 30 12:16:54 2018        
(r333113)
@@ -48,6 +48,7 @@ struct bthid_server
        int32_t                          ctrl;   /* control channel (listen) */
        int32_t                          intr;   /* intr. channel (listen) */
        int32_t                          maxfd;  /* max fd in sets */
+       int32_t                          uinput; /* enable evdev support */
        fd_set                           rfdset; /* read descriptor set */
        fd_set                           wfdset; /* write descriptor set */
        LIST_HEAD(, bthid_session)       sessions;
@@ -63,6 +64,10 @@ struct bthid_session
        int32_t                          intr;  /* interrupt channel */
        int32_t                          vkbd;  /* virual keyboard */
        void                            *ctx;   /* product specific dev state */
+       int32_t                          ukbd;  /* evdev user input */
+       int32_t                          umouse;/* evdev user input */
+       int32_t                          obutt; /* previous mouse buttons */
+       int32_t                          consk; /* last consumer page key */
        bdaddr_t                         bdaddr;/* remote bdaddr */
        uint16_t                         state; /* session state */
 #define CLOSED 0
@@ -87,6 +92,7 @@ int32_t               client_connect   (bthid_server_p srv, 
int fd)
 bthid_session_p        session_open     (bthid_server_p srv, hid_device_p 
const d);
 bthid_session_p        session_by_bdaddr(bthid_server_p srv, bdaddr_p bdaddr);
 bthid_session_p        session_by_fd    (bthid_server_p srv, int32_t fd);
+int32_t                session_run      (bthid_session_p s);
 void           session_close    (bthid_session_p s);
 
 void           hid_initialise   (bthid_session_p s);

Added: head/usr.sbin/bluetooth/bthidd/btuinput.c
==============================================================================
--- /dev/null   00:00:00 1970   (empty, because file is newly added)
+++ head/usr.sbin/bluetooth/bthidd/btuinput.c   Mon Apr 30 12:16:54 2018        
(r333113)
@@ -0,0 +1,618 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2015-2017 Vladimir Kondratyev <w...@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.
+ *
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/kbio.h>
+#include <sys/sysctl.h>
+
+#include <dev/evdev/input.h>
+#include <dev/evdev/uinput.h>
+#include <dev/usb/usb.h>
+#include <dev/usb/usbhid.h>
+
+#include <assert.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <syslog.h>
+#include <time.h>
+#include <unistd.h>
+#include <usbhid.h>
+
+#include "bthid_config.h"
+#include "bthidd.h"
+#include "btuinput.h"
+
+static int16_t const mbuttons[8] = {
+       BTN_LEFT,
+       BTN_MIDDLE,
+       BTN_RIGHT,
+       BTN_SIDE,
+       BTN_EXTRA,
+       BTN_FORWARD,
+       BTN_BACK,
+       BTN_TASK
+};
+
+static uint16_t const led_codes[3] = {
+       LED_CAPSL,      /* CLKED */
+       LED_NUML,       /* NLKED */
+       LED_SCROLLL,    /* SLKED */
+};
+
+#define        NONE    KEY_RESERVED
+
+static uint16_t const keymap[0x100] = {
+       /* 0x00 - 0x27 */
+       NONE,   NONE,   NONE,   NONE,   KEY_A,  KEY_B,  KEY_C,  KEY_D,
+       KEY_E,  KEY_F,  KEY_G,  KEY_H,  KEY_I,  KEY_J,  KEY_K,  KEY_L,
+       KEY_M,  KEY_N,  KEY_O,  KEY_P,  KEY_Q,  KEY_R,  KEY_S,  KEY_T,
+       KEY_U,  KEY_V,  KEY_W,  KEY_X,  KEY_Y,  KEY_Z,  KEY_1,  KEY_2,
+       KEY_3,  KEY_4,  KEY_5,  KEY_6,  KEY_7,  KEY_8,  KEY_9,  KEY_0,
+       /* 0x28 - 0x3f */
+       KEY_ENTER,      KEY_ESC,        KEY_BACKSPACE,  KEY_TAB,
+       KEY_SPACE,      KEY_MINUS,      KEY_EQUAL,      KEY_LEFTBRACE,
+       KEY_RIGHTBRACE, KEY_BACKSLASH,  KEY_BACKSLASH,  KEY_SEMICOLON,
+       KEY_APOSTROPHE, KEY_GRAVE,      KEY_COMMA,      KEY_DOT,
+       KEY_SLASH,      KEY_CAPSLOCK,   KEY_F1,         KEY_F2,
+       KEY_F3,         KEY_F4,         KEY_F5,         KEY_F6,
+       /* 0x40 - 0x5f */
+       KEY_F7,         KEY_F8,         KEY_F9,         KEY_F10,
+       KEY_F11,        KEY_F12,        KEY_SYSRQ,      KEY_SCROLLLOCK,
+       KEY_PAUSE,      KEY_INSERT,     KEY_HOME,       KEY_PAGEUP,
+       KEY_DELETE,     KEY_END,        KEY_PAGEDOWN,   KEY_RIGHT,
+       KEY_LEFT,       KEY_DOWN,       KEY_UP,         KEY_NUMLOCK,
+       KEY_KPSLASH,    KEY_KPASTERISK, KEY_KPMINUS,    KEY_KPPLUS,
+       KEY_KPENTER,    KEY_KP1,        KEY_KP2,        KEY_KP3,
+       KEY_KP4,        KEY_KP5,        KEY_KP6,        KEY_KP7,
+       /* 0x60 - 0x7f */
+       KEY_KP8,        KEY_KP9,        KEY_KP0,        KEY_KPDOT,
+       KEY_102ND,      KEY_COMPOSE,    KEY_POWER,      KEY_KPEQUAL,
+       KEY_F13,        KEY_F14,        KEY_F15,        KEY_F16,
+       KEY_F17,        KEY_F18,        KEY_F19,        KEY_F20,
+       KEY_F21,        KEY_F22,        KEY_F23,        KEY_F24,
+       KEY_OPEN,       KEY_HELP,       KEY_PROPS,      KEY_FRONT,
+       KEY_STOP,       KEY_AGAIN,      KEY_UNDO,       KEY_CUT,
+       KEY_COPY,       KEY_PASTE,      KEY_FIND,       KEY_MUTE,
+       /* 0x80 - 0x9f */
+       KEY_VOLUMEUP,   KEY_VOLUMEDOWN, NONE,           NONE,
+       NONE,           KEY_KPCOMMA,    NONE,           KEY_RO,
+       KEY_KATAKANAHIRAGANA,   KEY_YEN,KEY_HENKAN,     KEY_MUHENKAN,
+       KEY_KPJPCOMMA,  NONE,           NONE,           NONE,
+       KEY_HANGEUL,    KEY_HANJA,      KEY_KATAKANA,   KEY_HIRAGANA,
+       KEY_ZENKAKUHANKAKU,     NONE,   NONE,           NONE,
+       NONE,           NONE,           NONE,           NONE,
+       NONE,           NONE,           NONE,           NONE,
+       /* 0xa0 - 0xbf */
+       NONE,           NONE,           NONE,           NONE,
+       NONE,           NONE,           NONE,           NONE,
+       NONE,           NONE,           NONE,           NONE,
+       NONE,           NONE,           NONE,           NONE,
+       NONE,           NONE,           NONE,           NONE,
+       NONE,           NONE,           NONE,           NONE,
+       NONE,           NONE,           NONE,           NONE,
+       NONE,           NONE,           NONE,           NONE,
+       /* 0xc0 - 0xdf */
+       NONE,           NONE,           NONE,           NONE,
+       NONE,           NONE,           NONE,           NONE,
+       NONE,           NONE,           NONE,           NONE,
+       NONE,           NONE,           NONE,           NONE,
+       NONE,           NONE,           NONE,           NONE,
+       NONE,           NONE,           NONE,           NONE,
+       NONE,           NONE,           NONE,           NONE,
+       NONE,           NONE,           NONE,           NONE,
+       /* 0xe0 - 0xff */
+       KEY_LEFTCTRL,   KEY_LEFTSHIFT,  KEY_LEFTALT,    KEY_LEFTMETA,
+       KEY_RIGHTCTRL,  KEY_RIGHTSHIFT, KEY_RIGHTALT,   KEY_RIGHTMETA,
+       KEY_PLAYPAUSE,  KEY_STOPCD,     KEY_PREVIOUSSONG,KEY_NEXTSONG,
+       KEY_EJECTCD,    KEY_VOLUMEUP,   KEY_VOLUMEDOWN, KEY_MUTE,
+       KEY_WWW,        KEY_BACK,       KEY_FORWARD,    KEY_STOP,
+       KEY_FIND,       KEY_SCROLLUP,   KEY_SCROLLDOWN, KEY_EDIT,
+       KEY_SLEEP,      KEY_COFFEE,     KEY_REFRESH,    KEY_CALC,
+       NONE,           NONE,           NONE,           NONE,
+};
+
+/* Consumer page usage mapping */
+static uint16_t const consmap[0x300] = {
+       [0x030] = KEY_POWER,
+       [0x031] = KEY_RESTART,
+       [0x032] = KEY_SLEEP,
+       [0x034] = KEY_SLEEP,
+       [0x035] = KEY_KBDILLUMTOGGLE,
+       [0x036] = BTN_MISC,
+       [0x040] = KEY_MENU,
+       [0x041] = KEY_SELECT,
+       [0x042] = KEY_UP,
+       [0x043] = KEY_DOWN,
+       [0x044] = KEY_LEFT,
+       [0x045] = KEY_RIGHT,
+       [0x046] = KEY_ESC,
+       [0x047] = KEY_KPPLUS,
+       [0x048] = KEY_KPMINUS,
+       [0x060] = KEY_INFO,
+       [0x061] = KEY_SUBTITLE,
+       [0x063] = KEY_VCR,
+       [0x065] = KEY_CAMERA,
+       [0x069] = KEY_RED,
+       [0x06a] = KEY_GREEN,
+       [0x06b] = KEY_BLUE,
+       [0x06c] = KEY_YELLOW,
+       [0x06d] = KEY_ZOOM,
+       [0x06f] = KEY_BRIGHTNESSUP,
+       [0x070] = KEY_BRIGHTNESSDOWN,
+       [0x072] = KEY_BRIGHTNESS_TOGGLE,
+       [0x073] = KEY_BRIGHTNESS_MIN,
+       [0x074] = KEY_BRIGHTNESS_MAX,
+       [0x075] = KEY_BRIGHTNESS_AUTO,
+       [0x082] = KEY_VIDEO_NEXT,
+       [0x083] = KEY_LAST,
+       [0x084] = KEY_ENTER,
+       [0x088] = KEY_PC,
+       [0x089] = KEY_TV,
+       [0x08a] = KEY_WWW,
+       [0x08b] = KEY_DVD,
+       [0x08c] = KEY_PHONE,
+       [0x08d] = KEY_PROGRAM,
+       [0x08e] = KEY_VIDEOPHONE,
+       [0x08f] = KEY_GAMES,
+       [0x090] = KEY_MEMO,
+       [0x091] = KEY_CD,
+       [0x092] = KEY_VCR,
+       [0x093] = KEY_TUNER,
+       [0x094] = KEY_EXIT,
+       [0x095] = KEY_HELP,
+       [0x096] = KEY_TAPE,
+       [0x097] = KEY_TV2,
+       [0x098] = KEY_SAT,
+       [0x09a] = KEY_PVR,
+       [0x09c] = KEY_CHANNELUP,
+       [0x09d] = KEY_CHANNELDOWN,
+       [0x0a0] = KEY_VCR2,
+       [0x0b0] = KEY_PLAY,
+       [0x0b1] = KEY_PAUSE,
+       [0x0b2] = KEY_RECORD,
+       [0x0b3] = KEY_FASTFORWARD,
+       [0x0b4] = KEY_REWIND,
+       [0x0b5] = KEY_NEXTSONG,
+       [0x0b6] = KEY_PREVIOUSSONG,
+       [0x0b7] = KEY_STOPCD,
+       [0x0b8] = KEY_EJECTCD,
+       [0x0bc] = KEY_MEDIA_REPEAT,
+       [0x0b9] = KEY_SHUFFLE,
+       [0x0bf] = KEY_SLOW,
+       [0x0cd] = KEY_PLAYPAUSE,
+       [0x0cf] = KEY_VOICECOMMAND,
+       [0x0e2] = KEY_MUTE,
+       [0x0e5] = KEY_BASSBOOST,
+       [0x0e9] = KEY_VOLUMEUP,
+       [0x0ea] = KEY_VOLUMEDOWN,
+       [0x0f5] = KEY_SLOW,
+       [0x181] = KEY_BUTTONCONFIG,
+       [0x182] = KEY_BOOKMARKS,
+       [0x183] = KEY_CONFIG,
+       [0x184] = KEY_WORDPROCESSOR,
+       [0x185] = KEY_EDITOR,
+       [0x186] = KEY_SPREADSHEET,
+       [0x187] = KEY_GRAPHICSEDITOR,
+       [0x188] = KEY_PRESENTATION,
+       [0x189] = KEY_DATABASE,
+       [0x18a] = KEY_MAIL,
+       [0x18b] = KEY_NEWS,
+       [0x18c] = KEY_VOICEMAIL,
+       [0x18d] = KEY_ADDRESSBOOK,
+       [0x18e] = KEY_CALENDAR,
+       [0x18f] = KEY_TASKMANAGER,
+       [0x190] = KEY_JOURNAL,
+       [0x191] = KEY_FINANCE,
+       [0x192] = KEY_CALC,
+       [0x193] = KEY_PLAYER,
+       [0x194] = KEY_FILE,
+       [0x196] = KEY_WWW,
+       [0x199] = KEY_CHAT,
+       [0x19c] = KEY_LOGOFF,
+       [0x19e] = KEY_COFFEE,
+       [0x19f] = KEY_CONTROLPANEL,
+       [0x1a2] = KEY_APPSELECT,
+       [0x1a3] = KEY_NEXT,
+       [0x1a4] = KEY_PREVIOUS,
+       [0x1a6] = KEY_HELP,
+       [0x1a7] = KEY_DOCUMENTS,
+       [0x1ab] = KEY_SPELLCHECK,
+       [0x1ae] = KEY_KEYBOARD,
+       [0x1b1] = KEY_SCREENSAVER,
+       [0x1b4] = KEY_FILE,
+       [0x1b6] = KEY_IMAGES,
+       [0x1b7] = KEY_AUDIO,
+       [0x1b8] = KEY_VIDEO,
+       [0x1bc] = KEY_MESSENGER,
+       [0x1bd] = KEY_INFO,
+       [0x201] = KEY_NEW,
+       [0x202] = KEY_OPEN,
+       [0x203] = KEY_CLOSE,
+       [0x204] = KEY_EXIT,
+       [0x207] = KEY_SAVE,
+       [0x208] = KEY_PRINT,
+       [0x209] = KEY_PROPS,
+       [0x21a] = KEY_UNDO,
+       [0x21b] = KEY_COPY,
+       [0x21c] = KEY_CUT,
+       [0x21d] = KEY_PASTE,
+       [0x21f] = KEY_FIND,
+       [0x221] = KEY_SEARCH,
+       [0x222] = KEY_GOTO,
+       [0x223] = KEY_HOMEPAGE,
+       [0x224] = KEY_BACK,
+       [0x225] = KEY_FORWARD,
+       [0x226] = KEY_STOP,
+       [0x227] = KEY_REFRESH,
+       [0x22a] = KEY_BOOKMARKS,
+       [0x22d] = KEY_ZOOMIN,
+       [0x22e] = KEY_ZOOMOUT,
+       [0x22f] = KEY_ZOOMRESET,
+       [0x233] = KEY_SCROLLUP,
+       [0x234] = KEY_SCROLLDOWN,
+       [0x23d] = KEY_EDIT,
+       [0x25f] = KEY_CANCEL,
+       [0x269] = KEY_INSERT,
+       [0x26a] = KEY_DELETE,
+       [0x279] = KEY_REDO,
+       [0x289] = KEY_REPLY,
+       [0x28b] = KEY_FORWARDMAIL,
+       [0x28c] = KEY_SEND,
+       [0x2c7] = KEY_KBDINPUTASSIST_PREV,
+       [0x2c8] = KEY_KBDINPUTASSIST_NEXT,
+       [0x2c9] = KEY_KBDINPUTASSIST_PREVGROUP,
+       [0x2ca] = KEY_KBDINPUTASSIST_NEXTGROUP,
+       [0x2cb] = KEY_KBDINPUTASSIST_ACCEPT,
+       [0x2cc] = KEY_KBDINPUTASSIST_CANCEL,
+};
+
+static int32_t
+uinput_open_common(hid_device_p const p, bdaddr_p local, const uint8_t *name)
+{
+       struct uinput_setup     uisetup;
+       uint8_t                 phys[UINPUT_MAX_NAME_SIZE];
+       uint8_t                 uniq[UINPUT_MAX_NAME_SIZE];
+       int32_t                 fd;
+
+       /* Take local and remote bdaddr */
+       bt_ntoa(local, phys);
+       bt_ntoa(&p->bdaddr, uniq);
+
+       /* Take device name from bthidd.conf. Fallback to generic name. */
+       if (p->name != NULL)
+               name = p->name;
+
+       /* Set device name and bus/vendor information */
+       memset(&uisetup, 0, sizeof(uisetup));
+       snprintf(uisetup.name, UINPUT_MAX_NAME_SIZE,
+           "%s, bdaddr %s", name, uniq);
+       uisetup.id.bustype = BUS_BLUETOOTH;
+       uisetup.id.vendor  = p->vendor_id;
+       uisetup.id.product = p->product_id;
+       uisetup.id.version = p->version;
+
+       fd = open("/dev/uinput", O_RDWR | O_NONBLOCK);
+
+       if (ioctl(fd, UI_SET_PHYS, phys) < 0 ||
+           ioctl(fd, UI_SET_BSDUNIQ, uniq) < 0 ||
+           ioctl(fd, UI_DEV_SETUP, &uisetup) < 0)
+               return (-1);
+
+       return (fd);
+}
+
+/*
+ * Setup uinput device as 8button mouse with wheel(s)
+ * TODO: bring in more feature detection code from ums
+ */
+int32_t
+uinput_open_mouse(hid_device_p const p, bdaddr_p local)
+{
+       size_t  i;
+       int32_t fd;
+
+       assert(p != NULL);
+
+       if ((fd = uinput_open_common(p, local, "Bluetooth Mouse")) < 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 ||
+           (p->has_wheel && ioctl(fd, UI_SET_RELBIT, REL_WHEEL) < 0) ||
+           (p->has_hwheel && ioctl(fd, UI_SET_RELBIT, REL_HWHEEL) < 0) ||
+           ioctl(fd, UI_SET_PROPBIT, INPUT_PROP_POINTER) < 0)
+               goto bail_out;
+
+       /* Advertise mouse buttons */
+       for (i = 0; i < nitems(mbuttons); i++)
+               if (ioctl(fd, UI_SET_KEYBIT, mbuttons[i]) < 0)
+                       goto bail_out;
+
+       if (ioctl(fd, UI_DEV_CREATE) >= 0)
+               return (fd); /* SUCCESS */
+
+bail_out:
+       if (fd >= 0)
+               close(fd);
+       return (-1);
+}
+
+/*
+ * Setup uinput keyboard
+ */
+int32_t
+uinput_open_keyboard(hid_device_p const p, bdaddr_p local)
+{
+       size_t  i;
+       int32_t fd;
+
+       assert(p != NULL);
+
+       if ((fd = uinput_open_common(p, local, "Bluetooth Keyboard")) < 0)
+               goto bail_out;
+
+       /* Advertise key events and LEDs */
+       if (ioctl(fd, UI_SET_EVBIT, EV_KEY) < 0 ||
+           ioctl(fd, UI_SET_EVBIT, EV_LED) < 0 ||
+           ioctl(fd, UI_SET_EVBIT, EV_SYN) < 0 ||
+           ioctl(fd, UI_SET_EVBIT, EV_REP) < 0 ||
+           ioctl(fd, UI_SET_LEDBIT, LED_CAPSL) < 0 ||
+           ioctl(fd, UI_SET_LEDBIT, LED_NUML) < 0 ||
+           ioctl(fd, UI_SET_LEDBIT, LED_SCROLLL))
+               goto bail_out;
+
+       /* Advertise keycodes */
+       for (i = 0; i < nitems(keymap); i++)
+               if (keymap[i] != NONE &&
+                   ioctl(fd, UI_SET_KEYBIT, keymap[i]) < 0)
+                       goto bail_out;
+
+       /* Advertise consumer page keys if any */
+       if (p->has_cons) {
+               for (i = 0; i < nitems(consmap); i++) {
+                       if (consmap[i] != NONE &&
+                           ioctl(fd, UI_SET_KEYBIT, consmap[i]) < 0)
+                               goto bail_out;
+               }
+       }
+
+       if (ioctl(fd, UI_DEV_CREATE) >= 0)
+               return (fd); /* SUCCESS */
+
+bail_out:
+       if (fd >= 0)
+               close(fd);
+       return (-1);
+}
+
+/* from sys/dev/evdev/evdev.h */
+#define        EVDEV_RCPT_HW_MOUSE     (1<<2)
+#define        EVDEV_RCPT_HW_KBD       (1<<3)
+
+#define        MASK_POLL_INTERVAL      5 /* seconds */
+#define        MASK_SYSCTL             "kern.evdev.rcpt_mask"
+
+static int32_t
+uinput_get_rcpt_mask(void)
+{
+       static struct timespec last = { 0, 0 };
+       struct timespec now;
+       static int32_t mask = 0;
+       size_t len;
+       time_t elapsed;
+
+       if (clock_gettime(CLOCK_MONOTONIC_FAST, &now) == -1)
+               return mask;
+
+       elapsed = now.tv_sec - last.tv_sec;
+       if (now.tv_nsec < last.tv_nsec)
+               elapsed--;
+
+       if (elapsed >= MASK_POLL_INTERVAL) {
+               len = sizeof(mask);
+               if (sysctlbyname(MASK_SYSCTL, &mask, &len, NULL, 0) < 0) {
+                       if (errno == ENOENT)
+                               /* kernel is compiled w/o EVDEV_SUPPORT */
+                               mask = EVDEV_RCPT_HW_MOUSE | EVDEV_RCPT_HW_KBD;
+                       else
+                               mask = 0;
+               }
+               last = now;
+       }
+       return mask;
+}
+
+static int32_t
+uinput_write_event(int32_t fd, uint16_t type, uint16_t code, int32_t value)
+{
+       struct input_event ie;
+
+       assert(fd >= 0);
+
+       memset(&ie, 0, sizeof(ie));
+       ie.type = type;
+       ie.code = code;
+       ie.value = value;
+       return (write(fd, &ie, sizeof(ie)));
+}
+
+int32_t
+uinput_rep_mouse(int32_t fd, int32_t x, int32_t y, int32_t z, int32_t t,
+    int32_t buttons, int32_t obuttons)
+{
+       size_t i;
+       int32_t rcpt_mask, mask;
+
+       assert(fd >= 0);
+
+       rcpt_mask = uinput_get_rcpt_mask();
+       if (!(rcpt_mask & EVDEV_RCPT_HW_MOUSE))
+               return (0);
+
+       if ((x != 0 && uinput_write_event(fd, EV_REL, REL_X, x) < 0) ||
+           (y != 0 && uinput_write_event(fd, EV_REL, REL_Y, y) < 0) ||
+           (z != 0 && uinput_write_event(fd, EV_REL, REL_WHEEL, -z) < 0) ||
+           (t != 0 && uinput_write_event(fd, EV_REL, REL_HWHEEL, t) < 0))
+               return (-1);
+
+       for (i = 0; i < nitems(mbuttons); i++) {
+               mask = 1 << i;
+               if ((buttons & mask) == (obuttons & mask))
+                       continue;
+               if (uinput_write_event(fd, EV_KEY, mbuttons[i],
+                   (buttons & mask) != 0) < 0)
+                       return (-1);
+       }
+
+       if (uinput_write_event(fd, EV_SYN, SYN_REPORT, 0) < 0)
+               return (-1);
+
+       return (0);
+}
+
+/*
+ * Translate and report keyboard page key events
+ */
+int32_t
+uinput_rep_key(int32_t fd, int32_t key, int32_t make)
+{
+       int32_t rcpt_mask;
+
+       assert(fd >= 0);
+
+       rcpt_mask = uinput_get_rcpt_mask();
+       if (!(rcpt_mask & EVDEV_RCPT_HW_KBD))
+               return (0);
+
+       if (key >= 0 && key < (int32_t)nitems(keymap) &&
+           keymap[key] != NONE) {
+               if (uinput_write_event(fd, EV_KEY, keymap[key], make) > 0 &&
+                   uinput_write_event(fd, EV_SYN, SYN_REPORT, 0) > 0)
+                       return (0);
+       }
+       return (-1);
+}
+
+/*
+ * Translate and report consumer page key events
+ */
+int32_t
+uinput_rep_cons(int32_t fd, int32_t key, int32_t make)
+{
+       int32_t rcpt_mask;
+
+       assert(fd >= 0);
+
+       rcpt_mask = uinput_get_rcpt_mask();
+       if (!(rcpt_mask & EVDEV_RCPT_HW_KBD))
+               return (0);
+
+       if (key >= 0 && key < (int32_t)nitems(consmap) &&
+           consmap[key] != NONE) {
+               if (uinput_write_event(fd, EV_KEY, consmap[key], make) > 0 &&
+                   uinput_write_event(fd, EV_SYN, SYN_REPORT, 0) > 0)
+                       return (0);
+       }
+       return (-1);
+}
+
+/*
+ * Translate and report LED events
+ */
+int32_t
+uinput_rep_leds(int32_t fd, int state, int mask)
+{
+       size_t i;
+       int32_t rcpt_mask;
+
+       assert(fd >= 0);
+
+       rcpt_mask = uinput_get_rcpt_mask();
+       if (!(rcpt_mask & EVDEV_RCPT_HW_KBD))
+               return (0);
+
+       for (i = 0; i < nitems(led_codes); i++) {
+               if (mask & (1 << i) &&
+                   uinput_write_event(fd, EV_LED, led_codes[i],
+                                       state & (1 << i) ? 1 : 0) < 0)
+                       return (-1);
+       }
+
+       return (0);
+}
+
+/*
+ * Process status change from evdev
+ */
+int32_t
+uinput_kbd_status_changed(bthid_session_p s, uint8_t *data, int32_t len)
+{
+       struct input_event ie;
+       int32_t leds, oleds;
+       size_t i;
+
+       assert(s != NULL);
+       assert(s->vkbd >= 0);
+       assert(len == sizeof(struct input_event));
+
+       memcpy(&ie, data, sizeof(ie));
+       switch (ie.type) {
+       case EV_LED:
+               ioctl(s->vkbd, KDGETLED, &oleds);
+               leds = oleds;
+               for (i = 0; i < nitems(led_codes); i++) {
+                       if (led_codes[i] == ie.code) {
+                               if (ie.value)
+                                       leds |= 1 << i;
+                               else
+                                       leds &= ~(1 << i);
+                               if (leds != oleds)
+                                       ioctl(s->vkbd, KDSETLED, leds);
+                               break;
+                       }
+               }
+               break;
+       case EV_REP:
+               /* FALLTHROUGH. Repeats are handled by evdev subsystem */
+       default:
+               break;
+       }
+
+       return (0);
+}

Added: head/usr.sbin/bluetooth/bthidd/btuinput.h
==============================================================================
--- /dev/null   00:00:00 1970   (empty, because file is newly added)
+++ head/usr.sbin/bluetooth/bthidd/btuinput.h   Mon Apr 30 12:16:54 2018        
(r333113)
@@ -0,0 +1,44 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2015-2017 Vladimir Kondratyev <w...@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.
+ *
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _UINPUT_H_
+#define _UINPUT_H_
+
+int32_t uinput_open_mouse(hid_device_p const d, bdaddr_p local);
+int32_t uinput_open_keyboard(hid_device_p const d, bdaddr_p local);
+int32_t uinput_rep_mouse(int32_t fd, int32_t x, int32_t y, int32_t z,
+    int32_t t, int32_t buttons, int32_t obuttons);
+int32_t uinput_rep_key(int32_t fd, int32_t key, int32_t make);
+int32_t uinput_rep_cons(int32_t fd, int32_t key, int32_t make);
+int32_t uinput_rep_leds(int32_t fd, int state, int mask);
+int32_t uinput_kbd_status_changed(bthid_session_p s, uint8_t *data,
+    int32_t len);
+
+#endif /* ndef _UINPUT_H_ */

Modified: head/usr.sbin/bluetooth/bthidd/client.c
==============================================================================
--- head/usr.sbin/bluetooth/bthidd/client.c     Mon Apr 30 10:49:29 2018        
(r333112)
+++ head/usr.sbin/bluetooth/bthidd/client.c     Mon Apr 30 12:16:54 2018        
(r333113)
@@ -188,14 +188,11 @@ client_connect(bthid_server_p srv, int32_t fd)
                s->state = OPEN;
                connect_in_progress = 0;
 
-               /* Register session's vkbd descriptor (if any) for read */
-               if (s->state == OPEN && d->keyboard) {
-                       assert(s->vkbd != -1);
-
-                       FD_SET(s->vkbd, &srv->rfdset);
-                       if (s->vkbd > srv->maxfd)
-                               srv->maxfd = s->vkbd;
-               }
+               /* Create kbd/mouse after both channels are established */
+               if (session_run(s) < 0) {
+                       session_close(s);
+                       return (-1);
+               }
                break;
 
        default:

Modified: head/usr.sbin/bluetooth/bthidd/hid.c
==============================================================================
--- head/usr.sbin/bluetooth/bthidd/hid.c        Mon Apr 30 10:49:29 2018        
(r333112)
+++ head/usr.sbin/bluetooth/bthidd/hid.c        Mon Apr 30 12:16:54 2018        
(r333113)
@@ -50,6 +50,7 @@
 #include <usbhid.h>
 #include "bthid_config.h"
 #include "bthidd.h"
+#include "btuinput.h"
 #include "kbd.h"
 
 /*
@@ -280,6 +281,19 @@ hid_interrupt(bthid_session_p s, uint8_t *data, int32_
                        break;
 
                case HUP_CONSUMER:
+                       if (hid_device->keyboard && s->srv->uinput) {
+                               if (h.flags & HIO_VARIABLE) {
+                                       uinput_rep_cons(s->ukbd, usage, !!val);
+                               } else {
+                                       if (s->consk > 0)
+                                               uinput_rep_cons(s->ukbd,
+                                                   s->consk, 0);
+                                       if (uinput_rep_cons(s->ukbd, val, 1)
+                                           == 0)
+                                               s->consk = val;
+                               }
+                       }
+
                        if (!val)
                                break;
 
@@ -551,6 +565,14 @@ check_middle_button:
                        syslog(LOG_ERR, "Could not process mouse events from " \
                                "%s. %s (%d)", bt_ntoa(&s->bdaddr, NULL),
                                strerror(errno), errno);
+
+               if (hid_device->mouse && s->srv->uinput &&
+                   uinput_rep_mouse(s->umouse, mouse_x, mouse_y, mouse_z,
+                                       mouse_t, mouse_butt, s->obutt) < 0)
+                       syslog(LOG_ERR, "Could not process mouse events from " \
+                               "%s. %s (%d)", bt_ntoa(&s->bdaddr, NULL),
+                               strerror(errno), errno);
+               s->obutt = mouse_butt;
        }
 
        return (0);

Modified: head/usr.sbin/bluetooth/bthidd/kbd.c
==============================================================================
--- head/usr.sbin/bluetooth/bthidd/kbd.c        Mon Apr 30 10:49:29 2018        
(r333112)
+++ head/usr.sbin/bluetooth/bthidd/kbd.c        Mon Apr 30 12:16:54 2018        
(r333113)
@@ -56,10 +56,12 @@
 #include <usbhid.h>
 #include "bthid_config.h"
 #include "bthidd.h"
+#include "btuinput.h"
 #include "kbd.h"
 
 static void    kbd_write(bitstr_t *m, int32_t fb, int32_t make, int32_t fd);
 static int32_t kbd_xlate(int32_t code, int32_t make, int32_t *b, int32_t const 
*eob);
+static void    uinput_kbd_write(bitstr_t *m, int32_t fb, int32_t make, int32_t 
fd);
 
 /*
  * HID code to PS/2 set 1 code translation table.
@@ -354,6 +356,7 @@ kbd_process_keys(bthid_session_p s)
                if (f2 != -1) {
                        /* release old keys */
                        kbd_write(s->keys2, f2, 0, s->vkbd);
+                       uinput_kbd_write(s->keys2, f2, 0, s->ukbd);
                        memset(s->keys2, 0, bitstr_size(xsize));
                }
 
@@ -366,6 +369,7 @@ kbd_process_keys(bthid_session_p s)
                
                memcpy(s->keys2, s->keys1, bitstr_size(xsize));
                kbd_write(s->keys1, f1, 1, s->vkbd);
+               uinput_kbd_write(s->keys1, f1, 1, s->ukbd);
                memset(s->keys1, 0, bitstr_size(xsize));
 
                return (0);
@@ -393,12 +397,15 @@ kbd_process_keys(bthid_session_p s)
        }
 
        bit_ffs(diff, xsize, &f2);
-       if (f2 > 0)
+       if (f2 > 0) {
                kbd_write(diff, f2, 0, s->vkbd);
+               uinput_kbd_write(diff, f2, 0, s->ukbd);
+       }
 
        bit_ffs(s->keys1, xsize, &f1);
        if (f1 > 0) {
                kbd_write(s->keys1, f1, 1, s->vkbd);
+               uinput_kbd_write(s->keys1, f1, 1, s->ukbd);
                memset(s->keys1, 0, bitstr_size(xsize));
        }
 
@@ -407,6 +414,22 @@ kbd_process_keys(bthid_session_p s)
 
 /*
  * Translate given keymap and write keyscodes
+ */
+void
+uinput_kbd_write(bitstr_t *m, int32_t fb, int32_t make, int32_t fd)
+{
+       int32_t i;
+
+       if (fd >= 0) {
+               for (i = fb; i < xsize; i++) {
+                       if (bit_test(m, i))
+                               uinput_rep_key(fd, i, make);
+               }
+       }
+}
+
+/*
+ * Translate given keymap and write keyscodes
  */ 
 
 static void
@@ -520,6 +543,7 @@ kbd_status_changed(bthid_session_p s, uint8_t *data, i
        hid_device_p    hid_device;
        hid_data_t      d;
        hid_item_t      h;
+       uint8_t         leds_mask = 0;
 
        assert(s != NULL);
        assert(len == sizeof(vkbd_status_t));
@@ -553,16 +577,19 @@ kbd_status_changed(bthid_session_p s, uint8_t *data, i
                        case 0x01: /* Num Lock LED */

*** DIFF OUTPUT TRUNCATED AT 1000 LINES ***
_______________________________________________
svn-src-head@freebsd.org mailing list
https://lists.freebsd.org/mailman/listinfo/svn-src-head
To unsubscribe, send any mail to "svn-src-head-unsubscr...@freebsd.org"

Reply via email to