Edit

IABSD.fr/src/sys/dev/wscons/wsdisplay.c

Branch :

  • Show log

    Commit

  • Author : deraadt
    Date : 2026-04-17 06:18:19
    Hash : 572068f1
    Message : Some mapchar emulops require a question mark character, so don't permit loading if that is missing (bounded by firstchar and numchars). An AI triage report made a hastly conclusion there were bigger problems here but Miod figures it is just this ? problem. diff from miod report from Bruce Dang of Calif.io

  • sys/dev/wscons/wsdisplay.c
  • /* $OpenBSD: wsdisplay.c,v 1.156 2026/04/17 06:18:19 deraadt Exp $ */
    /* $NetBSD: wsdisplay.c,v 1.82 2005/02/27 00:27:52 perry 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.
     */
    
    #include <sys/param.h>
    #include <sys/conf.h>
    #include <sys/device.h>
    #include <sys/ioctl.h>
    #include <sys/kernel.h>
    #include <sys/malloc.h>
    #include <sys/syslog.h>
    #include <sys/systm.h>
    #include <sys/task.h>
    #include <sys/tty.h>
    #include <sys/signalvar.h>
    #include <sys/errno.h>
    #include <sys/fcntl.h>
    #include <sys/vnode.h>
    #include <sys/timeout.h>
    
    #include <dev/wscons/wscons_features.h>
    #include <dev/wscons/wsconsio.h>
    #include <dev/wscons/wsdisplayvar.h>
    #include <dev/wscons/wsksymvar.h>
    #include <dev/wscons/wsksymdef.h>
    #include <dev/wscons/wsemulvar.h>
    #include <dev/wscons/wscons_callbacks.h>
    #include <dev/cons.h>
    
    #include "wsdisplay.h"
    #include "wskbd.h"
    #include "wsmux.h"
    
    #if NWSKBD > 0
    #include <dev/wscons/wseventvar.h>
    #include <dev/wscons/wsmuxvar.h>
    #endif
    
    #ifdef DDB
    #include <ddb/db_output.h>
    #endif
    
    #include "wsmoused.h"
    
    struct wsscreen_internal {
    	const struct wsdisplay_emulops *emulops;
    	void	*emulcookie;
    
    	const struct wsscreen_descr *scrdata;
    
    	const struct wsemul_ops *wsemul;
    	void	*wsemulcookie;
    };
    
    struct wsscreen {
    	struct wsscreen_internal *scr_dconf;
    
    	struct task	scr_emulbell_task;
    
    	struct tty *scr_tty;
    	int	scr_hold_screen;		/* hold tty output */
    
    	int scr_flags;
    #define SCR_OPEN 1		/* is it open? */
    #define SCR_WAITACTIVE 2	/* someone waiting on activation */
    #define SCR_GRAPHICS 4		/* graphics mode, no text (emulation) output */
    #define	SCR_DUMBFB 8		/* in use as dumb fb (iff SCR_GRAPHICS) */
    
    #ifdef WSDISPLAY_COMPAT_USL
    	const struct wscons_syncops *scr_syncops;
    	void *scr_synccookie;
    #endif
    
    #ifdef WSDISPLAY_COMPAT_RAWKBD
    	int scr_rawkbd;
    #endif
    
    	struct wsdisplay_softc *sc;
    
    #ifdef HAVE_WSMOUSED_SUPPORT
    	/* mouse console support via wsmoused(8) */
    	u_int mouse;		/* mouse cursor position */
    	u_int cursor;		/* selection cursor position (if
    				   different from mouse cursor pos) */
    	u_int cpy_start;	/* position of the copy start mark*/
    	u_int cpy_end;		/* position of the copy end mark */
    	u_int orig_start;	/* position of the original sel. start*/
    	u_int orig_end;		/* position of the original sel. end */
    
    	u_int mouse_flags;	/* flags, status of the mouse */
    #define MOUSE_VISIBLE	0x01	/* flag, the mouse cursor is visible */
    #define SEL_EXISTS	0x02	/* flag, a selection exists */
    #define SEL_IN_PROGRESS 0x04	/* flag, a selection is in progress */
    #define SEL_EXT_AFTER	0x08	/* flag, selection is extended after */
    #define BLANK_TO_EOL	0x10	/* flag, there are only blanks
    				   characters to eol */
    #define SEL_BY_CHAR	0x20	/* flag, select character by character*/
    #define SEL_BY_WORD	0x40	/* flag, select word by word */
    #define SEL_BY_LINE	0x80	/* flag, select line by line */
    
    #define IS_MOUSE_VISIBLE(scr)	((scr)->mouse_flags & MOUSE_VISIBLE)
    #define IS_SEL_EXISTS(scr)	((scr)->mouse_flags & SEL_EXISTS)
    #define IS_SEL_IN_PROGRESS(scr)	((scr)->mouse_flags & SEL_IN_PROGRESS)
    #define IS_SEL_EXT_AFTER(scr)	((scr)->mouse_flags & SEL_EXT_AFTER)
    #define IS_BLANK_TO_EOL(scr)	((scr)->mouse_flags & BLANK_TO_EOL)
    #define IS_SEL_BY_CHAR(scr)	((scr)->mouse_flags & SEL_BY_CHAR)
    #define IS_SEL_BY_WORD(scr)	((scr)->mouse_flags & SEL_BY_WORD)
    #define IS_SEL_BY_LINE(scr)	((scr)->mouse_flags & SEL_BY_LINE)
    #endif	/* HAVE_WSMOUSED_SUPPORT */
    };
    
    struct wsscreen *wsscreen_attach(struct wsdisplay_softc *, int, const char *,
    	    const struct wsscreen_descr *, void *, int, int, uint32_t);
    void	wsscreen_detach(struct wsscreen *);
    int	wsdisplay_addscreen(struct wsdisplay_softc *, int, const char *,
    	    const char *);
    int	wsdisplay_getscreen(struct wsdisplay_softc *,
    	    struct wsdisplay_addscreendata *);
    void	wsdisplay_resume_device(struct device *);
    void	wsdisplay_suspend_device(struct device *);
    void	wsdisplay_addscreen_print(struct wsdisplay_softc *, int, int);
    void	wsdisplay_closescreen(struct wsdisplay_softc *, struct wsscreen *);
    int	wsdisplay_delscreen(struct wsdisplay_softc *, int, int);
    int	wsdisplay_driver_ioctl(struct wsdisplay_softc *, u_long, caddr_t,
    	    int, struct proc *);
    
    void	wsdisplay_burner_setup(struct wsdisplay_softc *, struct wsscreen *);
    void	wsdisplay_burner(void *v);
    
    struct wsdisplay_softc {
    	struct device sc_dv;
    
    	const struct wsdisplay_accessops *sc_accessops;
    	void	*sc_accesscookie;
    
    	const struct wsscreen_list *sc_scrdata;
    
    	struct wsscreen *sc_scr[WSDISPLAY_MAXSCREEN];
    	int sc_focusidx;	/* available only if sc_focus isn't null */
    	struct wsscreen *sc_focus;
    
    	struct taskq *sc_taskq;
    
    #ifdef HAVE_BURNER_SUPPORT
    	struct timeout sc_burner;
    	int	sc_burnoutintvl;	/* delay before blanking (ms) */
    	int	sc_burninintvl;		/* delay before unblanking (ms) */
    	int	sc_burnout;		/* current sc_burner delay (ms) */
    	int	sc_burnman;		/* nonzero if screen blanked */
    	int	sc_burnflags;
    #endif
    
    	int	sc_isconsole;
    
    	int sc_flags;
    #define SC_SWITCHPENDING	0x01
    #define	SC_PASTE_AVAIL		0x02
    	int sc_screenwanted, sc_oldscreen; /* valid with SC_SWITCHPENDING */
    	int sc_resumescreen; /* if set, can't switch until resume. */
    
    #if NWSKBD > 0
    	struct wsevsrc *sc_input;
    #ifdef WSDISPLAY_COMPAT_RAWKBD
    	int sc_rawkbd;
    #endif
    #endif /* NWSKBD > 0 */
    
    #ifdef HAVE_WSMOUSED_SUPPORT
    	char *sc_copybuffer;
    	u_int sc_copybuffer_size;
    #endif
    };
    
    extern struct cfdriver wsdisplay_cd;
    
    /* Autoconfiguration definitions. */
    int	wsdisplay_match(struct device *, void *, void *);
    void	wsdisplay_attach(struct device *, struct device *, void *);
    int	wsdisplay_detach(struct device *, int);
    
    int	wsdisplay_activate(struct device *, int);
    
    void	wsdisplay_emulbell_task(void *);
    
    struct cfdriver wsdisplay_cd = {
    	NULL, "wsdisplay", DV_TTY
    };
    
    const struct cfattach wsdisplay_ca = {
    	sizeof(struct wsdisplay_softc), wsdisplay_match,
    	wsdisplay_attach, wsdisplay_detach, wsdisplay_activate
    };
    
    void	wsdisplaystart(struct tty *);
    int	wsdisplayparam(struct tty *, struct termios *);
    
    /* Internal macros, functions, and variables. */
    #define	WSDISPLAYUNIT(dev)		(minor(dev) >> 8)
    #define	WSDISPLAYSCREEN(dev)		(minor(dev) & 0xff)
    #define ISWSDISPLAYCTL(dev)		(WSDISPLAYSCREEN(dev) == 255)
    #define WSDISPLAYMINOR(unit, screen)	(((unit) << 8) | (screen))
    
    #define	WSSCREEN_HAS_TTY(scr)		((scr)->scr_tty != NULL)
    
    void	wsdisplay_kbdholdscr(struct wsscreen *, int);
    
    #ifdef WSDISPLAY_COMPAT_RAWKBD
    int	wsdisplay_update_rawkbd(struct wsdisplay_softc *, struct wsscreen *);
    #endif
    
    int	wsdisplay_console_initted;
    struct wsdisplay_softc *wsdisplay_console_device;
    struct wsscreen_internal wsdisplay_console_conf;
    
    int	wsdisplay_getc_dummy(dev_t);
    void	wsdisplay_pollc(dev_t, int);
    
    int	wsdisplay_cons_pollmode;
    void	(*wsdisplay_cons_kbd_pollc)(dev_t, int);
    
    struct consdev wsdisplay_cons = {
    	NULL, NULL, wsdisplay_getc_dummy, wsdisplay_cnputc,
    	    wsdisplay_pollc, NULL, NODEV, CN_LOWPRI
    };
    
    /*
     * Function pointers for wsconsctl parameter handling.
     * These are used for firmware-provided display brightness control.
     */
    int	(*ws_get_param)(struct wsdisplay_param *);
    int	(*ws_set_param)(struct wsdisplay_param *);
    
    
    #ifndef WSDISPLAY_DEFAULTSCREENS
    #define WSDISPLAY_DEFAULTSCREENS	1
    #endif
    int	wsdisplay_defaultscreens = WSDISPLAY_DEFAULTSCREENS;
    
    int	wsdisplay_switch1(void *, int, int);
    int	wsdisplay_switch2(void *, int, int);
    int	wsdisplay_switch3(void *, int, int);
    
    int	wsdisplay_clearonclose;
    
    struct wsscreen *
    wsscreen_attach(struct wsdisplay_softc *sc, int console, const char *emul,
        const struct wsscreen_descr *type, void *cookie, int ccol, int crow,
        uint32_t defattr)
    {
    	struct wsscreen_internal *dconf;
    	struct wsscreen *scr;
    
    	scr = malloc(sizeof(*scr), M_DEVBUF, M_ZERO | M_NOWAIT);
    	if (!scr)
    		return (NULL);
    
    	if (console) {
    		dconf = &wsdisplay_console_conf;
    		/*
    		 * Tell the emulation about the callback argument.
    		 * The other stuff is already there.
    		 */
    		(void)(*dconf->wsemul->attach)(1, 0, 0, 0, 0, scr, 0);
    	} else { /* not console */
    		dconf = malloc(sizeof(*dconf), M_DEVBUF, M_NOWAIT);
    		if (dconf == NULL)
    			goto fail;
    		dconf->emulops = type->textops;
    		dconf->emulcookie = cookie;
    		if (dconf->emulops == NULL ||
    		    (dconf->wsemul = wsemul_pick(emul)) == NULL)
    			goto fail;
    		dconf->wsemulcookie = (*dconf->wsemul->attach)(0, type, cookie,
    		    ccol, crow, scr, defattr);
    		if (dconf->wsemulcookie == NULL)
    			goto fail;
    		dconf->scrdata = type;
    	}
    
    	task_set(&scr->scr_emulbell_task, wsdisplay_emulbell_task, scr);
    	scr->scr_dconf = dconf;
    	scr->scr_tty = ttymalloc(0);
    	scr->sc = sc;
    	return (scr);
    
    fail:
    	if (dconf != NULL)
    		free(dconf, M_DEVBUF, sizeof(*dconf));
    	free(scr, M_DEVBUF, sizeof(*scr));
    	return (NULL);
    }
    
    void
    wsscreen_detach(struct wsscreen *scr)
    {
    	int ccol, crow; /* XXX */
    
    	if (WSSCREEN_HAS_TTY(scr)) {
    		timeout_del(&scr->scr_tty->t_rstrt_to);
    		ttyfree(scr->scr_tty);
    	}
    	(*scr->scr_dconf->wsemul->detach)(scr->scr_dconf->wsemulcookie,
    	    &ccol, &crow);
    	taskq_del_barrier(scr->sc->sc_taskq, &scr->scr_emulbell_task);
    	free(scr->scr_dconf, M_DEVBUF, sizeof(*scr->scr_dconf));
    	free(scr, M_DEVBUF, sizeof(*scr));
    }
    
    const struct wsscreen_descr *
    wsdisplay_screentype_pick(const struct wsscreen_list *scrdata, const char *name)
    {
    	int i;
    	const struct wsscreen_descr *scr;
    
    	KASSERT(scrdata->nscreens > 0);
    
    	if (name == NULL || *name == '\0')
    		return (scrdata->screens[0]);
    
    	for (i = 0; i < scrdata->nscreens; i++) {
    		scr = scrdata->screens[i];
    		if (!strncmp(name, scr->name, WSSCREEN_NAME_SIZE))
    			return (scr);
    	}
    
    	return (0);
    }
    
    /*
     * print info about attached screen
     */
    void
    wsdisplay_addscreen_print(struct wsdisplay_softc *sc, int idx, int count)
    {
    	printf("%s: screen %d", sc->sc_dv.dv_xname, idx);
    	if (count > 1)
    		printf("-%d", idx + (count-1));
    	printf(" added (%s, %s emulation)\n",
    	    sc->sc_scr[idx]->scr_dconf->scrdata->name,
    	    sc->sc_scr[idx]->scr_dconf->wsemul->name);
    }
    
    int
    wsdisplay_addscreen(struct wsdisplay_softc *sc, int idx,
        const char *screentype, const char *emul)
    {
    	const struct wsscreen_descr *scrdesc;
    	int error;
    	void *cookie;
    	int ccol, crow;
    	uint32_t defattr;
    	struct wsscreen *scr;
    	int s;
    
    	if (idx < 0 || idx >= WSDISPLAY_MAXSCREEN)
    		return (EINVAL);
    	if (sc->sc_scr[idx] != NULL)
    		return (EBUSY);
    
    	scrdesc = wsdisplay_screentype_pick(sc->sc_scrdata, screentype);
    	if (!scrdesc)
    		return (ENXIO);
    	error = (*sc->sc_accessops->alloc_screen)(sc->sc_accesscookie,
    	    scrdesc, &cookie, &ccol, &crow, &defattr);
    	if (error)
    		return (error);
    
    	scr = wsscreen_attach(sc, 0, emul, scrdesc,
    	    cookie, ccol, crow, defattr);
    	if (scr == NULL) {
    		(*sc->sc_accessops->free_screen)(sc->sc_accesscookie, cookie);
    		return (ENXIO);
    	}
    
    	sc->sc_scr[idx] = scr;
    
    	/* if no screen has focus yet, activate the first we get */
    	s = spltty();
    	if (!sc->sc_focus) {
    		(*sc->sc_accessops->show_screen)(sc->sc_accesscookie,
    		    scr->scr_dconf->emulcookie, 0, 0, 0);
    		sc->sc_focusidx = idx;
    		sc->sc_focus = scr;
    	}
    	splx(s);
    
    #ifdef HAVE_WSMOUSED_SUPPORT
    	allocate_copybuffer(sc); /* enlarge the copy buffer if necessary */
    #endif
    	return (0);
    }
    
    int
    wsdisplay_getscreen(struct wsdisplay_softc *sc,
        struct wsdisplay_addscreendata *sd)
    {
    	struct wsscreen *scr;
    
    	if (sd->idx < 0 && sc->sc_focus)
    		sd->idx = sc->sc_focusidx;
    
    	if (sd->idx < 0 || sd->idx >= WSDISPLAY_MAXSCREEN)
    		return (EINVAL);
    
    	scr = sc->sc_scr[sd->idx];
    	if (scr == NULL)
    		return (ENXIO);
    
    	strlcpy(sd->screentype, scr->scr_dconf->scrdata->name,
    	    WSSCREEN_NAME_SIZE);
    	strlcpy(sd->emul, scr->scr_dconf->wsemul->name, WSEMUL_NAME_SIZE);
    
    	return (0);
    }
    
    void
    wsdisplay_closescreen(struct wsdisplay_softc *sc, struct wsscreen *scr)
    {
    	int maj, mn, idx;
    
    	/* hangup */
    	if (WSSCREEN_HAS_TTY(scr)) {
    		struct tty *tp = scr->scr_tty;
    		(*linesw[tp->t_line].l_modem)(tp, 0);
    	}
    
    	/* locate the major number */
    	for (maj = 0; maj < nchrdev; maj++)
    		if (cdevsw[maj].d_open == wsdisplayopen)
    			break;
    	/* locate the screen index */
    	for (idx = 0; idx < WSDISPLAY_MAXSCREEN; idx++)
    		if (scr == sc->sc_scr[idx])
    			break;
    #ifdef DIAGNOSTIC
    	if (idx == WSDISPLAY_MAXSCREEN)
    		panic("wsdisplay_forceclose: bad screen");
    #endif
    
    	/* nuke the vnodes */
    	mn = WSDISPLAYMINOR(sc->sc_dv.dv_unit, idx);
    	vdevgone(maj, mn, mn, VCHR);
    }
    
    int
    wsdisplay_delscreen(struct wsdisplay_softc *sc, int idx, int flags)
    {
    	struct wsscreen *scr;
    	int s;
    	void *cookie;
    
    	if (idx < 0 || idx >= WSDISPLAY_MAXSCREEN)
    		return (EINVAL);
    	if ((scr = sc->sc_scr[idx]) == NULL)
    		return (ENXIO);
    
    	if (scr->scr_dconf == &wsdisplay_console_conf ||
    #ifdef WSDISPLAY_COMPAT_USL
    	    scr->scr_syncops ||
    #endif
    	    ((scr->scr_flags & SCR_OPEN) && !(flags & WSDISPLAY_DELSCR_FORCE)))
    		return (EBUSY);
    
    	wsdisplay_closescreen(sc, scr);
    
    	/*
    	 * delete pointers, so neither device entries
    	 * nor keyboard input can reference it anymore
    	 */
    	s = spltty();
    	if (sc->sc_focus == scr) {
    		sc->sc_focus = NULL;
    #ifdef WSDISPLAY_COMPAT_RAWKBD
    		wsdisplay_update_rawkbd(sc, 0);
    #endif
    	}
    	sc->sc_scr[idx] = NULL;
    	splx(s);
    
    	/*
    	 * Wake up processes waiting for the screen to
    	 * be activated. Sleepers must check whether
    	 * the screen still exists.
    	 */
    	if (scr->scr_flags & SCR_WAITACTIVE)
    		wakeup(scr);
    
    	/* save a reference to the graphics screen */
    	cookie = scr->scr_dconf->emulcookie;
    
    	wsscreen_detach(scr);
    
    	(*sc->sc_accessops->free_screen)(sc->sc_accesscookie, cookie);
    
    	if ((flags & WSDISPLAY_DELSCR_QUIET) == 0)
    		printf("%s: screen %d deleted\n", sc->sc_dv.dv_xname, idx);
    	return (0);
    }
    
    /*
     * Autoconfiguration functions.
     */
    int
    wsdisplay_match(struct device *parent, void *match, void *aux)
    {
    	struct cfdata *cf = match;
    	struct wsemuldisplaydev_attach_args *ap = aux;
    
    	if (cf->wsemuldisplaydevcf_console != WSEMULDISPLAYDEVCF_CONSOLE_UNK) {
    		/*
    		 * If console-ness of device specified, either match
    		 * exactly (at high priority), or fail.
    		 */
    		if (cf->wsemuldisplaydevcf_console != 0 && ap->console != 0)
    			return (10);
    		else
    			return (0);
    	}
    
    	if (cf->wsemuldisplaydevcf_primary != WSEMULDISPLAYDEVCF_PRIMARY_UNK) {
    		/*
    		 * If primary-ness of device specified, either match
    		 * exactly (at high priority), or fail.
    		 */
    		if (cf->wsemuldisplaydevcf_primary != 0 && ap->primary != 0)
    			return (10);
    		else
    			return (0);
    	}
    
    	/* If console-ness and primary-ness unspecified, it wins. */
    	return (1);
    }
    
    int
    wsdisplay_activate(struct device *self, int act)
    {
    	int ret = 0;
    
    	switch (act) {
    	case DVACT_POWERDOWN:
    		wsdisplay_switchtoconsole();
    		break;
    	}
    
    	return (ret);
    }
    
    /*
     * Detach a display.
     */
    int
    wsdisplay_detach(struct device *self, int flags)
    {
    	struct wsdisplay_softc *sc = (struct wsdisplay_softc *)self;
    	int i;
    	int rc;
    
    	/* We don't support detaching the console display yet. */
    	if (sc->sc_isconsole)
    		return (EBUSY);
    
    	/* Delete all screens managed by this display */
    	for (i = 0; i < WSDISPLAY_MAXSCREEN; i++)
    		if (sc->sc_scr[i] != NULL) {
    			if ((rc = wsdisplay_delscreen(sc, i,
    			    WSDISPLAY_DELSCR_QUIET | (flags & DETACH_FORCE ?
    			     WSDISPLAY_DELSCR_FORCE : 0))) != 0)
    				return (rc);
    		}
    
    #ifdef HAVE_BURNER_SUPPORT
    	timeout_del(&sc->sc_burner);
    #endif
    
    #if NWSKBD > 0
    	if (sc->sc_input != NULL) {
    #if NWSMUX > 0
    		/*
    		 * If we are the display of the mux we are attached to,
    		 * disconnect all input devices from us.
    		 */
    		if (sc->sc_input->me_dispdv == &sc->sc_dv) {
    			if ((rc = wsmux_set_display((struct wsmux_softc *)
    						    sc->sc_input, NULL)) != 0)
    				return (rc);
    		}
    
    		/*
    		 * XXX
    		 * If we created a standalone mux (dmux), we should destroy it
    		 * there, but there is currently no support for this in wsmux.
    		 */
    #else
    		if ((rc = wskbd_set_display((struct device *)sc->sc_input,
    		    NULL)) != 0)
    			return (rc);
    #endif
    	}
    #endif
    
    	taskq_destroy(sc->sc_taskq);
    
    	return (0);
    }
    
    /* Print function (for parent devices). */
    int
    wsemuldisplaydevprint(void *aux, const char *pnp)
    {
    #if 0 /* -Wunused */
    	struct wsemuldisplaydev_attach_args *ap = aux;
    #endif
    
    	if (pnp)
    		printf("wsdisplay at %s", pnp);
    #if 0 /* don't bother; it's ugly */
    	printf(" console %d", ap->console);
    #endif
    
    	return (UNCONF);
    }
    
    /* Submatch function (for parent devices). */
    int
    wsemuldisplaydevsubmatch(struct device *parent, void *match, void *aux)
    {
    	extern struct cfdriver wsdisplay_cd;
    	struct cfdata *cf = match;
    
    	/* only allow wsdisplay to attach */
    	if (cf->cf_driver == &wsdisplay_cd)
    		return ((*cf->cf_attach->ca_match)(parent, match, aux));
    
    	return (0);
    }
    
    void
    wsdisplay_attach(struct device *parent, struct device *self, void *aux)
    {
    	struct wsdisplay_softc *sc = (struct wsdisplay_softc *)self;
    	struct wsemuldisplaydev_attach_args *ap = aux;
    	u_int defaultscreens = ap->defaultscreens;
    	int i, start = 0;
    #if NWSKBD > 0
    	struct wsevsrc *kme;
    #if NWSMUX > 0
    	int kbdmux = sc->sc_dv.dv_cfdata->wsemuldisplaydevcf_mux;
    	struct wsmux_softc *mux;
    
    	if (kbdmux >= 0)
    		mux = wsmux_getmux(kbdmux);
    	else
    		mux = wsmux_create("dmux", sc->sc_dv.dv_unit);
    	/* XXX panic()ing isn't nice, but attach cannot fail */
    	if (mux == NULL)
    		panic("wsdisplay_common_attach: no memory");
    	sc->sc_input = &mux->sc_base;
    
    	if (kbdmux >= 0)
    		printf(" mux %d", kbdmux);
    #else
    #if 0	/* not worth keeping, especially since the default value is not -1... */
    	if (kbdmux >= 0)
    		printf(" (mux ignored)");
    #endif
    #endif	/* NWSMUX > 0 */
    #endif	/* NWSKBD > 0 */
    
    	sc->sc_isconsole = ap->console;
    	sc->sc_resumescreen = WSDISPLAY_NULLSCREEN;
    
    	sc->sc_taskq = taskq_create(sc->sc_dv.dv_xname, 1, IPL_TTY, 0);
    
    	if (ap->console) {
    		KASSERT(wsdisplay_console_initted);
    		KASSERT(wsdisplay_console_device == NULL);
    
    		sc->sc_scr[0] = wsscreen_attach(sc, 1, 0, 0, 0, 0, 0, 0);
    		if (sc->sc_scr[0] == NULL)
    			return;
    		wsdisplay_console_device = sc;
    
    		printf(": console (%s, %s emulation)",
    		       wsdisplay_console_conf.scrdata->name,
    		       wsdisplay_console_conf.wsemul->name);
    
    #if NWSKBD > 0
    		kme = wskbd_set_console_display(&sc->sc_dv, sc->sc_input);
    		if (kme != NULL)
    			printf(", using %s", kme->me_dv.dv_xname);
    #if NWSMUX == 0
    		sc->sc_input = kme;
    #endif
    #endif
    
    		sc->sc_focusidx = 0;
    		sc->sc_focus = sc->sc_scr[0];
    		start = 1;
    	}
    	printf("\n");
    
    #if NWSKBD > 0 && NWSMUX > 0
    	/*
    	 * If this mux did not have a display device yet, volunteer for
    	 * the job.
    	 */
    	if (mux->sc_displaydv == NULL)
    		wsmux_set_display(mux, &sc->sc_dv);
    #endif
    
    	sc->sc_accessops = ap->accessops;
    	sc->sc_accesscookie = ap->accesscookie;
    	sc->sc_scrdata = ap->scrdata;
    
    	/*
    	 * Set up a number of virtual screens if wanted. The
    	 * WSDISPLAYIO_ADDSCREEN ioctl is more flexible, so this code
    	 * is for special cases like installation kernels, as well as
    	 * sane multihead defaults.
    	 */
    	if (defaultscreens == 0)
    		defaultscreens = wsdisplay_defaultscreens;
    	for (i = start; i < defaultscreens; i++) {
    		if (wsdisplay_addscreen(sc, i, 0, 0))
    			break;
    	}
    
    	if (i > start)
    		wsdisplay_addscreen_print(sc, start, i-start);
    
    #ifdef HAVE_BURNER_SUPPORT
    	sc->sc_burnoutintvl = WSDISPLAY_DEFBURNOUT_MSEC;
    	sc->sc_burninintvl = WSDISPLAY_DEFBURNIN_MSEC;
    	sc->sc_burnflags = WSDISPLAY_BURN_OUTPUT | WSDISPLAY_BURN_KBD |
    	    WSDISPLAY_BURN_MOUSE;
    	timeout_set(&sc->sc_burner, wsdisplay_burner, sc);
    	sc->sc_burnout = sc->sc_burnoutintvl;
    	wsdisplay_burn(sc, sc->sc_burnflags);
    #endif
    
    #if NWSKBD > 0 && NWSMUX == 0
    	if (ap->console == 0) {
    		/*
    		 * In the non-wsmux world, always connect wskbd0 and wsdisplay0
    		 * together.
    		 */
    		extern struct cfdriver wskbd_cd;
    
    		if (wskbd_cd.cd_ndevs != 0 && sc->sc_dv.dv_unit == 0) {
    			if (wsdisplay_set_kbd(&sc->sc_dv,
    			    (struct wsevsrc *)wskbd_cd.cd_devs[0]) == 0)
    				wskbd_set_display(wskbd_cd.cd_devs[0],
    				    &sc->sc_dv);
    		}
    	}
    #endif
    
    	if (ap->console && cn_tab == &wsdisplay_cons) {
    		int maj;
    
    		/* locate the major number */
    		for (maj = 0; maj < nchrdev; maj++)
    			if (cdevsw[maj].d_open == wsdisplayopen)
    				break;
    
    		cn_tab->cn_dev = makedev(maj, WSDISPLAYMINOR(self->dv_unit, 0));
    	}
    }
    
    void
    wsdisplay_cnattach(const struct wsscreen_descr *type, void *cookie, int ccol,
        int crow, uint32_t defattr)
    {
    	const struct wsemul_ops *wsemul;
    	const struct wsdisplay_emulops *emulops;
    
    	KASSERT(type->nrows > 0);
    	KASSERT(type->ncols > 0);
    	KASSERT(crow < type->nrows);
    	KASSERT(ccol < type->ncols);
    
    	wsdisplay_console_conf.emulops = emulops = type->textops;
    	wsdisplay_console_conf.emulcookie = cookie;
    	wsdisplay_console_conf.scrdata = type;
    
    #ifdef WSEMUL_DUMB
    	/*
    	 * If the emulops structure is crippled, force a dumb emulation.
    	 */
    	if (emulops->cursor == NULL ||
    	    emulops->copycols == NULL || emulops->copyrows == NULL ||
    	    emulops->erasecols == NULL || emulops->eraserows == NULL)
    		wsemul = wsemul_pick("dumb");
    	else
    #endif
    		wsemul = wsemul_pick("");
    	wsdisplay_console_conf.wsemul = wsemul;
    	wsdisplay_console_conf.wsemulcookie =
    	    (*wsemul->cnattach)(type, cookie, ccol, crow, defattr);
    
    	if (!wsdisplay_console_initted)
    		cn_tab = &wsdisplay_cons;
    
    	wsdisplay_console_initted = 1;
    
    #ifdef DDB
    	db_resize(type->ncols, type->nrows);
    #endif
    }
    
    /*
     * Tty and cdevsw functions.
     */
    int
    wsdisplayopen(dev_t dev, int flag, int mode, struct proc *p)
    {
    	struct wsdisplay_softc *sc;
    	struct tty *tp;
    	int unit, newopen, error;
    	struct wsscreen *scr;
    
    	unit = WSDISPLAYUNIT(dev);
    	if (unit >= wsdisplay_cd.cd_ndevs ||	/* make sure it was attached */
    	    (sc = wsdisplay_cd.cd_devs[unit]) == NULL)
    		return (ENXIO);
    
    	if (ISWSDISPLAYCTL(dev))
    		return (0);
    
    	if (WSDISPLAYSCREEN(dev) >= WSDISPLAY_MAXSCREEN)
    		return (ENXIO);
    	if ((scr = sc->sc_scr[WSDISPLAYSCREEN(dev)]) == NULL)
    		return (ENXIO);
    
    	if (WSSCREEN_HAS_TTY(scr)) {
    		tp = scr->scr_tty;
    		tp->t_oproc = wsdisplaystart;
    		tp->t_param = wsdisplayparam;
    		tp->t_dev = dev;
    		newopen = (tp->t_state & TS_ISOPEN) == 0;
    		if (newopen) {
    			ttychars(tp);
    			tp->t_iflag = TTYDEF_IFLAG;
    			tp->t_oflag = TTYDEF_OFLAG;
    			tp->t_cflag = TTYDEF_CFLAG;
    			tp->t_lflag = TTYDEF_LFLAG;
    			tp->t_ispeed = tp->t_ospeed = TTYDEF_SPEED;
    			wsdisplayparam(tp, &tp->t_termios);
    			ttsetwater(tp);
    		} else if ((tp->t_state & TS_XCLUDE) != 0 &&
    			   suser(p) != 0)
    			return (EBUSY);
    		tp->t_state |= TS_CARR_ON;
    
    		error = ((*linesw[tp->t_line].l_open)(dev, tp, p));
    		if (error)
    			return (error);
    
    		if (newopen) {
    			/* set window sizes as appropriate, and reset
    			   the emulation */
    			tp->t_winsize.ws_row = scr->scr_dconf->scrdata->nrows;
    			tp->t_winsize.ws_col = scr->scr_dconf->scrdata->ncols;
    		}
    	}
    
    	scr->scr_flags |= SCR_OPEN;
    	return (0);
    }
    
    int
    wsdisplayclose(dev_t dev, int flag, int mode, struct proc *p)
    {
    	struct wsdisplay_softc *sc;
    	struct tty *tp;
    	int unit;
    	struct wsscreen *scr;
    
    	unit = WSDISPLAYUNIT(dev);
    	sc = wsdisplay_cd.cd_devs[unit];
    
    	if (ISWSDISPLAYCTL(dev))
    		return (0);
    
    	if ((scr = sc->sc_scr[WSDISPLAYSCREEN(dev)]) == NULL)
    		return (ENXIO);
    
    	if (WSSCREEN_HAS_TTY(scr)) {
    		if (scr->scr_hold_screen) {
    			int s;
    
    			/* XXX RESET KEYBOARD LEDS, etc. */
    			s = spltty();	/* avoid conflict with keyboard */
    			wsdisplay_kbdholdscr(scr, 0);
    			splx(s);
    		}
    		tp = scr->scr_tty;
    		(*linesw[tp->t_line].l_close)(tp, flag, p);
    		ttyclose(tp);
    	}
    
    #ifdef WSDISPLAY_COMPAT_USL
    	if (scr->scr_syncops)
    		(*scr->scr_syncops->destroy)(scr->scr_synccookie);
    #endif
    
    	scr->scr_flags &= ~SCR_GRAPHICS;
    	(*scr->scr_dconf->wsemul->reset)(scr->scr_dconf->wsemulcookie,
    					 WSEMUL_RESET);
    	if (wsdisplay_clearonclose)
    		(*scr->scr_dconf->wsemul->reset)
    			(scr->scr_dconf->wsemulcookie, WSEMUL_CLEARSCREEN);
    
    #ifdef WSDISPLAY_COMPAT_RAWKBD
    	if (scr->scr_rawkbd) {
    		int kbmode = WSKBD_TRANSLATED;
    		(void) wsdisplay_internal_ioctl(sc, scr, WSKBDIO_SETMODE,
    		    (caddr_t)&kbmode, FWRITE, p);
    	}
    #endif
    
    	scr->scr_flags &= ~SCR_OPEN;
    
    #ifdef HAVE_WSMOUSED_SUPPORT
    	/* remove the selection at logout */
    	if (sc->sc_copybuffer != NULL)
    		explicit_bzero(sc->sc_copybuffer, sc->sc_copybuffer_size);
    	CLR(sc->sc_flags, SC_PASTE_AVAIL);
    #endif
    
    	return (0);
    }
    
    int
    wsdisplayread(dev_t dev, struct uio *uio, int flag)
    {
    	struct wsdisplay_softc *sc;
    	struct tty *tp;
    	int unit;
    	struct wsscreen *scr;
    
    	unit = WSDISPLAYUNIT(dev);
    	sc = wsdisplay_cd.cd_devs[unit];
    
    	if (ISWSDISPLAYCTL(dev))
    		return (0);
    
    	if ((scr = sc->sc_scr[WSDISPLAYSCREEN(dev)]) == NULL)
    		return (ENXIO);
    
    	if (!WSSCREEN_HAS_TTY(scr))
    		return (ENODEV);
    
    	tp = scr->scr_tty;
    	return ((*linesw[tp->t_line].l_read)(tp, uio, flag));
    }
    
    int
    wsdisplaywrite(dev_t dev, struct uio *uio, int flag)
    {
    	struct wsdisplay_softc *sc;
    	struct tty *tp;
    	int unit;
    	struct wsscreen *scr;
    
    	unit = WSDISPLAYUNIT(dev);
    	sc = wsdisplay_cd.cd_devs[unit];
    
    	if (ISWSDISPLAYCTL(dev))
    		return (0);
    
    	if ((scr = sc->sc_scr[WSDISPLAYSCREEN(dev)]) == NULL)
    		return (ENXIO);
    
    	if (!WSSCREEN_HAS_TTY(scr))
    		return (ENODEV);
    
    	tp = scr->scr_tty;
    	return ((*linesw[tp->t_line].l_write)(tp, uio, flag));
    }
    
    struct tty *
    wsdisplaytty(dev_t dev)
    {
    	struct wsdisplay_softc *sc;
    	int unit;
    	struct wsscreen *scr;
    
    	unit = WSDISPLAYUNIT(dev);
    	sc = wsdisplay_cd.cd_devs[unit];
    
    	if (ISWSDISPLAYCTL(dev))
    		panic("wsdisplaytty() on ctl device");
    
    	if ((scr = sc->sc_scr[WSDISPLAYSCREEN(dev)]) == NULL)
    		return (NULL);
    
    	return (scr->scr_tty);
    }
    
    int
    wsdisplayioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p)
    {
    	struct wsdisplay_softc *sc;
    	struct tty *tp;
    	int unit, error;
    	struct wsscreen *scr;
    
    	unit = WSDISPLAYUNIT(dev);
    	sc = wsdisplay_cd.cd_devs[unit];
    
    #ifdef WSDISPLAY_COMPAT_USL
    	error = wsdisplay_usl_ioctl1(sc, cmd, data, flag, p);
    	if (error >= 0)
    		return (error);
    #endif
    
    	if (ISWSDISPLAYCTL(dev)) {
    		switch (cmd) {
    		case WSDISPLAYIO_GTYPE:
    		case WSDISPLAYIO_GETSCREENTYPE:
    			/* pass to the first screen */
    			dev = makedev(major(dev), WSDISPLAYMINOR(unit, 0));
    			break;
    		default:
    			return (wsdisplay_cfg_ioctl(sc, cmd, data, flag, p));
    		}
    	}
    
    	if (WSDISPLAYSCREEN(dev) >= WSDISPLAY_MAXSCREEN)
    		return (ENODEV);
    
    	if ((scr = sc->sc_scr[WSDISPLAYSCREEN(dev)]) == NULL)
    		return (ENXIO);
    
    	if (WSSCREEN_HAS_TTY(scr)) {
    		tp = scr->scr_tty;
    
    /* printf("disc\n"); */
    		/* do the line discipline ioctls first */
    		error = (*linesw[tp->t_line].l_ioctl)(tp, cmd, data, flag, p);
    		if (error >= 0)
    			return (error);
    
    /* printf("tty\n"); */
    		/* then the tty ioctls */
    		error = ttioctl(tp, cmd, data, flag, p);
    		if (error >= 0)
    			return (error);
    	}
    
    #ifdef WSDISPLAY_COMPAT_USL
    	error = wsdisplay_usl_ioctl2(sc, scr, cmd, data, flag, p);
    	if (error >= 0)
    		return (error);
    #endif
    
    	error = wsdisplay_internal_ioctl(sc, scr, cmd, data, flag, p);
    	return (error != -1 ? error : ENOTTY);
    }
    
    int
    wsdisplay_param(struct device *dev, u_long cmd, struct wsdisplay_param *dp)
    {
    	struct wsdisplay_softc *sc = (struct wsdisplay_softc *)dev;
    	return wsdisplay_driver_ioctl(sc, cmd, (caddr_t)dp, 0, NULL);
    }
    
    int
    wsdisplay_internal_ioctl(struct wsdisplay_softc *sc, struct wsscreen *scr,
        u_long cmd, caddr_t data, int flag, struct proc *p)
    {
    	int error;
    
    #if NWSKBD > 0
    	struct wsevsrc *inp;
    
    #ifdef WSDISPLAY_COMPAT_RAWKBD
    	switch (cmd) {
    	case WSKBDIO_SETMODE:
    		if ((flag & FWRITE) == 0)
    			return (EACCES);
    		scr->scr_rawkbd = (*(int *)data == WSKBD_RAW);
    		return (wsdisplay_update_rawkbd(sc, scr));
    	case WSKBDIO_GETMODE:
    		*(int *)data = (scr->scr_rawkbd ?
    				WSKBD_RAW : WSKBD_TRANSLATED);
    		return (0);
    	}
    #endif
    	inp = sc->sc_input;
    	if (inp != NULL) {
    		error = wsevsrc_display_ioctl(inp, cmd, data, flag, p);
    		if (error >= 0)
    			return (error);
    	}
    #endif /* NWSKBD > 0 */
    
    	switch (cmd) {
    	case WSDISPLAYIO_SMODE:
    	case WSDISPLAYIO_USEFONT:
    #ifdef HAVE_BURNER_SUPPORT
    	case WSDISPLAYIO_SVIDEO:
    	case WSDISPLAYIO_SBURNER:
    #endif
    	case WSDISPLAYIO_SETSCREEN:
    		if ((flag & FWRITE) == 0)
    			return (EACCES);
    	}
    
    	switch (cmd) {
    	case WSDISPLAYIO_GMODE:
    		if (scr->scr_flags & SCR_GRAPHICS) {
    			if (scr->scr_flags & SCR_DUMBFB)
    				*(u_int *)data = WSDISPLAYIO_MODE_DUMBFB;
    			else
    				*(u_int *)data = WSDISPLAYIO_MODE_MAPPED;
    		} else
    			*(u_int *)data = WSDISPLAYIO_MODE_EMUL;
    		return (0);
    
    	case WSDISPLAYIO_SMODE:
    #define d (*(int *)data)
    		if (d != WSDISPLAYIO_MODE_EMUL &&
    		    d != WSDISPLAYIO_MODE_MAPPED &&
    		    d != WSDISPLAYIO_MODE_DUMBFB)
    			return (EINVAL);
    
    		scr->scr_flags &= ~SCR_GRAPHICS;
    		if (d == WSDISPLAYIO_MODE_MAPPED ||
    		    d == WSDISPLAYIO_MODE_DUMBFB) {
    			scr->scr_flags |= SCR_GRAPHICS |
    			    ((d == WSDISPLAYIO_MODE_DUMBFB) ?  SCR_DUMBFB : 0);
    
    			/* clear cursor */
    			(*scr->scr_dconf->wsemul->reset)
    			    (scr->scr_dconf->wsemulcookie, WSEMUL_CLEARCURSOR);
    		}
    
    #ifdef HAVE_BURNER_SUPPORT
    		wsdisplay_burner_setup(sc, scr);
    #endif
    
    		(void)(*sc->sc_accessops->ioctl)(sc->sc_accesscookie, cmd, data,
    		    flag, p);
    
    		return (0);
    #undef d
    
    	case WSDISPLAYIO_USEFONT:
    #define d ((struct wsdisplay_font *)data)
    		if (!sc->sc_accessops->load_font)
    			return (EINVAL);
    		d->data = NULL;
    		error = (*sc->sc_accessops->load_font)(sc->sc_accesscookie,
    		    scr->scr_dconf->emulcookie, d);
    		if (!error)
    			(*scr->scr_dconf->wsemul->reset)
    			    (scr->scr_dconf->wsemulcookie, WSEMUL_SYNCFONT);
    		return (error);
    #undef d
    #ifdef HAVE_BURNER_SUPPORT
    	case WSDISPLAYIO_GVIDEO:
    		*(u_int *)data = !sc->sc_burnman;
    		break;
    
    	case WSDISPLAYIO_SVIDEO:
    		if (*(u_int *)data != WSDISPLAYIO_VIDEO_OFF &&
    		    *(u_int *)data != WSDISPLAYIO_VIDEO_ON)
    			return (EINVAL);
    		if (sc->sc_accessops->burn_screen == NULL)
    			return (EOPNOTSUPP);
    		(*sc->sc_accessops->burn_screen)(sc->sc_accesscookie,
    		     *(u_int *)data, sc->sc_burnflags);
    		sc->sc_burnman = *(u_int *)data == WSDISPLAYIO_VIDEO_OFF;
    		break;
    
    	case WSDISPLAYIO_GBURNER:
    #define d ((struct wsdisplay_burner *)data)
    		d->on  = sc->sc_burninintvl;
    		d->off = sc->sc_burnoutintvl;
    		d->flags = sc->sc_burnflags;
    		return (0);
    
    	case WSDISPLAYIO_SBURNER:
    	    {
    		struct wsscreen *active;
    
    		if (d->flags & ~(WSDISPLAY_BURN_VBLANK | WSDISPLAY_BURN_KBD |
    		    WSDISPLAY_BURN_MOUSE | WSDISPLAY_BURN_OUTPUT))
    			return EINVAL;
    
    		error = 0;
    		sc->sc_burnflags = d->flags;
    		/* disable timeout if necessary */
    		if (d->off==0 || (sc->sc_burnflags & (WSDISPLAY_BURN_OUTPUT |
    		    WSDISPLAY_BURN_KBD | WSDISPLAY_BURN_MOUSE)) == 0) {
    			if (sc->sc_burnout)
    				timeout_del(&sc->sc_burner);
    		}
    
    		active = sc->sc_focus;
    		if (active == NULL)
    			active = scr;
    
    		if (d->on) {
    			sc->sc_burninintvl = d->on;
    			if (sc->sc_burnman) {
    				sc->sc_burnout = sc->sc_burninintvl;
    				/* reinit timeout if changed */
    				if ((active->scr_flags & SCR_GRAPHICS) == 0)
    					wsdisplay_burn(sc, sc->sc_burnflags);
    			}
    		}
    		sc->sc_burnoutintvl = d->off;
    		if (!sc->sc_burnman) {
    			sc->sc_burnout = sc->sc_burnoutintvl;
    			/* reinit timeout if changed */
    			if ((active->scr_flags & SCR_GRAPHICS) == 0)
    				wsdisplay_burn(sc, sc->sc_burnflags);
    		}
    		return (error);
    	    }
    #undef d
    #endif	/* HAVE_BURNER_SUPPORT */
    	case WSDISPLAYIO_GETSCREEN:
    		return (wsdisplay_getscreen(sc,
    		    (struct wsdisplay_addscreendata *)data));
    
    	case WSDISPLAYIO_SETSCREEN:
    		return (wsdisplay_switch((void *)sc, *(int *)data, 1));
    
    	case WSDISPLAYIO_GETSCREENTYPE:
    #define d ((struct wsdisplay_screentype *)data)
    		if (d->idx < 0 || d->idx >= sc->sc_scrdata->nscreens)
    			return(EINVAL);
    
    		d->nidx = sc->sc_scrdata->nscreens;
    		strlcpy(d->name, sc->sc_scrdata->screens[d->idx]->name,
    			WSSCREEN_NAME_SIZE);
    		d->ncols = sc->sc_scrdata->screens[d->idx]->ncols;
    		d->nrows = sc->sc_scrdata->screens[d->idx]->nrows;
    		d->fontwidth = sc->sc_scrdata->screens[d->idx]->fontwidth;
    		d->fontheight = sc->sc_scrdata->screens[d->idx]->fontheight;
    		return (0);
    #undef d
    	case WSDISPLAYIO_GETEMULTYPE:
    #define d ((struct wsdisplay_emultype *)data)
    		if (wsemul_getname(d->idx) == NULL)
    			return(EINVAL);
    		strlcpy(d->name, wsemul_getname(d->idx), WSEMUL_NAME_SIZE);
    		return (0);
    #undef d
            }
    
    	/* check ioctls for display */
    	return wsdisplay_driver_ioctl(sc, cmd, data, flag, p);
    }
    
    int
    wsdisplay_driver_ioctl(struct wsdisplay_softc *sc, u_long cmd, caddr_t data,
        int flag, struct proc *p)
    {
    	int error;
    
    	error = ((*sc->sc_accessops->ioctl)(sc->sc_accesscookie, cmd, data,
    	    flag, p));
    	/* Do not report parameters with empty ranges to userland. */
    	if (error == 0 && cmd == WSDISPLAYIO_GETPARAM) {
    		struct wsdisplay_param *dp = (struct wsdisplay_param *)data;
    		switch (dp->param) {
    		case WSDISPLAYIO_PARAM_BACKLIGHT:
    		case WSDISPLAYIO_PARAM_BRIGHTNESS:
    		case WSDISPLAYIO_PARAM_CONTRAST:
    			if (dp->min == dp->max)
    				error = ENOTTY;
    			break;
    		}
    	}
    
    	return error;
    }
    
    int
    wsdisplay_cfg_ioctl(struct wsdisplay_softc *sc, u_long cmd, caddr_t data,
        int flag, struct proc *p)
    {
    	int error;
    	void *buf;
    	size_t fontsz;
    #if NWSKBD > 0
    	struct wsevsrc *inp;
    #endif
    
    	switch (cmd) {
    #ifdef HAVE_WSMOUSED_SUPPORT
    	case WSDISPLAYIO_WSMOUSED:
    		error = wsmoused(sc, data, flag, p);
    		return (error);
    #endif
    	case WSDISPLAYIO_ADDSCREEN:
    #define d ((struct wsdisplay_addscreendata *)data)
    		if ((error = wsdisplay_addscreen(sc, d->idx,
    		    d->screentype, d->emul)) == 0)
    			wsdisplay_addscreen_print(sc, d->idx, 0);
    		return (error);
    #undef d
    	case WSDISPLAYIO_DELSCREEN:
    #define d ((struct wsdisplay_delscreendata *)data)
    		return (wsdisplay_delscreen(sc, d->idx, d->flags));
    #undef d
    	case WSDISPLAYIO_GETSCREEN:
    		return (wsdisplay_getscreen(sc,
    		    (struct wsdisplay_addscreendata *)data));
    	case WSDISPLAYIO_SETSCREEN:
    		return (wsdisplay_switch((void *)sc, *(int *)data, 1));
    	case WSDISPLAYIO_LDFONT:
    #define d ((struct wsdisplay_font *)data)
    		if (!sc->sc_accessops->load_font)
    			return (EINVAL);
    		if (d->fontheight > 64 || d->stride > 8) /* 64x64 pixels */
    			return (EINVAL);
    		if (d->numchars > 65536) /* unicode plane */
    			return (EINVAL);
    		/*
    		 * Some mapchar emulops, such as rasops_mapchar, require the
    		 * availability of the question mark character to use for
    		 * missing glyphs, so make sure it exists.
    		 */
    		if (d->firstchar > '?' || d->firstchar + d->numchars <= '?')
    			return (EINVAL);
    		fontsz = d->fontheight * d->stride * d->numchars;
    		if (fontsz > WSDISPLAY_MAXFONTSZ)
    			return (EINVAL);
    
    		buf = malloc(fontsz, M_DEVBUF, M_WAITOK);
    		error = copyin(d->data, buf, fontsz);
    		if (error) {
    			free(buf, M_DEVBUF, fontsz);
    			return (error);
    		}
    		d->data = buf;
    		error =
    		  (*sc->sc_accessops->load_font)(sc->sc_accesscookie, 0, d);
    		if (error)
    			free(buf, M_DEVBUF, fontsz);
    		return (error);
    
    	case WSDISPLAYIO_LSFONT:
    		if (!sc->sc_accessops->list_font)
    			return (EINVAL);
    		error =
    		  (*sc->sc_accessops->list_font)(sc->sc_accesscookie, d);
    		return (error);
    
    	case WSDISPLAYIO_DELFONT:
    		return (EINVAL);
    #undef d
    
    #if NWSKBD > 0
    	case WSMUXIO_ADD_DEVICE:
    #define d ((struct wsmux_device *)data)
    		if (d->idx == -1 && d->type == WSMUX_KBD)
    			d->idx = wskbd_pickfree();
    #undef d
    		/* FALLTHROUGH */
    	case WSMUXIO_INJECTEVENT:
    	case WSMUXIO_REMOVE_DEVICE:
    	case WSMUXIO_LIST_DEVICES:
    		inp = sc->sc_input;
    		if (inp == NULL)
    			return (ENXIO);
    		return (wsevsrc_ioctl(inp, cmd, data, flag,p));
    #endif /* NWSKBD > 0 */
    
    	}
    	return (EINVAL);
    }
    
    paddr_t
    wsdisplaymmap(dev_t dev, off_t offset, int prot)
    {
    	struct wsdisplay_softc *sc = wsdisplay_cd.cd_devs[WSDISPLAYUNIT(dev)];
    	struct wsscreen *scr;
    
    	if (ISWSDISPLAYCTL(dev))
    		return (-1);
    
    	if ((scr = sc->sc_scr[WSDISPLAYSCREEN(dev)]) == NULL)
    		return (-1);
    
    	if (!(scr->scr_flags & SCR_GRAPHICS))
    		return (-1);
    
    	/* pass mmap to display */
    	return ((*sc->sc_accessops->mmap)(sc->sc_accesscookie, offset, prot));
    }
    
    int
    wsdisplaykqfilter(dev_t dev, struct knote *kn)
    {
    	struct wsdisplay_softc *sc = wsdisplay_cd.cd_devs[WSDISPLAYUNIT(dev)];
    	struct wsscreen *scr;
    
    	if (ISWSDISPLAYCTL(dev))
    		return (ENXIO);
    
    	if ((scr = sc->sc_scr[WSDISPLAYSCREEN(dev)]) == NULL)
    		return (ENXIO);
    
    	if (!WSSCREEN_HAS_TTY(scr))
    		return (ENXIO);
    
    	return (ttkqfilter(dev, kn));
    }
    
    void
    wsdisplaystart(struct tty *tp)
    {
    	struct wsdisplay_softc *sc;
    	struct wsscreen *scr;
    	int s, n, done, unit;
    	u_char *buf;
    
    	unit = WSDISPLAYUNIT(tp->t_dev);
    	if (unit >= wsdisplay_cd.cd_ndevs ||
    	    (sc = wsdisplay_cd.cd_devs[unit]) == NULL)
    		return;
    
    	s = spltty();
    	if (tp->t_state & (TS_TIMEOUT | TS_BUSY | TS_TTSTOP)) {
    		splx(s);
    		return;
    	}
    	if (tp->t_outq.c_cc == 0)
    		goto low;
    
    	if ((scr = sc->sc_scr[WSDISPLAYSCREEN(tp->t_dev)]) == NULL) {
    		splx(s);
    		return;
    	}
    	if (scr->scr_hold_screen) {
    		tp->t_state |= TS_TIMEOUT;
    		splx(s);
    		return;
    	}
    	tp->t_state |= TS_BUSY;
    	splx(s);
    
    	/*
    	 * Drain output from ring buffer.
    	 * The output will normally be in one contiguous chunk, but when the
    	 * ring wraps, it will be in two pieces.. one at the end of the ring,
    	 * the other at the start.  For performance, rather than loop here,
    	 * we output one chunk, see if there's another one, and if so, output
    	 * it too.
    	 */
    
    	n = ndqb(&tp->t_outq, 0);
    	buf = tp->t_outq.c_cf;
    
    	if (!(scr->scr_flags & SCR_GRAPHICS)) {
    #ifdef HAVE_BURNER_SUPPORT
    		wsdisplay_burn(sc, WSDISPLAY_BURN_OUTPUT);
    #endif
    #ifdef HAVE_WSMOUSED_SUPPORT
    		if (scr == sc->sc_focus)
    			mouse_remove(scr);
    #endif
    		done = (*scr->scr_dconf->wsemul->output)
    		    (scr->scr_dconf->wsemulcookie, buf, n, 0);
    	} else
    		done = n;
    	ndflush(&tp->t_outq, done);
    
    	if (done == n) {
    		if ((n = ndqb(&tp->t_outq, 0)) > 0) {
    			buf = tp->t_outq.c_cf;
    
    			if (!(scr->scr_flags & SCR_GRAPHICS)) {
    				done = (*scr->scr_dconf->wsemul->output)
    				    (scr->scr_dconf->wsemulcookie, buf, n, 0);
    			} else
    				done = n;
    			ndflush(&tp->t_outq, done);
    		}
    	}
    
    	s = spltty();
    	tp->t_state &= ~TS_BUSY;
    	/* Come back if there's more to do */
    	if (tp->t_outq.c_cc) {
    		tp->t_state |= TS_TIMEOUT;
    		timeout_add(&tp->t_rstrt_to, (hz > 128) ? (hz / 128) : 1);
    	}
    low:
    	ttwakeupwr(tp);
    	splx(s);
    }
    
    int
    wsdisplaystop(struct tty *tp, int flag)
    {
    	int s;
    
    	s = spltty();
    	if (ISSET(tp->t_state, TS_BUSY))
    		if (!ISSET(tp->t_state, TS_TTSTOP))
    			SET(tp->t_state, TS_FLUSH);
    	splx(s);
    
    	return (0);
    }
    
    /* Set line parameters. */
    int
    wsdisplayparam(struct tty *tp, struct termios *t)
    {
    
    	tp->t_ispeed = t->c_ispeed;
    	tp->t_ospeed = t->c_ospeed;
    	tp->t_cflag = t->c_cflag;
    	return (0);
    }
    
    /*
     * Callbacks for the emulation code.
     */
    void
    wsdisplay_emulbell(void *v)
    {
    	struct wsscreen *scr = v;
    
    	if (scr == NULL)		/* console, before real attach */
    		return;
    
    	if (scr->scr_flags & SCR_GRAPHICS) /* can this happen? */
    		return;
    
    	task_add(scr->sc->sc_taskq, &scr->scr_emulbell_task);
    }
    
    void
    wsdisplay_emulbell_task(void *v)
    {
    	struct wsscreen *scr = v;
    
    	(void)wsdisplay_internal_ioctl(scr->sc, scr, WSKBDIO_BELL, NULL,
    	    FWRITE, NULL);
    }
    
    #if !defined(WSEMUL_NO_VT100)
    void
    wsdisplay_emulinput(void *v, const u_char *data, u_int count)
    {
    	struct wsscreen *scr = v;
    	struct tty *tp;
    
    	if (v == NULL)			/* console, before real attach */
    		return;
    
    	if (scr->scr_flags & SCR_GRAPHICS) /* XXX can't happen */
    		return;
    	if (!WSSCREEN_HAS_TTY(scr))
    		return;
    
    	tp = scr->scr_tty;
    	while (count-- > 0)
    		(*linesw[tp->t_line].l_rint)(*data++, tp);
    }
    #endif
    
    /*
     * Calls from the keyboard interface.
     */
    void
    wsdisplay_kbdinput(struct device *dev, kbd_t layout, keysym_t *ks, int num)
    {
    	struct wsdisplay_softc *sc = (struct wsdisplay_softc *)dev;
    	struct wsscreen *scr;
    	const u_char *dp;
    	int count;
    	struct tty *tp;
    
    	scr = sc->sc_focus;
    	if (!scr || !WSSCREEN_HAS_TTY(scr))
    		return;
    
    
    	tp = scr->scr_tty;
    	for (; num > 0; num--) {
    		count = (*scr->scr_dconf->wsemul->translate)
    		    (scr->scr_dconf->wsemulcookie, layout, *ks++, &dp);
    		while (count-- > 0)
    			(*linesw[tp->t_line].l_rint)(*dp++, tp);
    	}
    }
    
    #ifdef WSDISPLAY_COMPAT_RAWKBD
    void
    wsdisplay_rawkbdinput(struct device *dev, u_char *buf, int num)
    {
    	struct wsdisplay_softc *sc = (struct wsdisplay_softc *)dev;
    	struct wsscreen *scr;
    	struct tty *tp;
    
    	scr = sc->sc_focus;
    	if (!scr || !WSSCREEN_HAS_TTY(scr))
    		return;
    
    	tp = scr->scr_tty;
    	while (num-- > 0)
    		(*linesw[tp->t_line].l_rint)(*buf++, tp);
    }
    int
    wsdisplay_update_rawkbd(struct wsdisplay_softc *sc, struct wsscreen *scr)
    {
    #if NWSKBD > 0
    	int s, raw, data, error;
    	struct wsevsrc *inp;
    
    	s = spltty();
    
    	raw = (scr ? scr->scr_rawkbd : 0);
    
    	if (scr != sc->sc_focus || sc->sc_rawkbd == raw) {
    		splx(s);
    		return (0);
    	}
    
    	data = raw ? WSKBD_RAW : WSKBD_TRANSLATED;
    	inp = sc->sc_input;
    	if (inp == NULL) {
    		splx(s);
    		return (ENXIO);
    	}
    	error = wsevsrc_display_ioctl(inp, WSKBDIO_SETMODE, &data, FWRITE, 0);
    	if (!error)
    		sc->sc_rawkbd = raw;
    	splx(s);
    	return (error);
    #else
    	return (0);
    #endif
    }
    #endif
    
    int
    wsdisplay_switch3(void *arg, int error, int waitok)
    {
    	struct wsdisplay_softc *sc = arg;
    	int no;
    	struct wsscreen *scr;
    
    #ifdef WSDISPLAY_COMPAT_USL
    	if (!ISSET(sc->sc_flags, SC_SWITCHPENDING)) {
    		printf("wsdisplay_switch3: not switching\n");
    		return (EINVAL);
    	}
    
    	no = sc->sc_screenwanted;
    	if (no < 0 || no >= WSDISPLAY_MAXSCREEN)
    		panic("wsdisplay_switch3: invalid screen %d", no);
    	scr = sc->sc_scr[no];
    	if (!scr) {
    		printf("wsdisplay_switch3: screen %d disappeared\n", no);
    		error = ENXIO;
    	}
    
    	if (error) {
    		/* try to recover, avoid recursion */
    
    		if (sc->sc_oldscreen == WSDISPLAY_NULLSCREEN) {
    			printf("wsdisplay_switch3: giving up\n");
    			sc->sc_focus = NULL;
    #ifdef WSDISPLAY_COMPAT_RAWKBD
    			wsdisplay_update_rawkbd(sc, 0);
    #endif
    			CLR(sc->sc_flags, SC_SWITCHPENDING);
    			return (error);
    		}
    
    		sc->sc_screenwanted = sc->sc_oldscreen;
    		sc->sc_oldscreen = WSDISPLAY_NULLSCREEN;
    		return (wsdisplay_switch1(arg, 0, waitok));
    	}
    #else
    	/*
    	 * If we do not have syncops support, we come straight from
    	 * wsdisplay_switch2 which has already validated our arguments
    	 * and did not sleep.
    	 */
    	no = sc->sc_screenwanted;
    	scr = sc->sc_scr[no];
    #endif
    
    	CLR(sc->sc_flags, SC_SWITCHPENDING);
    
    #ifdef HAVE_BURNER_SUPPORT
    	if (!error)
    		wsdisplay_burner_setup(sc, scr);
    #endif
    
    	if (!error && (scr->scr_flags & SCR_WAITACTIVE))
    		wakeup(scr);
    	return (error);
    }
    
    int
    wsdisplay_switch2(void *arg, int error, int waitok)
    {
    	struct wsdisplay_softc *sc = arg;
    	int no;
    	struct wsscreen *scr;
    
    	if (!ISSET(sc->sc_flags, SC_SWITCHPENDING)) {
    		printf("wsdisplay_switch2: not switching\n");
    		return (EINVAL);
    	}
    
    	no = sc->sc_screenwanted;
    	if (no < 0 || no >= WSDISPLAY_MAXSCREEN)
    		panic("wsdisplay_switch2: invalid screen %d", no);
    	scr = sc->sc_scr[no];
    	if (!scr) {
    		printf("wsdisplay_switch2: screen %d disappeared\n", no);
    		error = ENXIO;
    	}
    
    	if (error) {
    		/* try to recover, avoid recursion */
    
    		if (sc->sc_oldscreen == WSDISPLAY_NULLSCREEN) {
    			printf("wsdisplay_switch2: giving up\n");
    			sc->sc_focus = NULL;
    			CLR(sc->sc_flags, SC_SWITCHPENDING);
    			return (error);
    		}
    
    		sc->sc_screenwanted = sc->sc_oldscreen;
    		sc->sc_oldscreen = WSDISPLAY_NULLSCREEN;
    		return (wsdisplay_switch1(arg, 0, waitok));
    	}
    
    	sc->sc_focusidx = no;
    	sc->sc_focus = scr;
    
    #ifdef WSDISPLAY_COMPAT_RAWKBD
    	(void) wsdisplay_update_rawkbd(sc, scr);
    #endif
    	/* keyboard map??? */
    
    #ifdef WSDISPLAY_COMPAT_USL
    #define wsswitch_cb3 ((void (*)(void *, int, int))wsdisplay_switch3)
    	if (scr->scr_syncops) {
    		error = (*scr->scr_syncops->attach)(scr->scr_synccookie, waitok,
    		    sc->sc_isconsole && wsdisplay_cons_pollmode ?
    		      0 : wsswitch_cb3, sc);
    		if (error == EAGAIN) {
    			/* switch will be done asynchronously */
    			return (0);
    		}
    	}
    #endif
    
    	return (wsdisplay_switch3(sc, error, waitok));
    }
    
    int
    wsdisplay_switch1(void *arg, int error, int waitok)
    {
    	struct wsdisplay_softc *sc = arg;
    	int no;
    	struct wsscreen *scr;
    
    	if (!ISSET(sc->sc_flags, SC_SWITCHPENDING)) {
    		printf("wsdisplay_switch1: not switching\n");
    		return (EINVAL);
    	}
    
    	no = sc->sc_screenwanted;
    	if (no == WSDISPLAY_NULLSCREEN) {
    		CLR(sc->sc_flags, SC_SWITCHPENDING);
    		if (!error) {
    			sc->sc_focus = NULL;
    		}
    		wakeup(sc);
    		return (error);
    	}
    	if (no < 0 || no >= WSDISPLAY_MAXSCREEN)
    		panic("wsdisplay_switch1: invalid screen %d", no);
    	scr = sc->sc_scr[no];
    	if (!scr) {
    		printf("wsdisplay_switch1: screen %d disappeared\n", no);
    		error = ENXIO;
    	}
    
    	if (error) {
    		CLR(sc->sc_flags, SC_SWITCHPENDING);
    		return (error);
    	}
    
    #define wsswitch_cb2 ((void (*)(void *, int, int))wsdisplay_switch2)
    	error = (*sc->sc_accessops->show_screen)(sc->sc_accesscookie,
    	    scr->scr_dconf->emulcookie, waitok,
    	    sc->sc_isconsole && wsdisplay_cons_pollmode ? 0 : wsswitch_cb2, sc);
    	if (error == EAGAIN) {
    		/* switch will be done asynchronously */
    		return (0);
    	}
    
    	return (wsdisplay_switch2(sc, error, waitok));
    }
    
    int
    wsdisplay_switch(struct device *dev, int no, int waitok)
    {
    	struct wsdisplay_softc *sc = (struct wsdisplay_softc *)dev;
    	int s, res = 0;
    	struct wsscreen *scr;
    
    	if (no != WSDISPLAY_NULLSCREEN) {
    		if (no < 0 || no >= WSDISPLAY_MAXSCREEN)
    			return (EINVAL);
    		if (sc->sc_scr[no] == NULL)
    			return (ENXIO);
    	}
    
    	s = spltty();
    
    	if (sc->sc_resumescreen != WSDISPLAY_NULLSCREEN && !waitok) {
    		splx(s);
    		return (EBUSY);
    	}
    
    	while (sc->sc_resumescreen != WSDISPLAY_NULLSCREEN && res == 0)
    		res = tsleep_nsec(&sc->sc_resumescreen, PCATCH, "wsrestore",
    		    INFSLP);
    	if (res) {
    		splx(s);
    		return (res);
    	}
    
    	if ((sc->sc_focus && no == sc->sc_focusidx) ||
    	    (sc->sc_focus == NULL && no == WSDISPLAY_NULLSCREEN)) {
    		splx(s);
    		return (0);
    	}
    
    	if (ISSET(sc->sc_flags, SC_SWITCHPENDING)) {
    		splx(s);
    		return (EBUSY);
    	}
    
    	SET(sc->sc_flags, SC_SWITCHPENDING);
    	sc->sc_screenwanted = no;
    
    	splx(s);
    
    	scr = sc->sc_focus;
    	if (!scr) {
    		sc->sc_oldscreen = WSDISPLAY_NULLSCREEN;
    		return (wsdisplay_switch1(sc, 0, waitok));
    	} else
    		sc->sc_oldscreen = sc->sc_focusidx;
    
    #ifdef WSDISPLAY_COMPAT_USL
    #define wsswitch_cb1 ((void (*)(void *, int, int))wsdisplay_switch1)
    	if (scr->scr_syncops) {
    		res = (*scr->scr_syncops->detach)(scr->scr_synccookie, waitok,
    		    sc->sc_isconsole && wsdisplay_cons_pollmode ?
    		      0 : wsswitch_cb1, sc);
    		if (res == EAGAIN) {
    			/* switch will be done asynchronously */
    			return (0);
    		}
    	} else if (scr->scr_flags & SCR_GRAPHICS) {
    		/* no way to save state */
    		res = EBUSY;
    	}
    #endif
    
    #ifdef HAVE_WSMOUSED_SUPPORT
    	mouse_remove(scr);
    #endif
    
    	return (wsdisplay_switch1(sc, res, waitok));
    }
    
    void
    wsdisplay_reset(struct device *dev, enum wsdisplay_resetops op)
    {
    	struct wsdisplay_softc *sc = (struct wsdisplay_softc *)dev;
    	struct wsscreen *scr;
    
    	scr = sc->sc_focus;
    
    	if (!scr)
    		return;
    
    	switch (op) {
    	case WSDISPLAY_RESETEMUL:
    		(*scr->scr_dconf->wsemul->reset)(scr->scr_dconf->wsemulcookie,
    		    WSEMUL_RESET);
    		break;
    	case WSDISPLAY_RESETCLOSE:
    		wsdisplay_closescreen(sc, scr);
    		break;
    	}
    }
    
    #ifdef WSDISPLAY_COMPAT_USL
    /*
     * Interface for (external) VT switch / process synchronization code
     */
    int
    wsscreen_attach_sync(struct wsscreen *scr, const struct wscons_syncops *ops,
        void *cookie)
    {
    	if (scr->scr_syncops) {
    		/*
    		 * The screen is already claimed.
    		 * Check if the owner is still alive.
    		 */
    		if ((*scr->scr_syncops->check)(scr->scr_synccookie))
    			return (EBUSY);
    	}
    	scr->scr_syncops = ops;
    	scr->scr_synccookie = cookie;
    	return (0);
    }
    
    int
    wsscreen_detach_sync(struct wsscreen *scr)
    {
    	if (!scr->scr_syncops)
    		return (EINVAL);
    	scr->scr_syncops = NULL;
    	return (0);
    }
    
    int
    wsscreen_lookup_sync(struct wsscreen *scr,
        const struct wscons_syncops *ops, /* used as ID */
        void **cookiep)
    {
    	if (!scr->scr_syncops || ops != scr->scr_syncops)
    		return (EINVAL);
    	*cookiep = scr->scr_synccookie;
    	return (0);
    }
    #endif
    
    /*
     * Interface to virtual screen stuff
     */
    int
    wsdisplay_maxscreenidx(struct wsdisplay_softc *sc)
    {
    	return (WSDISPLAY_MAXSCREEN - 1);
    }
    
    int
    wsdisplay_screenstate(struct wsdisplay_softc *sc, int idx)
    {
    	if (idx < 0 || idx >= WSDISPLAY_MAXSCREEN)
    		return (EINVAL);
    	if (!sc->sc_scr[idx])
    		return (ENXIO);
    	return ((sc->sc_scr[idx]->scr_flags & SCR_OPEN) ? EBUSY : 0);
    }
    
    int
    wsdisplay_getactivescreen(struct wsdisplay_softc *sc)
    {
    	return (sc->sc_focus ? sc->sc_focusidx : WSDISPLAY_NULLSCREEN);
    }
    
    int
    wsscreen_switchwait(struct wsdisplay_softc *sc, int no)
    {
    	struct wsscreen *scr;
    	int s, res = 0;
    
    	if (no == WSDISPLAY_NULLSCREEN) {
    		s = spltty();
    		while (sc->sc_focus && res == 0) {
    			res = tsleep_nsec(sc, PCATCH, "wswait", INFSLP);
    		}
    		splx(s);
    		return (res);
    	}
    
    	if (no < 0 || no >= WSDISPLAY_MAXSCREEN)
    		return (ENXIO);
    	scr = sc->sc_scr[no];
    	if (!scr)
    		return (ENXIO);
    
    	s = spltty();
    	if (scr != sc->sc_focus) {
    		scr->scr_flags |= SCR_WAITACTIVE;
    		res = tsleep_nsec(scr, PCATCH, "wswait2", INFSLP);
    		if (scr != sc->sc_scr[no])
    			res = ENXIO; /* disappeared in the meantime */
    		else
    			scr->scr_flags &= ~SCR_WAITACTIVE;
    	}
    	splx(s);
    	return (res);
    }
    
    void
    wsdisplay_kbdholdscr(struct wsscreen *scr, int hold)
    {
    	if (hold)
    		scr->scr_hold_screen = 1;
    	else {
    		scr->scr_hold_screen = 0;
    		timeout_add(&scr->scr_tty->t_rstrt_to, 0); /* "immediate" */
    	}
    }
    
    void
    wsdisplay_kbdholdscreen(struct device *dev, int hold)
    {
    	struct wsdisplay_softc *sc = (struct wsdisplay_softc *)dev;
    	struct wsscreen *scr;
    
    	scr = sc->sc_focus;
    	if (scr != NULL && WSSCREEN_HAS_TTY(scr))
    		wsdisplay_kbdholdscr(scr, hold);
    }
    
    #if NWSKBD > 0
    void
    wsdisplay_set_console_kbd(struct wsevsrc *src)
    {
    	if (wsdisplay_console_device == NULL) {
    		src->me_dispdv = NULL;
    		return;
    	}
    #if NWSMUX > 0
    	if (wsmux_attach_sc((struct wsmux_softc *)
    			    wsdisplay_console_device->sc_input, src)) {
    		src->me_dispdv = NULL;
    		return;
    	}
    #else
    	wsdisplay_console_device->sc_input = src;
    #endif
    	src->me_dispdv = &wsdisplay_console_device->sc_dv;
    }
    
    #if NWSMUX == 0
    int
    wsdisplay_set_kbd(struct device *disp, struct wsevsrc *kbd)
    {
    	struct wsdisplay_softc *sc = (struct wsdisplay_softc *)disp;
    
    	if (sc->sc_input != NULL)
    		return (EBUSY);
    
    	sc->sc_input = kbd;
    
    	return (0);
    }
    #endif
    
    #endif /* NWSKBD > 0 */
    
    /*
     * Console interface.
     */
    void
    wsdisplay_cnputc(dev_t dev, int i)
    {
    	struct wsscreen_internal *dc;
    	char c = i;
    
    	if (!wsdisplay_console_initted)
    		return;
    
    	if (wsdisplay_console_device != NULL &&
    	    (wsdisplay_console_device->sc_scr[0] != NULL) &&
    	    (wsdisplay_console_device->sc_scr[0]->scr_flags & SCR_GRAPHICS))
    		return;
    
    	dc = &wsdisplay_console_conf;
    #ifdef HAVE_BURNER_SUPPORT
    	/*wsdisplay_burn(wsdisplay_console_device, WSDISPLAY_BURN_OUTPUT);*/
    #endif
    	(void)(*dc->wsemul->output)(dc->wsemulcookie, &c, 1, 1);
    }
    
    int
    wsdisplay_getc_dummy(dev_t dev)
    {
    	/* panic? */
    	return (0);
    }
    
    void
    wsdisplay_pollc(dev_t dev, int on)
    {
    
    	wsdisplay_cons_pollmode = on;
    
    	/* notify to fb drivers */
    	if (wsdisplay_console_device != NULL &&
    	    wsdisplay_console_device->sc_accessops->pollc != NULL)
    		(*wsdisplay_console_device->sc_accessops->pollc)
    		    (wsdisplay_console_device->sc_accesscookie, on);
    
    	/* notify to kbd drivers */
    	if (wsdisplay_cons_kbd_pollc)
    		(*wsdisplay_cons_kbd_pollc)(dev, on);
    }
    
    void
    wsdisplay_set_cons_kbd(int (*get)(dev_t), void (*poll)(dev_t, int),
        void (*bell)(dev_t, u_int, u_int, u_int))
    {
    	wsdisplay_cons.cn_getc = get;
    	wsdisplay_cons.cn_bell = bell;
    	wsdisplay_cons_kbd_pollc = poll;
    }
    
    void
    wsdisplay_unset_cons_kbd(void)
    {
    	wsdisplay_cons.cn_getc = wsdisplay_getc_dummy;
    	wsdisplay_cons.cn_bell = NULL;
    	wsdisplay_cons_kbd_pollc = NULL;
    }
    
    /*
     * Switch the console display to its first screen.
     */
    void
    wsdisplay_switchtoconsole(void)
    {
    	struct wsdisplay_softc *sc;
    	struct wsscreen *scr;
    
    	if (wsdisplay_console_device != NULL && cn_tab == &wsdisplay_cons) {
    		sc = wsdisplay_console_device;
    		if ((scr = sc->sc_scr[0]) == NULL)
    			return;
    		(*sc->sc_accessops->show_screen)(sc->sc_accesscookie,
    		    scr->scr_dconf->emulcookie, 0, NULL, NULL);
    	}
    }
    
    /*
     * Switch the console display to its ddb screen, avoiding locking
     * where we can.
     */
    void
    wsdisplay_enter_ddb(void)
    {
    	struct wsdisplay_softc *sc;
    	struct wsscreen *scr;
    
    	if (wsdisplay_console_device != NULL && cn_tab == &wsdisplay_cons) {
    		sc = wsdisplay_console_device;
    		if ((scr = sc->sc_scr[0]) == NULL)
    			return;
    		if (sc->sc_accessops->enter_ddb) {
    			(*sc->sc_accessops->enter_ddb)(sc->sc_accesscookie,
    			    scr->scr_dconf->emulcookie);
    		} else {
    			(*sc->sc_accessops->show_screen)(sc->sc_accesscookie,
    			    scr->scr_dconf->emulcookie, 0, NULL, NULL);
    		}
    	}
    }
    
    /*
     * Deal with the xserver doing driver in userland and thus screwing up suspend
     * and resume by switching away from it at suspend/resume time.
     *
     * these functions must be called from the MD suspend callback, since we may
     * need to sleep if we have a user (probably an X server) on a vt. therefore
     * this can't be a config_suspend() hook.
     */
    void
    wsdisplay_suspend(void)
    {
    	int	i;
    
    	for (i = 0; i < wsdisplay_cd.cd_ndevs; i++)
    		if (wsdisplay_cd.cd_devs[i] != NULL)
    			wsdisplay_suspend_device(wsdisplay_cd.cd_devs[i]);
    }
    
    void
    wsdisplay_suspend_device(struct device *dev)
    {
    	struct wsdisplay_softc	*sc = (struct wsdisplay_softc *)dev;
    	struct wsscreen		*scr;
    	int			 active, idx, ret = 0, s;
    
    	if ((active = wsdisplay_getactivescreen(sc)) == WSDISPLAY_NULLSCREEN)
    		return;
    
    	scr = sc->sc_scr[active];
    	/*
    	 * We want to switch out of graphics mode for the suspend
    	 */
    retry:
    	idx = WSDISPLAY_MAXSCREEN;
    	if (scr->scr_flags & SCR_GRAPHICS) {
    		for (idx = 0; idx < WSDISPLAY_MAXSCREEN; idx++) {
    			if (sc->sc_scr[idx] == NULL || sc->sc_scr[idx] == scr)
    				continue;
    
    			if ((sc->sc_scr[idx]->scr_flags & SCR_GRAPHICS) == 0)
    				break;
    		}
    	}
    
    	/* if we don't have anything to switch to, we can't do anything */
    	if (idx == WSDISPLAY_MAXSCREEN)
    		return;
    
    	/*
    	 * we do a lot of magic here because we need to know that the
    	 * switch has completed before we return
    	 */
    	ret = wsdisplay_switch((struct device *)sc, idx, 1);
    	if (ret == EBUSY) {
    		/* XXX sleep on what's going on */
    		goto retry;
    	} else if (ret)
    		return;
    
    	s = spltty();
    	sc->sc_resumescreen = active; /* block other vt switches until resume */
    	splx(s);
    	/*
    	 * This will either return ENXIO (invalid (shouldn't happen) or
    	 * wsdisplay disappeared (problem solved)), or EINTR/ERESTART.
    	 * Not much we can do about the latter since we can't return to
    	 * userland.
    	 */
    	(void)wsscreen_switchwait(sc, idx);
    }
    
    void
    wsdisplay_resume(void)
    {
    	int	i;
    
    	for (i = 0; i < wsdisplay_cd.cd_ndevs; i++)
    		if (wsdisplay_cd.cd_devs[i] != NULL)
    			wsdisplay_resume_device(wsdisplay_cd.cd_devs[i]);
    }
    
    void
    wsdisplay_resume_device(struct device *dev)
    {
    	struct wsdisplay_softc	*sc = (struct wsdisplay_softc *)dev;
    	int			 idx, s;
    
    	if (sc->sc_resumescreen != WSDISPLAY_NULLSCREEN) {
    		s = spltty();
    		idx = sc->sc_resumescreen;
    		sc->sc_resumescreen = WSDISPLAY_NULLSCREEN;
    		wakeup(&sc->sc_resumescreen);
    		splx(s);
    		(void)wsdisplay_switch((struct device *)sc, idx, 1);
    	}
    }
    
    #ifdef HAVE_SCROLLBACK_SUPPORT
    void
    wsscrollback(void *arg, int op)
    {
    	struct wsdisplay_softc *sc = arg;
    	int lines;
    
    	if (sc->sc_focus == NULL)
    		return;
    
    	if (op == WSDISPLAY_SCROLL_RESET)
    		lines = 0;
    	else {
    		lines = sc->sc_focus->scr_dconf->scrdata->nrows - 1;
    		if (op == WSDISPLAY_SCROLL_BACKWARD)
    			lines = -lines;
    	}
    
    	if (sc->sc_accessops->scrollback) {
    		(*sc->sc_accessops->scrollback)(sc->sc_accesscookie,
    		    sc->sc_focus->scr_dconf->emulcookie, lines);
    	}
    }
    #endif
    
    #ifdef HAVE_BURNER_SUPPORT
    /*
     * Update screen burner behaviour after either a screen focus change or
     * a screen mode change.
     * This is needed to allow X11 to manage screen blanking without any
     * interference from the kernel.
     */
    void
    wsdisplay_burner_setup(struct wsdisplay_softc *sc, struct wsscreen *scr)
    {
    	if (scr->scr_flags & SCR_GRAPHICS) {
    		/* enable video _immediately_ if it needs to be... */
    		if (sc->sc_burnman)
    			wsdisplay_burner(sc);
    		/* ...and disable the burner while X is running */
    		if (sc->sc_burnout) {
    			timeout_del(&sc->sc_burner);
    			sc->sc_burnout = 0;
    		}
    	} else {
    		/* reenable the burner after exiting from X */
    		if (!sc->sc_burnman) {
    			sc->sc_burnout = sc->sc_burnoutintvl;
    			wsdisplay_burn(sc, sc->sc_burnflags);
    		}
    	}
    }
    
    void
    wsdisplay_burn(void *v, u_int flags)
    {
    	struct wsdisplay_softc *sc = v;
    
    	if ((flags & sc->sc_burnflags & (WSDISPLAY_BURN_OUTPUT |
    	    WSDISPLAY_BURN_KBD | WSDISPLAY_BURN_MOUSE)) &&
    	    sc->sc_accessops->burn_screen) {
    		if (sc->sc_burnout)
    			timeout_add_msec(&sc->sc_burner, sc->sc_burnout);
    		if (sc->sc_burnman)
    			sc->sc_burnout = 0;
    	}
    }
    
    void
    wsdisplay_burner(void *v)
    {
    	struct wsdisplay_softc *sc = v;
    	int s;
    
    	if (sc->sc_accessops->burn_screen) {
    		(*sc->sc_accessops->burn_screen)(sc->sc_accesscookie,
    		    sc->sc_burnman, sc->sc_burnflags);
    		s = spltty();
    		if (sc->sc_burnman) {
    			sc->sc_burnout = sc->sc_burnoutintvl;
    			timeout_add_msec(&sc->sc_burner, sc->sc_burnout);
    		} else
    			sc->sc_burnout = sc->sc_burninintvl;
    		sc->sc_burnman = !sc->sc_burnman;
    		splx(s);
    	}
    }
    #endif
    
    int
    wsdisplay_get_param(struct wsdisplay_softc *sc, struct wsdisplay_param *dp)
    {
    	int error = ENXIO;
    	int i;
    
    	if (sc != NULL)
    		return wsdisplay_param(&sc->sc_dv, WSDISPLAYIO_GETPARAM, dp);
    
    	for (i = 0; i < wsdisplay_cd.cd_ndevs; i++) {
    		sc = wsdisplay_cd.cd_devs[i];
    		if (sc == NULL)
    			continue;
    		error = wsdisplay_param(&sc->sc_dv, WSDISPLAYIO_GETPARAM, dp);
    		if (error == 0)
    			break;
    	}
    
    	if (error && ws_get_param)
    		error = ws_get_param(dp);
    
    	return error;
    }
    
    int
    wsdisplay_set_param(struct wsdisplay_softc *sc, struct wsdisplay_param *dp)
    {
    	int error = ENXIO;
    	int i;
    
    	if (sc != NULL)
    		return wsdisplay_param(&sc->sc_dv, WSDISPLAYIO_SETPARAM, dp);
    
    	for (i = 0; i < wsdisplay_cd.cd_ndevs; i++) {
    		sc = wsdisplay_cd.cd_devs[i];
    		if (sc == NULL)
    			continue;
    		error = wsdisplay_param(&sc->sc_dv, WSDISPLAYIO_SETPARAM, dp);
    		if (error == 0)
    			break;
    	}
    
    	if (error && ws_set_param)
    		error = ws_set_param(dp);
    
    	return error;
    }
    
    void
    wsdisplay_brightness_step(struct device *dev, int dir)
    {
    	struct wsdisplay_softc *sc = (struct wsdisplay_softc *)dev;
    	struct wsdisplay_param dp;
    	int delta, new;
    
    	dp.param = WSDISPLAYIO_PARAM_BRIGHTNESS;
    	if (wsdisplay_get_param(sc, &dp))
    		return;
    
    	/* Use a step size of approximately 5%. */
    	delta = max(1, ((dp.max - dp.min) * 5) / 100);
    	new = dp.curval;
    
    	if (dir > 0) {
    		if (delta > dp.max - dp.curval)
    			new = dp.max;
    		else
    			new += delta;
    	} else if (dir < 0) {
    		if (delta > dp.curval - dp.min)
    			new = dp.min;
    		else
    			new -= delta;
    	}
    
    	if (dp.curval == new)
    		return;
    
    	dp.curval = new;
    	wsdisplay_set_param(sc, &dp);
    }
    
    void
    wsdisplay_brightness_zero(struct device *dev)
    {
    	struct wsdisplay_softc *sc = (struct wsdisplay_softc *)dev;
    	struct wsdisplay_param dp;
    
    	dp.param = WSDISPLAYIO_PARAM_BRIGHTNESS;
    	if (wsdisplay_get_param(sc, &dp))
    		return;
    
    	dp.curval = dp.min;
    	wsdisplay_set_param(sc, &dp);
    }
    
    void
    wsdisplay_brightness_cycle(struct device *dev)
    {
    	struct wsdisplay_softc *sc = (struct wsdisplay_softc *)dev;
    	struct wsdisplay_param dp;
    
    	dp.param = WSDISPLAYIO_PARAM_BRIGHTNESS;
    	if (wsdisplay_get_param(sc, &dp))
    		return;
    
    	if (dp.curval == dp.max)
    		wsdisplay_brightness_zero(dev);
    	else
    		wsdisplay_brightness_step(dev, 1);
    }
    
    #ifdef HAVE_WSMOUSED_SUPPORT
    /*
     * wsmoused(8) support functions
     */
    
    /*
     * Main function, called from wsdisplay_cfg_ioctl.
     */
    int
    wsmoused(struct wsdisplay_softc *sc, caddr_t data, int flag, struct proc *p)
    {
    	struct wscons_event mouse_event = *(struct wscons_event *)data;
    
    	if (IS_MOTION_EVENT(mouse_event.type)) {
    		if (sc->sc_focus != NULL)
    			motion_event(sc->sc_focus, mouse_event.type,
    			    mouse_event.value);
    		return 0;
    	}
    	if (IS_BUTTON_EVENT(mouse_event.type)) {
    		if (sc->sc_focus != NULL) {
    			/* XXX tv_sec contains the number of clicks */
    			if (mouse_event.type ==
    			    WSCONS_EVENT_MOUSE_DOWN) {
    				button_event(sc->sc_focus,
    				    mouse_event.value,
    				    mouse_event.time.tv_sec);
    			} else
    				button_event(sc->sc_focus,
    				    mouse_event.value, 0);
    		}
    		return (0);
    	}
    	if (IS_CTRL_EVENT(mouse_event.type)) {
    		return ctrl_event(sc, mouse_event.type,
    		    mouse_event.value, p);
    	}
    	return -1;
    }
    
    /*
     * Mouse motion events
     */
    void
    motion_event(struct wsscreen *scr, u_int type, int value)
    {
    	switch (type) {
    	case WSCONS_EVENT_MOUSE_DELTA_X:
    		mouse_moverel(scr, value, 0);
    		break;
    	case WSCONS_EVENT_MOUSE_DELTA_Y:
    		mouse_moverel(scr, 0, -value);
    		break;
    #ifdef HAVE_SCROLLBACK_SUPPORT
    	case WSCONS_EVENT_MOUSE_DELTA_Z:
    		mouse_zaxis(scr, value);
    		break;
    #endif
    	default:
    		break;
    	}
    }
    
    /*
     * Button clicks events
     */
    void
    button_event(struct wsscreen *scr, int button, int clicks)
    {
    	switch (button) {
    	case MOUSE_COPY_BUTTON:
    		switch (clicks % 4) {
    		case 0: /* button is up */
    			mouse_copy_end(scr);
    			mouse_copy_selection(scr);
    			break;
    		case 1: /* single click */
    			mouse_copy_start(scr);
    			mouse_copy_selection(scr);
    			break;
    		case 2: /* double click */
    			mouse_copy_word(scr);
    			mouse_copy_selection(scr);
    			break;
    		case 3: /* triple click */
    			mouse_copy_line(scr);
    			mouse_copy_selection(scr);
    			break;
    		}
    		break;
    	case MOUSE_PASTE_BUTTON:
    		if (clicks != 0)
    			mouse_paste(scr);
    		break;
    	case MOUSE_EXTEND_BUTTON:
    		if (clicks != 0)
    			mouse_copy_extend_after(scr);
    		break;
    	default:
    		break;
    	}
    }
    
    /*
     * Control events
     */
    int
    ctrl_event(struct wsdisplay_softc *sc, u_int type, int value, struct proc *p)
    {
    	struct wsscreen *scr;
    	int i;
    
    	switch (type) {
    	case WSCONS_EVENT_WSMOUSED_OFF:
    		CLR(sc->sc_flags, SC_PASTE_AVAIL);
    		return (0);
    	case WSCONS_EVENT_WSMOUSED_ON:
    		if (!sc->sc_accessops->getchar)
    			/* no wsmoused(8) support in the display driver */
    			return (1);
    		allocate_copybuffer(sc);
    		CLR(sc->sc_flags, SC_PASTE_AVAIL);
    
    		for (i = 0 ; i < WSDISPLAY_DEFAULTSCREENS ; i++)
    			if ((scr = sc->sc_scr[i]) != NULL) {
    				scr->mouse =
    				    (WS_NCOLS(scr) * WS_NROWS(scr)) / 2;
    				scr->cursor = scr->mouse;
    				scr->cpy_start = 0;
    				scr->cpy_end = 0;
    				scr->orig_start = 0;
    				scr->orig_end = 0;
    				scr->mouse_flags = 0;
    			}
    		return (0);
    	default:	/* can't happen, really */
    		return 0;
    	}
    }
    
    void
    mouse_moverel(struct wsscreen *scr, int dx, int dy)
    {
    	struct wsscreen_internal *dconf = scr->scr_dconf;
    	u_int old_mouse = scr->mouse;
    	int mouse_col = scr->mouse % N_COLS(dconf);
    	int mouse_row = scr->mouse / N_COLS(dconf);
    
    	/* update position */
    	if (mouse_col + dx >= MAXCOL(dconf))
    		mouse_col = MAXCOL(dconf);
    	else {
    		if (mouse_col + dx <= 0)
    			mouse_col = 0;
    		else
    			mouse_col += dx;
    	}
    	if (mouse_row + dy >= MAXROW(dconf))
    		mouse_row = MAXROW(dconf);
    	else {
    		if (mouse_row + dy <= 0)
    			mouse_row = 0;
    		else
    			mouse_row += dy;
    	}
    	scr->mouse = mouse_row * N_COLS(dconf) + mouse_col;
    
    	/* if we have moved */
    	if (old_mouse != scr->mouse) {
    		/* XXX unblank screen if display.ms_act */
    		if (ISSET(scr->mouse_flags, SEL_IN_PROGRESS)) {
    			/* selection in progress */
    			mouse_copy_extend(scr);
    		} else {
    			inverse_char(scr, scr->mouse);
    			if (ISSET(scr->mouse_flags, MOUSE_VISIBLE))
    				inverse_char(scr, old_mouse);
    			else
    				SET(scr->mouse_flags, MOUSE_VISIBLE);
    		}
    	}
    }
    
    void
    inverse_char(struct wsscreen *scr, u_int pos)
    {
    	struct wsscreen_internal *dconf = scr->scr_dconf;
    	struct wsdisplay_charcell cell;
    	int fg, bg, ul;
    	int flags;
    	int tmp;
    	uint32_t attr;
    
    	GETCHAR(scr, pos, &cell);
    
    	(*dconf->emulops->unpack_attr)(dconf->emulcookie, cell.attr, &fg,
    	    &bg, &ul);
    
    	/*
    	 * Display the mouse cursor as a color inverted cell whenever
    	 * possible. If this is not possible, ask for the video reverse
    	 * attribute.
    	 */
    	flags = 0;
    	if (dconf->scrdata->capabilities & WSSCREEN_WSCOLORS) {
    		flags |= WSATTR_WSCOLORS;
    		tmp = fg;
    		fg = bg;
    		bg = tmp;
    	} else if (dconf->scrdata->capabilities & WSSCREEN_REVERSE) {
    		flags |= WSATTR_REVERSE;
    	}
    	if ((*dconf->emulops->pack_attr)(dconf->emulcookie, fg, bg, flags |
    	    (ul ? WSATTR_UNDERLINE : 0), &attr) == 0) {
    		cell.attr = attr;
    		PUTCHAR(dconf, pos, cell.uc, cell.attr);
    	}
    }
    
    void
    inverse_region(struct wsscreen *scr, u_int start, u_int end)
    {
    	struct wsscreen_internal *dconf = scr->scr_dconf;
    	u_int current_pos;
    	u_int abs_end;
    
    	/* sanity check, useful because 'end' can be (u_int)-1 */
    	abs_end = N_COLS(dconf) * N_ROWS(dconf);
    	if (end > abs_end)
    		return;
    	current_pos = start;
    	while (current_pos <= end)
    		inverse_char(scr, current_pos++);
    }
    
    /*
     * Return the number of contiguous blank characters between the right margin
     * if border == 1 or between the next non-blank character and the current mouse
     * cursor if border == 0
     */
    u_int
    skip_spc_right(struct wsscreen *scr, int border)
    {
    	struct wsscreen_internal *dconf = scr->scr_dconf;
    	struct wsdisplay_charcell cell;
    	u_int current = scr->cpy_end;
    	u_int mouse_col = scr->cpy_end % N_COLS(dconf);
    	u_int limit = current + (N_COLS(dconf) - mouse_col - 1);
    	u_int res = 0;
    
    	while (GETCHAR(scr, current, &cell) == 0 && cell.uc == ' ' &&
    	    current <= limit) {
    		current++;
    		res++;
    	}
    	if (border == BORDER) {
    		if (current > limit)
    			return (res - 1);
    		else
    			return (0);
    	} else {
    		if (res != 0)
    			return (res - 1);
    		else
    			return (res);
    	}
    }
    
    /*
     * Return the number of contiguous blank characters between the first of the
     * contiguous blank characters and the current mouse cursor
     */
    u_int
    skip_spc_left(struct wsscreen *scr)
    {
    	struct wsscreen_internal *dconf = scr->scr_dconf;
    	struct wsdisplay_charcell cell;
    	u_int current = scr->cpy_start;
    	u_int mouse_col = scr->mouse % N_COLS(dconf);
    	u_int limit = current - mouse_col;
    	u_int res = 0;
    
    	while (GETCHAR(scr, current, &cell) == 0 && cell.uc == ' ' &&
    	    current >= limit) {
    		current--;
    		res++;
    	}
    	if (res != 0)
    		res--;
    	return (res);
    }
    
    /*
     * Class of characters
     * Stolen from xterm sources of the Xfree project (see cvs tag below)
     * $TOG: button.c /main/76 1997/07/30 16:56:19 kaleb $
     */
    static const int charClass[256] = {
    /* NUL  SOH  STX  ETX  EOT  ENQ  ACK  BEL */
        32,   1,   1,   1,   1,   1,   1,   1,
    /*  BS   HT   NL   VT   NP   CR   SO   SI */
         1,  32,   1,   1,   1,   1,   1,   1,
    /* DLE  DC1  DC2  DC3  DC4  NAK  SYN  ETB */
         1,   1,   1,   1,   1,   1,   1,   1,
    /* CAN   EM  SUB  ESC   FS   GS   RS   US */
         1,   1,   1,   1,   1,   1,   1,   1,
    /*  SP    !    "    #    $    %    &    ' */
        32,  33,  34,  35,  36,  37,  38,  39,
    /*   (    )    *    +    ,    -    .    / */
        40,  41,  42,  43,  44,  45,  46,  47,
    /*   0    1    2    3    4    5    6    7 */
        48,  48,  48,  48,  48,  48,  48,  48,
    /*   8    9    :    ;    <    =    >    ? */
        48,  48,  58,  59,  60,  61,  62,  63,
    /*   @    A    B    C    D    E    F    G */
        64,  48,  48,  48,  48,  48,  48,  48,
    /*   H    I    J    K    L    M    N    O */
        48,  48,  48,  48,  48,  48,  48,  48,
    /*   P    Q    R    S    T    U    V    W */
        48,  48,  48,  48,  48,  48,  48,  48,
    /*   X    Y    Z    [    \    ]    ^    _ */
        48,  48,  48,  91,  92,  93,  94,  48,
    /*   `    a    b    c    d    e    f    g */
        96,  48,  48,  48,  48,  48,  48,  48,
    /*   h    i    j    k    l    m    n    o */
        48,  48,  48,  48,  48,  48,  48,  48,
    /*   p    q    r    s    t    u    v    w */
        48,  48,  48,  48,  48,  48,  48,  48,
    /*   x    y    z    {    |    }    ~  DEL */
        48,  48,  48, 123, 124, 125, 126,   1,
    /* x80  x81  x82  x83  IND  NEL  SSA  ESA */
         1,   1,   1,   1,   1,   1,   1,   1,
    /* HTS  HTJ  VTS  PLD  PLU   RI  SS2  SS3 */
         1,   1,   1,   1,   1,   1,   1,   1,
    /* DCS  PU1  PU2  STS  CCH   MW  SPA  EPA */
         1,   1,   1,   1,   1,   1,   1,   1,
    /* x98  x99  x9A  CSI   ST  OSC   PM  APC */
         1,   1,   1,   1,   1,   1,   1,   1,
    /*   -    i   c/    L   ox   Y-    |   So */
       160, 161, 162, 163, 164, 165, 166, 167,
    /*  ..   c0   ip   <<    _        R0    - */
       168, 169, 170, 171, 172, 173, 174, 175,
    /*   o   +-    2    3    '    u   q|    . */
       176, 177, 178, 179, 180, 181, 182, 183,
    /*   ,    1    2   >>  1/4  1/2  3/4    ? */
       184, 185, 186, 187, 188, 189, 190, 191,
    /*  A`   A'   A^   A~   A:   Ao   AE   C, */
        48,  48,  48,  48,  48,  48,  48,  48,
    /*  E`   E'   E^   E:   I`   I'   I^   I: */
        48,  48,  48,  48,  48,  48,  48,  48,
    /*  D-   N~   O`   O'   O^   O~   O:    X */
        48,  48,  48,  48,  48,  48,  48, 216,
    /*  O/   U`   U'   U^   U:   Y'    P    B */
        48,  48,  48,  48,  48,  48,  48,  48,
    /*  a`   a'   a^   a~   a:   ao   ae   c, */
        48,  48,  48,  48,  48,  48,  48,  48,
    /*  e`   e'   e^   e:    i`  i'   i^   i: */
        48,  48,  48,  48,  48,  48,  48,  48,
    /*   d   n~   o`   o'   o^   o~   o:   -: */
        48,  48,  48,  48,  48,  48,  48,  248,
    /*  o/   u`   u'   u^   u:   y'    P   y: */
        48,  48,  48,  48,  48,  48,  48,  48
    };
    
    /*
     * Find the first blank beginning after the current cursor position
     */
    u_int
    skip_char_right(struct wsscreen *scr, u_int offset)
    {
    	struct wsscreen_internal *dconf = scr->scr_dconf;
    	struct wsdisplay_charcell cell;
    	u_int current = offset;
    	u_int limit = current +
    	    (N_COLS(dconf) - (scr->mouse % N_COLS(dconf)) - 1);
    	u_int class;
    	u_int res = 0;
    
    	GETCHAR(scr, current, &cell);
    	class = charClass[cell.uc & 0xff];
    	while (GETCHAR(scr, current, &cell) == 0 &&
    	    charClass[cell.uc & 0xff] == class && current <= limit) {
    		current++;
    		res++;
    	}
    	if (res != 0)
    		res--;
    	return (res);
    }
    
    /*
     * Find the first non-blank character before the cursor position
     */
    u_int
    skip_char_left(struct wsscreen *scr, u_int offset)
    {
    	struct wsscreen_internal *dconf = scr->scr_dconf;
    	struct wsdisplay_charcell cell;
    	u_int current = offset;
    	u_int limit = current - (scr->mouse % N_COLS(dconf));
    	u_int class;
    	u_int res = 0;
    
    	GETCHAR(scr, current, &cell);
    	class = charClass[cell.uc & 0xff];
    	while (GETCHAR(scr, current, &cell) == 0 &&
    	    charClass[cell.uc & 0xff] == class && current >= limit) {
    		current--;
    		res++;
    	}
    	if (res != 0)
    		res--;
    	return (res);
    }
    
    /*
     * Compare character classes
     */
    u_int
    class_cmp(struct wsscreen *scr, u_int first, u_int second)
    {
    	struct wsdisplay_charcell cell;
    	u_int first_class;
    	u_int second_class;
    
    	if (GETCHAR(scr, first, &cell) != 0)
    		return (1);
    	first_class = charClass[cell.uc & 0xff];
    	if (GETCHAR(scr, second, &cell) != 0)
    		return (1);
    	second_class = charClass[cell.uc & 0xff];
    
    	if (first_class != second_class)
    		return (1);
    	else
    		return (0);
    }
    
    /*
     * Beginning of a copy operation
     */
    void
    mouse_copy_start(struct wsscreen *scr)
    {
    	u_int right;
    
    	/* if no selection, then that's the first one */
    	SET(scr->sc->sc_flags, SC_PASTE_AVAIL);
    
    	/* remove the previous selection */
    	if (ISSET(scr->mouse_flags, SEL_EXISTS))
    		remove_selection(scr);
    
    	/* initial show of the cursor */
    	if (!ISSET(scr->mouse_flags, MOUSE_VISIBLE))
    		inverse_char(scr, scr->mouse);
    
    	scr->cpy_start = scr->cpy_end = scr->mouse;
    	scr->orig_start = scr->cpy_start;
    	scr->orig_end = scr->cpy_end;
    	scr->cursor = scr->cpy_end + 1; /* init value */
    
    	/* useful later, in mouse_copy_extend */
    	right = skip_spc_right(scr, BORDER);
    	if (right)
    		SET(scr->mouse_flags, BLANK_TO_EOL);
    
    	SET(scr->mouse_flags, SEL_IN_PROGRESS | SEL_EXISTS | SEL_BY_CHAR);
    	CLR(scr->mouse_flags, SEL_BY_WORD | SEL_BY_LINE);
    	CLR(scr->mouse_flags, MOUSE_VISIBLE); /* cursor hidden in selection */
    }
    
    /*
     * Copy of the word under the cursor
     */
    void
    mouse_copy_word(struct wsscreen *scr)
    {
    	struct wsdisplay_charcell cell;
    	u_int right;
    	u_int left;
    
    	if (ISSET(scr->mouse_flags, SEL_EXISTS))
    		remove_selection(scr);
    
    	if (ISSET(scr->mouse_flags, MOUSE_VISIBLE))
    		inverse_char(scr, scr->mouse);
    
    	scr->cpy_start = scr->cpy_end = scr->mouse;
    
    	if (GETCHAR(scr, scr->mouse, &cell) == 0 &&
    	    IS_ALPHANUM(cell.uc)) {
    		right = skip_char_right(scr, scr->cpy_end);
    		left = skip_char_left(scr, scr->cpy_start);
    	} else {
    		right = skip_spc_right(scr, NO_BORDER);
    		left = skip_spc_left(scr);
    	}
    
    	scr->cpy_start -= left;
    	scr->cpy_end += right;
    	scr->orig_start = scr->cpy_start;
    	scr->orig_end = scr->cpy_end;
    	scr->cursor = scr->cpy_end + 1; /* init value, never happen */
    	inverse_region(scr, scr->cpy_start, scr->cpy_end);
    
    	SET(scr->mouse_flags, SEL_IN_PROGRESS | SEL_EXISTS | SEL_BY_WORD);
    	CLR(scr->mouse_flags, SEL_BY_CHAR | SEL_BY_LINE);
    	/* mouse cursor hidden in the selection */
    	CLR(scr->mouse_flags, BLANK_TO_EOL | MOUSE_VISIBLE);
    }
    
    /*
     * Copy of the current line
     */
    void
    mouse_copy_line(struct wsscreen *scr)
    {
    	struct wsscreen_internal *dconf = scr->scr_dconf;
    	u_int row = scr->mouse / N_COLS(dconf);
    
    	if (ISSET(scr->mouse_flags, SEL_EXISTS))
    		remove_selection(scr);
    
    	if (ISSET(scr->mouse_flags, MOUSE_VISIBLE))
    		inverse_char(scr, scr->mouse);
    
    	scr->cpy_start = row * N_COLS(dconf);
    	scr->cpy_end = scr->cpy_start + (N_COLS(dconf) - 1);
    	scr->orig_start = scr->cpy_start;
    	scr->orig_end = scr->cpy_end;
    	scr->cursor = scr->cpy_end + 1;
    	inverse_region(scr, scr->cpy_start, scr->cpy_end);
    
    	SET(scr->mouse_flags, SEL_IN_PROGRESS | SEL_EXISTS | SEL_BY_LINE);
    	CLR(scr->mouse_flags, SEL_BY_CHAR | SEL_BY_WORD);
    	/* mouse cursor hidden in the selection */
    	CLR(scr->mouse_flags, BLANK_TO_EOL | MOUSE_VISIBLE);
    }
    
    /*
     * End of a copy operation
     */
    void
    mouse_copy_end(struct wsscreen *scr)
    {
    	CLR(scr->mouse_flags, SEL_IN_PROGRESS);
    	if (ISSET(scr->mouse_flags, SEL_BY_WORD) ||
    	    ISSET(scr->mouse_flags, SEL_BY_LINE)) {
    		if (scr->cursor != scr->cpy_end + 1)
    			inverse_char(scr, scr->cursor);
    		scr->cursor = scr->cpy_end + 1;
    	}
    }
    
    
    /*
     * Generic selection extend function
     */
    void
    mouse_copy_extend(struct wsscreen *scr)
    {
    	if (ISSET(scr->mouse_flags, SEL_BY_CHAR))
    		mouse_copy_extend_char(scr);
    	if (ISSET(scr->mouse_flags, SEL_BY_WORD))
    		mouse_copy_extend_word(scr);
    	if (ISSET(scr->mouse_flags, SEL_BY_LINE))
    		mouse_copy_extend_line(scr);
    }
    
    /*
     * Extend a selected region, character by character
     */
    void
    mouse_copy_extend_char(struct wsscreen *scr)
    {
    	u_int right;
    
    	if (!ISSET(scr->mouse_flags, SEL_EXT_AFTER)) {
    		if (ISSET(scr->mouse_flags, BLANK_TO_EOL)) {
    			/*
    			 * First extension of selection. We handle special
    			 * cases of blank characters to eol
    			 */
    
    			right = skip_spc_right(scr, BORDER);
    			if (scr->mouse > scr->orig_start) {
    				/* the selection goes to the lower part of
    				   the screen */
    
    				/* remove the previous cursor, start of
    				   selection is now next line */
    				inverse_char(scr, scr->cpy_start);
    				scr->cpy_start += (right + 1);
    				scr->cpy_end = scr->cpy_start;
    				scr->orig_start = scr->cpy_start;
    				/* simulate the initial mark */
    				inverse_char(scr, scr->cpy_start);
    			} else {
    				/* the selection goes to the upper part
    				   of the screen */
    				/* remove the previous cursor, start of
    				   selection is now at the eol */
    				inverse_char(scr, scr->cpy_start);
    				scr->orig_start += (right + 1);
    				scr->cpy_start = scr->orig_start - 1;
    				scr->cpy_end = scr->orig_start - 1;
    				/* simulate the initial mark */
    				inverse_char(scr, scr->cpy_start);
    			}
    			CLR(scr->mouse_flags, BLANK_TO_EOL);
    		}
    
    		if (scr->mouse < scr->orig_start &&
    		    scr->cpy_end >= scr->orig_start) {
    			/* we go to the upper part of the screen */
    
    			/* reverse the old selection region */
    			remove_selection(scr);
    			scr->cpy_end = scr->orig_start - 1;
    			scr->cpy_start = scr->orig_start;
    		}
    		if (scr->cpy_start < scr->orig_start &&
    		    scr->mouse >= scr->orig_start) {
    			/* we go to the lower part of the screen */
    
    			/* reverse the old selection region */
    
    			remove_selection(scr);
    			scr->cpy_start = scr->orig_start;
    			scr->cpy_end = scr->orig_start - 1;
    		}
    		/* restore flags cleared in remove_selection() */
    		SET(scr->mouse_flags, SEL_IN_PROGRESS | SEL_EXISTS);
    	}
    
    	if (scr->mouse >= scr->orig_start) {
    		/* lower part of the screen */
    		if (scr->mouse > scr->cpy_end) {
    			/* extending selection */
    			inverse_region(scr, scr->cpy_end + 1, scr->mouse);
    		} else {
    			/* reducing selection */
    			inverse_region(scr, scr->mouse + 1, scr->cpy_end);
    		}
    		scr->cpy_end = scr->mouse;
    	} else {
    		/* upper part of the screen */
    		if (scr->mouse < scr->cpy_start) {
    			/* extending selection */
    			inverse_region(scr, scr->mouse, scr->cpy_start - 1);
    		} else {
    			/* reducing selection */
    			inverse_region(scr, scr->cpy_start, scr->mouse - 1);
    		}
    		scr->cpy_start = scr->mouse;
    	}
    }
    
    /*
     * Extend a selected region, word by word
     */
    void
    mouse_copy_extend_word(struct wsscreen *scr)
    {
    	u_int old_cpy_end;
    	u_int old_cpy_start;
    
    	if (!ISSET(scr->mouse_flags, SEL_EXT_AFTER)) {
    		/* remove cursor in selection (black one) */
    		if (scr->cursor != scr->cpy_end + 1)
    			inverse_char(scr, scr->cursor);
    
    		/* now, switch between lower and upper part of the screen */
    		if (scr->mouse < scr->orig_start &&
    		    scr->cpy_end >= scr->orig_start) {
    			/* going to the upper part of the screen */
    			inverse_region(scr, scr->orig_end + 1, scr->cpy_end);
    			scr->cpy_end = scr->orig_end;
    		}
    
    		if (scr->mouse > scr->orig_end &&
    		    scr->cpy_start <= scr->orig_start) {
    			/* going to the lower part of the screen */
    			inverse_region(scr, scr->cpy_start,
    			    scr->orig_start - 1);
    			scr->cpy_start = scr->orig_start;
    		}
    	}
    
    	if (scr->mouse >= scr->orig_start) {
    		/* lower part of the screen */
    		if (scr->mouse > scr->cpy_end) {
    			/* extending selection */
    			old_cpy_end = scr->cpy_end;
    			scr->cpy_end = scr->mouse +
    			    skip_char_right(scr, scr->mouse);
    			inverse_region(scr, old_cpy_end + 1, scr->cpy_end);
    		} else {
    			if (class_cmp(scr, scr->mouse, scr->mouse + 1)) {
    				/* reducing selection (remove last word) */
    				old_cpy_end = scr->cpy_end;
    				scr->cpy_end = scr->mouse;
    				inverse_region(scr, scr->cpy_end + 1,
    				    old_cpy_end);
    			} else {
    				old_cpy_end = scr->cpy_end;
    				scr->cpy_end = scr->mouse +
    				    skip_char_right(scr, scr->mouse);
    				if (scr->cpy_end != old_cpy_end) {
    					/* reducing selection, from the end of
    					 * next word */
    					inverse_region(scr, scr->cpy_end + 1,
    					    old_cpy_end);
    				}
    			}
    		}
    	} else {
    		/* upper part of the screen */
    		if (scr->mouse < scr->cpy_start) {
    			/* extending selection */
    			old_cpy_start = scr->cpy_start;
    			scr->cpy_start = scr->mouse -
    			    skip_char_left(scr, scr->mouse);
    			inverse_region(scr, scr->cpy_start, old_cpy_start - 1);
    		} else {
    			if (class_cmp(scr, scr->mouse - 1, scr->mouse)) {
    				/* reducing selection (remove last word) */
    				old_cpy_start = scr->cpy_start;
    				scr->cpy_start = scr->mouse;
    				inverse_region(scr, old_cpy_start,
    				    scr->cpy_start - 1);
    			} else {
    				old_cpy_start = scr->cpy_start;
    				scr->cpy_start = scr->mouse -
    				    skip_char_left(scr, scr->mouse);
    				if (scr->cpy_start != old_cpy_start) {
    					inverse_region(scr, old_cpy_start,
    					    scr->cpy_start - 1);
    				}
    			}
    		}
    	}
    
    	if (!ISSET(scr->mouse_flags, SEL_EXT_AFTER)) {
    		/* display new cursor */
    		scr->cursor = scr->mouse;
    		inverse_char(scr, scr->cursor);
    	}
    }
    
    /*
     * Extend a selected region, line by line
     */
    void
    mouse_copy_extend_line(struct wsscreen *scr)
    {
    	struct wsscreen_internal *dconf = scr->scr_dconf;
    	u_int old_row;
    	u_int new_row;
    	u_int old_cpy_start;
    	u_int old_cpy_end;
    
    	if (!ISSET(scr->mouse_flags, SEL_EXT_AFTER)) {
    		/* remove cursor in selection (black one) */
    		if (scr->cursor != scr->cpy_end + 1)
    			inverse_char(scr, scr->cursor);
    
    		/* now, switch between lower and upper part of the screen */
    		if (scr->mouse < scr->orig_start &&
    		    scr->cpy_end >= scr->orig_start) {
    			/* going to the upper part of the screen */
    			inverse_region(scr, scr->orig_end + 1, scr->cpy_end);
    			scr->cpy_end = scr->orig_end;
    		}
    
    		if (scr->mouse > scr->orig_end &&
    		    scr->cpy_start <= scr->orig_start) {
    			/* going to the lower part of the screen */
    			inverse_region(scr, scr->cpy_start,
    			    scr->orig_start - 1);
    			scr->cpy_start = scr->orig_start;
    		}
    	}
    
    	if (scr->mouse >= scr->orig_start) {
    		/* lower part of the screen */
    		if (scr->cursor == scr->cpy_end + 1)
    			scr->cursor = scr->cpy_end;
    		old_row = scr->cursor / N_COLS(dconf);
    		new_row = scr->mouse / N_COLS(dconf);
    		old_cpy_end = scr->cpy_end;
    		scr->cpy_end = new_row * N_COLS(dconf) + MAXCOL(dconf);
    		if (new_row > old_row)
    			inverse_region(scr, old_cpy_end + 1, scr->cpy_end);
    		else if (new_row < old_row)
    			inverse_region(scr, scr->cpy_end + 1, old_cpy_end);
    	} else {
    		/* upper part of the screen */
    		old_row = scr->cursor / N_COLS(dconf);
    		new_row = scr->mouse / N_COLS(dconf);
    		old_cpy_start = scr->cpy_start;
    		scr->cpy_start = new_row * N_COLS(dconf);
    		if (new_row < old_row)
    			inverse_region(scr, scr->cpy_start, old_cpy_start - 1);
    		else if (new_row > old_row)
    			inverse_region(scr, old_cpy_start, scr->cpy_start - 1);
    	}
    
    	if (!ISSET(scr->mouse_flags, SEL_EXT_AFTER)) {
    		/* display new cursor */
    		scr->cursor = scr->mouse;
    		inverse_char(scr, scr->cursor);
    	}
    }
    
    /*
     * Add an extension to a selected region, word by word
     */
    void
    mouse_copy_extend_after(struct wsscreen *scr)
    {
    	u_int start_dist;
    	u_int end_dist;
    
    	if (ISSET(scr->mouse_flags, SEL_EXISTS)) {
    		SET(scr->mouse_flags, SEL_EXT_AFTER);
    		mouse_hide(scr); /* hide current cursor */
    
    		if (scr->cpy_start > scr->mouse)
    			start_dist = scr->cpy_start - scr->mouse;
    		else
    			start_dist = scr->mouse - scr->cpy_start;
    		if (scr->mouse > scr->cpy_end)
    			end_dist = scr->mouse - scr->cpy_end;
    		else
    			end_dist = scr->cpy_end - scr->mouse;
    		if (start_dist < end_dist) {
    			/* upper part of the screen*/
    			scr->orig_start = scr->mouse + 1;
    			/* only used in mouse_copy_extend_line() */
    			scr->cursor = scr->cpy_start;
    		} else {
    			/* lower part of the screen */
    			scr->orig_start = scr->mouse;
    			/* only used in mouse_copy_extend_line() */
    			scr->cursor = scr->cpy_end;
    		}
    		if (ISSET(scr->mouse_flags, SEL_BY_CHAR))
    			mouse_copy_extend_char(scr);
    		if (ISSET(scr->mouse_flags, SEL_BY_WORD))
    			mouse_copy_extend_word(scr);
    		if (ISSET(scr->mouse_flags, SEL_BY_LINE))
    			mouse_copy_extend_line(scr);
    		mouse_copy_selection(scr);
    	}
    }
    
    void
    mouse_hide(struct wsscreen *scr)
    {
    	if (ISSET(scr->mouse_flags, MOUSE_VISIBLE)) {
    		inverse_char(scr, scr->mouse);
    		CLR(scr->mouse_flags, MOUSE_VISIBLE);
    	}
    }
    
    /*
     * Remove a previously selected region
     */
    void
    remove_selection(struct wsscreen *scr)
    {
    	if (ISSET(scr->mouse_flags, SEL_EXT_AFTER)) {
    		/* reset the flag indicating an extension of selection */
    		CLR(scr->mouse_flags, SEL_EXT_AFTER);
    	}
    	inverse_region(scr, scr->cpy_start, scr->cpy_end);
    	CLR(scr->mouse_flags, SEL_IN_PROGRESS | SEL_EXISTS);
    }
    
    /*
     * Put the current visual selection in the selection buffer
     */
    void
    mouse_copy_selection(struct wsscreen *scr)
    {
    	struct wsscreen_internal *dconf = scr->scr_dconf;
    	struct wsdisplay_charcell cell;
    	u_int current = 0;
    	u_int blank = current;
    	u_int buf_end = (N_COLS(dconf) + 1) * N_ROWS(dconf);
    	u_int sel_cur;
    	u_int sel_end;
    
    	sel_cur = scr->cpy_start;
    	sel_end = scr->cpy_end;
    
    	while (sel_cur <= sel_end && current < buf_end - 1) {
    		if (GETCHAR(scr, sel_cur, &cell) != 0)
    			break;
    		scr->sc->sc_copybuffer[current] = cell.uc;
    		if (!IS_SPACE(cell.uc))
    			blank = current + 1; /* first blank after non-blank */
    		current++;
    		if (sel_cur % N_COLS(dconf) == MAXCOL(dconf)) {
    			/*
    			 * If we are on the last column of the screen,
    			 * insert a carriage return.
    			 */
    			scr->sc->sc_copybuffer[blank] = '\r';
    			current = ++blank;
    		}
    		sel_cur++;
    	}
    
    	scr->sc->sc_copybuffer[current] = '\0';
    }
    
    /*
     * Paste the current selection
     */
    void
    mouse_paste(struct wsscreen *scr)
    {
    	char *current = scr->sc->sc_copybuffer;
    	struct tty *tp;
    	u_int len;
    
    	if (ISSET(scr->sc->sc_flags, SC_PASTE_AVAIL)) {
    		if (!WSSCREEN_HAS_TTY(scr))
    			return;
    
    		tp = scr->scr_tty;
    		for (len = strlen(scr->sc->sc_copybuffer); len != 0; len--)
    			(*linesw[tp->t_line].l_rint)(*current++, tp);
    	}
    }
    
    #ifdef HAVE_SCROLLBACK_SUPPORT
    /*
     * Handle the z axis.
     * The z axis (roller or wheel) is mapped by default to scrollback.
     */
    void
    mouse_zaxis(struct wsscreen *scr, int z)
    {
    	if (z < 0)
    		wsscrollback(scr->sc, WSDISPLAY_SCROLL_BACKWARD);
    	else
    		wsscrollback(scr->sc, WSDISPLAY_SCROLL_FORWARD);
    }
    #endif
    
    /*
     * Allocate the copy buffer. The size is:
     * (cols + 1) * (rows)
     * (+1 for '\n' at the end of lines),
     * where cols and rows are the maximum of column and rows of all screens.
     */
    void
    allocate_copybuffer(struct wsdisplay_softc *sc)
    {
    	int nscreens = sc->sc_scrdata->nscreens;
    	int i, s;
    	const struct wsscreen_descr **screens_list = sc->sc_scrdata->screens;
    	const struct wsscreen_descr *current;
    	u_int size = sc->sc_copybuffer_size;
    
    	s = spltty();
    	for (i = 0; i < nscreens; i++) {
    		current = *screens_list;
    		if ((current->ncols + 1) * current->nrows > size)
    			size = (current->ncols + 1) * current->nrows;
    		screens_list++;
    	}
    	if (size != sc->sc_copybuffer_size && sc->sc_copybuffer_size != 0) {
    		bzero(sc->sc_copybuffer, sc->sc_copybuffer_size);
    		free(sc->sc_copybuffer, M_DEVBUF, sc->sc_copybuffer_size);
    	}
    	if ((sc->sc_copybuffer = (char *)malloc(size, M_DEVBUF, M_NOWAIT)) ==
    	    NULL) {
    		printf("%s: couldn't allocate copy buffer\n",
    		    sc->sc_dv.dv_xname);
    		size = 0;
    	}
    	sc->sc_copybuffer_size = size;
    	splx(s);
    }
    
    /* Remove selection and cursor on current screen */
    void
    mouse_remove(struct wsscreen *scr)
    {
    	if (ISSET(scr->mouse_flags, SEL_EXISTS))
    		remove_selection(scr);
    
    	mouse_hide(scr);
    }
    
    #endif /* HAVE_WSMOUSED_SUPPORT */