/* $NetBSD: wskbd.c,v 1.146 2025/04/07 11:25:42 hans Exp $ */
/*
* Copyright (c) 1996, 1997 Christopher G. Demetriou. 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 Christopher G. Demetriou
* for the NetBSD Project.
* 4. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission
*
* 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.
*/
/*
* Copyright (c) 1998 The NetBSD Foundation, Inc.
* All rights reserved.
*
* Keysym translator contributed to The NetBSD Foundation by
* Juergen Hannken-Illjes.
*
* 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
*/
/*
* Copyright (c) 1992, 1993
* The Regents of the University of California. All rights reserved.
*
* This software was developed by the Computer Systems Engineering group
* at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and
* contributed to Berkeley.
*
* All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Lawrence Berkeley Laboratory.
*
* 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. Neither the name of the University 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 REGENTS 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 REGENTS 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.
*
* @(#)kbd.c 8.2 (Berkeley) 10/30/93
*/
/*
* Keyboard driver (/dev/wskbd*). Translates incoming bytes to ASCII or
* to `wscons_events' and passes them up to the appropriate reader.
*/
if (match->wskbddevcf_console != WSKBDDEVCF_CONSOLE_UNK) {
/*
* If console-ness of device specified, either match
* exactly (at high priority), or fail.
*/
if (match->wskbddevcf_console != 0 && ap->console != 0)
return (10);
else
return (0);
}
/* If console-ness unspecified, it wins. */
return (1);
}
if (!pmf_device_register(self, wskbd_suspend, NULL))
aprint_error_dev(self, "couldn't establish power handler\n");
else if (!pmf_class_input_register(self))
aprint_error_dev(self, "couldn't register as input device\n");
}
if (act == DVACT_DEACTIVATE)
sc->sc_dying = 1;
return (0);
}
/*
* Detach a keyboard. To keep track of users of the softc we keep
* a reference count that's incremented while inside, e.g., read.
* If the keyboard is active and the reference count is > 0 (0 is the
* normal state) we post an event and then wait for the process
* that had the reference to wake us up again. Then we blow away the
* vnode and return (which will deallocate the softc).
*/
int
wskbd_detach(device_t self, int flags)
{
struct wskbd_softc *sc = device_private(self);
struct wseventvar *evar;
int maj, mn;
int s;
if (sc->sc_repeating) {
sc->sc_repeating = 0;
callout_stop(&sc->sc_repeat_ch);
}
device_active(dev, DVA_HARDWARE);
#if NWSDISPLAY > 0
/*
* If /dev/wskbdN is not connected in event mode translate and
* send upstream.
*/
if (sc->sc_translating) {
num = wskbd_translate(sc->id, type, value);
if (num > 0) {
if (sc->sc_base.me_dispdv != NULL) {
#ifdef WSDISPLAY_SCROLLSUPPORT
if (sc->id->t_symbols [0] != KS_Print_Screen) {
wsdisplay_scroll(sc->sc_base.
me_dispdv, WSDISPLAY_SCROLL_RESET);
}
#endif
for (i = 0; i < num; i++)
wsdisplay_kbdinput(
sc->sc_base.me_dispdv,
sc->id->t_symbols[i]);
}
/*
* Keyboard is generating events. Turn this keystroke into an
* event and put it in the queue. If the queue is full, the
* keystroke is lost (sorry!).
*/
static void
wskbd_deliver_event(struct wskbd_softc *sc, u_int type, int value)
{
struct wseventvar *evar;
struct wscons_event event;
evar = sc->sc_base.me_evp;
if (evar == NULL || evar->q == NULL) {
DPRINTF(("wskbd_input: not open\n"));
return;
}
if (sc->sc_base.me_dispdv != NULL)
for (i = 0; i < len; i++)
wsdisplay_kbdinput(sc->sc_base.me_dispdv, tbuf[i]);
/* this is KS_GROUP_Plain */
#endif
}
#endif /* WSDISPLAY_COMPAT_RAWKBD */
#if NWSDISPLAY > 0
static void
wskbd_holdscreen(struct wskbd_softc *sc, int hold)
{
int new_state;
static int
wskbd_enable(struct wskbd_softc *sc, int on)
{
int error;
#if 0
/* I don't understand the purpose of this code. And it seems to
* break things, so it's out. -- Lennart
*/
if (!on && (!sc->sc_translating
#if NWSDISPLAY > 0
|| sc->sc_base.me_dispdv
#endif
))
return (EBUSY);
#endif
#if NWSDISPLAY > 0
if (sc->sc_base.me_dispdv != NULL)
return (0);
#endif
/* Always cancel auto repeat when fiddling with the kbd. */
if (sc->sc_repeating) {
sc->sc_repeating = 0;
callout_stop(&sc->sc_repeat_ch);
}
if ((flags & (FREAD | FWRITE)) == FWRITE)
/* Not opening for read, only ioctl is available. */
return (0);
#if NWSMUX > 0
if (sc->sc_base.me_parent != NULL) {
/* Grab the keyboard out of the greedy hands of the mux. */
DPRINTF(("wskbdopen: detach\n"));
wsmux_detach_sc(&sc->sc_base);
}
#endif
int
wskbdioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l)
{
return (wskbd_do_ioctl(device_lookup(&wskbd_cd, minor(dev)),
cmd, data, flag,l));
}
/* A wrapper around the ioctl() workhorse to make reference counting easy. */
int
wskbd_do_ioctl(device_t dv, u_long cmd, void *data, int flag,
struct lwp *l)
{
struct wskbd_softc *sc = device_private(dv);
int error;
int
wskbd_do_ioctl_sc(struct wskbd_softc *sc, u_long cmd, void *data, int flag,
struct lwp *l)
{
/*
* Try the generic ioctls that the wskbd interface supports.
*/
switch (cmd) {
case FIONBIO: /* we will remove this someday (soon???) */
return (0);
case FIOASYNC:
if (sc->sc_base.me_evp == NULL)
return (EINVAL);
sc->sc_base.me_evp->async = *(int *)data != 0;
return (0);
case FIOSETOWN:
if (sc->sc_base.me_evp == NULL)
return (EINVAL);
if (-*(int *)data != sc->sc_base.me_evp->io->p_pgid
&& *(int *)data != sc->sc_base.me_evp->io->p_pid)
return (EPERM);
return (0);
case TIOCSPGRP:
if (sc->sc_base.me_evp == NULL)
return (EINVAL);
if (*(int *)data != sc->sc_base.me_evp->io->p_pgid)
return (EPERM);
return (0);
}
/*
* Try the keyboard driver for WSKBDIO ioctls. It returns EPASSTHROUGH
* if it didn't recognize the request.
*/
return (wskbd_displayioctl(sc->sc_base.me_dv, cmd, data, flag, l));
}
/*
* WSKBDIO ioctls, handled in both emulation mode and in ``raw'' mode.
* Some of these have no real effect in raw mode, however.
*/
static int
wskbd_displayioctl(device_t dev, u_long cmd, void *data, int flag,
struct lwp *l)
{
#ifdef WSDISPLAY_SCROLLSUPPORT
struct wskbd_scroll_data *usdp, *ksdp;
#endif
struct wskbd_softc *sc = device_private(dev);
struct wskbd_bell_data *ubdp, *kbdp;
struct wskbd_keyrepeat_data *ukdp, *kkdp;
struct wskbd_map_data *umdp;
struct wskbd_mapdata md;
kbd_t enc;
void *tbuf;
int len, error;
switch (cmd) {
case WSKBDIO_BELL:
if ((flag & FWRITE) == 0)
return (EACCES);
return ((*sc->sc_accessops->ioctl)(sc->sc_accesscookie,
WSKBDIO_COMPLEXBELL, (void *)&sc->sc_bell_data, flag, l));
if (displaydv)
aprint_verbose_dev(sc->sc_base.me_dv, "connecting to %s\n",
device_xname(displaydv));
else
aprint_verbose_dev(sc->sc_base.me_dv, "disconnecting from %s\n",
device_xname(odisplaydv));
return (0);
}
#endif /* NWSDISPLAY > 0 */
#if NWSMUX > 0
int
wskbd_add_mux(int unit, struct wsmux_softc *muxsc)
{
struct wskbd_softc *sc = device_lookup_private(&wskbd_cd, unit);
if (sc == NULL)
return (ENXIO);
if (sc->sc_base.me_parent != NULL || sc->sc_base.me_evp != NULL)
return (EBUSY);
/*
* Console interface.
*/
int
wskbd_cngetc(dev_t dev)
{
static int num = 0;
static int pos;
u_int type;
int data;
keysym_t ks;
if (!wskbd_console_initted)
return -1;
if (wskbd_console_device != NULL &&
!wskbd_console_device->sc_translating)
return -1;
for(;;) {
if (num-- > 0) {
ks = wskbd_console_data.t_symbols[pos++];
if (KS_GROUP(ks) == KS_GROUP_Plain)
return (KS_VALUE(ks));
} else {
(*wskbd_console_data.t_consops->getc)
(wskbd_console_data.t_consaccesscookie,
&type, &data);
if (type == 0) {
/* No data returned */
return -1;
}
if (type == WSCONS_EVENT_ASCII) {
/*
* We assume that when the driver falls back
* to deliver pure ASCII it is in a state that
* it can not track press/release events
* reliable - so we clear all previously
* accumulated modifier state.
*/
wskbd_console_data.t_modifiers = 0;
return(data);
}
num = wskbd_translate(&wskbd_console_data, type, data);
pos = 0;
}
}
}
void
wskbd_cnpollc(dev_t dev, int poll)
{
if (!wskbd_console_initted)
return;
if (wskbd_console_device != NULL &&
!wskbd_console_device->sc_translating)
return;
if (sc != NULL) {
if (sc->sc_hotkey != NULL)
ishotkey = sc->sc_hotkey(sc, sc->sc_hotkeycookie,
type, value);
if (ishotkey)
return 0;
if (value < 0 || value >= sc->sc_maplen) {
#ifdef DEBUG
printf("%s: keycode %d out of range\n",
__func__, value);
#endif
return (0);
}
kp = sc->sc_map + value;
} else {
kp = &kpbuf;
wskbd_get_mapentry(id->t_keymap, value, kp);
}
/* if this key has a command, process it first */
if (sc != NULL && kp->command != KS_voidSymbol)
iscommand = internal_command(sc, &type, kp->command,
kp->group1[0]);
/* Now update modifiers */
switch (kp->group1[0]) {
case KS_Shift_L:
update_modifier(id, type, 0, MOD_SHIFT_L);
break;
case KS_Shift_R:
update_modifier(id, type, 0, MOD_SHIFT_R);
break;
case KS_Shift_Lock:
update_modifier(id, type, 1, MOD_SHIFTLOCK);
break;
case KS_Caps_Lock:
update_modifier(id, type, 1, MOD_CAPSLOCK);
break;
case KS_Control_L:
update_modifier(id, type, 0, MOD_CONTROL_L);
break;
case KS_Control_R:
update_modifier(id, type, 0, MOD_CONTROL_R);
break;
case KS_Alt_L:
update_modifier(id, type, 0, MOD_META_L);
break;
case KS_Alt_R:
update_modifier(id, type, 0, MOD_META_R);
break;
case KS_Mode_switch:
update_modifier(id, type, 0, MOD_MODESHIFT);
break;
case KS_Num_Lock:
update_modifier(id, type, 1, MOD_NUMLOCK);
break;
/* Process compose sequence and dead accents */
res = KS_voidSymbol;
switch (KS_GROUP(ksym)) {
case KS_GROUP_Plain:
case KS_GROUP_Keypad:
case KS_GROUP_Function:
res = ksym;
break;
case KS_GROUP_Mod:
if (ksym == KS_Multi_key) {
update_modifier(id, 1, 0, MOD_COMPOSE);
id->t_composelen = 2;
}
break;
case KS_GROUP_Dead:
if (id->t_composelen == 0) {
update_modifier(id, 1, 0, MOD_COMPOSE);
id->t_composelen = 1;
id->t_composebuf[0] = ksym;
} else
res = ksym;
break;
}
if (res == KS_voidSymbol) {
update_leds(id);
return (0);
}
if (id->t_composelen > 0) {
id->t_composebuf[2 - id->t_composelen] = res;
if (--id->t_composelen == 0) {
res = wskbd_compose_value(id->t_composebuf);
update_modifier(id, 0, 0, MOD_COMPOSE);
} else {
return (0);
}
}
update_leds(id);
/* We are done, return the symbol */
if (KS_GROUP(res) == KS_GROUP_Plain) {
if (MOD_ONESET(id, MOD_ANYCONTROL)) {
if ((res >= KS_at && res <= KS_z) || res == KS_space)
res = res & 0x1f;
else if (res == KS_2)
res = 0x00;
else if (res >= KS_3 && res <= KS_7)
res = KS_Escape + (res - KS_3);
else if (res == KS_8)
res = KS_Delete;
/* convert CTL-/ to ^_ as xterm does (undo in emacs) */
else if (res == KS_slash)
res = KS_underscore & 0x1f;
}
if (MOD_ONESET(id, MOD_ANYMETA)) {
if (id->t_flags & WSKFL_METAESC) {
id->t_symbols[0] = KS_Escape;
id->t_symbols[1] = res;
return (2);
} else
res |= 0x80;
}
}