From 3cc8faedc686fd568f648f08f6946887543c473f Mon Sep 17 00:00:00 2001 From: danij Date: Sun, 2 Nov 2014 01:47:15 +0000 Subject: [PATCH] Refactor|InputSystem|Client: Moved InputDevices and ddevent_t queue into InputSystem --- doomsday/client/include/ui/b_util.h | 1 + doomsday/client/include/ui/dd_input.h | 98 -- doomsday/client/include/ui/inputsystem.h | 68 ++ doomsday/client/src/busymode.cpp | 8 +- doomsday/client/src/dd_loop.cpp | 7 +- doomsday/client/src/dd_main.cpp | 8 +- doomsday/client/src/sys_system.cpp | 5 +- doomsday/client/src/ui/b_command.cpp | 14 +- doomsday/client/src/ui/b_context.cpp | 22 +- doomsday/client/src/ui/b_device.cpp | 12 +- doomsday/client/src/ui/b_main.cpp | 11 +- doomsday/client/src/ui/b_util.cpp | 24 +- doomsday/client/src/ui/clientwindow.cpp | 11 +- doomsday/client/src/ui/dd_input.cpp | 1014 +---------------- .../src/ui/inputdevicebuttoncontrol.cpp | 2 +- doomsday/client/src/ui/inputsystem.cpp | 955 +++++++++++++++- doomsday/client/src/ui/p_control.cpp | 9 +- doomsday/client/src/ui/widgetactions.cpp | 7 +- .../src/ui/widgets/inputbindingwidget.cpp | 3 +- .../src/ui/widgets/keygrabberwidget.cpp | 3 +- doomsday/client/src/world/worldsystem.cpp | 2 +- 21 files changed, 1151 insertions(+), 1133 deletions(-) diff --git a/doomsday/client/include/ui/b_util.h b/doomsday/client/include/ui/b_util.h index 5695e86005..047183079b 100644 --- a/doomsday/client/include/ui/b_util.h +++ b/doomsday/client/include/ui/b_util.h @@ -24,6 +24,7 @@ #include "dd_input.h" struct bcontext_t; +class InputDevice; // Event Binding Toggle State enum ebstate_t diff --git a/doomsday/client/include/ui/dd_input.h b/doomsday/client/include/ui/dd_input.h index 24c0d1e831..f483331bb7 100644 --- a/doomsday/client/include/ui/dd_input.h +++ b/doomsday/client/include/ui/dd_input.h @@ -24,8 +24,6 @@ #include #include "api_event.h" -class InputDevice; - // Input device identifiers: enum { @@ -113,102 +111,6 @@ struct ddevent_t void I_ConsoleRegister(); -/** - * Initialize the virtual input devices. - * - * @note There need not be actual physical devices available in order to use - * these state tables. - */ -void I_InitAllDevices(); - -/** - * Free the memory allocated for the input devices. - */ -void I_ShutdownAllDevices(); - -/** - * Lookup an InputDevice by it's unique @a id. - */ -InputDevice &I_Device(int id); - -/** - * Lookup an InputDevice by it's unique @a id. - * - * @return Pointer to the associated InputDevice; otherwise @c nullptr. - */ -InputDevice *I_DevicePtr(int id); - -/** - * Iterate through all the InputDevices. - */ -de::LoopResult I_ForAllDevices(std::function func); - -/** - * Initializes the key mappings to the default values. - */ -void I_InitKeyMappings(); - -/** - * Checks the current keyboard state, generates input events based on pressed/held - * keys and posts them. - */ -void I_ReadKeyboard(); - -/** - * Checks the current mouse state (axis, buttons and wheel). - * Generates events and mickeys and posts them. - */ -void I_ReadMouse(); - -/** - * Checks the current joystick state (axis, sliders, hat and buttons). - * Generates events and posts them. Axis clamps and dead zone is done - * here. - */ -void I_ReadJoystick(); - -void I_ReadHeadTracker(); - -/** - * Clear the input event queue. - */ -void I_ClearEvents(); - -bool I_IgnoreEvents(bool yes = true); - -/** - * Process all incoming input for the given timestamp. - * This is called only in the main thread, and also from the busy loop. - * - * This gets called at least 35 times per second. Usually more frequently - * than that. - */ -void I_ProcessEvents(timespan_t ticLength); - -void I_ProcessSharpEvents(timespan_t ticLength); - -/** - * @param ev A copy is made. - */ -void I_PostEvent(ddevent_t *ev); - -bool I_ConvertEvent(ddevent_t const *ddEvent, event_t *ev); - -/** - * Converts a libcore Event into an old-fashioned ddevent_t. - * - * @param event Event instance. - * @param ddEvent ddevent_t instance. - */ -void I_ConvertEvent(de::Event const &event, ddevent_t *ddEvent); - -/** - * Update the input device state table. - */ -void I_TrackInput(ddevent_t *ev); - -bool I_ShiftDown(); - #ifdef DENG2_DEBUG /** * Render a visual representation of the current state of all input devices. diff --git a/doomsday/client/include/ui/inputsystem.h b/doomsday/client/include/ui/inputsystem.h index 33f90024a9..0679473678 100644 --- a/doomsday/client/include/ui/inputsystem.h +++ b/doomsday/client/include/ui/inputsystem.h @@ -20,9 +20,14 @@ #ifndef CLIENT_INPUTSYSTEM_H #define CLIENT_INPUTSYSTEM_H +#include +#include #include +#include "dd_input.h" // ddevent_t #include "SettingsRegister" +class InputDevice; + /** * Input devices and events. @ingroup ui * @@ -38,6 +43,69 @@ class InputSystem : public de::System // System. void timeChanged(de::Clock const &); + /** + * Lookup an InputDevice by it's unique @a id. + */ + InputDevice &device(int id) const; + + /** + * Lookup an InputDevice by it's unique @a id. + * + * @return Pointer to the associated InputDevice; otherwise @c nullptr. + */ + InputDevice *devicePtr(int id) const; + + /** + * Iterate through all the InputDevices. + */ + de::LoopResult forAllDevices(std::function func) const; + + /** + * Returns @c true if the shift key of the keyboard is thought to be down. + * @todo: Refactor away + */ + bool shiftDown() const; + +public: /// Event processing -------------------------------------------------- + /** + * Clear the input event queue. + */ + void clearEvents(); + + bool ignoreEvents(bool yes = true); + + /** + * @param ev A copy is made. + */ + void postEvent(ddevent_t *ev); + + /** + * Process all incoming input for the given timestamp. + * This is called only in the main thread, and also from the busy loop. + * + * This gets called at least 35 times per second. Usually more frequently + * than that. + */ + void processEvents(timespan_t ticLength); + + void processSharpEvents(timespan_t ticLength); + + /** + * Update the input devices. + */ + void trackEvent(ddevent_t *ev); + +public: + static bool convertEvent(ddevent_t const *ddEvent, event_t *ev); + + /** + * Converts a libcore Event into an old-fashioned ddevent_t. + * + * @param event Event instance. + * @param ddEvent ddevent_t instance. + */ + static void convertEvent(de::Event const &event, ddevent_t *ddEvent); + public: /** * Register the console commands and variables of this module. diff --git a/doomsday/client/src/busymode.cpp b/doomsday/client/src/busymode.cpp index a6ca106430..6a9bf2aa51 100644 --- a/doomsday/client/src/busymode.cpp +++ b/doomsday/client/src/busymode.cpp @@ -267,7 +267,7 @@ static void preBusySetup(int initialMode) Con_TransitionConfigure(); } - busyWasIgnoringInput = I_IgnoreEvents(); + busyWasIgnoringInput = ClientApp::inputSystem().ignoreEvents(); // Load any resources needed beforehand. //BusyVisual_PrepareResources(); @@ -290,7 +290,7 @@ static void postBusyCleanup() { #ifdef __CLIENT__ // Discard input events so that any and all accumulated input events are ignored. - I_IgnoreEvents(busyWasIgnoringInput); + ClientApp::inputSystem().ignoreEvents(busyWasIgnoringInput); DD_ResetTimer(); //BusyVisual_ReleaseTextures(); @@ -471,8 +471,8 @@ void BusyMode_Loop(void) timespan_t oldTime; // Post and discard all input events. - I_ProcessEvents(0); - I_ProcessSharpEvents(0); + ClientApp::inputSystem().processEvents(0); + ClientApp::inputSystem().processSharpEvents(0); if(canUpload) { diff --git a/doomsday/client/src/dd_loop.cpp b/doomsday/client/src/dd_loop.cpp index 71500b4e6f..32c460c314 100644 --- a/doomsday/client/src/dd_loop.cpp +++ b/doomsday/client/src/dd_loop.cpp @@ -38,6 +38,7 @@ #endif #ifdef __CLIENT__ +# include "clientapp.h" # include "ui/busyvisual.h" # include "ui/clientwindow.h" #endif @@ -407,11 +408,11 @@ void Loop_RunTics(void) #ifdef __CLIENT__ // Process input events. - I_ProcessEvents(ticLength); + ClientApp::inputSystem().processEvents(ticLength); if(!processSharpEventsAfterTickers) { // We are allowed to process sharp events before tickers. - I_ProcessSharpEvents(ticLength); + ClientApp::inputSystem().processSharpEvents(ticLength); } #endif @@ -422,7 +423,7 @@ void Loop_RunTics(void) if(processSharpEventsAfterTickers) { // This is done after tickers for compatibility with ye olde game logic. - I_ProcessSharpEvents(ticLength); + ClientApp::inputSystem().processSharpEvents(ticLength); } #endif diff --git a/doomsday/client/src/dd_main.cpp b/doomsday/client/src/dd_main.cpp index 7ce008f3f3..bc16e12b50 100644 --- a/doomsday/client/src/dd_main.cpp +++ b/doomsday/client/src/dd_main.cpp @@ -1549,10 +1549,6 @@ bool App_ChangeGame(Game &game, bool allowReload) Con_InitDatabases(); consoleRegister(); -#ifdef __CLIENT__ - I_InitAllDevices(); -#endif - R_InitSvgs(); #ifdef __CLIENT__ @@ -1699,7 +1695,7 @@ bool App_ChangeGame(Game &game, bool allowReload) * @note Only necessary here because we might not have been able to use * busy mode (which would normally do this for us on end). */ - I_ClearEvents(); + ClientApp::inputSystem().clearEvents(); if(!App_GameLoaded()) { @@ -2468,7 +2464,7 @@ int DD_GetInteger(int ddvalue) { #ifdef __CLIENT__ case DD_SHIFT_DOWN: - return int(I_ShiftDown()); + return int(ClientApp::inputSystem().shiftDown()); case DD_WINDOW_WIDTH: return DENG_GAMEVIEW_WIDTH; diff --git a/doomsday/client/src/sys_system.cpp b/doomsday/client/src/sys_system.cpp index 679376cf2b..3fcfa8fa25 100644 --- a/doomsday/client/src/sys_system.cpp +++ b/doomsday/client/src/sys_system.cpp @@ -34,6 +34,9 @@ #include "de_system.h" #include "de_graphics.h" #include "de_misc.h" +#ifdef __CLIENT__ +# include "clientapp.h" +#endif #include "dd_main.h" #include "dd_loop.h" @@ -132,7 +135,7 @@ void Sys_Shutdown(void) S_Shutdown(); #ifdef __CLIENT__ GL_Shutdown(); - I_ClearEvents(); + ClientApp::inputSystem().clearEvents(); #endif DD_DestroyGames(); diff --git a/doomsday/client/src/ui/b_command.cpp b/doomsday/client/src/ui/b_command.cpp index e3c5ee41d1..787b7d2ee2 100644 --- a/doomsday/client/src/ui/b_command.cpp +++ b/doomsday/client/src/ui/b_command.cpp @@ -22,6 +22,7 @@ #include #include +#include "clientapp.h" #include "world/p_players.h" // P_ConsoleToLocal #include "ui/b_main.h" #include "ui/inputdevice.h" @@ -34,6 +35,11 @@ using namespace de; +static inline InputSystem &inputSys() +{ + return ClientApp::inputSystem(); +} + void B_InitCommandBindingList(evbinding_t *listRoot) { DENG2_ASSERT(listRoot); @@ -141,7 +147,7 @@ static dd_bool B_ParseEvent(evbinding_t *eb, char const *desc) // Next part defined button, axis, or hat. desc = Str_CopyDelim(str, desc, '-'); - if(!B_ParseJoystickTypeAndId(I_Device(eb->device), Str_Text(str), &eb->type, &eb->id)) + if(!B_ParseJoystickTypeAndId(inputSys().device(eb->device), Str_Text(str), &eb->type, &eb->id)) { return false; } @@ -354,7 +360,7 @@ Action *EventBinding_ActionForEvent(evbinding_t *eb, ddevent_t const *event, InputDevice *dev = nullptr; if(event->type != E_SYMBOLIC) { - dev = I_DevicePtr(eb->device); + dev = inputSys().devicePtr(eb->device); if(!dev || !dev->isActive()) { // The device is not active, there is no way this could get executed. @@ -420,7 +426,7 @@ Action *EventBinding_ActionForEvent(evbinding_t *eb, ddevent_t const *event, // Is the position as required? if(!B_CheckAxisPos(eb->state, eb->pos, - I_Device(event->device).axis(event->axis.id) + inputSys().device(event->device).axis(event->axis.id) .translateRealPosition(event->axis.pos))) return nullptr; break; @@ -468,7 +474,7 @@ void B_EventBindingToString(evbinding_t const *eb, ddstring_t *str) DENG2_ASSERT(eb && str); Str_Clear(str); - B_AppendDeviceDescToString(I_Device(eb->device), eb->type, eb->id, str); + B_AppendDeviceDescToString(inputSys().device(eb->device), eb->type, eb->id, str); switch(eb->type) { diff --git a/doomsday/client/src/ui/b_context.cpp b/doomsday/client/src/ui/b_context.cpp index bc543c8f87..2ef3209b39 100644 --- a/doomsday/client/src/ui/b_context.cpp +++ b/doomsday/client/src/ui/b_context.cpp @@ -25,6 +25,7 @@ #include #include #include +#include "clientapp.h" #include "dd_main.h" #include "m_misc.h" #include "ui/b_main.h" @@ -39,6 +40,11 @@ using namespace de; static int bindContextCount; static bcontext_t **bindContexts; +static inline InputSystem &inputSys() +{ + return ClientApp::inputSystem(); +} + void B_DestroyAllContexts() { if(bindContexts) @@ -60,7 +66,7 @@ void B_DestroyAllContexts() void B_UpdateAllDeviceStateAssociations() { // Clear all existing associations. - I_ForAllDevices([] (InputDevice &device) + inputSys().forAllDevices([] (InputDevice &device) { device.forAllControls([] (InputDeviceControl &control) { @@ -82,7 +88,7 @@ void B_UpdateAllDeviceStateAssociations() // Mark all event bindings in the context. for(evbinding_t *eb = bc->commandBinds.next; eb != &bc->commandBinds; eb = eb->next) { - InputDevice &dev = I_Device(eb->device); + InputDevice &dev = inputSys().device(eb->device); switch(eb->type) { @@ -126,7 +132,7 @@ void B_UpdateAllDeviceStateAssociations() for(int k = 0; k < DDMAXPLAYERS; ++k) for(dbinding_t *db = conBin->deviceBinds[k].next; db != &conBin->deviceBinds[k]; db = db->next) { - InputDevice &dev = I_Device(db->device); + InputDevice &dev = inputSys().device(db->device); switch(db->type) { @@ -165,7 +171,7 @@ void B_UpdateAllDeviceStateAssociations() // relevant states. if(bc->flags & BCF_ACQUIRE_KEYBOARD) { - InputDevice &device = I_Device(IDEV_KEYBOARD); + InputDevice &device = inputSys().device(IDEV_KEYBOARD); if(device.isActive()) { device.forAllControls([&bc] (InputDeviceControl &control) @@ -181,7 +187,7 @@ void B_UpdateAllDeviceStateAssociations() if(bc->flags & BCF_ACQUIRE_ALL) { - I_ForAllDevices([&bc] (InputDevice &device) + inputSys().forAllDevices([&bc] (InputDevice &device) { if(device.isActive()) { @@ -201,7 +207,7 @@ void B_UpdateAllDeviceStateAssociations() // Now that we know what are the updated context associations, let's check // the devices and see if any of the states need to be expired. - I_ForAllDevices([] (InputDevice &device) + inputSys().forAllDevices([] (InputDevice &device) { device.forAllControls([] (InputDeviceControl &control) { @@ -292,7 +298,7 @@ void B_ActivateContext(bcontext_t *bc, dd_bool doActivate) if(bc->flags & BCF_ACQUIRE_ALL) { - I_ForAllDevices([] (InputDevice &device) + inputSys().forAllDevices([] (InputDevice &device) { device.reset(); return LoopContinue; @@ -520,7 +526,7 @@ de::Action *BindContext_ActionForEvent(bcontext_t *bc, ddevent_t const *event, Action *B_ActionForEvent(ddevent_t const *event) { event_t ev; - bool validGameEvent = I_ConvertEvent(event, &ev); + bool validGameEvent = InputSystem::convertEvent(event, &ev); for(int i = 0; i < bindContextCount; ++i) { diff --git a/doomsday/client/src/ui/b_device.cpp b/doomsday/client/src/ui/b_device.cpp index b7dcbcf980..b9a81208f1 100644 --- a/doomsday/client/src/ui/b_device.cpp +++ b/doomsday/client/src/ui/b_device.cpp @@ -21,6 +21,7 @@ #include #include +#include "clientapp.h" #include "dd_main.h" #include "ui/b_main.h" #include "ui/b_context.h" @@ -39,6 +40,11 @@ byte zeroControlUponConflict = true; static float stageThreshold = 6.f/35; static float stageFactor = .5f; +static inline InputSystem &inputSys() +{ + return ClientApp::inputSystem(); +} + static dbinding_t *B_AllocDeviceBinding() { dbinding_t *cb = (dbinding_t *) M_Calloc(sizeof(*cb)); @@ -114,7 +120,7 @@ dd_bool B_ParseDevice(dbinding_t *cb, char const *desc) // Next part defined button, axis, or hat. desc = Str_CopyDelim(str, desc, '-'); - if(!B_ParseJoystickTypeAndId(I_Device(cb->device), Str_Text(str), &type, &cb->id)) + if(!B_ParseJoystickTypeAndId(inputSys().device(cb->device), Str_Text(str), &type, &cb->id)) { return false; } @@ -269,7 +275,7 @@ void B_EvaluateDeviceBindingList(int localNum, dbinding_t *listRoot, float *pos, if(skip) continue; // Get the device. - InputDevice *dev = I_DevicePtr(cb->device); + InputDevice *dev = inputSys().devicePtr(cb->device); if(!dev || !dev->isActive()) continue; // Not available. float devicePos = 0; @@ -397,7 +403,7 @@ void B_DeviceBindingToString(dbinding_t const *b, ddstring_t *str) Str_Clear(str); // Name of the device and the key/axis/hat. - B_AppendDeviceDescToString(I_Device(b->device), CBDTYPE_TO_EVTYPE(b->type), b->id, str); + B_AppendDeviceDescToString(inputSys().device(b->device), CBDTYPE_TO_EVTYPE(b->type), b->id, str); if(b->type == CBD_ANGLE) { diff --git a/doomsday/client/src/ui/b_main.cpp b/doomsday/client/src/ui/b_main.cpp index c3648e3d39..81f19fe670 100644 --- a/doomsday/client/src/ui/b_main.cpp +++ b/doomsday/client/src/ui/b_main.cpp @@ -123,6 +123,11 @@ static keyname_t const keyNames[] = { { 0, nullptr} }; +static inline InputSystem &inputSys() +{ + return ClientApp::inputSystem(); +} + /** * Binding context fallback for the "global" context. * @@ -136,7 +141,7 @@ static int globalContextFallback(ddevent_t const *ddev) if(App_GameLoaded()) { event_t ev; - if(I_ConvertEvent(ddev, &ev)) + if(InputSystem::convertEvent(ddev, &ev)) { // The game's normal responder only returns true if the bindings can't // be used (like when chatting). Note that if the event is eaten here, @@ -441,7 +446,7 @@ dd_bool B_Responder(ddevent_t *ev) // Axis events need a bit of filtering. if(ev->type == E_AXIS) { - float pos = I_Device(ev->device).axis(ev->axis.id).translateRealPosition(ev->axis.pos); + float pos = inputSys().device(ev->device).axis(ev->axis.id).translateRealPosition(ev->axis.pos); if((ev->axis.type == EAXIS_ABSOLUTE && fabs(pos) < .5f) || (ev->axis.type == EAXIS_RELATIVE && fabs(pos) < .02f)) { @@ -461,7 +466,7 @@ dd_bool B_Responder(ddevent_t *ev) echo.symbolic.name = Str_Text(&name); LOG_INPUT_XVERBOSE("Symbolic echo: %s") << echo.symbolic.name; - I_PostEvent(&echo); + inputSys().postEvent(&echo); Str_Free(&name); return true; diff --git a/doomsday/client/src/ui/b_util.cpp b/doomsday/client/src/ui/b_util.cpp index 5c5c11c141..2e242cd014 100644 --- a/doomsday/client/src/ui/b_util.cpp +++ b/doomsday/client/src/ui/b_util.cpp @@ -23,6 +23,7 @@ #include "de_console.h" #include "dd_main.h" #include "de_misc.h" +#include "clientapp.h" #include "ui/b_util.h" @@ -34,6 +35,13 @@ #include "ui/inputdevicehatcontrol.h" #include "network/net_main.h" // netGame +using namespace de; + +static inline InputSystem &inputSys() +{ + return ClientApp::inputSystem(); +} + dd_bool B_ParseToggleState(char const *toggleName, ebstate_t *state) { DENG2_ASSERT(toggleName && state); @@ -144,7 +152,7 @@ dd_bool B_ParseMouseTypeAndId(char const *desc, ddeventtype_t *type, int *id) DENG2_ASSERT(desc && type && id); // Maybe it's one of the named buttons? - *id = I_Device(IDEV_MOUSE).toButtonId(desc); + *id = inputSys().device(IDEV_MOUSE).toButtonId(desc); if(*id >= 0) { *type = E_TOGGLE; @@ -156,7 +164,7 @@ dd_bool B_ParseMouseTypeAndId(char const *desc, ddeventtype_t *type, int *id) { *type = E_TOGGLE; *id = strtoul(desc + 6, nullptr, 10) - 1; - if(I_Device(IDEV_MOUSE).hasButton(*id)) + if(inputSys().device(IDEV_MOUSE).hasButton(*id)) return true; LOG_INPUT_WARNING("Mouse button %i does not exist") << *id; @@ -165,7 +173,7 @@ dd_bool B_ParseMouseTypeAndId(char const *desc, ddeventtype_t *type, int *id) // Must be an axis, then. *type = E_AXIS; - *id = I_Device(IDEV_MOUSE).toAxisId(desc); + *id = inputSys().device(IDEV_MOUSE).toAxisId(desc); if(*id >= 0) return true; LOG_INPUT_WARNING("Mouse axis \"%s\" does not exist") << desc; @@ -322,7 +330,7 @@ dd_bool B_ParseStateCondition(statecondition_t *cond, char const *desc) // What is being targeted? desc = Str_CopyDelim(str, desc, '-'); ddeventtype_t type; - if(!B_ParseJoystickTypeAndId(I_Device(cond->device), Str_Text(str), &type, &cond->id)) + if(!B_ParseJoystickTypeAndId(inputSys().device(cond->device), Str_Text(str), &type, &cond->id)) { return false; } @@ -410,7 +418,7 @@ dd_bool B_CheckCondition(statecondition_t *cond, int localNum, bcontext_t *conte DENG2_ASSERT(cond); dd_bool const fulfilled = !cond->flags.negate; - InputDevice &dev = I_Device(cond->device); + InputDevice &dev = inputSys().device(cond->device); switch(cond->type) { @@ -485,7 +493,7 @@ void B_AppendDeviceDescToString(InputDevice const &device, ddeventtype_t type, i { Str_Append(str, device.button(id).name().toUtf8().constData()); } - else if(&device == I_DevicePtr(IDEV_KEYBOARD)) + else if(&device == inputSys().devicePtr(IDEV_KEYBOARD)) { char const *name = B_ShortNameForKey(id); if(name) @@ -568,7 +576,7 @@ void B_AppendConditionToString(statecondition_t const *cond, ddstring_t *str) } else { - B_AppendDeviceDescToString(I_Device(cond->device), + B_AppendDeviceDescToString(inputSys().device(cond->device), ( cond->type == SCT_TOGGLE_STATE? E_TOGGLE : cond->type == SCT_AXIS_BEYOND ? E_AXIS : E_ANGLE), @@ -599,7 +607,7 @@ void B_AppendEventToString(ddevent_t const *ev, ddstring_t *str) { DENG2_ASSERT(ev); - B_AppendDeviceDescToString(I_Device(ev->device), ev->type, + B_AppendDeviceDescToString(inputSys().device(ev->device), ev->type, ( ev->type == E_TOGGLE ? ev->toggle.id : ev->type == E_AXIS ? ev->axis.id : ev->type == E_ANGLE ? ev->angle.id diff --git a/doomsday/client/src/ui/clientwindow.cpp b/doomsday/client/src/ui/clientwindow.cpp index 86f14e2b45..ba44658112 100644 --- a/doomsday/client/src/ui/clientwindow.cpp +++ b/doomsday/client/src/ui/clientwindow.cpp @@ -61,6 +61,11 @@ using namespace de; +static inline InputSystem &inputSys() +{ + return ClientApp::inputSystem(); +} + DENG2_PIMPL(ClientWindow) , DENG2_OBSERVES(MouseEventSource, MouseStateChange) , DENG2_OBSERVES(Canvas, FocusChange) @@ -454,8 +459,8 @@ DENG2_PIMPL(ClientWindow) if(!hasFocus) { - I_ClearEvents(); - I_ForAllDevices([] (InputDevice &device) + inputSys().clearEvents(); + inputSys().forAllDevices([] (InputDevice &device) { device.reset(); return LoopContinue; @@ -475,7 +480,7 @@ DENG2_PIMPL(ClientWindow) ev.type = E_FOCUS; ev.focus.gained = hasFocus; ev.focus.inWindow = 1; /// @todo Ask WindowSystem for an identifier number. - I_PostEvent(&ev); + inputSys().postEvent(&ev); } void updateFpsNotification(float fps) diff --git a/doomsday/client/src/ui/dd_input.cpp b/doomsday/client/src/ui/dd_input.cpp index 28e3397282..28e653bbfe 100644 --- a/doomsday/client/src/ui/dd_input.cpp +++ b/doomsday/client/src/ui/dd_input.cpp @@ -19,30 +19,24 @@ */ #include "de_platform.h" // strdup macro + +#ifdef DENG2_DEBUG + #include "ui/dd_input.h" -//#include -//#include #include #include -#include -//#include #include // SECONDSPERTIC #include #include -//#include #include +#include "clientapp.h" #include "dd_def.h" #include "dd_main.h" -#include "dd_loop.h" #include "sys_system.h" // novideo -#include "render/vr.h" - #include "ui/b_main.h" -#include "ui/clientwindow.h" -#include "ui/clientwindowsystem.h" #include "ui/joystick.h" #include "ui/infine/finale.h" #include "ui/inputdevice.h" @@ -52,52 +46,15 @@ #include "ui/sys_input.h" #include "ui/ui_main.h" -// For the debug visuals: -#ifdef DENG2_DEBUG -# include -# include "de_graphics.h" -# include "api_fontrender.h" -#endif +#include +#include "de_graphics.h" +#include "api_fontrender.h" using namespace de; -#define DEFAULT_JOYSTICK_DEADZONE .05f ///< 5% - -#define MAX_AXIS_FILTER 40 - -// The initial and secondary repeater delays (tics). -//int repWait1 = 15; -//int repWait2 = 3; -bool shiftDown; -bool altDown; - -static bool ignoreInput; -#ifdef OLD_FILTER -static uint mouseFreq; -#endif - -typedef QList Devices; -static Devices devices; - -#if 0 -struct repeater_t -{ - int key; ///< The DDKEY code (@c 0 if not in use). - int native; ///< Used to determine which key is repeating. - char text[8]; ///< Text to insert. - timespan_t timer; ///< How's the time? - int count; ///< How many times has been repeated? -}; -#endif - -struct eventqueue_t -{ - ddevent_t events[MAXEVENTS]; - int head; - int tail; -}; -static eventqueue_t queue; -static eventqueue_t sharpQueue; +static byte devRendJoyState; +static byte devRendKeyState; +static byte devRendMouseState; #define MAX_KEYMAPPINGS 256 static byte altKeyMappings[MAX_KEYMAPPINGS]; @@ -117,279 +74,13 @@ static char defaultShiftTable[96] = // Contains characters 32 to 127. /* 120 */ 'X', 'Y', 'Z', 0, 0, 0, 0, 0 }; -static float oldPOV = IJOY_POV_CENTER; - -static char *eventStrings[MAXEVENTS]; - -static byte useSharpInputEvents = true; ///< cvar - -#ifdef DENG2_DEBUG -static byte devRendKeyState; ///< cvar -static byte devRendMouseState; ///< cvar -static byte devRendJoyState; ///< cvar -#endif - -static InputDevice *makeKeyboard(String const &name, String const &title = "") -{ - InputDevice *keyboard = new InputDevice(name); - - keyboard->setTitle(title); - - // DDKEYs are used as button indices. - for(int i = 0; i < 256; ++i) - { - keyboard->addButton(new InputDeviceButtonControl); - } - - return keyboard; -} - -static InputDevice *makeMouse(String const &name, String const &title = "") -{ - InputDevice *mouse = new InputDevice(name); - - mouse->setTitle(title); - - for(int i = 0; i < IMB_MAXBUTTONS; ++i) - { - mouse->addButton(new InputDeviceButtonControl); - } - - // Some of the mouse buttons have symbolic names. - mouse->button(IMB_LEFT ).setName("left"); - mouse->button(IMB_MIDDLE ).setName("middle"); - mouse->button(IMB_RIGHT ).setName("right"); - mouse->button(IMB_MWHEELUP ).setName("wheelup"); - mouse->button(IMB_MWHEELDOWN ).setName("wheeldown"); - mouse->button(IMB_MWHEELLEFT ).setName("wheelleft"); - mouse->button(IMB_MWHEELRIGHT).setName("wheelright"); - - // The mouse wheel is translated to keys, so there is no need to - // create an axis for it. - InputDeviceAxisControl *axis; - mouse->addAxis(axis = new InputDeviceAxisControl("x", InputDeviceAxisControl::Pointer)); - //axis->setFilter(1); // On by default. - axis->setScale(1.f/1000); - - mouse->addAxis(axis = new InputDeviceAxisControl("y", InputDeviceAxisControl::Pointer)); - //axis->setFilter(1); // On by default. - axis->setScale(1.f/1000); - - return mouse; -} - -static InputDevice *makeJoystick(String const &name, String const &title = "") -{ - InputDevice *joy = new InputDevice(name); - - joy->setTitle(title); - - for(int i = 0; i < IJOY_MAXBUTTONS; ++i) - { - joy->addButton(new InputDeviceButtonControl); - } - - for(int i = 0; i < IJOY_MAXAXES; ++i) - { - char name[32]; - if(i < 4) - { - strcpy(name, i == 0? "x" : i == 1? "y" : i == 2? "z" : "w"); - } - else - { - sprintf(name, "axis%02i", i + 1); - } - auto *axis = new InputDeviceAxisControl(name, InputDeviceAxisControl::Stick); - joy->addAxis(axis); - axis->setScale(1.0f / IJOY_AXISMAX); - axis->setDeadZone(DEFAULT_JOYSTICK_DEADZONE); - } - - for(int i = 0; i < IJOY_MAXHATS; ++i) - { - joy->addHat(new InputDeviceHatControl); - } - - return joy; -} - -static InputDevice *makeHeadTracker(String const &name, String const &title) -{ - InputDevice *head = new InputDevice(name); - - head->setTitle(title); - - auto *axis = new InputDeviceAxisControl("yaw", InputDeviceAxisControl::Stick); - head->addAxis(axis); - axis->setRawInput(); - - head->addAxis(axis = new InputDeviceAxisControl("pitch", InputDeviceAxisControl::Stick)); - axis->setRawInput(); - - head->addAxis(axis = new InputDeviceAxisControl("roll", InputDeviceAxisControl::Stick)); - axis->setRawInput(); - - return head; -} - -/** - * @param device InputDevice to add. - * @return Same as @a device for caller convenience. - */ -static InputDevice *addDevice(InputDevice *device) -{ - if(device) - { - if(!devices.contains(device)) - { - // Ensure the name is unique. - for(InputDevice *otherDevice : devices) - { - if(!otherDevice->name().compareWithoutCase(device->name())) - { - throw Error("InputSystem::addDevice", "Multiple devices with name:" + device->name() + " cannot coexist"); - } - } - - // Add this device to the collection. - devices.append(device); - } - } - return device; -} - -void I_InitAllDevices() -{ - // Allow re-init. - I_ShutdownAllDevices(); - - addDevice(makeKeyboard("key", "Keyboard"))->activate(); // A keyboard is assumed to always be present. - - addDevice(makeMouse("mouse", "Mouse"))->activate(Mouse_IsPresent()); // A mouse may not be present. - - addDevice(makeJoystick("joy", "Joystick"))->activate(Joystick_IsPresent()); // A joystick may not be present. - - /// @todo: Add support for multiple joysticks (just some generics, for now). - addDevice(new InputDevice("joy2")); - addDevice(new InputDevice("joy3")); - addDevice(new InputDevice("joy4")); - - addDevice(makeHeadTracker("head", "Head Tracker")); // Head trackers are activated later. - - // Register console variables for the controls of all devices. - for(InputDevice *device : devices) - { - device->consoleRegister(); - } -} - -void I_ShutdownAllDevices() -{ - qDeleteAll(devices); - devices.clear(); -} - -InputDevice &I_Device(int id) -{ - if(id >= 0 && id < devices.count()) - { - return *devices.at(id); - } - throw Error("I_InputDevice", "Unknown id:" + String::number(id)); -} - -InputDevice *I_DevicePtr(int id) -{ - if(id >= 0 && id < devices.count()) - { - return devices.at(id); - } - return nullptr; -} - -LoopResult I_ForAllDevices(std::function func) +// Initialize key mapping table. +static void initKeyMappingsOnce() { - for(InputDevice *device : devices) - { - if(auto result = func(*device)) return result; - } - return LoopContinue; -} - -bool I_ShiftDown() -{ - return shiftDown; -} - -void I_TrackInput(ddevent_t *ev) -{ - DENG2_ASSERT(ev); - - if(ev->type == E_FOCUS || ev->type == E_SYMBOLIC) - return; // Not a tracked device state. + // Already been here? + if(shiftKeyMappings[1] != 1) return; - InputDevice *dev = I_DevicePtr(ev->device); - if(!dev || !dev->isActive()) return; - - // Track the state of Shift and Alt. - if(IS_KEY_TOGGLE(ev)) - { - if(ev->toggle.id == DDKEY_RSHIFT) - { - if(ev->toggle.state == ETOG_DOWN) - shiftDown = true; - else if(ev->toggle.state == ETOG_UP) - shiftDown = false; - } - else if(ev->toggle.id == DDKEY_RALT) - { - if(ev->toggle.state == ETOG_DOWN) - { - altDown = true; - //qDebug() << "Alt down"; - } - else if(ev->toggle.state == ETOG_UP) - { - altDown = false; - //qDebug() << "Alt up"; - } - } - } - - // Update the state table. - /// @todo Offer the event to each control in turn. - if(ev->type == E_AXIS) - { - dev->axis(ev->axis.id).applyRealPosition(ev->axis.pos); - } - else if(ev->type == E_TOGGLE) - { - dev->button(ev->toggle.id).setDown(ev->toggle.state == ETOG_DOWN || ev->toggle.state == ETOG_REPEAT); - } - else if(ev->type == E_ANGLE) - { - dev->hat(ev->angle.id).setPosition(ev->angle.pos); - } -} - -#if 0 -static int DD_KeyOrCode(char *token) -{ - char *end = M_FindWhite(token); - - if(end - token > 1) - { - // Longer than one character, it must be a number. - return strtol(token, 0, !strnicmp(token, "0x", 2) ? 16 : 10); - } - // Direct mapping. - return (unsigned char) *token; -} -#endif - -void I_InitKeyMappings() -{ + /// @todo does not belong at this level. for(int i = 0; i < 256; ++i) { if(i >= 32 && i <= 127) @@ -400,359 +91,45 @@ void I_InitKeyMappings() } } -/** - * Returns a copy of the string @a str. The caller does not get ownership of - * the string. The string is valid until it gets overwritten by a new - * allocation. There are at most MAXEVENTS strings allocated at a time. - * - * These are intended for strings in ddevent_t that are valid during the - * processing of an event. - */ -static char const *allocEventString(char const *str) -{ - DENG2_ASSERT(str); - static int eventStringRover = 0; - - DENG2_ASSERT(eventStringRover >= 0 && eventStringRover < MAXEVENTS); - M_Free(eventStrings[eventStringRover]); - char const *returnValue = eventStrings[eventStringRover] = strdup(str); - - if(++eventStringRover >= MAXEVENTS) - { - eventStringRover = 0; - } - return returnValue; -} - -void DD_ClearEventStrings() +static inline InputSystem &inputSys() { - for(int i = 0; i < MAXEVENTS; ++i) - { - M_Free(eventStrings[i]); eventStrings[i] = nullptr; - } + return ClientApp::inputSystem(); } -static void clearQueue(eventqueue_t *q) -{ - DENG2_ASSERT(q); - q->head = q->tail; -} - -/** - * Poll all event sources (i.e., input devices) and post events. - */ -static void postEventsForAllDevices() -{ -#ifdef __CLIENT__ - // On the client may have have input devices. - I_ReadKeyboard(); - I_ReadMouse(); - I_ReadJoystick(); - I_ReadHeadTracker(); -#endif -} - -bool I_IgnoreEvents(bool yes) -{ - bool const oldIgnoreInput = ignoreInput; - - ignoreInput = yes; - LOG_INPUT_VERBOSE("Ignoring input: %b") << yes; - if(!yes) - { - // Clear all the event buffers. - postEventsForAllDevices(); - I_ClearEvents(); - } - return oldIgnoreInput; -} - -void I_ClearEvents() -{ - clearQueue(&queue); - clearQueue(&sharpQueue); - - DD_ClearEventStrings(); -} - -static void postToQueue(eventqueue_t *q, ddevent_t *ev) -{ - DENG2_ASSERT(q && ev); - q->events[q->head] = *ev; - - if(ev->type == E_SYMBOLIC) - { - // Allocate a throw-away string from our buffer. - q->events[q->head].symbolic.name = allocEventString(ev->symbolic.name); - } - - q->head++; - q->head &= MAXEVENTS - 1; -} - -/// @note Called by the I/O functions when input is detected. -void I_PostEvent(ddevent_t *ev) -{ - DENG2_ASSERT(ev);// && ev->device < NUM_INPUT_DEVICES); - - eventqueue_t *q = &queue; - if(useSharpInputEvents && - (ev->type == E_TOGGLE || ev->type == E_AXIS || ev->type == E_ANGLE)) - { - q = &sharpQueue; - } - - // Cleanup: make sure only keyboard toggles can have a text insert. - if(ev->type == E_TOGGLE && ev->device != IDEV_KEYBOARD) - { - std::memset(ev->toggle.text, 0, sizeof(ev->toggle.text)); - } - - postToQueue(q, ev); - -#ifdef LIBDENG_CAMERA_MOVEMENT_ANALYSIS - if(ev->device == IDEV_KEYBOARD && ev->type == E_TOGGLE && ev->toggle.state == ETOG_DOWN) - { - extern float devCameraMovementStartTime; - extern float devCameraMovementStartTimeRealSecs; - - // Restart timer on each key down. - devCameraMovementStartTime = sysTime; - devCameraMovementStartTimeRealSecs = Sys_GetRealSeconds(); - } -#endif -} - -/** - * Gets the next event from an input event queue. - * @param q Event queue. - * @return @c NULL if no more events are available. - */ -static ddevent_t *nextFromQueue(eventqueue_t *q) -{ - DENG2_ASSERT(q); - - if(q->head == q->tail) - return nullptr; - - ddevent_t *ev = &q->events[q->tail]; - q->tail = (q->tail + 1) & (MAXEVENTS - 1); - - return ev; -} - -void I_ConvertEvent(de::Event const &event, ddevent_t *ddEvent) +static void initDrawStateForVisual(Point2Raw const *origin) { - DENG2_ASSERT(ddEvent); - using de::KeyEvent; - - de::zapPtr(ddEvent); + FR_PushAttrib(); - switch(event.type()) + // Ignore zero offsets. + if(origin && !(origin->x == 0 && origin->y == 0)) { - case de::Event::KeyPress: - case de::Event::KeyRelease: { - KeyEvent const &kev = event.as(); - - ddEvent->device = IDEV_KEYBOARD; - ddEvent->type = E_TOGGLE; - ddEvent->toggle.id = kev.ddKey(); - ddEvent->toggle.state = (kev.state() == KeyEvent::Pressed? ETOG_DOWN : ETOG_UP); - strcpy(ddEvent->toggle.text, kev.text().toLatin1()); - break; } - - default: break; + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glTranslatef(origin->x, origin->y, 0); } } -bool I_ConvertEvent(ddevent_t const *ddEvent, event_t *ev) +static void endDrawStateForVisual(Point2Raw const *origin) { - DENG2_ASSERT(ddEvent && ev); - // Copy the essentials into a cutdown version for the game. - // Ensure the format stays the same for future compatibility! - // - /// @todo This is probably broken! (DD_MICKEY_ACCURACY=1000 no longer used...) - // - de::zapPtr(ev); - if(ddEvent->type == E_SYMBOLIC) - { - ev->type = EV_SYMBOLIC; -#ifdef __64BIT__ - ASSERT_64BIT(ddEvent->symbolic.name); - ev->data1 = (int)(((uint64_t) ddEvent->symbolic.name) & 0xffffffff); // low dword - ev->data2 = (int)(((uint64_t) ddEvent->symbolic.name) >> 32); // high dword -#else - ASSERT_NOT_64BIT(ddEvent->symbolic.name); - - ev->data1 = (int) ddEvent->symbolic.name; - ev->data2 = 0; -#endif - } - else if(ddEvent->type == E_FOCUS) - { - ev->type = EV_FOCUS; - ev->data1 = ddEvent->focus.gained; - ev->data2 = ddEvent->focus.inWindow; - } - else + // Ignore zero offsets. + if(origin && !(origin->x == 0 && origin->y == 0)) { - switch(ddEvent->device) - { - case IDEV_KEYBOARD: - ev->type = EV_KEY; - if(ddEvent->type == E_TOGGLE) - { - ev->state = ( ddEvent->toggle.state == ETOG_UP ? EVS_UP - : ddEvent->toggle.state == ETOG_DOWN? EVS_DOWN - : EVS_REPEAT ); - ev->data1 = ddEvent->toggle.id; - } - break; - - case IDEV_MOUSE: - if(ddEvent->type == E_AXIS) - { - ev->type = EV_MOUSE_AXIS; - } - else if(ddEvent->type == E_TOGGLE) - { - ev->type = EV_MOUSE_BUTTON; - ev->data1 = ddEvent->toggle.id; - ev->state = ( ddEvent->toggle.state == ETOG_UP ? EVS_UP - : ddEvent->toggle.state == ETOG_DOWN? EVS_DOWN - : EVS_REPEAT ); - } - break; - - case IDEV_JOY1: - case IDEV_JOY2: - case IDEV_JOY3: - case IDEV_JOY4: - if(ddEvent->type == E_AXIS) - { - int *data = &ev->data1; - ev->type = EV_JOY_AXIS; - ev->state = (evstate_t) 0; - if(ddEvent->axis.id >= 0 && ddEvent->axis.id < 6) - { - data[ddEvent->axis.id] = ddEvent->axis.pos; - } - /// @todo The other dataN's must contain up-to-date information - /// as well. Read them from the current joystick status. - } - else if(ddEvent->type == E_TOGGLE) - { - ev->type = EV_JOY_BUTTON; - ev->state = ( ddEvent->toggle.state == ETOG_UP ? EVS_UP - : ddEvent->toggle.state == ETOG_DOWN? EVS_DOWN - : EVS_REPEAT ); - ev->data1 = ddEvent->toggle.id; - } - else if(ddEvent->type == E_ANGLE) - { - ev->type = EV_POV; - } - break; - - case IDEV_HEAD_TRACKER: - // No game-side equivalent exists. - return false; - - default: -#ifdef DENG2_DEBUG - App_Error("DD_ProcessEvents: Unknown deviceID in ddevent_t"); -#endif - return false; - } + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); } - return true; -} -static void updateAllDeviceAxes(timespan_t ticLength) -{ - for(InputDevice *dev : devices) - { - dev->forAllControls([&ticLength] (InputDeviceControl &control) - { - if(auto *axis = control.maybeAs()) - { - axis->update(ticLength); - } - return LoopContinue; - }); - } + FR_PopAttrib(); } /** - * Send all the events of the given timestamp down the responder chain. + * Apply all active modifiers to the key. */ -static void dispatchEvents(eventqueue_t *q, timespan_t ticLength, bool updateAxes) +static byte modKey(byte key) { - bool const callGameResponders = App_GameLoaded(); - - ddevent_t *ddev; - while((ddev = nextFromQueue(q))) - { - // Update the state of the input device tracking table. - I_TrackInput(ddev); - - if(ignoreInput && ddev->type != E_FOCUS) - continue; - - event_t ev; - bool validGameEvent = I_ConvertEvent(ddev, &ev); + extern bool shiftDown, altDown; - if(validGameEvent && callGameResponders) - { - // Does the game's special responder use this event? This is - // intended for grabbing events when creating bindings in the - // Controls menu. - if(gx.PrivilegedResponder && gx.PrivilegedResponder(&ev)) - { - continue; - } - } - - // The bindings responder. - if(B_Responder(ddev)) continue; - - // The "fallback" responder. Gets the event if no one else is interested. - if(validGameEvent && callGameResponders && gx.FallbackResponder) - { - gx.FallbackResponder(&ev); - } - } - - if(updateAxes) - { - // Input events have modified input device state: update the axis positions. - updateAllDeviceAxes(ticLength); - } -} + initKeyMappingsOnce(); -void I_ProcessEvents(timespan_t ticLength) -{ - // Poll all event sources (i.e., input devices) and post events. - postEventsForAllDevices(); - - // Dispatch all accumulated events down the responder chain. - dispatchEvents(&queue, ticLength, !useSharpInputEvents); -} - -void I_ProcessSharpEvents(timespan_t ticLength) -{ - // Sharp ticks may have some events queued on the side. - if(DD_IsSharpTick() || !DD_IsFrameTimeAdvancing()) - { - dispatchEvents(&sharpQueue, DD_IsFrameTimeAdvancing()? SECONDSPERTIC : ticLength, true); - } -} - -/** - * Apply all active modifiers to the key. - */ -static byte DD_ModKey(byte key) -{ if(shiftDown) key = shiftKeyMappings[key]; if(altDown) @@ -787,287 +164,6 @@ static byte DD_ModKey(byte key) return key; } -void I_ReadKeyboard() -{ -#define QUEUESIZE 32 - - if(novideo) return; - - ddevent_t ev; de::zap(ev); - ev.device = IDEV_KEYBOARD; - ev.type = E_TOGGLE; - ev.toggle.state = ETOG_REPEAT; - - // Read the new keyboard events, convert to ddevents and post them. - keyevent_t keyevs[QUEUESIZE]; - size_t const numkeyevs = Keyboard_GetEvents(keyevs, QUEUESIZE); - for(size_t n = 0; n < numkeyevs; ++n) - { - keyevent_t *ke = &keyevs[n]; - - // Check the type of the event. - switch(ke->type) - { - case IKE_DOWN: ev.toggle.state = ETOG_DOWN; break; - case IKE_REPEAT: ev.toggle.state = ETOG_REPEAT; break; - case IKE_UP: ev.toggle.state = ETOG_UP; break; - - default: break; - } - - ev.toggle.id = ke->ddkey; - - // Text content to insert? - DENG2_ASSERT(sizeof(ev.toggle.text) == sizeof(ke->text)); - std::memcpy(ev.toggle.text, ke->text, sizeof(ev.toggle.text)); - - LOG_INPUT_XVERBOSE("toggle.id: %i/%c [%s:%u]") - << ev.toggle.id << ev.toggle.id << ev.toggle.text << strlen(ev.toggle.text); - - I_PostEvent(&ev); - } - -#undef QUEUESIZE -} - -void I_ReadMouse() -{ - if(!Mouse_IsPresent()) - return; - - mousestate_t mouse; - -#ifdef OLD_FILTER - // Should we test the mouse input frequency? - if(mouseFreq > 0) - { - static uint lastTime = 0; - uint nowTime = Timer_RealMilliseconds(); - - if(nowTime - lastTime < 1000 / mouseFreq) - { - // Don't ask yet. - std::memset(&mouse, 0, sizeof(mouse)); - } - else - { - lastTime = nowTime; - Mouse_GetState(&mouse); - } - } - else -#endif - { - // Get the mouse state. - Mouse_GetState(&mouse); - } - - ddevent_t ev; de::zap(ev); - ev.device = IDEV_MOUSE; - ev.type = E_AXIS; - - float xpos = mouse.axis[IMA_POINTER].x; - float ypos = mouse.axis[IMA_POINTER].y; - - /*if(uiMouseMode) - { - ev.axis.type = EAXIS_ABSOLUTE; - } - else*/ - { - ev.axis.type = EAXIS_RELATIVE; - ypos = -ypos; - } - - // Post an event per axis. - // Don't post empty events. - if(xpos) - { - ev.axis.id = 0; - ev.axis.pos = xpos; - I_PostEvent(&ev); - } - if(ypos) - { - ev.axis.id = 1; - ev.axis.pos = ypos; - I_PostEvent(&ev); - } - - // Some very verbose output about mouse buttons. - int i = 0; - for(; i < IMB_MAXBUTTONS; ++i) - { - if(mouse.buttonDowns[i] || mouse.buttonUps[i]) - break; - } - if(i < IMB_MAXBUTTONS) - { - for(i = 0; i < IMB_MAXBUTTONS; ++i) - { - LOGDEV_INPUT_XVERBOSE("[%02i] %i/%i") << i << mouse.buttonDowns[i] << mouse.buttonUps[i]; - } - } - - // Post mouse button up and down events. - ev.type = E_TOGGLE; - for(i = 0; i < IMB_MAXBUTTONS; ++i) - { - ev.toggle.id = i; - while(mouse.buttonDowns[i] > 0 || mouse.buttonUps[i] > 0) - { - if(mouse.buttonDowns[i]-- > 0) - { - ev.toggle.state = ETOG_DOWN; - LOG_INPUT_XVERBOSE("Mouse button %i down") << i; - I_PostEvent(&ev); - } - if(mouse.buttonUps[i]-- > 0) - { - ev.toggle.state = ETOG_UP; - LOG_INPUT_XVERBOSE("Mouse button %i up") << i; - I_PostEvent(&ev); - } - } - } -} - -void I_ReadJoystick() -{ - if(!Joystick_IsPresent()) - return; - - joystate_t state; - Joystick_GetState(&state); - - // Joystick buttons. - ddevent_t ev; de::zap(ev); - ev.device = IDEV_JOY1; - ev.type = E_TOGGLE; - - for(int i = 0; i < state.numButtons; ++i) - { - ev.toggle.id = i; - while(state.buttonDowns[i] > 0 || state.buttonUps[i] > 0) - { - if(state.buttonDowns[i]-- > 0) - { - ev.toggle.state = ETOG_DOWN; - I_PostEvent(&ev); - LOG_INPUT_XVERBOSE("Joy button %i down") << i; - } - if(state.buttonUps[i]-- > 0) - { - ev.toggle.state = ETOG_UP; - I_PostEvent(&ev); - LOG_INPUT_XVERBOSE("Joy button %i up") << i; - } - } - } - - if(state.numHats > 0) - { - // Check for a POV change. - /// @todo: Some day it would be nice to support multiple hats here. -jk - if(state.hatAngle[0] != oldPOV) - { - ev.type = E_ANGLE; - ev.angle.id = 0; - - if(state.hatAngle[0] < 0) - { - ev.angle.pos = -1; - } - else - { - // The new angle becomes active. - ev.angle.pos = ROUND(state.hatAngle[0] / 45); - } - I_PostEvent(&ev); - - oldPOV = state.hatAngle[0]; - } - } - - // Send joystick axis events, one per axis. - ev.type = E_AXIS; - - for(int i = 0; i < state.numAxes; ++i) - { - ev.axis.id = i; - ev.axis.pos = state.axis[i]; - ev.axis.type = EAXIS_ABSOLUTE; - I_PostEvent(&ev); - } -} - -void I_ReadHeadTracker() -{ - // These values are for the input subsystem and gameplay. The renderer will check the head - // orientation independently, with as little latency as possible. - - // If a head tracking device is connected, the device is marked active. - if(!DD_GetInteger(DD_USING_HEAD_TRACKING)) - { - I_Device(IDEV_HEAD_TRACKER).deactivate(); - return; - } - - I_Device(IDEV_HEAD_TRACKER).activate(); - - // Get the latest values. - //vrCfg().oculusRift().allowUpdate(); - //vrCfg().oculusRift().update(); - - ddevent_t ev; de::zap(ev); - ev.device = IDEV_HEAD_TRACKER; - ev.type = E_AXIS; - ev.axis.type = EAXIS_ABSOLUTE; - - Vector3f const pry = vrCfg().oculusRift().headOrientation(); - - // Yaw (1.0 means 180 degrees). - ev.axis.id = 0; // Yaw. - ev.axis.pos = de::radianToDegree(pry[2]) * 1.0 / 180.0; - I_PostEvent(&ev); - - ev.axis.id = 1; // Pitch (1.0 means 85 degrees). - ev.axis.pos = de::radianToDegree(pry[0]) * 1.0 / 85.0; - I_PostEvent(&ev); - - // So I'll assume that if roll ever gets used, 1.0 will mean 180 degrees there too. - ev.axis.id = 2; // Roll. - ev.axis.pos = de::radianToDegree(pry[1]) * 1.0 / 180.0; - I_PostEvent(&ev); -} - -#ifdef DENG2_DEBUG - -static void initDrawStateForVisual(Point2Raw const *origin) -{ - FR_PushAttrib(); - - // Ignore zero offsets. - if(origin && !(origin->x == 0 && origin->y == 0)) - { - glMatrixMode(GL_MODELVIEW); - glPushMatrix(); - glTranslatef(origin->x, origin->y, 0); - } -} - -static void endDrawStateForVisual(Point2Raw const *origin) -{ - // Ignore zero offsets. - if(origin && !(origin->x == 0 && origin->y == 0)) - { - glMatrixMode(GL_MODELVIEW); - glPopMatrix(); - } - - FR_PopAttrib(); -} - void Rend_RenderButtonStateVisual(InputDevice &device, int buttonID, Point2Raw const *_origin, RectRaw *geometry) { @@ -1100,11 +196,11 @@ void Rend_RenderButtonStateVisual(InputDevice &device, int buttonID, Point2Raw c // Use the symbolic name. buttonLabel = button.name().toUtf8().constData(); } - else if(&device == I_DevicePtr(IDEV_KEYBOARD)) + else if(&device == ClientApp::inputSystem().devicePtr(IDEV_KEYBOARD)) { // Perhaps a printable ASCII character? // Apply all active modifiers to the key. - byte asciiCode = DD_ModKey((byte)buttonID); + byte asciiCode = modKey((byte)buttonID); if(asciiCode > 32 && asciiCode < 127) { buttonLabelBuf[0] = asciiCode; @@ -1691,19 +787,19 @@ void Rend_DrawInputDeviceVisuals() if(devRendKeyState) { - Rend_RenderInputDeviceStateVisual(I_Device(IDEV_KEYBOARD), &keyLayout, &origin, &dimensions); + Rend_RenderInputDeviceStateVisual(inputSys().device(IDEV_KEYBOARD), &keyLayout, &origin, &dimensions); origin.y += dimensions.height + SPACING; } if(devRendMouseState) { - Rend_RenderInputDeviceStateVisual(I_Device(IDEV_MOUSE), &mouseLayout, &origin, &dimensions); + Rend_RenderInputDeviceStateVisual(inputSys().device(IDEV_MOUSE), &mouseLayout, &origin, &dimensions); origin.y += dimensions.height + SPACING; } if(devRendJoyState) { - Rend_RenderInputDeviceStateVisual(I_Device(IDEV_JOY1), &joyLayout, &origin, &dimensions); + Rend_RenderInputDeviceStateVisual(inputSys().device(IDEV_JOY1), &joyLayout, &origin, &dimensions); } glMatrixMode(GL_PROJECTION); @@ -1714,40 +810,8 @@ void Rend_DrawInputDeviceVisuals() } #endif // DENG2_DEBUG -D_CMD(ListAllDevices) -{ - DENG2_UNUSED3(src, argc, argv); - LOG_INPUT_MSG(_E(b) "Input Devices:"); - for(InputDevice *dev : devices) - { - LOG_INPUT_MSG("") << dev->description(); - } - return true; -} - -D_CMD(ReleaseMouse) -{ - DENG2_UNUSED3(src, argc, argv); - if(WindowSystem::mainExists()) - { - ClientWindowSystem::main().canvas().trapMouse(false); - return true; - } - return false; -} - void I_ConsoleRegister() { - // Cvars - C_VAR_BYTE("input-sharp", &useSharpInputEvents, 0, 0, 1); - - // Ccmds - C_CMD("listinputdevices", "", ListAllDevices); - C_CMD("releasemouse", "", ReleaseMouse); - //C_CMD_FLAGS("setaxis", "s", AxisPrintConfig, CMDF_NO_DEDICATED); - //C_CMD_FLAGS("setaxis", "ss", AxisChangeOption, CMDF_NO_DEDICATED); - //C_CMD_FLAGS("setaxis", "sss", AxisChangeValue, CMDF_NO_DEDICATED); - #ifdef DENG2_DEBUG C_VAR_BYTE("rend-dev-input-joy-state", &devRendJoyState, CVF_NO_ARCHIVE, 0, 1); C_VAR_BYTE("rend-dev-input-key-state", &devRendKeyState, CVF_NO_ARCHIVE, 0, 1); diff --git a/doomsday/client/src/ui/inputdevicebuttoncontrol.cpp b/doomsday/client/src/ui/inputdevicebuttoncontrol.cpp index 6465137ed5..ef28d95b0e 100644 --- a/doomsday/client/src/ui/inputdevicebuttoncontrol.cpp +++ b/doomsday/client/src/ui/inputdevicebuttoncontrol.cpp @@ -19,7 +19,7 @@ */ #include "ui/inputdevicebuttoncontrol.h" -#include // SECONDSPERTIC +#include // Timer_RealMilliseconds() using namespace de; diff --git a/doomsday/client/src/ui/inputsystem.cpp b/doomsday/client/src/ui/inputsystem.cpp index e9cee783b5..3176e247b8 100644 --- a/doomsday/client/src/ui/inputsystem.cpp +++ b/doomsday/client/src/ui/inputsystem.cpp @@ -17,18 +17,146 @@ * http://www.gnu.org/licenses */ -//#include "de_platform.h" +#include "de_platform.h" // strdup macro #include "ui/inputsystem.h" +#include +#include +#include // SECONDSPERTIC #include #include +#include +#include + #include "clientapp.h" +#include "dd_def.h" // MAXEVENTS, ASSERT_NOT_64BIT +#include "dd_main.h" // App_GameLoaded() +#include "dd_loop.h" // DD_IsFrameTimeAdvancing() + +#include "render/vr.h" + #include "ui/dd_input.h" #include "ui/b_main.h" +#include "ui/clientwindow.h" +#include "ui/clientwindowsystem.h" +#include "ui/inputdevice.h" +#include "ui/inputdeviceaxiscontrol.h" +#include "ui/inputdevicebuttoncontrol.h" +#include "ui/inputdevicehatcontrol.h" #include "ui/sys_input.h" +#include "sys_system.h" // novideo + using namespace de; +#define DEFAULT_JOYSTICK_DEADZONE .05f ///< 5% + +#define MAX_AXIS_FILTER 40 + +static InputDevice *makeKeyboard(String const &name, String const &title = "") +{ + InputDevice *keyboard = new InputDevice(name); + + keyboard->setTitle(title); + + // DDKEYs are used as button indices. + for(int i = 0; i < 256; ++i) + { + keyboard->addButton(new InputDeviceButtonControl); + } + + return keyboard; +} + +static InputDevice *makeMouse(String const &name, String const &title = "") +{ + InputDevice *mouse = new InputDevice(name); + + mouse->setTitle(title); + + for(int i = 0; i < IMB_MAXBUTTONS; ++i) + { + mouse->addButton(new InputDeviceButtonControl); + } + + // Some of the mouse buttons have symbolic names. + mouse->button(IMB_LEFT ).setName("left"); + mouse->button(IMB_MIDDLE ).setName("middle"); + mouse->button(IMB_RIGHT ).setName("right"); + mouse->button(IMB_MWHEELUP ).setName("wheelup"); + mouse->button(IMB_MWHEELDOWN ).setName("wheeldown"); + mouse->button(IMB_MWHEELLEFT ).setName("wheelleft"); + mouse->button(IMB_MWHEELRIGHT).setName("wheelright"); + + // The mouse wheel is translated to keys, so there is no need to + // create an axis for it. + InputDeviceAxisControl *axis; + mouse->addAxis(axis = new InputDeviceAxisControl("x", InputDeviceAxisControl::Pointer)); + //axis->setFilter(1); // On by default. + axis->setScale(1.f/1000); + + mouse->addAxis(axis = new InputDeviceAxisControl("y", InputDeviceAxisControl::Pointer)); + //axis->setFilter(1); // On by default. + axis->setScale(1.f/1000); + + return mouse; +} + +static InputDevice *makeJoystick(String const &name, String const &title = "") +{ + InputDevice *joy = new InputDevice(name); + + joy->setTitle(title); + + for(int i = 0; i < IJOY_MAXBUTTONS; ++i) + { + joy->addButton(new InputDeviceButtonControl); + } + + for(int i = 0; i < IJOY_MAXAXES; ++i) + { + char name[32]; + if(i < 4) + { + strcpy(name, i == 0? "x" : i == 1? "y" : i == 2? "z" : "w"); + } + else + { + sprintf(name, "axis%02i", i + 1); + } + auto *axis = new InputDeviceAxisControl(name, InputDeviceAxisControl::Stick); + joy->addAxis(axis); + axis->setScale(1.0f / IJOY_AXISMAX); + axis->setDeadZone(DEFAULT_JOYSTICK_DEADZONE); + } + + for(int i = 0; i < IJOY_MAXHATS; ++i) + { + joy->addHat(new InputDeviceHatControl); + } + + return joy; +} + +static InputDevice *makeHeadTracker(String const &name, String const &title) +{ + InputDevice *head = new InputDevice(name); + + head->setTitle(title); + + auto *axis = new InputDeviceAxisControl("yaw", InputDeviceAxisControl::Stick); + head->addAxis(axis); + axis->setRawInput(); + + head->addAxis(axis = new InputDeviceAxisControl("pitch", InputDeviceAxisControl::Stick)); + axis->setRawInput(); + + head->addAxis(axis = new InputDeviceAxisControl("roll", InputDeviceAxisControl::Stick)); + axis->setRawInput(); + + return head; +} + static Value *Function_InputSystem_BindEvent(Context &, Function::ArgumentValues const &args) { String eventDesc = args[0]->asText(); @@ -44,11 +172,121 @@ static Value *Function_InputSystem_BindEvent(Context &, Function::ArgumentValues return new NumberValue(false); } +#if 0 +struct repeater_t +{ + int key; ///< The DDKEY code (@c 0 if not in use). + int native; ///< Used to determine which key is repeating. + char text[8]; ///< Text to insert. + timespan_t timer; ///< How's the time? + int count; ///< How many times has been repeated? +}; +// The initial and secondary repeater delays (tics). +int repWait1 = 15; +int repWait2 = 3; +#endif + +bool shiftDown; +bool altDown; +#ifdef OLD_FILTER +static uint mouseFreq; +#endif +static float oldPOV = IJOY_POV_CENTER; + +static char *eventStrings[MAXEVENTS]; + +/** + * Returns a copy of the string @a str. The caller does not get ownership of + * the string. The string is valid until it gets overwritten by a new + * allocation. There are at most MAXEVENTS strings allocated at a time. + * + * These are intended for strings in ddevent_t that are valid during the + * processing of an event. + */ +static char const *allocEventString(char const *str) +{ + DENG2_ASSERT(str); + static int eventStringRover = 0; + + DENG2_ASSERT(eventStringRover >= 0 && eventStringRover < MAXEVENTS); + M_Free(eventStrings[eventStringRover]); + char const *returnValue = eventStrings[eventStringRover] = strdup(str); + + if(++eventStringRover >= MAXEVENTS) + { + eventStringRover = 0; + } + return returnValue; +} + +static void clearEventStrings() +{ + for(int i = 0; i < MAXEVENTS; ++i) + { + M_Free(eventStrings[i]); eventStrings[i] = nullptr; + } +} + +struct eventqueue_t +{ + ddevent_t events[MAXEVENTS]; + int head; + int tail; +}; + +/** + * Gets the next event from an input event queue. + * @param q Event queue. + * @return @c NULL if no more events are available. + */ +static ddevent_t *nextFromQueue(eventqueue_t *q) +{ + DENG2_ASSERT(q); + + if(q->head == q->tail) + return nullptr; + + ddevent_t *ev = &q->events[q->tail]; + q->tail = (q->tail + 1) & (MAXEVENTS - 1); + + return ev; +} + +static void clearQueue(eventqueue_t *q) +{ + DENG2_ASSERT(q); + q->head = q->tail; +} + +static void postToQueue(eventqueue_t *q, ddevent_t *ev) +{ + DENG2_ASSERT(q && ev); + q->events[q->head] = *ev; + + if(ev->type == E_SYMBOLIC) + { + // Allocate a throw-away string from our buffer. + q->events[q->head].symbolic.name = allocEventString(ev->symbolic.name); + } + + q->head++; + q->head &= MAXEVENTS - 1; +} + +static eventqueue_t queue; +static eventqueue_t sharpQueue; + +static byte useSharpInputEvents = true; ///< cvar + DENG2_PIMPL(InputSystem) { Binder binder; SettingsRegister settings; - Record *scriptBindings; + Record *scriptBindings = nullptr; + bool ignoreInput = false; + + typedef QList Devices; + Devices devices; Instance(Public *i) : Base(i) { @@ -67,18 +305,403 @@ DENG2_PIMPL(InputSystem) App::scriptSystem().addNativeModule("Input", binder.module()); - // Initialize the system. + // Initialize system APIs. I_InitInterfaces(); - I_InitKeyMappings(); - I_InitAllDevices(); + + // Initialize devices. + addDevice(makeKeyboard("key", "Keyboard"))->activate(); // A keyboard is assumed to always be present. + + addDevice(makeMouse("mouse", "Mouse"))->activate(Mouse_IsPresent()); // A mouse may not be present. + + addDevice(makeJoystick("joy", "Joystick"))->activate(Joystick_IsPresent()); // A joystick may not be present. + + /// @todo: Add support for multiple joysticks (just some generics, for now). + addDevice(new InputDevice("joy2")); + addDevice(new InputDevice("joy3")); + addDevice(new InputDevice("joy4")); + + addDevice(makeHeadTracker("head", "Head Tracker")); // Head trackers are activated later. + + // Register console variables for the controls of all devices. + for(InputDevice *device : devices) device->consoleRegister(); } ~Instance() { - // Shutdown. - I_ShutdownAllDevices(); + qDeleteAll(devices); I_ShutdownInterfaces(); } + + /** + * @param device InputDevice to add. + * @return Same as @a device for caller convenience. + */ + InputDevice *addDevice(InputDevice *device) + { + if(device) + { + if(!devices.contains(device)) + { + // Ensure the name is unique. + for(InputDevice *otherDevice : devices) + { + if(!otherDevice->name().compareWithoutCase(device->name())) + { + throw Error("InputSystem::addDevice", "Multiple devices with name:" + device->name() + " cannot coexist"); + } + } + + // Add this device to the collection. + devices.append(device); + } + } + return device; + } + + /** + * Send all the events of the given timestamp down the responder chain. + */ + void dispatchEvents(eventqueue_t *q, timespan_t ticLength, bool updateAxes) + { + bool const callGameResponders = App_GameLoaded(); + + ddevent_t *ddev; + while((ddev = nextFromQueue(q))) + { + // Update the state of the input device tracking table. + self.trackEvent(ddev); + + if(ignoreInput && ddev->type != E_FOCUS) + continue; + + event_t ev; + bool validGameEvent = self.convertEvent(ddev, &ev); + + if(validGameEvent && callGameResponders) + { + // Does the game's special responder use this event? This is + // intended for grabbing events when creating bindings in the + // Controls menu. + if(gx.PrivilegedResponder && gx.PrivilegedResponder(&ev)) + { + continue; + } + } + + // The bindings responder. + if(B_Responder(ddev)) continue; + + // The "fallback" responder. Gets the event if no one else is interested. + if(validGameEvent && callGameResponders && gx.FallbackResponder) + { + gx.FallbackResponder(&ev); + } + } + + if(updateAxes) + { + // Input events have modified input device state: update the axis positions. + for(InputDevice *dev : devices) + { + dev->forAllControls([&ticLength] (InputDeviceControl &control) + { + if(auto *axis = control.maybeAs()) + { + axis->update(ticLength); + } + return LoopContinue; + }); + } + } + } + + /** + * Poll all event sources (i.e., input devices) and post events. + */ + void postEventsForAllDevices() + { + // On the client may have have input devices. + readKeyboard(); + readMouse(); + readJoystick(); + readHeadTracker(); + } + + /** + * Check the current keyboard state, generate input events based on pressed/held + * keys and poss them. + * + * @todo Does not belong at this level. + */ + void readKeyboard() + { +#define QUEUESIZE 32 + + if(novideo) return; + + ddevent_t ev; de::zap(ev); + ev.device = IDEV_KEYBOARD; + ev.type = E_TOGGLE; + ev.toggle.state = ETOG_REPEAT; + + // Read the new keyboard events, convert to ddevents and post them. + keyevent_t keyevs[QUEUESIZE]; + size_t const numkeyevs = Keyboard_GetEvents(keyevs, QUEUESIZE); + for(size_t n = 0; n < numkeyevs; ++n) + { + keyevent_t *ke = &keyevs[n]; + + // Check the type of the event. + switch(ke->type) + { + case IKE_DOWN: ev.toggle.state = ETOG_DOWN; break; + case IKE_REPEAT: ev.toggle.state = ETOG_REPEAT; break; + case IKE_UP: ev.toggle.state = ETOG_UP; break; + + default: break; + } + + ev.toggle.id = ke->ddkey; + + // Text content to insert? + DENG2_ASSERT(sizeof(ev.toggle.text) == sizeof(ke->text)); + std::memcpy(ev.toggle.text, ke->text, sizeof(ev.toggle.text)); + + LOG_INPUT_XVERBOSE("toggle.id: %i/%c [%s:%u]") + << ev.toggle.id << ev.toggle.id + << ev.toggle.text << strlen(ev.toggle.text); + + self.postEvent(&ev); + } + +#undef QUEUESIZE + } + + /** + * Check the current mouse state (axis, buttons and wheel). + * Generates events and mickeys and posts them. + * + * @todo Does not belong at this level. + */ + void readMouse() + { + if(!Mouse_IsPresent()) + return; + + mousestate_t mouse; + +#ifdef OLD_FILTER + // Should we test the mouse input frequency? + if(mouseFreq > 0) + { + static uint lastTime = 0; + uint nowTime = Timer_RealMilliseconds(); + + if(nowTime - lastTime < 1000 / mouseFreq) + { + // Don't ask yet. + std::memset(&mouse, 0, sizeof(mouse)); + } + else + { + lastTime = nowTime; + Mouse_GetState(&mouse); + } + } + else +#endif + { + // Get the mouse state. + Mouse_GetState(&mouse); + } + + ddevent_t ev; de::zap(ev); + ev.device = IDEV_MOUSE; + ev.type = E_AXIS; + + float xpos = mouse.axis[IMA_POINTER].x; + float ypos = mouse.axis[IMA_POINTER].y; + + /*if(uiMouseMode) + { + ev.axis.type = EAXIS_ABSOLUTE; + } + else*/ + { + ev.axis.type = EAXIS_RELATIVE; + ypos = -ypos; + } + + // Post an event per axis. + // Don't post empty events. + if(xpos) + { + ev.axis.id = 0; + ev.axis.pos = xpos; + self.postEvent(&ev); + } + if(ypos) + { + ev.axis.id = 1; + ev.axis.pos = ypos; + self.postEvent(&ev); + } + + // Some very verbose output about mouse buttons. + int i = 0; + for(; i < IMB_MAXBUTTONS; ++i) + { + if(mouse.buttonDowns[i] || mouse.buttonUps[i]) + break; + } + if(i < IMB_MAXBUTTONS) + { + for(i = 0; i < IMB_MAXBUTTONS; ++i) + { + LOGDEV_INPUT_XVERBOSE("[%02i] %i/%i") + << i << mouse.buttonDowns[i] << mouse.buttonUps[i]; + } + } + + // Post mouse button up and down events. + ev.type = E_TOGGLE; + for(i = 0; i < IMB_MAXBUTTONS; ++i) + { + ev.toggle.id = i; + while(mouse.buttonDowns[i] > 0 || mouse.buttonUps[i] > 0) + { + if(mouse.buttonDowns[i]-- > 0) + { + ev.toggle.state = ETOG_DOWN; + LOG_INPUT_XVERBOSE("Mouse button %i down") << i; + self.postEvent(&ev); + } + if(mouse.buttonUps[i]-- > 0) + { + ev.toggle.state = ETOG_UP; + LOG_INPUT_XVERBOSE("Mouse button %i up") << i; + self.postEvent(&ev); + } + } + } + } + + /** + * Check the current joystick state (axis, sliders, hat and buttons). + * Generates events and posts them. Axis clamps and dead zone is done + * here. + * + * @todo Does not belong at this level. + */ + void readJoystick() + { + if(!Joystick_IsPresent()) + return; + + joystate_t state; + Joystick_GetState(&state); + + // Joystick buttons. + ddevent_t ev; de::zap(ev); + ev.device = IDEV_JOY1; + ev.type = E_TOGGLE; + + for(int i = 0; i < state.numButtons; ++i) + { + ev.toggle.id = i; + while(state.buttonDowns[i] > 0 || state.buttonUps[i] > 0) + { + if(state.buttonDowns[i]-- > 0) + { + ev.toggle.state = ETOG_DOWN; + self.postEvent(&ev); + LOG_INPUT_XVERBOSE("Joy button %i down") << i; + } + if(state.buttonUps[i]-- > 0) + { + ev.toggle.state = ETOG_UP; + self.postEvent(&ev); + LOG_INPUT_XVERBOSE("Joy button %i up") << i; + } + } + } + + if(state.numHats > 0) + { + // Check for a POV change. + /// @todo: Some day it would be nice to support multiple hats here. -jk + if(state.hatAngle[0] != oldPOV) + { + ev.type = E_ANGLE; + ev.angle.id = 0; + + if(state.hatAngle[0] < 0) + { + ev.angle.pos = -1; + } + else + { + // The new angle becomes active. + ev.angle.pos = ROUND(state.hatAngle[0] / 45); + } + self.postEvent(&ev); + + oldPOV = state.hatAngle[0]; + } + } + + // Send joystick axis events, one per axis. + ev.type = E_AXIS; + + for(int i = 0; i < state.numAxes; ++i) + { + ev.axis.id = i; + ev.axis.pos = state.axis[i]; + ev.axis.type = EAXIS_ABSOLUTE; + self.postEvent(&ev); + } + } + + /// @todo Does not belong at this level. + void readHeadTracker() + { + // These values are for the input subsystem and gameplay. The renderer will check the head + // orientation independently, with as little latency as possible. + + // If a head tracking device is connected, the device is marked active. + if(!DD_GetInteger(DD_USING_HEAD_TRACKING)) + { + self.device(IDEV_HEAD_TRACKER).deactivate(); + return; + } + + self.device(IDEV_HEAD_TRACKER).activate(); + + // Get the latest values. + //vrCfg().oculusRift().allowUpdate(); + //vrCfg().oculusRift().update(); + + ddevent_t ev; de::zap(ev); + ev.device = IDEV_HEAD_TRACKER; + ev.type = E_AXIS; + ev.axis.type = EAXIS_ABSOLUTE; + + Vector3f const pry = vrCfg().oculusRift().headOrientation(); + + // Yaw (1.0 means 180 degrees). + ev.axis.id = 0; // Yaw. + ev.axis.pos = de::radianToDegree(pry[2]) * 1.0 / 180.0; + self.postEvent(&ev); + + ev.axis.id = 1; // Pitch (1.0 means 85 degrees). + ev.axis.pos = de::radianToDegree(pry[0]) * 1.0 / 85.0; + self.postEvent(&ev); + + // So I'll assume that if roll ever gets used, 1.0 will mean 180 degrees there too. + ev.axis.id = 2; // Roll. + ev.axis.pos = de::radianToDegree(pry[1]) * 1.0 / 180.0; + self.postEvent(&ev); + } }; InputSystem::InputSystem() : d(new Instance(this)) @@ -92,8 +715,326 @@ SettingsRegister &InputSystem::settings() return d->settings; } +InputDevice &InputSystem::device(int id) const +{ + if(id >= 0 && id < d->devices.count()) + { + return *d->devices.at(id); + } + throw Error("InputSystem::device", "Unknown id:" + String::number(id)); +} + +InputDevice *InputSystem::devicePtr(int id) const +{ + if(id >= 0 && id < d->devices.count()) + { + return d->devices.at(id); + } + return nullptr; +} + +LoopResult InputSystem::forAllDevices(std::function func) const +{ + for(InputDevice *device : d->devices) + { + if(auto result = func(*device)) return result; + } + return LoopContinue; +} + +void InputSystem::trackEvent(ddevent_t *ev) +{ + DENG2_ASSERT(ev); + + if(ev->type == E_FOCUS || ev->type == E_SYMBOLIC) + return; // Not a tracked device state. + + InputDevice *dev = devicePtr(ev->device); + if(!dev || !dev->isActive()) return; + + // Track the state of Shift and Alt. + if(IS_KEY_TOGGLE(ev)) + { + if(ev->toggle.id == DDKEY_RSHIFT) + { + if(ev->toggle.state == ETOG_DOWN) + ::shiftDown = true; + else if(ev->toggle.state == ETOG_UP) + ::shiftDown = false; + } + else if(ev->toggle.id == DDKEY_RALT) + { + if(ev->toggle.state == ETOG_DOWN) + { + ::altDown = true; + //qDebug() << "Alt down"; + } + else if(ev->toggle.state == ETOG_UP) + { + ::altDown = false; + //qDebug() << "Alt up"; + } + } + } + + // Update the state table. + /// @todo Offer the event to each control in turn. + if(ev->type == E_AXIS) + { + dev->axis(ev->axis.id).applyRealPosition(ev->axis.pos); + } + else if(ev->type == E_TOGGLE) + { + dev->button(ev->toggle.id).setDown(ev->toggle.state == ETOG_DOWN || ev->toggle.state == ETOG_REPEAT); + } + else if(ev->type == E_ANGLE) + { + dev->hat(ev->angle.id).setPosition(ev->angle.pos); + } +} + +bool InputSystem::ignoreEvents(bool yes) +{ + bool const oldIgnoreInput = d->ignoreInput; + + d->ignoreInput = yes; + LOG_INPUT_VERBOSE("Ignoring input: %b") << yes; + if(!yes) + { + // Clear all the event buffers. + d->postEventsForAllDevices(); + clearEvents(); + } + return oldIgnoreInput; +} + +void InputSystem::clearEvents() +{ + clearQueue(&queue); + clearQueue(&sharpQueue); + + clearEventStrings(); +} + +/// @note Called by the I/O functions when input is detected. +void InputSystem::postEvent(ddevent_t *ev) +{ + DENG2_ASSERT(ev);// && ev->device < NUM_INPUT_DEVICES); + + eventqueue_t *q = &queue; + if(useSharpInputEvents && + (ev->type == E_TOGGLE || ev->type == E_AXIS || ev->type == E_ANGLE)) + { + q = &sharpQueue; + } + + // Cleanup: make sure only keyboard toggles can have a text insert. + if(ev->type == E_TOGGLE && ev->device != IDEV_KEYBOARD) + { + std::memset(ev->toggle.text, 0, sizeof(ev->toggle.text)); + } + + postToQueue(q, ev); + +#ifdef LIBDENG_CAMERA_MOVEMENT_ANALYSIS + if(ev->device == IDEV_KEYBOARD && ev->type == E_TOGGLE && ev->toggle.state == ETOG_DOWN) + { + extern float devCameraMovementStartTime; + extern float devCameraMovementStartTimeRealSecs; + + // Restart timer on each key down. + devCameraMovementStartTime = sysTime; + devCameraMovementStartTimeRealSecs = Sys_GetRealSeconds(); + } +#endif +} + +void InputSystem::convertEvent(de::Event const &event, ddevent_t *ddEvent) // static +{ + DENG2_ASSERT(ddEvent); + using de::KeyEvent; + + de::zapPtr(ddEvent); + + switch(event.type()) + { + case de::Event::KeyPress: + case de::Event::KeyRelease: { + KeyEvent const &kev = event.as(); + + ddEvent->device = IDEV_KEYBOARD; + ddEvent->type = E_TOGGLE; + ddEvent->toggle.id = kev.ddKey(); + ddEvent->toggle.state = (kev.state() == KeyEvent::Pressed? ETOG_DOWN : ETOG_UP); + strcpy(ddEvent->toggle.text, kev.text().toLatin1()); + break; } + + default: break; + } +} + +bool InputSystem::convertEvent(ddevent_t const *ddEvent, event_t *ev) // static +{ + DENG2_ASSERT(ddEvent && ev); + // Copy the essentials into a cutdown version for the game. + // Ensure the format stays the same for future compatibility! + // + /// @todo This is probably broken! (DD_MICKEY_ACCURACY=1000 no longer used...) + // + de::zapPtr(ev); + if(ddEvent->type == E_SYMBOLIC) + { + ev->type = EV_SYMBOLIC; +#ifdef __64BIT__ + ASSERT_64BIT(ddEvent->symbolic.name); + ev->data1 = (int)(((uint64_t) ddEvent->symbolic.name) & 0xffffffff); // low dword + ev->data2 = (int)(((uint64_t) ddEvent->symbolic.name) >> 32); // high dword +#else + ASSERT_NOT_64BIT(ddEvent->symbolic.name); + + ev->data1 = (int) ddEvent->symbolic.name; + ev->data2 = 0; +#endif + } + else if(ddEvent->type == E_FOCUS) + { + ev->type = EV_FOCUS; + ev->data1 = ddEvent->focus.gained; + ev->data2 = ddEvent->focus.inWindow; + } + else + { + switch(ddEvent->device) + { + case IDEV_KEYBOARD: + ev->type = EV_KEY; + if(ddEvent->type == E_TOGGLE) + { + ev->state = ( ddEvent->toggle.state == ETOG_UP ? EVS_UP + : ddEvent->toggle.state == ETOG_DOWN? EVS_DOWN + : EVS_REPEAT ); + ev->data1 = ddEvent->toggle.id; + } + break; + + case IDEV_MOUSE: + if(ddEvent->type == E_AXIS) + { + ev->type = EV_MOUSE_AXIS; + } + else if(ddEvent->type == E_TOGGLE) + { + ev->type = EV_MOUSE_BUTTON; + ev->data1 = ddEvent->toggle.id; + ev->state = ( ddEvent->toggle.state == ETOG_UP ? EVS_UP + : ddEvent->toggle.state == ETOG_DOWN? EVS_DOWN + : EVS_REPEAT ); + } + break; + + case IDEV_JOY1: + case IDEV_JOY2: + case IDEV_JOY3: + case IDEV_JOY4: + if(ddEvent->type == E_AXIS) + { + int *data = &ev->data1; + ev->type = EV_JOY_AXIS; + ev->state = (evstate_t) 0; + if(ddEvent->axis.id >= 0 && ddEvent->axis.id < 6) + { + data[ddEvent->axis.id] = ddEvent->axis.pos; + } + /// @todo The other dataN's must contain up-to-date information + /// as well. Read them from the current joystick status. + } + else if(ddEvent->type == E_TOGGLE) + { + ev->type = EV_JOY_BUTTON; + ev->state = ( ddEvent->toggle.state == ETOG_UP ? EVS_UP + : ddEvent->toggle.state == ETOG_DOWN? EVS_DOWN + : EVS_REPEAT ); + ev->data1 = ddEvent->toggle.id; + } + else if(ddEvent->type == E_ANGLE) + { + ev->type = EV_POV; + } + break; + + case IDEV_HEAD_TRACKER: + // No game-side equivalent exists. + return false; + + default: +#ifdef DENG2_DEBUG + App_Error("InputSystem::convertEvent: Unknown deviceID in ddevent_t"); +#endif + return false; + } + } + return true; +} + +void InputSystem::processEvents(timespan_t ticLength) +{ + // Poll all event sources (i.e., input devices) and post events. + d->postEventsForAllDevices(); + + // Dispatch all accumulated events down the responder chain. + d->dispatchEvents(&queue, ticLength, !useSharpInputEvents); +} + +void InputSystem::processSharpEvents(timespan_t ticLength) +{ + // Sharp ticks may have some events queued on the side. + if(DD_IsSharpTick() || !DD_IsFrameTimeAdvancing()) + { + d->dispatchEvents(&sharpQueue, DD_IsFrameTimeAdvancing()? SECONDSPERTIC : ticLength, true); + } +} + +bool InputSystem::shiftDown() const +{ + return ::shiftDown; +} + +D_CMD(ListAllDevices) +{ + DENG2_UNUSED3(src, argc, argv); + LOG_INPUT_MSG(_E(b) "Input Devices:"); + ClientApp::inputSystem().forAllDevices([] (InputDevice &device) + { + LOG_INPUT_MSG("") << device.description(); + return LoopContinue; + }); + return true; +} + +D_CMD(ReleaseMouse) +{ + DENG2_UNUSED3(src, argc, argv); + if(WindowSystem::mainExists()) + { + ClientWindowSystem::main().canvas().trapMouse(false); + return true; + } + return false; +} + void InputSystem::consoleRegister() // static { B_ConsoleRegister(); // for control bindings + + // Cvars + C_VAR_BYTE("input-sharp", &useSharpInputEvents, 0, 0, 1); + + // Ccmds + C_CMD("listinputdevices", "", ListAllDevices); + C_CMD("releasemouse", "", ReleaseMouse); + //C_CMD_FLAGS("setaxis", "s", AxisPrintConfig, CMDF_NO_DEDICATED); + //C_CMD_FLAGS("setaxis", "ss", AxisChangeOption, CMDF_NO_DEDICATED); + //C_CMD_FLAGS("setaxis", "sss", AxisChangeValue, CMDF_NO_DEDICATED); + I_ConsoleRegister(); } diff --git a/doomsday/client/src/ui/p_control.cpp b/doomsday/client/src/ui/p_control.cpp index 219e479031..c026e4e332 100644 --- a/doomsday/client/src/ui/p_control.cpp +++ b/doomsday/client/src/ui/p_control.cpp @@ -31,6 +31,9 @@ #include "de_system.h" #include "de_graphics.h" #include "dd_main.h" +#ifdef __CLIENT__ +# include "clientapp.h" +#endif #include "world/p_players.h" #ifdef __CLIENT__ @@ -302,7 +305,7 @@ void P_MaintainControlDoubleClicks(int playerNum, int control, float pos) ev.symbolic.id = playerNum; ev.symbolic.name = Str_Text(symbolicName); - I_PostEvent(&ev); + ClientApp::inputSystem().postEvent(&ev); Str_Delete(symbolicName); } @@ -334,7 +337,7 @@ DENG_EXTERN_C int P_IsControlBound(int playerNum, int control) // There must be bindings to active input devices. for(dbinding_t *cb = binds->next; cb != binds; cb = cb->next) { - if(InputDevice *dev = I_DevicePtr(cb->device)) + if(InputDevice *dev = ClientApp::inputSystem().devicePtr(cb->device)) { if(dev->isActive()) return true; } @@ -473,7 +476,7 @@ void P_ControlTicker(timespan_t time) #ifdef __CLIENT__ D_CMD(ClearControlAccumulation) { - I_ForAllDevices([] (InputDevice &device) + ClientApp::inputSystem().forAllDevices([] (InputDevice &device) { device.reset(); return LoopContinue; diff --git a/doomsday/client/src/ui/widgetactions.cpp b/doomsday/client/src/ui/widgetactions.cpp index 018f05ab32..41619776c7 100644 --- a/doomsday/client/src/ui/widgetactions.cpp +++ b/doomsday/client/src/ui/widgetactions.cpp @@ -17,6 +17,7 @@ */ #include "WidgetActions" +#include "clientapp.h" #include "ui/b_main.h" #include "ui/b_context.h" @@ -41,7 +42,7 @@ WidgetActions::WidgetActions() : d(new Instance(this)) bool WidgetActions::tryEvent(Event const &event, String const &context) { ddevent_t ddev; - I_ConvertEvent(event, &ddev); + InputSystem::convertEvent(event, &ddev); if(context.isEmpty()) { // Check all enabled contexts. @@ -78,8 +79,8 @@ bool WidgetActions::tryEvent(ddevent_t const *ev) void WidgetActions::trackInput(Event const &event) { ddevent_t ddev; - I_ConvertEvent(event, &ddev); - I_TrackInput(&ddev); + InputSystem::convertEvent(event, &ddev); + ClientApp::inputSystem().trackEvent(&ddev); } void WidgetActions::activateContext(String const &context, bool yes) diff --git a/doomsday/client/src/ui/widgets/inputbindingwidget.cpp b/doomsday/client/src/ui/widgets/inputbindingwidget.cpp index 143e967b6c..448a52f7f7 100644 --- a/doomsday/client/src/ui/widgets/inputbindingwidget.cpp +++ b/doomsday/client/src/ui/widgets/inputbindingwidget.cpp @@ -17,6 +17,7 @@ */ #include "ui/widgets/inputbindingwidget.h" +#include "clientapp.h" #include "ui/b_main.h" #include "ui/b_context.h" #include @@ -209,7 +210,7 @@ bool InputBindingWidget::handleEvent(Event const &event) AutoStr *name = AutoStr_New(); ddevent_t ev; - I_ConvertEvent(event, &ev); + InputSystem::convertEvent(event, &ev); B_AppendEventToString(&ev, name); String desc = Str_Text(name); diff --git a/doomsday/client/src/ui/widgets/keygrabberwidget.cpp b/doomsday/client/src/ui/widgets/keygrabberwidget.cpp index fa66334e77..32c1e1af14 100644 --- a/doomsday/client/src/ui/widgets/keygrabberwidget.cpp +++ b/doomsday/client/src/ui/widgets/keygrabberwidget.cpp @@ -17,6 +17,7 @@ */ #include "ui/widgets/keygrabberwidget.h" +#include "clientapp.h" #include "ui/dd_input.h" #include "ui/b_util.h" @@ -81,7 +82,7 @@ bool KeyGrabberWidget::handleEvent(Event const &event) Str_Init(&name); ddevent_t ev; - I_ConvertEvent(event, &ev); + InputSystem::convertEvent(event, &ev); B_AppendEventToString(&ev, &name); diff --git a/doomsday/client/src/world/worldsystem.cpp b/doomsday/client/src/world/worldsystem.cpp index 658743dcb0..11656ad867 100644 --- a/doomsday/client/src/world/worldsystem.cpp +++ b/doomsday/client/src/world/worldsystem.cpp @@ -646,7 +646,7 @@ DENG2_PIMPL(WorldSystem) R_ResetViewer(); // Clear any input events that might have accumulated during setup. - I_ClearEvents(); + ClientApp::inputSystem().clearEvents(); // Inform the timing system to suspend the starting of the clock. firstFrameAfterLoad = true;