/*
 * Copyright 2002-2003 Red Hat Inc., Durham, North Carolina.
 *
 * All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation on the rights to use, copy, modify, merge,
 * publish, distribute, sublicense, and/or sell copies of the Software,
 * and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the
 * next paragraph) shall be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NON-INFRINGEMENT.  IN NO EVENT SHALL RED HAT AND/OR THEIR SUPPLIERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/*
 * Authors:
 *   Rickard E. (Rik) Faith <faith@redhat.com>
 *
 */

/** \file
 * This file provides generic input support.  Functions here set up
 * input and lead to the calling of low-level device drivers for
 * input. */

#ifdef HAVE_DMX_CONFIG_H
#include <dmx-config.h>
#endif

#define DMX_WINDOW_DEBUG 0

#include "dmxinputinit.h"
#include "dmxextension.h"       /* For dmxInputCount */

#include "dmxdummy.h"
#include "dmxbackend.h"
#include "dmxconsole.h"
#include "dmxcommon.h"
#include "dmxevents.h"
#include "dmxmotion.h"
#include "dmxprop.h"
#include "config/dmxconfig.h"
#include "dmxcursor.h"

#include "lnx-keyboard.h"
#include "lnx-ms.h"
#include "lnx-ps2.h"
#include "usb-keyboard.h"
#include "usb-mouse.h"
#include "usb-other.h"
#include "usb-common.h"

#include "dmxsigio.h"
#include "dmxarg.h"

#include "inputstr.h"
#include "input.h"
#include "mipointer.h"
#include "windowstr.h"
#include "mi.h"
#include "xkbsrv.h"

#include <X11/extensions/XI.h>
#include <X11/extensions/XIproto.h>
#include "exevents.h"
#include "extinit.h"

DMXLocalInputInfoPtr dmxLocalCorePointer, dmxLocalCoreKeyboard;

static DMXLocalInputInfoRec DMXDummyMou = {
    "dummy-mou", DMX_LOCAL_MOUSE, DMX_LOCAL_TYPE_LOCAL, 1,
    NULL, NULL, NULL, NULL, NULL, dmxDummyMouGetInfo
};

static DMXLocalInputInfoRec DMXDummyKbd = {
    "dummy-kbd", DMX_LOCAL_KEYBOARD, DMX_LOCAL_TYPE_LOCAL, 1,
    NULL, NULL, NULL, NULL, NULL, dmxDummyKbdGetInfo
};

static DMXLocalInputInfoRec DMXBackendMou = {
    "backend-mou", DMX_LOCAL_MOUSE, DMX_LOCAL_TYPE_BACKEND, 2,
    dmxBackendCreatePrivate, dmxBackendDestroyPrivate,
    dmxBackendInit, NULL, dmxBackendLateReInit, dmxBackendMouGetInfo,
    dmxCommonMouOn, dmxCommonMouOff, dmxBackendUpdatePosition,
    NULL, NULL, NULL,
    dmxBackendCollectEvents, dmxBackendProcessInput, dmxBackendFunctions, NULL,
    dmxCommonMouCtrl
};

static DMXLocalInputInfoRec DMXBackendKbd = {
    "backend-kbd", DMX_LOCAL_KEYBOARD, DMX_LOCAL_TYPE_BACKEND,
    1, /* With backend-mou or console-mou */
    dmxCommonCopyPrivate, NULL,
    dmxBackendInit, NULL, NULL, dmxBackendKbdGetInfo,
    dmxCommonKbdOn, dmxCommonKbdOff, NULL,
    NULL, NULL, NULL,
    NULL, NULL, NULL, NULL,
    NULL, dmxCommonKbdCtrl, dmxCommonKbdBell
};

static DMXLocalInputInfoRec DMXConsoleMou = {
    "console-mou", DMX_LOCAL_MOUSE, DMX_LOCAL_TYPE_CONSOLE, 2,
    dmxConsoleCreatePrivate, dmxConsoleDestroyPrivate,
    dmxConsoleInit, dmxConsoleReInit, NULL, dmxConsoleMouGetInfo,
    dmxCommonMouOn, dmxCommonMouOff, dmxConsoleUpdatePosition,
    NULL, NULL, NULL,
    dmxConsoleCollectEvents, NULL, dmxConsoleFunctions, dmxConsoleUpdateInfo,
    dmxCommonMouCtrl
};

static DMXLocalInputInfoRec DMXConsoleKbd = {
    "console-kbd", DMX_LOCAL_KEYBOARD, DMX_LOCAL_TYPE_CONSOLE,
    1, /* With backend-mou or console-mou */
    dmxCommonCopyPrivate, NULL,
    dmxConsoleInit, dmxConsoleReInit, NULL, dmxConsoleKbdGetInfo,
    dmxCommonKbdOn, dmxCommonKbdOff, NULL,
    NULL, NULL, NULL,
    NULL, NULL, NULL, NULL,
    NULL, dmxCommonKbdCtrl, dmxCommonKbdBell
};

static DMXLocalInputInfoRec DMXCommonOth = {
    "common-oth", DMX_LOCAL_OTHER, DMX_LOCAL_TYPE_COMMON, 1,
    dmxCommonCopyPrivate, NULL,
    NULL, NULL, NULL, dmxCommonOthGetInfo,
    dmxCommonOthOn, dmxCommonOthOff
};


static DMXLocalInputInfoRec DMXLocalDevices[] = {
                                /* Dummy drivers that can compile on any OS */
#ifdef __linux__
                                /* Linux-specific drivers */
    {
        "kbd", DMX_LOCAL_KEYBOARD, DMX_LOCAL_TYPE_LOCAL, 1,
        kbdLinuxCreatePrivate, kbdLinuxDestroyPrivate,
        kbdLinuxInit, NULL, NULL, kbdLinuxGetInfo,
        kbdLinuxOn, kbdLinuxOff, NULL,
        kbdLinuxVTPreSwitch, kbdLinuxVTPostSwitch, kbdLinuxVTSwitch,
        kbdLinuxRead, NULL, NULL, NULL,
        NULL, kbdLinuxCtrl, kbdLinuxBell
    },
    {
        "ms", DMX_LOCAL_MOUSE, DMX_LOCAL_TYPE_LOCAL, 1,
        msLinuxCreatePrivate, msLinuxDestroyPrivate,
        msLinuxInit, NULL, NULL, msLinuxGetInfo,
        msLinuxOn, msLinuxOff, NULL,
        msLinuxVTPreSwitch, msLinuxVTPostSwitch, NULL,
        msLinuxRead
    },
    {
        "ps2", DMX_LOCAL_MOUSE, DMX_LOCAL_TYPE_LOCAL, 1,
        ps2LinuxCreatePrivate, ps2LinuxDestroyPrivate,
        ps2LinuxInit, NULL, NULL, ps2LinuxGetInfo,
        ps2LinuxOn, ps2LinuxOff, NULL,
        ps2LinuxVTPreSwitch, ps2LinuxVTPostSwitch, NULL,
        ps2LinuxRead
    },
#endif
#ifdef __linux__
                                /* USB drivers, currently only for
                                   Linux, but relatively easy to port to
                                   other OSs */
    {
        "usb-kbd", DMX_LOCAL_KEYBOARD, DMX_LOCAL_TYPE_LOCAL, 1,
        usbCreatePrivate, usbDestroyPrivate,
        kbdUSBInit, NULL, NULL, kbdUSBGetInfo,
        kbdUSBOn, usbOff, NULL,
        NULL, NULL, NULL,
        kbdUSBRead, NULL, NULL, NULL,
        NULL, kbdUSBCtrl
    },
    {
        "usb-mou", DMX_LOCAL_MOUSE, DMX_LOCAL_TYPE_LOCAL, 1,
        usbCreatePrivate, usbDestroyPrivate,
        mouUSBInit, NULL, NULL, mouUSBGetInfo,
        mouUSBOn, usbOff, NULL,
        NULL, NULL, NULL,
        mouUSBRead
    },
    {
        "usb-oth", DMX_LOCAL_OTHER, DMX_LOCAL_TYPE_LOCAL, 1,
        usbCreatePrivate, usbDestroyPrivate,
        othUSBInit, NULL, NULL, othUSBGetInfo,
        othUSBOn, usbOff, NULL,
        NULL, NULL, NULL,
        othUSBRead
    },
#endif
    {
        "dummy-mou", DMX_LOCAL_MOUSE, DMX_LOCAL_TYPE_LOCAL, 1,
        NULL, NULL, NULL, NULL, NULL, dmxDummyMouGetInfo
    },
    {
        "dummy-kbd", DMX_LOCAL_KEYBOARD, DMX_LOCAL_TYPE_LOCAL, 1,
        NULL, NULL, NULL, NULL, NULL, dmxDummyKbdGetInfo
    },
    { NULL }                    /* Must be last */
};


#if 11 /*BP*/
void
DDXRingBell(int volume, int pitch, int duration)
{
   /* NO-OP */
}

/* taken from kdrive/src/kinput.c: */
static void
dmxKbdCtrl (DeviceIntPtr pDevice, KeybdCtrl *ctrl)
{
#if 0
    KdKeyboardInfo *ki;

    for (ki = kdKeyboards; ki; ki = ki->next) {
        if (ki->dixdev && ki->dixdev->id == pDevice->id)
            break;
    }

    if (!ki || !ki->dixdev || ki->dixdev->id != pDevice->id || !ki->driver)
        return;

    KdSetLeds(ki, ctrl->leds);
    ki->bellPitch = ctrl->bell_pitch;
    ki->bellDuration = ctrl->bell_duration; 
#endif
}

/* taken from kdrive/src/kinput.c: */
static void
dmxBell(int volume, DeviceIntPtr pDev, pointer arg, int something)
{
#if 0
    KeybdCtrl *ctrl = arg;
    KdKeyboardInfo *ki = NULL;
    
    for (ki = kdKeyboards; ki; ki = ki->next) {
        if (ki->dixdev && ki->dixdev->id == pDev->id)
            break;
    }

    if (!ki || !ki->dixdev || ki->dixdev->id != pDev->id || !ki->driver)
        return;
    
    KdRingBell(ki, volume, ctrl->bell_pitch, ctrl->bell_duration);
#endif
}

#endif /*BP*/

static void _dmxChangePointerControl(DMXLocalInputInfoPtr dmxLocal,
                                     PtrCtrl *ctrl)
{
    if (!dmxLocal) return;
    dmxLocal->mctrl = *ctrl;
    if (dmxLocal->mCtrl) dmxLocal->mCtrl(&dmxLocal->pDevice->public, ctrl);
}

/** Change the pointer control information for the \a pDevice.  If the
 * device sends core events, then also change the control information
 * for all of the pointer devices that send core events. */
void dmxChangePointerControl(DeviceIntPtr pDevice, PtrCtrl *ctrl)
{
    GETDMXLOCALFROMPDEVICE;
    int i, j;

    if (dmxLocal->sendsCore) {       /* Do for all core devices */
        for (i = 0; i < dmxNumInputs; i++) {
            DMXInputInfo *dmxInput = &dmxInputs[i];
            if (dmxInput->detached) continue;
            for (j = 0; j < dmxInput->numDevs; j++)
                if (dmxInput->devs[j]->sendsCore)
                    _dmxChangePointerControl(dmxInput->devs[j], ctrl);
        }
    } else {                    /* Do for this device only */
        _dmxChangePointerControl(dmxLocal, ctrl);
    }
}

static void _dmxKeyboardKbdCtrlProc(DMXLocalInputInfoPtr dmxLocal,
                                    KeybdCtrl *ctrl)
{
    dmxLocal->kctrl = *ctrl;
    if (dmxLocal->kCtrl) {
        dmxLocal->kCtrl(&dmxLocal->pDevice->public, ctrl);
        if (dmxLocal->pDevice->kbdfeed) {
            XkbEventCauseRec cause;
            XkbSetCauseUnknown(&cause);
            /* Generate XKB events, as necessary */
            XkbUpdateIndicators(dmxLocal->pDevice, XkbAllIndicatorsMask, False,
                                NULL, &cause);
        }
    }
}


/** Change the keyboard control information for the \a pDevice.  If the
 * device sends core events, then also change the control information
 * for all of the keyboard devices that send core events. */
void dmxKeyboardKbdCtrlProc(DeviceIntPtr pDevice, KeybdCtrl *ctrl)
{
    GETDMXLOCALFROMPDEVICE;
    int i, j;

    if (dmxLocal->sendsCore) {       /* Do for all core devices */
        for (i = 0; i < dmxNumInputs; i++) {
            DMXInputInfo *dmxInput = &dmxInputs[i];
            if (dmxInput->detached) continue;
            for (j = 0; j < dmxInput->numDevs; j++)
                if (dmxInput->devs[j]->sendsCore)
                    _dmxKeyboardKbdCtrlProc(dmxInput->devs[j], ctrl);
        }
    } else {                    /* Do for this device only */
        _dmxKeyboardKbdCtrlProc(dmxLocal, ctrl);
    }
}

static void _dmxKeyboardBellProc(DMXLocalInputInfoPtr dmxLocal, int percent)
{
    if (dmxLocal->kBell) dmxLocal->kBell(&dmxLocal->pDevice->public,
                                         percent,
                                         dmxLocal->kctrl.bell,
                                         dmxLocal->kctrl.bell_pitch,
                                         dmxLocal->kctrl.bell_duration);
}

/** Sound the bell on the device.  If the device send core events, then
 * sound the bell on all of the devices that send core events. */
void dmxKeyboardBellProc(int percent, DeviceIntPtr pDevice,
                         pointer ctrl, int unknown)
{
    GETDMXLOCALFROMPDEVICE;
    int i, j;

    if (dmxLocal->sendsCore) {       /* Do for all core devices */
        for (i = 0; i < dmxNumInputs; i++) {
            DMXInputInfo *dmxInput = &dmxInputs[i];
            if (dmxInput->detached) continue;
            for (j = 0; j < dmxInput->numDevs; j++)
                if (dmxInput->devs[j]->sendsCore)
                    _dmxKeyboardBellProc(dmxInput->devs[j], percent);
        }
    } else {                    /* Do for this device only */
        _dmxKeyboardBellProc(dmxLocal, percent);
    }
}

static void dmxKeyboardFreeNames(XkbComponentNamesPtr names)
{
    if (names->keycodes) XFree(names->keycodes);
    if (names->types)    XFree(names->types);
    if (names->compat)   XFree(names->compat);
    if (names->symbols)  XFree(names->symbols);
    if (names->geometry) XFree(names->geometry);
}


static int dmxKeyboardOn(DeviceIntPtr pDevice, DMXLocalInitInfo *info)
{
    GETDMXINPUTFROMPDEVICE;
    XkbRMLVOSet rmlvo;

    rmlvo.rules = dmxConfigGetXkbRules();
    rmlvo.model = dmxConfigGetXkbModel();
    rmlvo.layout = dmxConfigGetXkbLayout();
    rmlvo.variant = dmxConfigGetXkbVariant();
    rmlvo.options = dmxConfigGetXkbOptions();

    XkbSetRulesDflts(&rmlvo);
    if (!info->force && (dmxInput->keycodes
                         || dmxInput->symbols
                         || dmxInput->geometry)) {
        if (info->freenames) dmxKeyboardFreeNames(&info->names);
        info->freenames      = 0;
        info->names.keycodes = dmxInput->keycodes;
        info->names.types    = NULL;
        info->names.compat   = NULL;
        info->names.symbols  = dmxInput->symbols;
        info->names.geometry = dmxInput->geometry;

        dmxLogInput(dmxInput, "XKEYBOARD: From command line: %s",
                    info->names.keycodes);
        if (info->names.symbols && *info->names.symbols)
            dmxLogInputCont(dmxInput, " %s", info->names.symbols);
        if (info->names.geometry && *info->names.geometry)
            dmxLogInputCont(dmxInput, " %s", info->names.geometry);
        dmxLogInputCont(dmxInput, "\n");
    } else if (info->names.keycodes) {
        dmxLogInput(dmxInput, "XKEYBOARD: From device: %s",
                    info->names.keycodes);
        if (info->names.symbols && *info->names.symbols)
            dmxLogInputCont(dmxInput, " %s", info->names.symbols);
        if (info->names.geometry && *info->names.geometry)
            dmxLogInputCont(dmxInput, " %s", info->names.geometry);
        dmxLogInputCont(dmxInput, "\n");
    } else {
        dmxLogInput(dmxInput, "XKEYBOARD: Defaults: %s %s %s %s %s\n",
                    dmxConfigGetXkbRules(),
                    dmxConfigGetXkbLayout(),
                    dmxConfigGetXkbModel(),
                    dmxConfigGetXkbVariant()
                    ? dmxConfigGetXkbVariant() : "",
                    dmxConfigGetXkbOptions()
                    ? dmxConfigGetXkbOptions() : "");
    }
    InitKeyboardDeviceStruct(pDevice, &rmlvo,
                                dmxKeyboardBellProc,
                                dmxKeyboardKbdCtrlProc);

    if (info->freenames) dmxKeyboardFreeNames(&info->names);

    return Success;
}

    
static int dmxDeviceOnOff(DeviceIntPtr pDevice, int what)
{
    GETDMXINPUTFROMPDEVICE;
    int              fd;
    DMXLocalInitInfo info;
    int              i;
    Atom             btn_labels[MAX_BUTTONS] = {0}; /* FIXME */
    Atom             axis_labels[MAX_VALUATORS] = {0}; /* FIXME */

    if (dmxInput->detached) return Success;

    memset(&info, 0, sizeof(info));
    switch (what) {
    case DEVICE_INIT:
        if (dmxLocal->init)
            dmxLocal->init(pDev);
        if (dmxLocal->get_info)
            dmxLocal->get_info(pDev, &info);
        if (info.keyboard) {    /* XKEYBOARD makes this a special case */
            dmxKeyboardOn(pDevice, &info);
            break;
        }
        if (info.keyClass) {
            XkbRMLVOSet rmlvo;

            rmlvo.rules = dmxConfigGetXkbRules();
            rmlvo.model = dmxConfigGetXkbModel();
            rmlvo.layout = dmxConfigGetXkbLayout();
            rmlvo.variant = dmxConfigGetXkbVariant();
            rmlvo.options = dmxConfigGetXkbOptions();

            InitKeyboardDeviceStruct(pDevice,
                                     &rmlvo,
                                     dmxBell, dmxKbdCtrl);
        }
        if (info.buttonClass) {
            InitButtonClassDeviceStruct(pDevice, info.numButtons,
                                        btn_labels, info.map);
        }
        if (info.valuatorClass) {
            if (info.numRelAxes && dmxLocal->sendsCore) {
                InitValuatorClassDeviceStruct(pDevice, info.numRelAxes,
                                              axis_labels,
                                              GetMaximumEventsNum(),
                                              Relative);
                for (i = 0; i < info.numRelAxes; i++)
                    InitValuatorAxisStruct(pDevice, i, axis_labels[i],
                                           info.minval[i], info.maxval[i],
                                           info.res[i],
                                           info.minres[i], info.maxres[i]);
            } else if (info.numRelAxes) {
                InitValuatorClassDeviceStruct(pDevice, info.numRelAxes,
                                              axis_labels,
                                              dmxPointerGetMotionBufferSize(),
                                              Relative);
                for (i = 0; i < info.numRelAxes; i++)
                    InitValuatorAxisStruct(pDevice, i, axis_labels[i],
                                           info.minval[i],
                                           info.maxval[i], info.res[i],
                                           info.minres[i], info.maxres[i]);
            } else if (info.numAbsAxes) {
                InitValuatorClassDeviceStruct(pDevice, info.numAbsAxes,
                                              axis_labels,
                                              dmxPointerGetMotionBufferSize(),
                                              Absolute);
                for (i = 0; i < info.numAbsAxes; i++)
                    InitValuatorAxisStruct(pDevice, i,
                                           axis_labels[i],
                                           info.minval[i], info.maxval[i],
                                           info.res[i], info.minres[i],
                                           info.maxres[i]);
            }
        }
        if (info.focusClass)       InitFocusClassDeviceStruct(pDevice);
        if (info.proximityClass)   InitProximityClassDeviceStruct(pDevice);
        if (info.ptrFeedbackClass)
            InitPtrFeedbackClassDeviceStruct(pDevice, dmxChangePointerControl);
        if (info.intFeedbackClass || info.strFeedbackClass)
            dmxLog(dmxWarning,
                   "Integer and string feedback not supported for %s\n",
                   pDevice->name);
        if (!info.keyboard && (info.ledFeedbackClass || info.belFeedbackClass))
            dmxLog(dmxWarning,
                   "Led and bel feedback not supported for non-keyboard %s\n",
                   pDevice->name);
        break;
    case DEVICE_ON:
        if (!pDev->on) {
            if (dmxLocal->on && (fd = dmxLocal->on(pDev)) >= 0)
                dmxSigioRegister(dmxInput, fd);
            pDev->on = TRUE;
        }
        break;
    case DEVICE_OFF:
    case DEVICE_CLOSE:
            /* This can get called twice consecutively: once for a
             * detached screen (DEVICE_OFF), and then again at server
             * generation time (DEVICE_CLOSE). */
        if (pDev->on) {
            dmxSigioUnregister(dmxInput);
            if (dmxLocal->off) dmxLocal->off(pDev);
            pDev->on = FALSE;
        }
        break;
    }
    if (info.keySyms.map && info.freemap) {
        XFree(info.keySyms.map);
        info.keySyms.map = NULL;
    }
    if (info.xkb) XkbFreeKeyboard(info.xkb, 0, True);
    return Success;
}

static void dmxProcessInputEvents(DMXInputInfo *dmxInput)
{
    int i;

    mieqProcessInputEvents();
#if 00 /*BP*/
    miPointerUpdate();
#endif
    if (dmxInput->detached)
        return;
    for (i = 0; i < dmxInput->numDevs; i += dmxInput->devs[i]->binding)
        if (dmxInput->devs[i]->process_input) {
#if 11 /*BP*/
            miPointerUpdateSprite(dmxInput->devs[i]->pDevice);
#endif
            dmxInput->devs[i]->process_input(dmxInput->devs[i]->private);
        }

#if 11 /*BP*/
    mieqProcessInputEvents();
#endif
}

static void dmxUpdateWindowInformation(DMXInputInfo *dmxInput,
                                       DMXUpdateType type,
                                       WindowPtr pWindow)
{
    int i;

#ifdef PANORAMIX
    if (!noPanoramiXExtension && pWindow && pWindow->parent != screenInfo.screens[0]->root)
        return;
#endif
#if DMX_WINDOW_DEBUG
    {
        const char *name = "Unknown";
        switch (type) {
        case DMX_UPDATE_REALIZE:            name = "Realize";         break;
        case DMX_UPDATE_UNREALIZE:          name = "Unrealize";       break;
        case DMX_UPDATE_RESTACK:            name = "Restack";         break;
        case DMX_UPDATE_COPY:               name = "Copy";            break;
        case DMX_UPDATE_RESIZE:             name = "Resize";          break;
        case DMX_UPDATE_REPARENT:           name = "Repaint";         break;
        }
        dmxLog(dmxDebug, "Window %p changed: %s\n", pWindow, name);
    }
#endif

    if (dmxInput->detached)
        return;
    for (i = 0; i < dmxInput->numDevs; i += dmxInput->devs[i]->binding)
        if (dmxInput->devs[i]->update_info)
            dmxInput->devs[i]->update_info(dmxInput->devs[i]->private,
                                           type, pWindow);
}

static void dmxCollectAll(DMXInputInfo *dmxInput)
{
    int i;

    if (dmxInput->detached)
        return;
    for (i = 0; i < dmxInput->numDevs; i += dmxInput->devs[i]->binding)
        if (dmxInput->devs[i]->collect_events)
            dmxInput->devs[i]->collect_events(&dmxInput->devs[i]->pDevice->public,
                                              dmxMotion,
                                              dmxEnqueue,
                                              dmxCheckSpecialKeys, DMX_BLOCK);
}

static void dmxBlockHandler(pointer blockData, OSTimePtr pTimeout,
                            pointer pReadMask)
{
    DMXInputInfo    *dmxInput = &dmxInputs[(int)blockData];
    static unsigned long generation = 0;
    
    if (generation != serverGeneration) {
        generation = serverGeneration;
        dmxCollectAll(dmxInput);
    }
}

static void dmxSwitchReturn(pointer p)
{
    DMXInputInfo *dmxInput = p;
    int          i;
    
    dmxLog(dmxInfo, "Returning from VT %d\n", dmxInput->vt_switched);

    if (!dmxInput->vt_switched)
        dmxLog(dmxFatal, "dmxSwitchReturn called, but not switched\n");
    dmxSigioEnableInput();
    for (i = 0; i < dmxInput->numDevs; i++)
        if (dmxInput->devs[i]->vt_post_switch)
            dmxInput->devs[i]->vt_post_switch(dmxInput->devs[i]->private);
    dmxInput->vt_switched = 0;
}

static void dmxWakeupHandler(pointer blockData, int result, pointer pReadMask)
{
    DMXInputInfo *dmxInput = &dmxInputs[(int)blockData];
    int          i;

    if (dmxInput->vt_switch_pending) {
        dmxLog(dmxInfo, "Switching to VT %d\n", dmxInput->vt_switch_pending);
        for (i = 0; i < dmxInput->numDevs; i++)
            if (dmxInput->devs[i]->vt_pre_switch)
                dmxInput->devs[i]->vt_pre_switch(dmxInput->devs[i]->private);
        dmxInput->vt_switched       = dmxInput->vt_switch_pending;
        dmxInput->vt_switch_pending = 0;
        for (i = 0; i < dmxInput->numDevs; i++) {
            if (dmxInput->devs[i]->vt_switch) {
                dmxSigioDisableInput();
                if (!dmxInput->devs[i]->vt_switch(dmxInput->devs[i]->private,
                                                  dmxInput->vt_switched,
                                                  dmxSwitchReturn,
                                                  dmxInput))
                    dmxSwitchReturn(dmxInput);
                break;          /* Only call one vt_switch routine */
            }
        }
    }
    dmxCollectAll(dmxInput);
}

static char *dmxMakeUniqueDeviceName(DMXLocalInputInfoPtr dmxLocal)
{
    static int           k = 0;
    static int           m = 0;
    static int           o = 0;
    static unsigned long dmxGeneration = 0;
#define LEN  32
    char *               buf = malloc(LEN);

    if (dmxGeneration != serverGeneration) {
        k = m = o     = 0;
        dmxGeneration = serverGeneration;
    }

    switch (dmxLocal->type) {
    case DMX_LOCAL_KEYBOARD: XmuSnprintf(buf, LEN, "Keyboard%d", k++); break;
    case DMX_LOCAL_MOUSE:    XmuSnprintf(buf, LEN, "Mouse%d", m++);    break;
    default:                 XmuSnprintf(buf, LEN, "Other%d", o++);    break;
    }

    return buf;
}

static DeviceIntPtr dmxAddDevice(DMXLocalInputInfoPtr dmxLocal)
{
    DeviceIntPtr pDevice;
    Atom         atom;
    const char   *name = NULL;
    void         (*registerProcPtr)(DeviceIntPtr)   = NULL;
    char         *devname;
    DMXInputInfo *dmxInput;

    if (!dmxLocal)
        return NULL;
    dmxInput = &dmxInputs[dmxLocal->inputIdx];

    if (dmxLocal->sendsCore) {
        if (dmxLocal->type == DMX_LOCAL_KEYBOARD && !dmxLocalCoreKeyboard) {
            dmxLocal->isCore     = 1;
            dmxLocalCoreKeyboard = dmxLocal;
            name                 = "keyboard";
            registerProcPtr      = RegisterKeyboardDevice;
        }
        if (dmxLocal->type == DMX_LOCAL_MOUSE && !dmxLocalCorePointer) {
            dmxLocal->isCore     = 1;
            dmxLocalCorePointer  = dmxLocal;
            name                 = "pointer";
            registerProcPtr      = RegisterPointerDevice;
        }
    }

    if (!name) {
        name            = "extension";
        registerProcPtr = RegisterOtherDevice;
    }

    if (!name || !registerProcPtr)
        dmxLog(dmxFatal, "Cannot add device %s\n", dmxLocal->name);

    pDevice                       = AddInputDevice(serverClient, dmxDeviceOnOff, TRUE);
    if (!pDevice) {
        dmxLog(dmxError, "Too many devices -- cannot add device %s\n",
               dmxLocal->name);
        return NULL;
    }
    pDevice->public.devicePrivate = dmxLocal;
    dmxLocal->pDevice             = pDevice;

    devname       = dmxMakeUniqueDeviceName(dmxLocal);
    atom          = MakeAtom((char *)devname, strlen(devname), TRUE);
    pDevice->type = atom;
    pDevice->name = devname;

    registerProcPtr(pDevice);

    if (dmxLocal->isCore && dmxLocal->type == DMX_LOCAL_MOUSE) {
#if 00 /*BP*/
        miRegisterPointerDevice(screenInfo.screens[0], pDevice);
#else
        /* Nothing? dmxDeviceOnOff() should get called to init, right? */
#endif
    }

    if (dmxLocal->create_private)
        dmxLocal->private = dmxLocal->create_private(pDevice);

    dmxLogInput(dmxInput, "Added %s as %s device called %s%s\n",
                dmxLocal->name, name, devname,
                dmxLocal->isCore
                ? " [core]"
                : (dmxLocal->sendsCore
                   ? " [sends core events]"
                   : ""));

    return pDevice;
}

static DMXLocalInputInfoPtr dmxLookupLocal(const char *name)
{
    DMXLocalInputInfoPtr pt;
    
    for (pt = &DMXLocalDevices[0]; pt->name; ++pt)
        if (!strcmp(pt->name, name)) return pt; /* search for device name */
    return NULL;
}

/** Copy the local input information from \a s into a new \a devs slot
 * in \a dmxInput. */
DMXLocalInputInfoPtr dmxInputCopyLocal(DMXInputInfo *dmxInput,
                                       DMXLocalInputInfoPtr s)
{
    DMXLocalInputInfoPtr dmxLocal = malloc(sizeof(*dmxLocal));
    
    if (!dmxLocal)
        dmxLog(dmxFatal, "DMXLocalInputInfoPtr: out of memory\n");

    memcpy(dmxLocal, s, sizeof(*dmxLocal));
    dmxLocal->inputIdx       = dmxInput->inputIdx;
    dmxLocal->sendsCore      = dmxInput->core;
    dmxLocal->savedSendsCore = dmxInput->core;
    dmxLocal->deviceId       = -1;

    ++dmxInput->numDevs;
    dmxInput->devs = realloc(dmxInput->devs,
                              dmxInput->numDevs * sizeof(*dmxInput->devs));
    dmxInput->devs[dmxInput->numDevs-1] = dmxLocal;
    
    return dmxLocal;
}

static void dmxPopulateLocal(DMXInputInfo *dmxInput, dmxArg a)
{
    int                  i;
    int                  help = 0;
    DMXLocalInputInfoRec *pt;

    for (i = 1; i < dmxArgC(a); i++) {
        const char *name = dmxArgV(a, i);
        if ((pt = dmxLookupLocal(name))) {
            dmxInputCopyLocal(dmxInput, pt);
        } else {
            if (strlen(name))
                dmxLog(dmxWarning,
                       "Could not find a driver called %s\n", name);
            ++help;
        }
    }
    if (help) {
        dmxLog(dmxInfo, "Available local device drivers:\n");
        for (pt = &DMXLocalDevices[0]; pt->name; ++pt) {
            const char *type;
            switch (pt->type) {
            case DMX_LOCAL_KEYBOARD: type = "keyboard"; break;
            case DMX_LOCAL_MOUSE:    type = "pointer";  break;
            default:                 type = "unknown";  break;
            }
            dmxLog(dmxInfo, "   %s (%s)\n", pt->name, type);
        }
        dmxLog(dmxFatal, "Must have valid local device driver\n");
    }
}

int dmxInputExtensionErrorHandler(Display *dsp, _Xconst char *name, _Xconst char *reason)
{
    return 0;
}

static void dmxInputScanForExtensions(DMXInputInfo *dmxInput, int doXI)
{
    XExtensionVersion    *ext;
    XDeviceInfo          *devices;
    Display              *display;
    int                  num;
    int                  i, j;
    XextErrorHandler     handler;

    if (!(display = XOpenDisplay(dmxInput->name))) return;
    
    /* Print out information about the XInput Extension. */
    handler = XSetExtensionErrorHandler(dmxInputExtensionErrorHandler);
    ext     = XGetExtensionVersion(display, INAME);
    XSetExtensionErrorHandler(handler);
    
    if (!ext || ext == (XExtensionVersion *)NoSuchExtension) {
        dmxLogInput(dmxInput, "%s is not available\n", INAME);
    } else {
        dmxLogInput(dmxInput, "Locating devices on %s (%s version %d.%d)\n",
                    dmxInput->name, INAME,
                    ext->major_version, ext->minor_version);
        devices = XListInputDevices(display, &num);

        XFree(ext);
        ext = NULL;

                                /* Print a list of all devices */
        for (i = 0; i < num; i++) {
            const char *use = "Unknown";
            switch (devices[i].use) {
            case IsXPointer:           use = "XPointer";         break;
            case IsXKeyboard:          use = "XKeyboard";        break;
            case IsXExtensionDevice:   use = "XExtensionDevice"; break;
            case IsXExtensionPointer:  use = "XExtensionPointer"; break;
            case IsXExtensionKeyboard: use = "XExtensionKeyboard"; break;
            }
            dmxLogInput(dmxInput, "  %2d %-10.10s %-16.16s\n",
                        devices[i].id,
                        devices[i].name ? devices[i].name : "",
                        use);
        }

                                /* Search for extensions */
        for (i = 0; i < num; i++) {
            switch (devices[i].use) {
            case IsXKeyboard:
                for (j = 0; j < dmxInput->numDevs; j++) {
                    DMXLocalInputInfoPtr dmxL = dmxInput->devs[j];
                    if (dmxL->type == DMX_LOCAL_KEYBOARD
                        && dmxL->deviceId < 0) {
                        dmxL->deviceId   = devices[i].id;
                        dmxL->deviceName = (devices[i].name
                                            ? strdup(devices[i].name)
                                            : NULL);
                    }
                }
                break;
            case IsXPointer:
                for (j = 0; j < dmxInput->numDevs; j++) {
                    DMXLocalInputInfoPtr dmxL = dmxInput->devs[j];
                    if (dmxL->type == DMX_LOCAL_MOUSE && dmxL->deviceId < 0) {
                        dmxL->deviceId   = devices[i].id;
                        dmxL->deviceName = (devices[i].name
                                            ? xstrdup(devices[i].name)
                                            : NULL);
                    }
                }
                break;
#if 0
            case IsXExtensionDevice:
            case IsXExtensionKeyboard:
            case IsXExtensionPointer:
                if (doXI) {
                    if (!dmxInput->numDevs) {
                        dmxLog(dmxWarning,
                               "Cannot use remote (%s) XInput devices if"
                               " not also using core devices\n",
                               dmxInput->name);
                    } else {
                        dmxLocal             = dmxInputCopyLocal(dmxInput,
                                                                &DMXCommonOth);
                        dmxLocal->isCore     = FALSE;
                        dmxLocal->sendsCore  = FALSE;
                        dmxLocal->deviceId   = devices[i].id;
                        dmxLocal->deviceName = (devices[i].name
                                                ? strdup(devices[i].name)
                                                : NULL);
                    }
                }
                break;
#endif
            }
        }
        XFreeDeviceList(devices);
    }
    XCloseDisplay(display);
}

/** Re-initialize all the devices described in \a dmxInput.  Called from
    #dmxAdjustCursorBoundaries before the cursor is redisplayed. */
void dmxInputReInit(DMXInputInfo *dmxInput)
{
    int i;

    for (i = 0; i < dmxInput->numDevs; i++) {
        DMXLocalInputInfoPtr dmxLocal = dmxInput->devs[i];
        if (dmxLocal->reinit)
            dmxLocal->reinit(&dmxLocal->pDevice->public);
    }
}

/** Re-initialize all the devices described in \a dmxInput.  Called from
    #dmxAdjustCursorBoundaries after the cursor is redisplayed. */
void dmxInputLateReInit(DMXInputInfo *dmxInput)
{
    int i;

    for (i = 0; i < dmxInput->numDevs; i++) {
        DMXLocalInputInfoPtr dmxLocal = dmxInput->devs[i];
        if (dmxLocal->latereinit)
            dmxLocal->latereinit(&dmxLocal->pDevice->public);
    }
}

/** Initialize all of the devices described in \a dmxInput. */
void dmxInputInit(DMXInputInfo *dmxInput)
{
    DeviceIntPtr         pPointer = NULL, pKeyboard = NULL;
    dmxArg               a;
    const char           *name;
    int                  i;
    int                  doXI               = 1; /* Include by default */
    int                  forceConsole       = 0;
    int                  doWindows          = 1; /* On by default */
    int                  hasXkb             = 0;

    a = dmxArgParse(dmxInput->name);

    for (i = 1; i < dmxArgC(a); i++) {
        switch (hasXkb) {
        case 1:
            dmxInput->keycodes = xstrdup(dmxArgV(a, i));
            ++hasXkb;
            break;
        case 2:
            dmxInput->symbols  = xstrdup(dmxArgV(a, i));
            ++hasXkb;
            break;
        case 3:
            dmxInput->geometry = xstrdup(dmxArgV(a, i));
            hasXkb = 0;
            break;
        case 0:
            if      (!strcmp(dmxArgV(a, i), "noxi"))      doXI         = 0;
            else if (!strcmp(dmxArgV(a, i), "xi"))        doXI         = 1;
            else if (!strcmp(dmxArgV(a, i), "console"))   forceConsole = 1;
            else if (!strcmp(dmxArgV(a, i), "noconsole")) forceConsole = 0;
            else if (!strcmp(dmxArgV(a, i), "windows"))   doWindows    = 1;
            else if (!strcmp(dmxArgV(a, i), "nowindows")) doWindows    = 0;
            else if (!strcmp(dmxArgV(a, i), "xkb"))       hasXkb       = 1;
            else {
                dmxLog(dmxFatal,
                       "Unknown input argument: %s\n", dmxArgV(a, i));
            }
        }
    }

    name = dmxArgV(a, 0);

    if (!strcmp(name, "local")) {
        dmxPopulateLocal(dmxInput, a);
    } else if (!strcmp(name, "dummy")) {
        dmxInputCopyLocal(dmxInput, &DMXDummyMou);
        dmxInputCopyLocal(dmxInput, &DMXDummyKbd);
        dmxLogInput(dmxInput, "Using dummy input\n");
    } else {
        int found;

        for (found = 0, i = 0; i < dmxNumScreens; i++) {
            if (dmxPropertySameDisplay(&dmxScreens[i], name)) {
                if (dmxScreens[i].shared)
                    dmxLog(dmxFatal,
                           "Cannot take input from shared backend (%s)\n",
                           name);
                if (!dmxInput->core) {
                    dmxLog(dmxWarning,
                           "Cannot use core devices on a backend (%s)"
                           " as XInput devices\n", name);
                } else {
                    char *pt;
                    for (pt = (char *)dmxInput->name; pt && *pt; pt++)
                        if (*pt == ',') *pt = '\0';
                    dmxInputCopyLocal(dmxInput, &DMXBackendMou);
                    dmxInputCopyLocal(dmxInput, &DMXBackendKbd);
                    dmxInput->scrnIdx = i;
                    dmxLogInput(dmxInput,
                                "Using backend input from %s\n", name);
                }
                ++found;
                break;
            }
        }
        if (!found || forceConsole) {
            char *pt;
            if (found) dmxInput->console = TRUE;
            for (pt = (char *)dmxInput->name; pt && *pt; pt++)
                if (*pt == ',') *pt = '\0';
            dmxInputCopyLocal(dmxInput, &DMXConsoleMou);
            dmxInputCopyLocal(dmxInput, &DMXConsoleKbd);
            if (doWindows) {
                dmxInput->windows          = TRUE;
                dmxInput->updateWindowInfo = dmxUpdateWindowInformation;
            }
            dmxLogInput(dmxInput,
                        "Using console input from %s (%s windows)\n",
                        name, doWindows ? "with" : "without");
        }
    }

    dmxArgFree(a);

                                /* Locate extensions we may be interested in */
    dmxInputScanForExtensions(dmxInput, doXI);
    
    for (i = 0; i < dmxInput->numDevs; i++) {
        DMXLocalInputInfoPtr dmxLocal = dmxInput->devs[i];
        dmxLocal->pDevice = dmxAddDevice(dmxLocal);
        if (dmxLocal->isCore) {
            if (dmxLocal->type == DMX_LOCAL_MOUSE)
                pPointer  = dmxLocal->pDevice;
            if (dmxLocal->type == DMX_LOCAL_KEYBOARD)
                pKeyboard = dmxLocal->pDevice;
        }
    }
    
    dmxInput->processInputEvents    = dmxProcessInputEvents;
    dmxInput->detached              = False;
    
    RegisterBlockAndWakeupHandlers(dmxBlockHandler,
                                   dmxWakeupHandler,
                                   (void *)dmxInput->inputIdx);
}

static void dmxInputFreeLocal(DMXLocalInputInfoRec *local)
{
    if (!local) return;
    if (local->isCore && local->type == DMX_LOCAL_MOUSE)
        dmxLocalCorePointer  = NULL;
    if (local->isCore && local->type == DMX_LOCAL_KEYBOARD)
        dmxLocalCoreKeyboard = NULL;
    if (local->destroy_private) local->destroy_private(local->private);
    free(local->history);
    free(local->valuators);
    free(local->deviceName);
    local->private    = NULL;
    local->history    = NULL;
    local->deviceName = NULL;
    free(local);
}

/** Free all of the memory associated with \a dmxInput */
void dmxInputFree(DMXInputInfo *dmxInput)
{
    int i;
    
    if (!dmxInput) return;

    free(dmxInput->keycodes);
    free(dmxInput->symbols);
    free(dmxInput->geometry);

    for (i = 0; i < dmxInput->numDevs; i++) {
        dmxInputFreeLocal(dmxInput->devs[i]);
        dmxInput->devs[i] = NULL;
    }
    free(dmxInput->devs);
    dmxInput->devs    = NULL;
    dmxInput->numDevs = 0;
    if (dmxInput->freename) free(dmxInput->name);
    dmxInput->name    = NULL;
}

/** Log information about all of the known devices using #dmxLog(). */
void dmxInputLogDevices(void)
{
    int i, j;

    dmxLog(dmxInfo, "%d devices:\n", dmxGetInputCount());
    dmxLog(dmxInfo, "  Id  Name                 Classes\n");
    for (j = 0; j < dmxNumInputs; j++) {
        DMXInputInfo *dmxInput = &dmxInputs[j];
        const char   *pt = strchr(dmxInput->name, ',');
        int          len = (pt
                            ? (size_t)(pt-dmxInput->name)
                            : strlen(dmxInput->name));

        for (i = 0; i < dmxInput->numDevs; i++) {
            DeviceIntPtr pDevice = dmxInput->devs[i]->pDevice;
            if (pDevice) {
                dmxLog(dmxInfo, "  %2d%c %-20.20s",
                       pDevice->id,
                       dmxInput->detached ? 'D' : ' ',
                       pDevice->name);
                if (pDevice->key)        dmxLogCont(dmxInfo, " key");
                if (pDevice->valuator)   dmxLogCont(dmxInfo, " val");
                if (pDevice->button)     dmxLogCont(dmxInfo, " btn");
                if (pDevice->focus)      dmxLogCont(dmxInfo, " foc");
                if (pDevice->kbdfeed)    dmxLogCont(dmxInfo, " fb/kbd");
                if (pDevice->ptrfeed)    dmxLogCont(dmxInfo, " fb/ptr");
                if (pDevice->intfeed)    dmxLogCont(dmxInfo, " fb/int");
                if (pDevice->stringfeed) dmxLogCont(dmxInfo, " fb/str");
                if (pDevice->bell)       dmxLogCont(dmxInfo, " fb/bel");
                if (pDevice->leds)       dmxLogCont(dmxInfo, " fb/led");
                if (!pDevice->key && !pDevice->valuator && !pDevice->button
                    && !pDevice->focus && !pDevice->kbdfeed
                    && !pDevice->ptrfeed && !pDevice->intfeed
                    && !pDevice->stringfeed && !pDevice->bell
                    && !pDevice->leds)   dmxLogCont(dmxInfo, " (none)");
                                                                 
                dmxLogCont(dmxInfo, "\t[i%d/%*.*s",
                           dmxInput->inputIdx, len, len, dmxInput->name);
                if (dmxInput->devs[i]->deviceId >= 0)
                    dmxLogCont(dmxInfo, "/id%d", dmxInput->devs[i]->deviceId);
                if (dmxInput->devs[i]->deviceName)
                    dmxLogCont(dmxInfo, "=%s", dmxInput->devs[i]->deviceName);
                dmxLogCont(dmxInfo, "] %s\n",
                           dmxInput->devs[i]->isCore
                           ? "core"
                           : (dmxInput->devs[i]->sendsCore
                              ? "extension (sends core events)"
                              : "extension"));
            }
        }
    }
}

/** Detach an input */
int dmxInputDetach(DMXInputInfo *dmxInput)
{
    int i;

    if (dmxInput->detached) return BadAccess;
    
    for (i = 0; i < dmxInput->numDevs; i++) {
        DMXLocalInputInfoPtr dmxLocal = dmxInput->devs[i];
        dmxLogInput(dmxInput, "Detaching device id %d: %s%s\n",
                    dmxLocal->pDevice->id,
                    dmxLocal->pDevice->name,
                    dmxLocal->isCore
                    ? " [core]"
                    : (dmxLocal->sendsCore
                       ? " [sends core events]"
                       : ""));
        DisableDevice(dmxLocal->pDevice, TRUE);
    }
    dmxInput->detached = True;
    dmxInputLogDevices();
    return 0;
}

/** Search for input associated with \a dmxScreen, and detach. */
void dmxInputDetachAll(DMXScreenInfo *dmxScreen)
{
    int i;

    for (i = 0; i < dmxNumInputs; i++) {
        DMXInputInfo *dmxInput = &dmxInputs[i];
        if (dmxInput->scrnIdx == dmxScreen->index) dmxInputDetach(dmxInput);
    }
}

/** Search for input associated with \a deviceId, and detach. */
int dmxInputDetachId(int id)
{
    DMXInputInfo *dmxInput = dmxInputLocateId(id);

    if (!dmxInput) return BadValue;
    
    return dmxInputDetach(dmxInput);
}

DMXInputInfo *dmxInputLocateId(int id)
{
    int i, j;
    
    for (i = 0; i < dmxNumInputs; i++) {
        DMXInputInfo *dmxInput = &dmxInputs[i];
        for (j = 0; j < dmxInput->numDevs; j++) {
            DMXLocalInputInfoPtr dmxLocal = dmxInput->devs[j];
            if (dmxLocal->pDevice->id == id) return dmxInput;
        }
    }
    return NULL;
}

static int dmxInputAttachNew(DMXInputInfo *dmxInput, int *id)
{
    dmxInputInit(dmxInput);
    InitAndStartDevices();
    if (id && dmxInput->devs) *id = dmxInput->devs[0]->pDevice->id;
    dmxInputLogDevices();
    return 0;
}

static int dmxInputAttachOld(DMXInputInfo *dmxInput, int *id)
{
    int i;
    
    dmxInput->detached = False;
    for (i = 0; i < dmxInput->numDevs; i++) {
        DMXLocalInputInfoPtr dmxLocal = dmxInput->devs[i];
        if (id) *id = dmxLocal->pDevice->id;
        dmxLogInput(dmxInput,
                    "Attaching device id %d: %s%s\n",
                    dmxLocal->pDevice->id,
                    dmxLocal->pDevice->name,
                    dmxLocal->isCore
                    ? " [core]"
                    : (dmxLocal->sendsCore
                       ? " [sends core events]"
                       : ""));
        EnableDevice(dmxLocal->pDevice, TRUE);
    }
    dmxInputLogDevices();
    return 0;
}

int dmxInputAttachConsole(const char *name, int isCore, int *id)
{
    DMXInputInfo  *dmxInput;
    int           i;

    for (i = 0; i < dmxNumInputs; i++) {
        dmxInput = &dmxInputs[i];
        if (dmxInput->scrnIdx == -1
            && dmxInput->detached
            && !strcmp(dmxInput->name, name)) {
                                /* Found match */
            dmxLogInput(dmxInput, "Reattaching detached console input\n");
            return dmxInputAttachOld(dmxInput, id);
        }
    }

                                /* No match found */
    dmxInput = dmxConfigAddInput(xstrdup(name), isCore);
    dmxInput->freename = TRUE;
    dmxLogInput(dmxInput, "Attaching new console input\n");
    return dmxInputAttachNew(dmxInput, id);
}

int dmxInputAttachBackend(int physicalScreen, int isCore, int *id)
{
    DMXInputInfo  *dmxInput;
    DMXScreenInfo *dmxScreen;
    int           i;
    
    if (physicalScreen < 0 || physicalScreen >= dmxNumScreens) return BadValue;
    for (i = 0; i < dmxNumInputs; i++) {
        dmxInput = &dmxInputs[i];
        if (dmxInput->scrnIdx != -1 && dmxInput->scrnIdx == physicalScreen) {
                                /* Found match */
            if (!dmxInput->detached) return BadAccess; /* Already attached */
            dmxScreen = &dmxScreens[physicalScreen];
            if (!dmxScreen->beDisplay) return BadAccess; /* Screen detached */
            dmxLogInput(dmxInput, "Reattaching detached backend input\n");
            return dmxInputAttachOld(dmxInput, id);
        }
    }
                                /* No match found */
    dmxScreen = &dmxScreens[physicalScreen];
    if (!dmxScreen->beDisplay) return BadAccess; /* Screen detached */
    dmxInput = dmxConfigAddInput(dmxScreen->name, isCore);
    dmxLogInput(dmxInput, "Attaching new backend input\n");
    return dmxInputAttachNew(dmxInput, id);
}
