From 4189af8b500af98f079dab9293c979956ebfa6b6 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sat, 14 Apr 2018 02:42:41 +0300 Subject: [PATCH] Engine input port. Incomplete integration of touch, gamepad, evdev input --- engine/cdll_exp.h | 6 +- engine/client/cl_game.c | 3 + engine/client/client.h | 3 +- engine/client/in_evdev.c | 337 ++++++++ engine/client/in_joy.c | 473 ++++++++++ engine/client/in_touch.c | 1768 ++++++++++++++++++++++++++++++++++++++ engine/common/input.c | 527 ++++++++---- engine/common/input.h | 81 +- engine/common/keys.c | 18 +- 9 files changed, 3028 insertions(+), 188 deletions(-) create mode 100644 engine/client/in_evdev.c create mode 100644 engine/client/in_joy.c create mode 100644 engine/client/in_touch.c diff --git a/engine/cdll_exp.h b/engine/cdll_exp.h index 9ba867b0..9d72df37 100644 --- a/engine/cdll_exp.h +++ b/engine/cdll_exp.h @@ -62,6 +62,10 @@ typedef struct cldll_func_s // Xash3D extension int (*pfnGetRenderInterface)( int version, render_api_t *renderfuncs, render_interface_t *callback ); void (*pfnClipMoveToEntity)( struct physent_s *pe, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, struct pmtrace_s *tr ); + // Xash3D FWGS extension + int (*pfnTouchEvent)( int type, int fingerID, float x, float y, float dx, float dy ); + void (*pfnMoveEvent)( float forwardmove, float sidemove ); + void (*pfnLookEvent)( float relyaw, float relpitch ); } cldll_func_t; -#endif//CDLL_EXP_H \ No newline at end of file +#endif//CDLL_EXP_H diff --git a/engine/client/cl_game.c b/engine/client/cl_game.c index 66f768f9..a0ea8380 100644 --- a/engine/client/cl_game.c +++ b/engine/client/cl_game.c @@ -88,6 +88,9 @@ static dllfunc_t cdll_new_exports[] = // allowed only in SDK 2.3 and higher { "HUD_ChatInputPosition", (void **)&clgame.dllFuncs.pfnChatInputPosition }, { "HUD_GetRenderInterface", (void **)&clgame.dllFuncs.pfnGetRenderInterface }, // Xash3D ext { "HUD_ClipMoveToEntity", (void **)&clgame.dllFuncs.pfnClipMoveToEntity }, // Xash3D ext +{ "IN_ClientTouchEvent", (void **)&clgame.dllFuncs.pfnTouchEvent}, // Xash3D FWGS ext +{ "IN_ClientMoveEvent", (void **)&clgame.dllFuncs.pfnMoveEvent}, // Xash3D FWGS ext +{ "IN_ClientLookEvent", (void **)&clgame.dllFuncs.pfnLookEvent}, // Xash3D FWGS ext { NULL, NULL } }; diff --git a/engine/client/client.h b/engine/client/client.h index 55baf787..3e5ad2bc 100644 --- a/engine/client/client.h +++ b/engine/client/client.h @@ -695,6 +695,7 @@ extern convar_t *scr_loading; extern convar_t *v_dark; // start from dark extern convar_t *net_graph; extern convar_t *rate; +extern convar_t *m_ignore; //============================================================================= @@ -1047,4 +1048,4 @@ void SCR_RunCinematic( void ); void SCR_StopCinematic( void ); void CL_PlayVideo_f( void ); -#endif//CLIENT_H \ No newline at end of file +#endif//CLIENT_H diff --git a/engine/client/in_evdev.c b/engine/client/in_evdev.c new file mode 100644 index 00000000..691d9214 --- /dev/null +++ b/engine/client/in_evdev.c @@ -0,0 +1,337 @@ +/* +in_evdev.c - linux evdev interface support +Copyright (C) 2015-2018 mittorn + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ +#ifdef XASH_USE_EVDEV + +#include +#include +#include +#include +#include "common.h" +#include "input.h" +#include "client.h" +#include "vgui_draw.h" + +#define MAX_EVDEV_DEVICES 5 + +struct evdev_s +{ + int initialized, devices; + int fds[MAX_EVDEV_DEVICES]; + string paths[MAX_EVDEV_DEVICES]; + qboolean grab; + float grabtime; + float x, y; +} evdev; + +int KeycodeFromEvdev(int keycode, int value); + +static void Evdev_CheckPermissions() +{ +#ifdef __ANDROID__ + system( "su 0 chmod 664 /dev/input/event*" ); +#endif +} + +void Evdev_Setup( void ) +{ + if( evdev.initialized ) + return; +#ifdef __ANDROID__ + system( "su 0 supolicy --live \"allow appdomain input_device dir { ioctl read getattr search open }\" \"allow appdomain input_device chr_file { ioctl read write getattr lock append open }\"" ); + system( "su 0 setenforce permissive" ); +#endif + evdev.initialized = true; +} + +#define EV_HASBIT( x, y ) ( x[y / 32] & (1 << y % 32) ) + +void Evdev_Autodetect_f( void ) +{ + int i; + DIR *dir; + struct dirent *entry; + + Evdev_Setup(); + + Evdev_CheckPermissions(); + + if( !( dir = opendir( "/dev/input" ) ) ) + return; + + while( ( entry = readdir( dir ) ) ) + { + int fd; + string path; + uint types[EV_MAX] = {0}; + uint codes[( KEY_MAX - 1 ) / 32 + 1] = {0}; + qboolean hasbtnmouse; + + if( evdev.devices >= MAX_EVDEV_DEVICES ) + continue; + + Q_snprintf( path, MAX_STRING, "/dev/input/%s", entry->d_name ); + + for( i = 0; i < evdev.devices; i++ ) + if( !Q_strncmp( evdev.paths[i], path, MAX_STRING ) ) + goto next; + + if( Q_strncmp( entry->d_name, "event", 5 ) ) + continue; + + fd = open( path, O_RDONLY | O_NONBLOCK ); + + if( fd == -1 ) + continue; + + ioctl( fd, EVIOCGBIT( 0, EV_MAX ), types ); + + if( !EV_HASBIT( types, EV_KEY ) ) + goto close; + + ioctl( fd, EVIOCGBIT( EV_KEY, KEY_MAX ), codes ); + + if( EV_HASBIT( codes, KEY_LEFTCTRL ) && EV_HASBIT( codes, KEY_SPACE ) ) + goto open; + + if( !EV_HASBIT( codes, BTN_MOUSE ) ) + goto close; + + if( EV_HASBIT( types, EV_REL ) ) + { + Q_memset( codes, 0, sizeof( codes ) ); + ioctl( fd, EVIOCGBIT( EV_REL, KEY_MAX ), codes ); + + if( !EV_HASBIT( codes, REL_X ) ) + goto close; + + if( !EV_HASBIT( codes, REL_Y ) ) + goto close; + + if( !EV_HASBIT( codes, REL_WHEEL ) ) + goto close; + + goto open; + } + goto close; +open: + Q_strncpy( evdev.paths[evdev.devices], path, MAX_STRING ); + evdev.fds[evdev.devices++] = fd; + Msg( "Opened device %s\n", path ); + goto next; +close: + close( fd ); +next: + continue; + } + closedir( dir ); + +} + +/* +=========== +Evdev_OpenDevice + +For shitty systems that cannot provide relative mouse axes +=========== +*/ +void Evdev_OpenDevice ( const char *path ) +{ + int ret, i; + + if ( evdev.devices >= MAX_EVDEV_DEVICES ) + { + Msg( "Only %d devices supported!\n", MAX_EVDEV_DEVICES ); + return; + } + + Evdev_Setup(); + + Evdev_CheckPermissions(); // use root to grant access to evdev + + for( i = 0; i < evdev.devices; i++ ) + { + if( !Q_strncmp( evdev.paths[i], path, MAX_STRING ) ) + { + Msg( "device %s already open!\n", path ); + return; + } + } + + ret = open( path, O_RDONLY | O_NONBLOCK ); + if( ret < 0 ) + { + MsgDev( D_ERROR, "Could not open input device %s: %s\n", path, strerror( errno ) ); + return; + } + Msg( "Input device #%d: %s opened sucessfully\n", evdev.devices, path ); + evdev.fds[evdev.devices] = ret; + Q_strncpy( evdev.paths[evdev.devices++], path, MAX_STRING ); +} + +void Evdev_OpenDevice_f( void ) +{ + if( Cmd_Argc() < 2 ) + Msg( "Usage: evdev_opendevice \n" ); + + Evdev_OpenDevice( Cmd_Argv( 1 ) ); +} + +/* +=========== +Evdev_CloseDevice_f +=========== +*/ +void Evdev_CloseDevice_f ( void ) +{ + uint i; + char *arg; + + if( Cmd_Argc() < 2 ) + return; + + arg = Cmd_Argv( 1 ); + + if( Q_isdigit( arg ) ) + i = Q_atoi( arg ); + else for( i = 0; i < evdev.devices; i++ ) + if( !Q_strncmp( evdev.paths[i], arg, MAX_STRING ) ) + break; + + if( i >= evdev.devices ) + { + Msg( "Device %s is not open\n", arg ); + return; + } + + close( evdev.fds[i] ); + evdev.devices--; + Msg( "Device %s closed successfully\n", evdev.paths[i] ); + + for( ; i < evdev.devices; i++ ) + { + Q_strncpy( evdev.paths[i], evdev.paths[i+1], MAX_STRING ); + evdev.fds[i] = evdev.fds[i+1]; + } +} + +void IN_EvdevFrame () +{ + int dx = 0, dy = 0, i; + + for( i = 0; i < evdev.devices; i++ ) + { + struct input_event ev; + + while ( read( evdev.fds[i], &ev, 16) == 16 ) + { + if ( ev.type == EV_REL ) + { + switch ( ev.code ) + { + case REL_X: dx += ev.value; + break; + + case REL_Y: dy += ev.value; + break; + + case REL_WHEEL: + if( ev.value > 0) + { + Key_Event( K_MWHEELDOWN, 1 ); + Key_Event( K_MWHEELDOWN, 0 ); + } + else + { + Key_Event( K_MWHEELUP, 1 ); + Key_Event( K_MWHEELUP, 0 ); + } + break; + } + } + else if ( ( ev.type == EV_KEY ) && cls.key_dest == key_game ) + { + switch( ev.code ) + { + case BTN_LEFT: + Key_Event( K_MOUSE1, ev.value ); + break; + case BTN_MIDDLE: + Key_Event( K_MOUSE3, ev.value ); + break; + case BTN_RIGHT: + Key_Event( K_MOUSE2, ev.value ); + break; + default: + Key_Event ( KeycodeFromEvdev( ev.code, ev.value ) , ev.value); + } + } + } + + if( evdev.grab && evdev.grabtime <= host.realtime ) + { + ioctl( evdev.fds[i], EVIOCGRAB, (void*) 1 ); + Key_ClearStates(); + } + + if( m_ignore->integer ) + continue; + + evdev.x += -dx * m_yaw->value; + evdev.y += dy * m_pitch->value; + } + if( evdev.grabtime <= host.realtime ) + evdev.grab = false; +} + +void Evdev_SetGrab( qboolean grab ) +{ + int i; + + if( grab ) + { + Key_Event( K_ESCAPE, 0 ); //Do not leave ESC down + evdev.grabtime = host.realtime + 0.5; + Key_ClearStates(); + } + else + { + for( i = 0; i < evdev.devices; i++ ) + ioctl( evdev.fds[i], EVIOCGRAB, (void*) 0 ); + } + evdev.grab = grab; +} + +void IN_EvdevMove( float *yaw, float *pitch ) +{ + *yaw += evdev.x; + *pitch += evdev.y; + evdev.x = evdev.y = 0.0f; +} + +void Evdev_Init( void ) +{ + Cmd_AddCommand ("evdev_open", Evdev_OpenDevice_f, "Open event device"); + Cmd_AddCommand ("evdev_close", Evdev_CloseDevice_f, "Close event device"); + Cmd_AddCommand ("evdev_autodetect", Evdev_Autodetect_f, "Automaticly open mouses and keyboards"); +} + +void Evdev_Shutdown( void ) +{ + Cmd_RemoveCommand( "evdev_open" ); + Cmd_RemoveCommand( "evdev_close" ); + Cmd_RemoveCommand( "evdev_autodetect" ); +} + +#endif // XASH_USE_EVDEV diff --git a/engine/client/in_joy.c b/engine/client/in_joy.c new file mode 100644 index 00000000..5351861d --- /dev/null +++ b/engine/client/in_joy.c @@ -0,0 +1,473 @@ +/* +joyinput.c - joystick common input code + +Copyright (C) 2016 a1batross + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef XASH_DEDICATED + +#include "common.h" +#include "input.h" +#include "keydefs.h" +#include "joyinput.h" +#include "client.h" +#include "gl_local.h" + +#if defined(XASH_SDL) +#include "platform/sdl/events.h" +#endif + +#ifndef SHRT_MAX +#define SHRT_MAX 0x7FFF +#endif + +typedef enum engineAxis_e +{ + JOY_AXIS_SIDE = 0, + JOY_AXIS_FWD, + JOY_AXIS_PITCH, + JOY_AXIS_YAW, + JOY_AXIS_RT, + JOY_AXIS_LT, + JOY_AXIS_NULL +} engineAxis_t; + +#define MAX_AXES JOY_AXIS_NULL + +#define JOY_SIMULATED_HAT_ID ( -1 ) + +// index - axis num come from event +// value - inner axis +static engineAxis_t joyaxesmap[MAX_AXES] = +{ + JOY_AXIS_SIDE, // left stick, x + JOY_AXIS_FWD, // left stick, y + JOY_AXIS_PITCH, // right stick, y + JOY_AXIS_YAW, // right stick, x + JOY_AXIS_RT, // right trigger + JOY_AXIS_LT // left trigger +}; + +static struct joy_axis_s +{ + short val; + short prevval; +} joyaxis[MAX_AXES] = { 0 }; +static qboolean initialized = false, forcedisable = false; +static convar_t *joy_enable; +static byte currentbinding; // add posibility to remap keys, to place it in joykeys[] + +float IN_TouchDrawText( float x1, float y1, float x2, float y2, const char *s, byte *color, float size ); +float IN_TouchDrawCharacter( float x, float y, int number, float size ); + +convar_t *joy_pitch; +convar_t *joy_yaw; +convar_t *joy_forward; +convar_t *joy_side; +convar_t *joy_found; +convar_t *joy_index; +convar_t *joy_lt_threshold; +convar_t *joy_rt_threshold; +convar_t *joy_side_deadzone; +convar_t *joy_forward_deadzone; +convar_t *joy_side_key_threshold; +convar_t *joy_forward_key_threshold; +convar_t *joy_pitch_deadzone; +convar_t *joy_yaw_deadzone; +convar_t *joy_axis_binding; + +/* +============ +Joy_IsActive +============ +*/ +qboolean Joy_IsActive( void ) +{ + return !forcedisable && initialized; +} + +/* +============ +Joy_HatMotionEvent + +DPad events +============ +*/ +void Joy_HatMotionEvent( int id, byte hat, byte value ) +{ + struct + { + int mask; + int key; + } keys[] = + { + { JOY_HAT_UP, K_UPARROW }, + { JOY_HAT_DOWN, K_DOWNARROW }, + { JOY_HAT_LEFT, K_LEFTARROW }, + { JOY_HAT_RIGHT, K_RIGHTARROW }, + }; + int i; + + if( !initialized ) + return; + + for( i = 0; i < ARRAYSIZE( keys ); i++ ) + { + if( value & keys[i].mask ) + { + if( !Key_IsDown( keys[i].key )) + Key_Event( keys[i].key, true ); + } + else + { + if( Key_IsDown( keys[i].key )) + Key_Event( keys[i].key, false ); + } + } +} + +/* +============= +Joy_ProcessTrigger +============= +*/ +void Joy_ProcessTrigger( const engineAxis_t engineAxis, short value ) +{ + int trigButton = 0, trigThreshold = 0; + + switch( engineAxis ) + { + case JOY_AXIS_RT: + trigButton = K_JOY2; + trigThreshold = joy_rt_threshold->integer; + break; + case JOY_AXIS_LT: + trigButton = K_JOY1; + trigThreshold = joy_lt_threshold->integer; + break; + default: + MsgDev( D_ERROR, "Joy_ProcessTrigger: invalid axis = %i", engineAxis ); + break; + } + + // update axis values + joyaxis[engineAxis].prevval = joyaxis[engineAxis].val; + joyaxis[engineAxis].val = value; + + if( joyaxis[engineAxis].val > trigThreshold && + joyaxis[engineAxis].prevval <= trigThreshold ) // ignore random press + { + Key_Event( trigButton, true ); + } + else if( joyaxis[engineAxis].val < trigThreshold && + joyaxis[engineAxis].prevval >= trigThreshold ) // we're unpressing (inverted) + { + Key_Event( trigButton, false ); + } +} + +int Joy_GetHatValueForAxis( const engineAxis_t engineAxis ) +{ + int threshold, negative, positive; + + switch( engineAxis ) + { + case JOY_AXIS_SIDE: + threshold = joy_side_key_threshold->integer; + negative = JOY_HAT_LEFT; + positive = JOY_HAT_RIGHT; + break; + case JOY_AXIS_FWD: + threshold = joy_side_key_threshold->integer; + negative = JOY_HAT_UP; + positive = JOY_HAT_DOWN; + break; + default: + ASSERT( false ); // only fwd/side axes can emit key events + return 0; + } + + // similar code in Joy_ProcessTrigger + if( joyaxis[engineAxis].val > threshold && + joyaxis[engineAxis].prevval <= threshold ) // ignore random press + { + return positive; + } + if( joyaxis[engineAxis].val < -threshold && + joyaxis[engineAxis].prevval >= -threshold ) // we're unpressing (inverted) + { + return negative; + } + return 0; +} + +/* +============= +Joy_ProcessStick +============= +*/ +void Joy_ProcessStick( const engineAxis_t engineAxis, short value ) +{ + int deadzone = 0; + + switch( engineAxis ) + { + case JOY_AXIS_FWD: deadzone = joy_forward_deadzone->integer; break; + case JOY_AXIS_SIDE: deadzone = joy_side_deadzone->integer; break; + case JOY_AXIS_PITCH: deadzone = joy_pitch_deadzone->integer; break; + case JOY_AXIS_YAW: deadzone = joy_yaw_deadzone->integer; break; + default: + MsgDev( D_ERROR, "Joy_ProcessStick: invalid axis = %i", engineAxis ); + break; + } + + if( value < deadzone && value > -deadzone ) + value = 0; // caught new event in deadzone, fill it with zero(no motion) + + // update axis values + joyaxis[engineAxis].prevval = joyaxis[engineAxis].val; + joyaxis[engineAxis].val = value; + + // fwd/side axis simulate hat movement + if( ( engineAxis == JOY_AXIS_SIDE || engineAxis == JOY_AXIS_FWD ) && + ( CL_IsInMenu() || CL_IsInConsole() ) ) + { + int val = 0; + + val |= Joy_GetHatValueForAxis( JOY_AXIS_SIDE ); + val |= Joy_GetHatValueForAxis( JOY_AXIS_FWD ); + + Joy_HatMotionEvent( JOY_SIMULATED_HAT_ID, 0, val ); + } +} + +/* +============= +Joy_AxisMotionEvent + +Axis events +============= +*/ +void Joy_AxisMotionEvent( int id, byte axis, short value ) +{ + byte engineAxis; + + if( !initialized ) + return; + + if( axis >= MAX_AXES ) + { + MsgDev( D_INFO, "Only 6 axes is supported\n" ); + return; + } + + engineAxis = joyaxesmap[axis]; // convert to engine inner axis control + + if( engineAxis == JOY_AXIS_NULL ) + return; + + if( value == joyaxis[engineAxis].val ) + return; // it is not an update + + if( engineAxis >= JOY_AXIS_RT ) + Joy_ProcessTrigger( engineAxis, value ); + else + Joy_ProcessStick( engineAxis, value ); +} + +/* +============= +Joy_BallMotionEvent + +Trackball events. UNDONE +============= +*/ +void Joy_BallMotionEvent( int id, byte ball, short xrel, short yrel ) +{ + //if( !initialized ) + // return; +} + +/* +============= +Joy_ButtonEvent + +Button events +============= +*/ +void Joy_ButtonEvent( int id, byte button, byte down ) +{ + if( !initialized ) + return; + + // generic game button code. + if( button > 32 ) + { + int origbutton = button; + button = ( button & 31 ) + K_AUX1; + + MsgDev( D_INFO, "Only 32 joybuttons is supported, converting %i button ID to %s\n", origbutton, Key_KeynumToString( button ) ); + } + else button += K_AUX1; + + Key_Event( button, down ); +} + +/* +============= +Joy_RemoveEvent + +Called when joystick is removed. For future expansion +============= +*/ +void Joy_RemoveEvent( int id ) +{ + if( !forcedisable && initialized && joy_found->integer ) + Cvar_SetFloat("joy_found", joy_found->value - 1.0f); +} + +/* +============= +Joy_RemoveEvent + +Called when joystick is removed. For future expansion +============= +*/ +void Joy_AddEvent( int id ) +{ + if( forcedisable ) + return; + + initialized = true; + + Cvar_SetFloat("joy_found", joy_found->value + 1.0f); +} + +/* +============= +Joy_FinalizeMove + +Append movement from axis. Called everyframe +============= +*/ +void Joy_FinalizeMove( float *fw, float *side, float *dpitch, float *dyaw ) +{ + if( !initialized || !joy_enable->integer ) + return; + + if( joy_axis_binding->modified ) + { + char bind[7] = { 0 }; // fill it with zeros + size_t i; + Q_strncpy( bind, joy_axis_binding->string, sizeof(bind) ); + + for( i = 0; i < sizeof(bind); i++ ) + { + switch( bind[i] ) + { + case 's': joyaxesmap[i] = JOY_AXIS_SIDE; break; + case 'f': joyaxesmap[i] = JOY_AXIS_FWD; break; + case 'y': joyaxesmap[i] = JOY_AXIS_YAW; break; + case 'p': joyaxesmap[i] = JOY_AXIS_PITCH; break; + case 'r': joyaxesmap[i] = JOY_AXIS_RT; break; + case 'l': joyaxesmap[i] = JOY_AXIS_LT; break; + default : joyaxesmap[i] = JOY_AXIS_NULL; break; + } + } + joy_axis_binding->modified = false; + } + + *fw -= joy_forward->value * (float)joyaxis[JOY_AXIS_FWD ].val/(float)SHRT_MAX; // must be form -1.0 to 1.0 + *side += joy_side->value * (float)joyaxis[JOY_AXIS_SIDE].val/(float)SHRT_MAX; +#if !defined(XASH_SDL) + *dpitch += joy_pitch->value * (float)joyaxis[JOY_AXIS_PITCH].val/(float)SHRT_MAX * host.realframetime; // abs axis rotate is frametime related + *dyaw -= joy_yaw->value * (float)joyaxis[JOY_AXIS_YAW ].val/(float)SHRT_MAX * host.realframetime; +#else + // HACKHACK: SDL have inverted look axis. + *dpitch -= joy_pitch->value * (float)joyaxis[JOY_AXIS_PITCH].val/(float)SHRT_MAX * host.realframetime; + *dyaw += joy_yaw->value * (float)joyaxis[JOY_AXIS_YAW ].val/(float)SHRT_MAX * host.realframetime; +#endif +} + +/* +============= +Joy_Init + +Main init procedure +============= +*/ +void Joy_Init( void ) +{ + joy_pitch = Cvar_Get( "joy_pitch", "100.0", CVAR_ARCHIVE, "joystick pitch sensitivity" ); + joy_yaw = Cvar_Get( "joy_yaw", "100.0", CVAR_ARCHIVE, "joystick yaw sensitivity" ); + joy_side = Cvar_Get( "joy_side", "1.0", CVAR_ARCHIVE, "joystick side sensitivity. Values from -1.0 to 1.0" ); + joy_forward = Cvar_Get( "joy_forward", "1.0", CVAR_ARCHIVE, "joystick forward sensitivity. Values from -1.0 to 1.0" ); + + joy_lt_threshold = Cvar_Get( "joy_lt_threshold", "-16384", CVAR_ARCHIVE, "left trigger threshold. Value from -32768 to 32767"); + joy_rt_threshold = Cvar_Get( "joy_rt_threshold", "-16384", CVAR_ARCHIVE, "right trigger threshold. Value from -32768 to 32767" ); + + // emit a key event at 75% axis move + joy_side_key_threshold = Cvar_Get( "joy_side_key_threshold", "24576", CVAR_ARCHIVE, "side axis key event emit threshold. Value from 0 to 32767" ); + joy_forward_key_threshold = Cvar_Get( "joy_forward_key_threshold", "24576", CVAR_ARCHIVE, "forward axis key event emit threshold. Value from 0 to 32767"); + + // by default, we rely on deadzone detection come from system, but some glitchy devices report false deadzones + joy_side_deadzone = Cvar_Get( "joy_side_deadzone", "0", CVAR_ARCHIVE, "side axis deadzone. Value from 0 to 32767" ); + joy_forward_deadzone = Cvar_Get( "joy_forward_deadzone", "0", CVAR_ARCHIVE, "forward axis deadzone. Value from 0 to 32767"); + joy_pitch_deadzone = Cvar_Get( "joy_pitch_deadzone", "0", CVAR_ARCHIVE, "pitch axis deadzone. Value from 0 to 32767"); + joy_yaw_deadzone = Cvar_Get( "joy_yaw_deadzone", "0", CVAR_ARCHIVE, "yaw axis deadzone. Value from 0 to 32767" ); + + joy_axis_binding = Cvar_Get( "joy_axis_binding", "sfpyrl", CVAR_ARCHIVE, "axis hardware id to engine inner axis binding, " + "s - side, f - forward, y - yaw, p - pitch, r - left trigger, l - right trigger" ); + joy_found = Cvar_Get( "joy_found", "0", CVAR_READ_ONLY, "total num of connected joysticks" ); + // we doesn't loaded config.cfg yet, so this cvar is not archive. + // change by +set joy_index in cmdline + joy_index = Cvar_Get( "joy_index", "0", CVAR_READ_ONLY, "current active joystick" ); + + joy_enable = Cvar_Get( "joy_enable", "1", CVAR_ARCHIVE, "enable joystick" ); + + if( Sys_CheckParm("-nojoy" ) ) + { + forcedisable = true; + return; + } + +#if defined(XASH_SDL) + // SDL can tell us about connected joysticks + Cvar_SetFloat( "joy_found", SDLash_JoyInit( joy_index->integer ) ); +#elif defined(ANDROID) + // Initalized after first Joy_AddEvent +#else +#warning "Any platform must implement platform-dependent JoyInit, start event system. Otherwise no joystick support" +#endif + + if( joy_found->integer > 0 ) + initialized = true; + else + initialized = false; +} + +/* +=========== +Joy_Shutdown + +Shutdown joystick code +=========== +*/ +void Joy_Shutdown( void ) +{ + Cvar_SetFloat( "joy_found", 0 ); + + initialized = false; +} + +#endif // XASH_DEDICATED diff --git a/engine/client/in_touch.c b/engine/client/in_touch.c new file mode 100644 index 00000000..e9167fab --- /dev/null +++ b/engine/client/in_touch.c @@ -0,0 +1,1768 @@ +/* +touch.c - touchscreen support prototype +Copyright (C) 2015 a1batross + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef XASH_DEDICATED +#include "common.h" +#include "gl_local.h" +#include "input.h" +#include "client.h" +#include "touch.h" +#include "math.h" +#include "vgui_draw.h" +#ifdef XASH_SDL +#include +#include +#endif + +typedef enum +{ + touch_command, // Just tap a button + touch_move, // Like a joystick stick. + touch_joy, // Like a joystick stick, centered. + touch_dpad, // Only two directions. + touch_look // Like a touchpad. +} touchButtonType; + +typedef enum +{ + state_none = 0, + state_edit, + state_edit_move +} touchState; + +typedef enum +{ + round_none = 0, + round_grid, + round_aspect +} touchRound; + + + +typedef struct touchbutton2_s +{ + // Touch button type: tap, stick or slider + touchButtonType type; + // Field of button in pixels + float x1, y1, x2, y2; + // Button texture + int texture; + rgba_t color; + char texturefile[256]; + char command[256]; + char name[32]; + int finger; + int flags; + float fade; + float fadespeed; + float fadeend; + float aspect; + // Double-linked list + struct touchbutton2_s *next; + struct touchbutton2_s *prev; + +} touchbutton2_t; + +typedef struct touchdefaultbutton_s +{ + char name[32]; + char texturefile[256]; + char command[256]; + float x1, y1, x2, y2; + rgba_t color; + touchRound round; + float aspect; + int flags; +} touchdefaultbutton_t; + +typedef struct touchbuttonlist_s +{ + touchbutton2_t *first; + touchbutton2_t *last; +} touchbuttonlist_t; + +struct touch_s +{ + qboolean initialized; + touchbuttonlist_t list_user; + byte *mempool; + touchState state; + int look_finger; + int move_finger; + touchbutton2_t *move; + float move_start_x; + float move_start_y; + float forward; + float side; + float yaw; + float pitch; + // editing + touchbutton2_t *edit; + touchbutton2_t *selection; + int resize_finger; + qboolean showbuttons; + qboolean clientonly; + rgba_t scolor; + int swidth; + qboolean precision; + // textures + int showtexture; + int hidetexture; + int resettexture; + int closetexture; + int joytexture; // touch indicator + qboolean configchanged; +} touch; + +touchdefaultbutton_t g_DefaultButtons[256]; +int g_LastDefaultButton; + +convar_t *touch_pitch; +convar_t *touch_yaw; +convar_t *touch_forwardzone; +convar_t *touch_sidezone; +convar_t *touch_nonlinear_look; +convar_t *touch_pow_mult; +convar_t *touch_pow_factor; +convar_t *touch_exp_mult; +convar_t *touch_grid_enable; +convar_t *touch_grid_count; +convar_t *touch_config_file; +convar_t *touch_enable; +convar_t *touch_in_menu; +convar_t *touch_joy_radius; +convar_t *touch_dpad_radius; +convar_t *touch_move_indicator; +convar_t *touch_highlight_r; +convar_t *touch_highlight_g; +convar_t *touch_highlight_b; +convar_t *touch_highlight_a; +convar_t *touch_precise_amount; +convar_t *touch_joy_texture; + +// code looks smaller with it +#define B(x) button->x +#define SCR_W (scr_width->value) +#define SCR_H (scr_height->value) +#define TO_SCRN_Y(x) (scr_height->integer * (x)) +#define TO_SCRN_X(x) (scr_width->integer * (x)) + +int pfnDrawCharacter( int x, int y, int number, int r, int g, int b ); +static void IN_TouchCheckCoords( float *x1, float *y1, float *x2, float *y2 ); + +void IN_TouchWriteConfig( void ) +{ + file_t *f; + char newconfigfile[64]; + char oldconfigfile[64]; + + if( !touch.list_user.first ) return; + + if( Sys_CheckParm( "-nowriteconfig" ) || !touch.configchanged ) + return; + + MsgDev( D_NOTE, "IN_TouchWriteConfig(): %s\n", touch_config_file->string ); + + Q_snprintf( newconfigfile, 64, "%s.new", touch_config_file->string ); + Q_snprintf( oldconfigfile, 64, "%s.bak", touch_config_file->string ); + + f = FS_Open( newconfigfile, "w", true ); + if( f ) + { + touchbutton2_t *button; + FS_Printf( f, "//=======================================================================\n"); + FS_Printf( f, "//\tCopyright SDLash3D team & XashXT group %s ©\n", Q_timestamp( TIME_YEAR_ONLY )); + FS_Printf( f, "//\t\t\ttouchscreen config\n" ); + FS_Printf( f, "//=======================================================================\n" ); + FS_Printf( f, "\ntouch_config_file \"%s\"\n", touch_config_file->string ); + FS_Printf( f, "\n// touch cvars\n" ); + FS_Printf( f, "\n// sensitivity settings\n" ); + FS_Printf( f, "touch_pitch \"%f\"\n", touch_pitch->value ); + FS_Printf( f, "touch_yaw \"%f\"\n", touch_yaw->value ); + FS_Printf( f, "touch_forwardzone \"%f\"\n", touch_forwardzone->value ); + FS_Printf( f, "touch_sidezone \"%f\"\n", touch_sidezone->value ); + FS_Printf( f, "touch_nonlinear_look \"%d\"\n", touch_nonlinear_look->integer ); + FS_Printf( f, "touch_pow_factor \"%f\"\n", touch_pow_factor->value ); + FS_Printf( f, "touch_pow_mult \"%f\"\n", touch_pow_mult->value ); + FS_Printf( f, "touch_exp_mult \"%f\"\n", touch_exp_mult->value ); FS_Printf( f, "\n// grid settings\n" ); + FS_Printf( f, "touch_grid_count \"%d\"\n", touch_grid_count->integer ); + FS_Printf( f, "touch_grid_enable \"%d\"\n", touch_grid_enable->integer ); + FS_Printf( f, "\n// global overstroke (width, r, g, b, a)\n" ); + FS_Printf( f, "touch_set_stroke %d %d %d %d %d\n", touch.swidth, touch.scolor[0], touch.scolor[1], touch.scolor[2], touch.scolor[3] ); + FS_Printf( f, "\n// highlight when pressed\n" ); + FS_Printf( f, "touch_highlight_r \"%f\"\n", touch_highlight_r->value ); + FS_Printf( f, "touch_highlight_g \"%f\"\n", touch_highlight_g->value ); + FS_Printf( f, "touch_highlight_b \"%f\"\n", touch_highlight_b->value ); + FS_Printf( f, "touch_highlight_a \"%f\"\n", touch_highlight_a->value ); + FS_Printf( f, "\n// _joy and _dpad options\n" ); + FS_Printf( f, "touch_dpad_radius \"%f\"\n", touch_dpad_radius->value ); + FS_Printf( f, "touch_joy_radius \"%f\"\n", touch_joy_radius->value ); + FS_Printf( f, "\n// how much slowdown when Precise Look button pressed\n" ); + FS_Printf( f, "touch_precise_amount \"%f\"\n", touch_precise_amount->value ); + FS_Printf( f, "\n// enable/disable move indicator\n" ); + FS_Printf( f, "touch_move_indicator \"%d\"\n", touch_move_indicator->integer ); + + FS_Printf( f, "\n// reset menu state when execing config\n" ); + FS_Printf( f, "touch_setclientonly 0\n" ); + FS_Printf( f, "\n// touch buttons\n" ); + FS_Printf( f, "touch_removeall\n" ); + for( button = touch.list_user.first; button; button = button->next ) + { + int flags = button->flags; + if( flags & TOUCH_FL_CLIENT ) + continue; //skip temporary buttons + if( flags & TOUCH_FL_DEF_SHOW ) + flags &= ~TOUCH_FL_HIDE; + if( flags & TOUCH_FL_DEF_HIDE ) + flags |= TOUCH_FL_HIDE; + + FS_Printf( f, "touch_addbutton \"%s\" \"%s\" \"%s\" %f %f %f %f %d %d %d %d %d\n", + B(name), B(texturefile), B(command), + B(x1), B(y1), B(x2), B(y2), + B(color[0]), B(color[1]), B(color[2]), B(color[3]), flags ); + } + + FS_Close( f ); + FS_Delete( oldconfigfile ); + FS_Rename( touch_config_file->string, oldconfigfile ); + FS_Delete( touch_config_file->string ); + FS_Rename( newconfigfile, touch_config_file->string ); + } + else MsgDev( D_ERROR, "Couldn't write %s.\n", touch_config_file->string ); +} + +void IN_TouchExportConfig_f( void ) +{ + file_t *f; + char *name; + + if( Cmd_Argc() != 2 ) + { + Msg( "Usage: touch_exportconfig \n" ); + return; + } + + if( !touch.list_user.first ) return; + + name = Cmd_Argv( 1 ); + + MsgDev( D_NOTE, "Exporting config to %s\n", name ); + f = FS_Open( name, "w", true ); + if( f ) + { + char profilename[256]; + char profilebase[256]; + touchbutton2_t *button; + if( Q_strstr( name, "touch_presets/" ) ) + { + FS_FileBase( name, profilebase ); + Q_snprintf( profilename, 256, "touch_profiles/%s (copy).cfg", profilebase ); + } + else Q_strncpy( profilename, name, 256 ); + FS_Printf( f, "//=======================================================================\n"); + FS_Printf( f, "//\tCopyright SDLash3D team & XashXT group %s ©\n", Q_timestamp( TIME_YEAR_ONLY )); + FS_Printf( f, "//\t\t\ttouchscreen preset\n" ); + FS_Printf( f, "//=======================================================================\n" ); + FS_Printf( f, "\ntouch_config_file \"%s\"\n", profilename ); + FS_Printf( f, "\n// touch cvars\n" ); + FS_Printf( f, "\n// sensitivity settings\n" ); + FS_Printf( f, "touch_pitch \"%f\"\n", touch_pitch->value ); + FS_Printf( f, "touch_yaw \"%f\"\n", touch_yaw->value ); + FS_Printf( f, "touch_forwardzone \"%f\"\n", touch_forwardzone->value ); + FS_Printf( f, "touch_sidezone \"%f\"\n", touch_sidezone->value ); + FS_Printf( f, "touch_nonlinear_look \"%d\"\n", touch_nonlinear_look->integer ); + FS_Printf( f, "touch_pow_factor \"%f\"\n", touch_pow_factor->value ); + FS_Printf( f, "touch_pow_mult \"%f\"\n", touch_pow_mult->value ); + FS_Printf( f, "touch_exp_mult \"%f\"\n", touch_exp_mult->value ); + FS_Printf( f, "\n// grid settings\n" ); + FS_Printf( f, "touch_grid_count \"%d\"\n", touch_grid_count->integer ); + FS_Printf( f, "touch_grid_enable \"%d\"\n", touch_grid_enable->integer ); + FS_Printf( f, "\n// global overstroke (width, r, g, b, a)\n" ); + FS_Printf( f, "touch_set_stroke %d %d %d %d %d\n", touch.swidth, touch.scolor[0], touch.scolor[1], touch.scolor[2], touch.scolor[3] ); + FS_Printf( f, "\n// highlight when pressed\n" ); + FS_Printf( f, "touch_highlight_r \"%f\"\n", touch_highlight_r->value ); + FS_Printf( f, "touch_highlight_g \"%f\"\n", touch_highlight_g->value ); + FS_Printf( f, "touch_highlight_b \"%f\"\n", touch_highlight_b->value ); + FS_Printf( f, "touch_highlight_a \"%f\"\n", touch_highlight_a->value ); + FS_Printf( f, "\n// _joy and _dpad options\n" ); + FS_Printf( f, "touch_dpad_radius \"%f\"\n", touch_dpad_radius->value ); + FS_Printf( f, "touch_joy_radius \"%f\"\n", touch_joy_radius->value ); + FS_Printf( f, "\n// how much slowdown when Precise Look button pressed\n" ); + FS_Printf( f, "touch_precise_amount \"%f\"\n", touch_precise_amount->value ); + FS_Printf( f, "\n// enable/disable move indicator\n" ); + FS_Printf( f, "touch_move_indicator \"%d\"\n", touch_move_indicator->integer ); + + FS_Printf( f, "\n// reset menu state when execing config\n" ); + FS_Printf( f, "touch_setclientonly 0\n" ); + FS_Printf( f, "\n// touch buttons\n" ); + FS_Printf( f, "touch_removeall\n" ); + for( button = touch.list_user.first; button; button = button->next ) + { + float aspect; + int flags = button->flags; + if( flags & TOUCH_FL_CLIENT ) + continue; //skip temporary buttons + if( flags & TOUCH_FL_DEF_SHOW ) + flags &= ~TOUCH_FL_HIDE; + if( flags & TOUCH_FL_DEF_HIDE ) + flags |= TOUCH_FL_HIDE; + + aspect = ( B(y2) - B(y1) ) / ( ( B(x2) - B(x1) ) /(SCR_H/SCR_W) ); + + FS_Printf( f, "touch_addbutton \"%s\" \"%s\" \"%s\" %f %f %f %f %d %d %d %d %d %f\n", + B(name), B(texturefile), B(command), + B(x1), B(y1), B(x2), B(y2), + B(color[0]), B(color[1]), B(color[2]), B(color[3]), flags, aspect ); + } + FS_Printf( f, "\n// round button coordinates to grid\n" ); + FS_Printf( f, "touch_roundall\n" ); + FS_Close( f ); + } + else MsgDev( D_ERROR, "Couldn't write %s.\n", name ); +} + +void IN_TouchGenetateCode_f( void ) +{ + touchbutton2_t *button; + rgba_t c = {0,0,0,0}; + + if( Cmd_Argc() != 1 ) + { + Msg( "Usage: touch_generate_code\n" ); + return; + } + + if( !touch.list_user.first ) return; + + for( button = touch.list_user.first; button; button = button->next ) + { + float aspect; + int flags = button->flags; + if( flags & TOUCH_FL_CLIENT ) + continue; //skip temporary buttons + if( flags & TOUCH_FL_DEF_SHOW ) + flags &= ~TOUCH_FL_HIDE; + if( flags & TOUCH_FL_DEF_HIDE ) + flags |= TOUCH_FL_HIDE; + + aspect = ( B(y2) - B(y1) ) / ( ( B(x2) - B(x1) ) /(SCR_H/SCR_W) ); + if( Q_memcmp( &c, &B(color), sizeof( rgba_t ) ) ) + { + Msg( "MakeRGBA( color, %d, %d, %d, %d );\n", B(color[0]), B(color[1]), B(color[2]), B(color[3]) ); + Q_memcpy( &c, &B(color), sizeof( rgba_t ) ); + } + Msg( "TOUCH_ADDDEFAULT( \"%s\", \"%s\", \"%s\", %f, %f, %f, %f, color, %d, %f, %d );\n", + B(name), B(texturefile), B(command), + B(x1), B(y1), B(x2), B(y2), (B(type) == touch_command)?(fabs( aspect - 1.0f) < 0.0001)?2:1:0, aspect, flags ); + } +} + +void IN_TouchRoundAll_f( void ) +{ + touchbutton2_t *button; + if( !touch_grid_enable->value ) + return; + for( button = touch.list_user.first; button; button = button->next ) + IN_TouchCheckCoords( &B(x1), &B(y1), &B(x2), &B(y2) ); +} + +void IN_TouchListButtons_f( void ) +{ + touchbutton2_t *button; + for( button = touch.list_user.first; button; button = button->next ) + { + Msg( "%s %s %s %f %f %f %f %d %d %d %d %d\n", + B(name), B(texturefile), B(command), + B(x1), B(y1), B(x2), B(y2), + B(color[0]), B(color[1]), B(color[2]), B(color[3]), B(flags) ); + if( B(flags) & TOUCH_FL_CLIENT) + continue; + UI_AddTouchButtonToList( B(name), B(texturefile), B(command),B(color), B(flags) ); + } + touch.configchanged = true; +} + +void IN_TouchStroke_f( void ) +{ + touch.swidth = Q_atoi( Cmd_Argv( 1 ) ); + MakeRGBA( touch.scolor, Q_atoi( Cmd_Argv( 2 ) ), Q_atoi( Cmd_Argv( 3 ) ), Q_atoi( Cmd_Argv( 4 ) ), Q_atoi( Cmd_Argv( 5 ) ) ); +} + +touchbutton2_t *IN_TouchFindButton( touchbuttonlist_t *list, const char *name ) +{ + touchbutton2_t *button; + + for ( button = list->first; button; button = button->next ) + if( !Q_strncmp( button->name, name, 32 ) ) + return button; + return NULL; +} + +touchbutton2_t *IN_TouchFindFirst( touchbuttonlist_t *list, const char *name ) +{ + touchbutton2_t *button; + + for ( button = list->first; button; button = button->next ) + if( ( Q_strstr( name, "*" ) && Q_stricmpext( name, button->name ) ) || !Q_strncmp( name, button->name, 32 ) ) + return button; + return NULL; +} + +void IN_TouchSetClientOnly( qboolean state ) +{ + touch.clientonly = state; + host.mouse_visible = state; +#ifdef XASH_SDL + if( state ) + { + SDL_SetRelativeMouseMode( SDL_FALSE ); + SDL_ShowCursor( true ); + IN_DeactivateMouse(); + } + else + { + SDL_ShowCursor( false ); + SDL_GetRelativeMouseState( 0, 0 ); + } +#endif +} + +void IN_TouchSetClientOnly_f( void ) +{ + IN_TouchSetClientOnly( Q_atoi( Cmd_Argv( 1 ) ) ); +} + +void Touch_RemoveButton( touchbuttonlist_t *list, const char *name ) +{ + touchbutton2_t *button; + + IN_TouchEditClear(); + + while( ( button = IN_TouchFindFirst( &touch.list_user, name ) ) ) + { + if( button->prev ) + button->prev->next = button->next; + else + list->first = button->next; + if( button->next ) + button->next->prev = button->prev; + else + list->last = button->prev; + Mem_Free( button ); + } + +} + +void IN_TouchRemoveButton( const char *name ) +{ + Touch_RemoveButton( &touch.list_user, name ); +} + +void IN_TouchRemoveButton_f( void ) +{ + IN_TouchRemoveButton( Cmd_Argv( 1 ) ); +} + +void IN_TouchRemoveAll_f( void ) +{ + IN_TouchEditClear(); + while( touch.list_user.first ) + { + touchbutton2_t *remove = touch.list_user.first; + touch.list_user.first = touch.list_user.first->next; + Mem_Free ( remove ); + } + touch.list_user.last = NULL; +} + +void IN_TouchSetColor( touchbuttonlist_t *list, const char *name, byte *color ) +{ + touchbutton2_t *button; + for( button = list->first; button; button = button->next ) + { + if( ( Q_strstr( name, "*" ) && Q_stricmpext( name, button->name ) ) || !Q_strncmp( name, button->name, 32 ) ) + MakeRGBA( button->color, color[0], color[1], color[2], color[3] ); + } +} + +void IN_TouchSetTexture( touchbuttonlist_t *list, const char *name, const char *texture ) +{ + touchbutton2_t *button = IN_TouchFindButton( list, name ); + if( !button ) + return; + button->texture = -1; // mark for texture load + Q_strncpy( button->texturefile, texture, sizeof( button->texturefile ) ); +} + +void IN_TouchSetCommand( touchbuttonlist_t *list, const char *name, const char *command ) +{ + touchbutton2_t *button = IN_TouchFindButton( list, name ); + + if( !button ) + return; + + if( !Q_strcmp( command, "_look" ) ) + button->type = touch_look; + if( !Q_strcmp( command, "_move" ) ) + button->type = touch_move; + if( !Q_strcmp( command, "_joy" ) ) + button->type = touch_joy; + if( !Q_strcmp( command, "_dpad" ) ) + button->type = touch_dpad; + + Q_strncpy( button->command, command, sizeof( button->command ) ); +} + +void IN_TouchHideButtons( const char *name, qboolean hide ) +{ + touchbutton2_t *button; + + for( button = touch.list_user.first; button; button = button->next) + { + if( ( Q_strstr( name, "*" ) && Q_stricmpext( name, button->name ) ) || !Q_strncmp( name, button->name, 32 ) ) + { + if( hide ) + button->flags |= TOUCH_FL_HIDE; + else + button->flags &= ~TOUCH_FL_HIDE; + } + } + +} +void IN_TouchHide_f( void ) +{ + IN_TouchHideButtons( Cmd_Argv( 1 ), true ); +} + +void IN_TouchShow_f( void ) +{ + IN_TouchHideButtons( Cmd_Argv( 1 ), false ); +} + +void IN_TouchFadeButtons( touchbuttonlist_t *list, const char *name, float speed, float end, float start ) +{ + touchbutton2_t *button; + for( button = list->first; button; button = button->next) + { + if( ( Q_strstr( name, "*" ) && Q_stricmpext( name, button->name ) ) || !Q_strncmp( name, button->name, 32 ) ) + { + if( start >= 0 ) + button->fade = start; + button->fadespeed = speed; + button->fadeend = end; + } + } +} +void IN_TouchFade_f( void ) +{ + float start = -1; + if( Cmd_Argc() < 4 ) + return; + if( Cmd_Argc() > 4 ) + start = Q_atof( Cmd_Argv( 4 ) ); + IN_TouchFadeButtons( &touch.list_user, Cmd_Argv( 1 ), Q_atof( Cmd_Argv( 2 )), Q_atof( Cmd_Argv( 3 )), start ); +} + +void IN_TouchSetColor_f( void ) +{ + rgba_t color; + if( Cmd_Argc() == 6 ) + { + MakeRGBA( color, Q_atoi( Cmd_Argv(2) ), Q_atoi( Cmd_Argv(3) ), Q_atoi( Cmd_Argv(4) ), Q_atoi( Cmd_Argv(5) ) ); + IN_TouchSetColor( &touch.list_user, Cmd_Argv(1), color ); + return; + } + Msg( "Usage: touch_setcolor \n" ); +} + +void IN_TouchSetTexture_f( void ) +{ + if( Cmd_Argc() == 3 ) + { + IN_TouchSetTexture( &touch.list_user, Cmd_Argv( 1 ), Cmd_Argv( 2 ) ); + return; + } + Msg( "Usage: touch_settexture \n" ); +} + +void IN_TouchSetFlags_f( void ) +{ + if( Cmd_Argc() == 3 ) + { + touchbutton2_t *button = IN_TouchFindButton( &touch.list_user, Cmd_Argv( 1 ) ); + if( button ) + button->flags = Q_atoi( Cmd_Argv( 2 ) ); + return; + } + Msg( "Usage: touch_setflags \n" ); +} + +void IN_TouchSetCommand_f( void ) +{ + if( Cmd_Argc() == 3 ) + { + IN_TouchSetCommand( &touch.list_user, Cmd_Argv( 1 ), Cmd_Argv( 2 ) ); + return; + } + Msg( "Usage: touch_command \n" ); +} +void IN_TouchReloadConfig_f( void ) +{ + Cbuf_AddText( va("exec %s\n", touch_config_file->string ) ); +} + +touchbutton2_t *IN_TouchAddButton( touchbuttonlist_t *list, const char *name, const char *texture, const char *command, float x1, float y1, float x2, float y2, byte *color ) +{ + touchbutton2_t *button = Mem_Alloc( touch.mempool, sizeof( touchbutton2_t ) ); + button->texture = -1; + Q_strncpy( button->texturefile, texture, sizeof( button->texturefile ) ); + Q_strncpy( button->name, name, 32 ); + Touch_RemoveButton( list, name ); //replace if exist + button->x1 = x1; + button->y1 = y1; + button->x2 = x2; + button->y2 = y2; + MakeRGBA( button->color, color[0], color[1], color[2], color[3] ); + button->command[0] = 0; + button->flags = 0; + button->fade = 1; + + // check keywords + if( !Q_strcmp( command, "_look" ) ) + button->type = touch_look; + if( !Q_strcmp( command, "_move" ) ) + button->type = touch_move; + if( !Q_strcmp( command, "_joy" ) ) + button->type = touch_joy; + if( !Q_strcmp( command, "_dpad" ) ) + button->type = touch_dpad; + + Q_strncpy( button->command, command, sizeof( button->command ) ); + button->finger = -1; + button->next = NULL; + button->prev = list->last; + if( list->last ) + list->last->next = button; + list->last = button; + + if( !list->first ) + list->first = button; + + return button; +} + +void IN_TouchAddClientButton( const char *name, const char *texture, const char *command, float x1, float y1, float x2, float y2, byte *color, int round, float aspect, int flags ) +{ + touchbutton2_t *button; + + if( !touch.initialized ) + return; + if( round ) + IN_TouchCheckCoords( &x1, &y1, &x2, &y2 ); + if( round == round_aspect ) + { + y2 = y1 + ( x2 - x1 ) * (SCR_W/SCR_H) * aspect; + } + button = IN_TouchAddButton( &touch.list_user, name, texture, command, x1, y1, x2, y2, color ); + button->flags |= flags | TOUCH_FL_CLIENT | TOUCH_FL_NOEDIT; + button->aspect = aspect; +} + +void IN_TouchLoadDefaults_f( void ) +{ + int i; + for( i = 0; i < g_LastDefaultButton; i++ ) + { + touchbutton2_t *button; + float x1 = g_DefaultButtons[i].x1, + y1 = g_DefaultButtons[i].y1, + x2 = g_DefaultButtons[i].x2, + y2 = g_DefaultButtons[i].y2; + + IN_TouchCheckCoords( &x1, &y1, &x2, &y2 ); + + if( g_DefaultButtons[i].aspect && g_DefaultButtons[i].round == round_aspect ) + { + if( g_DefaultButtons[i].texturefile[0] == '#' ) + y2 = y1 + ( (float)clgame.scrInfo.iCharHeight / (float)clgame.scrInfo.iHeight ) * g_DefaultButtons[i].aspect + touch.swidth*2/SCR_H; + else + y2 = y1 + ( x2 - x1 ) * (SCR_W/SCR_H) * g_DefaultButtons[i].aspect; + } + + IN_TouchCheckCoords( &x1, &y1, &x2, &y2 ); + button = IN_TouchAddButton( &touch.list_user, g_DefaultButtons[i].name, g_DefaultButtons[i].texturefile, g_DefaultButtons[i].command, x1, y1, x2, y2, g_DefaultButtons[i].color ); + button->flags |= g_DefaultButtons[i].flags; + button->aspect = g_DefaultButtons[i].aspect; + } +} + + +// Add default button from client +void IN_TouchAddDefaultButton( const char *name, const char *texturefile, const char *command, float x1, float y1, float x2, float y2, byte *color, int round, float aspect, int flags ) +{ + if( g_LastDefaultButton >= 255 ) + return; + Q_strncpy( g_DefaultButtons[g_LastDefaultButton].name, name, 32 ); + Q_strncpy( g_DefaultButtons[g_LastDefaultButton].texturefile, texturefile, 256 ); + Q_strncpy( g_DefaultButtons[g_LastDefaultButton].command, command, 256 ); + g_DefaultButtons[g_LastDefaultButton].x1 = x1; + g_DefaultButtons[g_LastDefaultButton].y1 = y1; + g_DefaultButtons[g_LastDefaultButton].x2 = x2; + g_DefaultButtons[g_LastDefaultButton].y2 = y2; + MakeRGBA( g_DefaultButtons[g_LastDefaultButton].color, color[0], color[1], color[2], color[3] ); + g_DefaultButtons[g_LastDefaultButton].round = round; + g_DefaultButtons[g_LastDefaultButton].aspect = aspect; + g_DefaultButtons[g_LastDefaultButton].flags = flags; + g_LastDefaultButton++; +} + +// Client may remove all default buttons from engine +void IN_TouchResetDefaultButtons( void ) +{ + g_LastDefaultButton = 0; +} +void IN_TouchAddButton_f( void ) +{ + rgba_t color; + int argc = Cmd_Argc( ); + + if( argc >= 12 ) + { + touchbutton2_t *button; + MakeRGBA( color, Q_atoi( Cmd_Argv(8) ), Q_atoi( Cmd_Argv(9) ), + Q_atoi( Cmd_Argv(10) ), Q_atoi( Cmd_Argv(11) ) ); + button = IN_TouchAddButton( &touch.list_user, Cmd_Argv(1), Cmd_Argv(2), Cmd_Argv(3), + Q_atof( Cmd_Argv(4) ), Q_atof( Cmd_Argv(5) ), + Q_atof( Cmd_Argv(6) ), Q_atof( Cmd_Argv(7) ) , + color ); + if( argc >= 13 ) + button->flags = Q_atoi( Cmd_Argv(12) ); + if( argc >= 14 ) + { + // Recalculate button coordinates aspect ratio + // This is feature for distributed configs + float aspect = Q_atof( Cmd_Argv(13) ); + if( aspect ) + { + if( B(texturefile)[0] != '#' ) + B(y2) = B(y1) + ( B(x2) - B(x1) ) * (SCR_W/SCR_H) * aspect; + B(aspect) = aspect; + } + } + + return; + } + if( argc == 8 ) + { + MakeRGBA( color, 255, 255, 255, 255 ); + IN_TouchAddButton( &touch.list_user, Cmd_Argv(1), Cmd_Argv(2), Cmd_Argv(3), + Q_atof( Cmd_Argv(4) ), Q_atof( Cmd_Argv(5) ), + Q_atof( Cmd_Argv(6) ), Q_atof( Cmd_Argv(7) ), + color ); + return; + } + if( argc == 4 ) + { + MakeRGBA( color, 255, 255, 255, 255 ); + IN_TouchAddButton( &touch.list_user, Cmd_Argv(1), Cmd_Argv(2), Cmd_Argv(3), 0.4, 0.4, 0.6, 0.6, color ); + return; + } + Msg( "Usage: touch_addbutton [ [ r g b a ] ]\n" ); +} + +void IN_TouchEnableEdit_f( void ) +{ + if( touch.state == state_none ) + touch.state = state_edit; + touch.resize_finger = touch.move_finger = touch.look_finger = -1; + touch.move = NULL; + touch.configchanged = true; +} + +void IN_TouchDisableEdit_f( void ) +{ + touch.state = state_none; + if( touch.edit ) + touch.edit->finger = -1; + if( touch.selection ) + touch.selection->finger = -1; + touch.edit = touch.selection = NULL; + touch.resize_finger = touch.move_finger = touch.look_finger = -1; +} + +void IN_TouchDeleteProfile_f( void ) +{ + if( Cmd_Argc() != 2 ) + { + Msg( "Usage: touch_deleteprofile \n" ); + return; + } + + // delete profile + FS_Delete( va( "touch_profiles/%s.cfg", Cmd_Argv( 1 ))); +} + + +void IN_TouchInit( void ) +{ + rgba_t color; + if( touch.initialized ) + return; + touch.mempool = Mem_AllocPool( "Touch" ); + //touch.first = touch.last = NULL; + MsgDev( D_NOTE, "IN_TouchInit()\n"); + touch.move_finger = touch.resize_finger = touch.look_finger = -1; + touch.state = state_none; + touch.showbuttons = true; + touch.clientonly = false; + touch.precision = false; + MakeRGBA( touch.scolor, 255, 255, 255, 255 ); + touch.swidth = 1; + g_LastDefaultButton = 0; + + // fill default buttons list + MakeRGBA( color, 255, 255, 255, 255 ); + IN_TouchAddDefaultButton( "look", "", "_look", 0.500000, 0.000000, 1.000000, 1, color, 0, 0, 0 ); + IN_TouchAddDefaultButton( "move", "", "_move", 0.000000, 0.000000, 0.500000, 1, color, 0, 0, 0 ); + IN_TouchAddDefaultButton( "invnext", "touch_default/next_weap.tga", "invnext", 0.000000, 0.530200, 0.120000, 0.757428, color, 2, 1, 0 ); + IN_TouchAddDefaultButton( "invprev", "touch_default/prev_weap.tga", "invprev", 0.000000, 0.075743, 0.120000, 0.302971, color, 2, 1, 0 ); + IN_TouchAddDefaultButton( "use", "touch_default/use.tga", "+use", 0.880000, 0.454457, 1.000000, 0.681685, color, 2, 1, 0 ); + IN_TouchAddDefaultButton( "jump", "touch_default/jump.tga", "+jump", 0.880000, 0.227228, 1.000000, 0.454457, color, 2, 1, 0 ); + IN_TouchAddDefaultButton( "attack", "touch_default/shoot.tga", "+attack", 0.760000, 0.530200, 0.880000, 0.757428, color, 2, 1, 0 ); + IN_TouchAddDefaultButton( "attack2", "touch_default/shoot_alt.tga", "+attack2", 0.760000, 0.302971, 0.880000, 0.530200, color, 2, 1, 0 ); + IN_TouchAddDefaultButton( "loadquick", "touch_default/load.tga", "loadquick", 0.760000, 0.000000, 0.840000, 0.151486, color, 2, 1, 16 ); + IN_TouchAddDefaultButton( "savequick", "touch_default/save.tga", "savequick", 0.840000, 0.000000, 0.920000, 0.151486, color, 2, 1, 16 ); + IN_TouchAddDefaultButton( "messagemode", "touch_default/keyboard.tga", "messagemode", 0.840000, 0.000000, 0.920000, 0.151486, color, 2, 1, 8 ); + IN_TouchAddDefaultButton( "reload", "touch_default/reload.tga", "+reload", 0.000000, 0.302971, 0.120000, 0.530200, color, 2, 1, 0 ); + IN_TouchAddDefaultButton( "flashlight", "touch_default/flash_light_filled.tga", "impulse 100", 0.920000, 0.000000, 1.000000, 0.151486, color, 2, 1, 0 ); + IN_TouchAddDefaultButton( "scores", "touch_default/map.tga", "+showscores", 0.760000, 0.000000, 0.840000, 0.151486, color, 2, 1, 8 ); + IN_TouchAddDefaultButton( "show_numbers", "touch_default/show_weapons.tga", "exec touch_default/numbers.cfg", 0.440000, 0.833171, 0.520000, 0.984656, color, 2, 1, 0 ); + IN_TouchAddDefaultButton( "duck", "touch_default/crouch.tga", "+duck", 0.880000, 0.757428, 1.000000, 0.984656, color, 2, 1, 0 ); + IN_TouchAddDefaultButton( "tduck", "touch_default/tduck.tga", ";+duck", 0.560000, 0.833171, 0.620000, 0.946785, color, 2, 1, 0 ); + IN_TouchAddDefaultButton( "edit", "touch_default/settings.tga", "touch_enableedit", 0.420000, 0.000000, 0.500000, 0.151486, color, 2, 1, 32 ); + IN_TouchAddDefaultButton( "menu", "touch_default/menu.tga", "escape", 0.000000, 0.833171, 0.080000, 0.984656, color, 2, 1, 0 ); + + + Cmd_AddCommand( "touch_addbutton", IN_TouchAddButton_f, "add native touch button" ); + Cmd_AddCommand( "touch_removebutton", IN_TouchRemoveButton_f, "remove native touch button" ); + Cmd_AddCommand( "touch_enableedit", IN_TouchEnableEdit_f, "enable button editing mode" ); + Cmd_AddCommand( "touch_disableedit", IN_TouchDisableEdit_f, "disable button editing mode" ); + Cmd_AddCommand( "touch_settexture", IN_TouchSetTexture_f, "change button texture" ); + Cmd_AddCommand( "touch_setcolor", IN_TouchSetColor_f, "change button color" ); + Cmd_AddCommand( "touch_setcommand", IN_TouchSetCommand_f, "change button command" ); + Cmd_AddCommand( "touch_setflags", IN_TouchSetFlags_f, "change button flags (be careful)" ); + Cmd_AddCommand( "touch_show", IN_TouchShow_f, "show button" ); + Cmd_AddCommand( "touch_hide", IN_TouchHide_f, "hide button" ); + Cmd_AddCommand( "touch_list", IN_TouchListButtons_f, "list buttons" ); + Cmd_AddCommand( "touch_removeall", IN_TouchRemoveAll_f, "remove all buttons" ); + Cmd_AddCommand( "touch_loaddefaults", IN_TouchLoadDefaults_f, "generate config from defaults" ); + Cmd_AddCommand( "touch_roundall", IN_TouchRoundAll_f, "round all buttons coordinates to grid" ); + Cmd_AddCommand( "touch_exportconfig", IN_TouchExportConfig_f, "export config keeping aspect ratio" ); + Cmd_AddCommand( "touch_set_stroke", IN_TouchStroke_f, "set global stroke width and color" ); + Cmd_AddCommand( "touch_setclientonly", IN_TouchSetClientOnly_f, "when 1, only client buttons are shown" ); + Cmd_AddCommand( "touch_reloadconfig", IN_TouchReloadConfig_f, "load config, not saving changes" ); + Cmd_AddCommand( "touch_writeconfig", IN_TouchWriteConfig, "save current config" ); + Cmd_AddCommand( "touch_deleteprofile", IN_TouchDeleteProfile_f, "delete profile by name" ); + Cmd_AddCommand( "touch_generate_code", IN_TouchGenetateCode_f, "create code sample for mobility API" ); + Cmd_AddCommand( "touch_fade", IN_TouchFade_f, "create code sample for mobility API" ); + + // not saved, just runtime state for scripting + touch_in_menu = Cvar_Get( "touch_in_menu", "0", 0, "draw touch in menu (for internal use only)" ); + + // sensitivity configuration + touch_forwardzone = Cvar_Get( "touch_forwardzone", "0.06", 0, "forward touch zone" ); + touch_sidezone = Cvar_Get( "touch_sidezone", "0.06", 0, "side touch zone" ); + touch_pitch = Cvar_Get( "touch_pitch", "90", 0, "touch pitch sensitivity" ); + touch_yaw = Cvar_Get( "touch_yaw", "120", 0, "touch yaw sensitivity" ); + touch_nonlinear_look = Cvar_Get( "touch_nonlinear_look", "0", 0, "enable nonlinear touch look" ); + touch_pow_factor = Cvar_Get( "touch_pow_factor", "1.0", 0, "set > 1 to enable" ); + touch_pow_mult = Cvar_Get( "touch_pow_mult", "300.0", 0, "power multiplier, usually 200-1000" ); + touch_exp_mult = Cvar_Get( "touch_exp_mult", "0", 0, "exponent multiplier, usually 20-200, 0 to disable" ); + + // touch.cfg + touch_grid_count = Cvar_Get( "touch_grid_count", "50", 0, "touch grid count" ); + touch_grid_enable = Cvar_Get( "touch_grid_enable", "1", 0, "enable touch grid" ); + touch_config_file = Cvar_Get( "touch_config_file", "touch.cfg", CVAR_ARCHIVE, "current touch profile file" ); + touch_precise_amount = Cvar_Get( "touch_precise_amount", "0.5", 0, "sensitivity multiplier for precise-look" ); + touch_highlight_r = Cvar_Get( "touch_highlight_r", "1.0", 0, "highlight r color" ); + touch_highlight_g = Cvar_Get( "touch_highlight_g", "1.0", 0, "highlight g color" ); + touch_highlight_b = Cvar_Get( "touch_highlight_b", "1.0", 0, "highlight b color" ); + touch_highlight_a = Cvar_Get( "touch_highlight_a", "1.0", 0, "highlight alpha" ); + touch_dpad_radius = Cvar_Get( "touch_dpad_radius", "1.0", 0, "dpad radius multiplier" ); + touch_joy_radius = Cvar_Get( "touch_joy_radius", "1.0", 0, "joy radius multiplier" ); + touch_move_indicator = Cvar_Get( "touch_move_indicator", "0.0", 0, "indicate move events (0 to disable)" ); + touch_joy_texture = Cvar_Get( "touch_joy_texture", "touch_default/joy.tga", 0, "texture for move indicator"); + + // input devices cvar + touch_enable = Cvar_Get( "touch_enable", DEFAULT_TOUCH_ENABLE, CVAR_ARCHIVE, "enable touch controls" ); +#if defined(XASH_SDL) && defined(__ANDROID__) + SDL_SetHint( SDL_HINT_ANDROID_SEPARATE_MOUSE_AND_TOUCH, "1" ); +#endif + touch.initialized = true; +} +int pfnGetScreenInfo( SCREENINFO *pscrinfo ); +// must be called after executing config.cfg +void IN_TouchInitConfig( void ) +{ + if( !touch.initialized ) + return; + + pfnGetScreenInfo( NULL ); //HACK: update hud screen parameters like iHeight + if( FS_FileExists( touch_config_file->string, true ) ) + Cbuf_AddText( va( "exec \"%s\"\n", touch_config_file->string ) ); + else IN_TouchLoadDefaults_f( ); + touch.closetexture = GL_LoadTexture( "touch_default/edit_close.tga", NULL, 0, TF_NOPICMIP, NULL ); + touch.hidetexture = GL_LoadTexture( "touch_default/edit_hide.tga", NULL, 0, TF_NOPICMIP, NULL ); + touch.showtexture = GL_LoadTexture( "touch_default/edit_show.tga", NULL, 0, TF_NOPICMIP, NULL ); + touch.resettexture = GL_LoadTexture( "touch_default/edit_reset.tga", NULL, 0, TF_NOPICMIP, NULL ); + touch.joytexture = GL_LoadTexture( touch_joy_texture->string, NULL, 0, TF_NOPICMIP, NULL ); + touch.configchanged = false; +} +qboolean IN_TouchIsVisible( touchbutton2_t *button ) +{ + if( !(button->flags & TOUCH_FL_CLIENT) && touch.clientonly ) + return false; // skip nonclient buttons in clientonly mode + + if( touch.state >= state_edit ) + return true; //!!! Draw when editor is open + + if( button->flags & TOUCH_FL_HIDE ) + return false; // skip hidden + + if( button->flags & TOUCH_FL_SP && CL_GetMaxClients() != 1 ) + return false; // skip singleplayer(load, save) buttons in multiplayer + + if( button->flags & TOUCH_FL_MP && CL_GetMaxClients() == 1 ) + return false; // skip multiplayer buttons in singleplayer + + return true; + /* + return ( !touch.clientonly || ( button->flags & TOUCH_FL_CLIENT) ) && + ( + ( touch.state >= state_edit ) + ||( !( button->flags & TOUCH_FL_HIDE ) + && ( !(button->flags & TOUCH_FL_SP) || ( CL_GetMaxClients() == 1 ) ) + && ( !(button->flags & TOUCH_FL_MP) || ( CL_GetMaxClients() != 1 ) ) ) + ); + */ +} + +void Touch_DrawTexture ( float x1, float y1, float x2, float y2, int texture, byte r, byte g, byte b, byte a ) +{ + if( x1 >= x2 ) + return; + + if( y1 >= y2 ) + return; + + pglColor4ub( r, g, b, a ); + R_DrawStretchPic( TO_SCRN_X(x1), + TO_SCRN_Y(y1), + TO_SCRN_X(x2 - x1), + TO_SCRN_Y(y2 - y1), + 0, 0, 1, 1, texture ); +} + +#if defined(_MSC_VER) && (_MSC_VER < 1700) +static __inline int round(float f) +{ + return (int)(f + 0.5); + +} +#endif + +#define GRID_COUNT_X (touch_grid_count->integer) +#define GRID_COUNT_Y (touch_grid_count->integer * SCR_H / SCR_W) +#define GRID_X (1.0/GRID_COUNT_X) +#define GRID_Y (SCR_W/SCR_H/GRID_COUNT_X) +#define GRID_ROUND_X(x) ((float)round( x * GRID_COUNT_X ) / GRID_COUNT_X) +#define GRID_ROUND_Y(x) ((float)round( x * GRID_COUNT_Y ) / GRID_COUNT_Y) + +static void IN_TouchCheckCoords( float *x1, float *y1, float *x2, float *y2 ) +{ + /// TODO: grid check here + if( *x2 - *x1 < GRID_X * 2 ) + *x2 = *x1 + GRID_X * 2; + if( *y2 - *y1 < GRID_Y * 2) + *y2 = *y1 + GRID_Y * 2; + if( *x1 < 0 ) + *x2 -= *x1, *x1 = 0; + if( *y1 < 0 ) + *y2 -= *y1, *y1 = 0; + if( *y2 > 1 ) + *y1 -= *y2 - 1, *y2 = 1; + if( *x2 > 1 ) + *x1 -= *x2 - 1, *x2 = 1; + if ( touch_grid_enable->integer ) + { + *x1 = GRID_ROUND_X( *x1 ); + *x2 = GRID_ROUND_X( *x2 ); + *y1 = GRID_ROUND_Y( *y1 ); + *y2 = GRID_ROUND_Y( *y2 ); + } +} + +float Touch_DrawCharacter( float x, float y, int number, float size ) +{ + float s1, s2, t1, t2, width, height; + int w, h; + wrect_t *prc; + if( !cls.creditsFont.valid ) + return 0; + + number &= 255; + number = Con_UtfProcessChar( number ); + + + R_GetTextureParms( &w, &h, cls.creditsFont.hFontTexture ); + prc = &cls.creditsFont.fontRc[number]; + + s1 = ((float)prc->left) / (float)w; + t1 = ((float)prc->top) / (float)h; + s2 = ((float)prc->right) / (float)w; + t2 = ((float)prc->bottom) / (float)h; + + width = ((float)( prc->right - prc->left )) / 1024.0f * size; + height = ((float)( prc->bottom - prc->top )) / 1024.0f * size; + + R_DrawStretchPic( TO_SCRN_X(x), TO_SCRN_Y(y), TO_SCRN_X(width), TO_SCRN_X(height), s1, t1, s2, t2, cls.creditsFont.hFontTexture ); + return width; +} + +float Touch_DrawText( float x1, float y1, float x2, float y2, const char *s, byte *color, float size ) +{ + float x = x1; + float maxy = y2; + float maxx; + if( x2 ) + maxx = x2 - cls.creditsFont.charWidths['M'] / 1024.0f * size; + else + maxx = 1; + + if( !cls.creditsFont.valid ) + return GRID_X * 2; + Con_UtfProcessChar( 0 ); + + GL_SetRenderMode( kRenderTransAdd ); + + // text is additive and alpha does not work + pglColor4ub( color[0] * ( (float)color[3] /255.0f ), color[1] * ( (float)color[3] /255.0f ), + color[2] * ( (float)color[3] /255.0f ), 255 ); + + while( *s ) + { + while( *s && ( *s != '\n' ) && ( *s != ';' ) && ( x1 < maxx ) ) + x1 += Touch_DrawCharacter( x1, y1, *s++, size ); + y1 += cls.creditsFont.charHeight / 1024.f * size / SCR_H * SCR_W; + + if( y1 >= maxy ) + break; + + if( *s == '\n' || *s == ';' ) + s++; + x1 = x; + } + GL_SetRenderMode( kRenderTransTexture ); + return x1; +} + +void Touch_DrawButtons( touchbuttonlist_t *list ) +{ + touchbutton2_t *button; + + for( button = list->first; button; button = button->next ) + { + if( IN_TouchIsVisible( button ) ) + { + rgba_t color; + MakeRGBA( color, B( color[0] ), B( color[1] ), B( color[2] ), B( color[3] ) ); + + if( B( fadespeed ) ) + { + button->fade += B( fadespeed ) * host.frametime; + button->fade = bound( 0, B(fade), 1 ); + if( ( B( fade ) == 0 ) || ( B(fade) == 1 ) ) + B( fadespeed ) = 0; + if( ( ( B( fade ) >= B( fadeend ) ) && ( B( fadespeed ) > 0 ) ) || + ( ( B( fade ) <= B( fadeend ) ) && ( B( fadespeed ) < 0 ) ) ) + B( fadespeed ) = 0, B( fade ) = B( fadeend ) ; + } + + if( ( B( finger ) != -1 ) && !( B( flags ) & TOUCH_FL_CLIENT ) ) + { + color[0] = bound( 0,(float) color[0] * touch_highlight_r->value, 255 ); + color[1] = bound( 0,(float) color[1] * touch_highlight_g->value, 255 ); + color[2] = bound( 0,(float) color[2] * touch_highlight_b->value, 255 ); + color[3] = bound( 0,(float) color[3] * touch_highlight_a->value, 255 ); + } + + color[3] *= B( fade ); + if( button->texturefile[0] == '#' ) + Touch_DrawText( touch.swidth/SCR_W + B(x1), touch.swidth/SCR_H + B(y1), B(x2), B(y2), button->texturefile + 1, color, B( aspect )?B(aspect):1 ); + else if( button->texturefile[0] ) + { + if( button->texture == -1 ) + { + button->texture = GL_LoadTexture( button->texturefile, NULL, 0, TF_NOPICMIP, NULL ); + } + + if( B(flags) & TOUCH_FL_DRAW_ADDITIVE ) + GL_SetRenderMode( kRenderTransAdd ); + + Touch_DrawTexture( B(x1), B(y1), B(x2), B(y2), B(texture), color[0], color[1], color[2], color[3] ); + + GL_SetRenderMode( kRenderTransTexture ); + } + if( B(flags) & TOUCH_FL_STROKE ) + { + pglColor4ub( touch.scolor[0], touch.scolor[1], touch.scolor[2], touch.scolor[3] * B( fade ) ); + R_DrawStretchPic( TO_SCRN_X(B(x1)), + TO_SCRN_Y(B(y1)), + touch.swidth, + TO_SCRN_Y(B(y2)-B(y1)) - touch.swidth, + 0, 0, 1, 1, cls.fillImage ); + R_DrawStretchPic( TO_SCRN_X(B(x1)) + touch.swidth, + TO_SCRN_Y(B(y1)), + TO_SCRN_X(B(x2)-B(x1)) - touch.swidth, + touch.swidth, + 0, 0, 1, 1, cls.fillImage ); + R_DrawStretchPic( TO_SCRN_X(B(x2))-touch.swidth, + TO_SCRN_Y(B(y1)) + touch.swidth, + touch.swidth, + TO_SCRN_Y(B(y2)-B(y1)) - touch.swidth, + 0, 0, 1, 1, cls.fillImage ); + R_DrawStretchPic( TO_SCRN_X(B(x1)), + TO_SCRN_Y(B(y2))-touch.swidth, + TO_SCRN_X(B(x2)-B(x1)) - touch.swidth, + touch.swidth, + 0, 0, 1, 1, cls.fillImage ); + pglColor4ub( 255, 255, 255, 255 ); + } + } + if( touch.state >= state_edit ) + { + rgba_t color; + if( !( button->flags & TOUCH_FL_HIDE ) ) + Touch_DrawTexture( B(x1), B(y1), B(x2), B(y2), cls.fillImage, 255, 255, 0, 32 ); + else + Touch_DrawTexture( B(x1), B(y1), B(x2), B(y2), cls.fillImage, 128, 128, 128, 128 ); + MakeRGBA( color, 255, 255,127, 255 ); + Con_DrawString( TO_SCRN_X( B(x1) ), TO_SCRN_Y( B(y1) ), B(name), color ); + } + } + +} + +void IN_TouchDraw( void ) +{ + touchbutton2_t *button; + + if( !touch.initialized || (!touch_enable->integer && !touch.clientonly) ) + return; + + if( cls.key_dest != key_game && touch_in_menu->integer == 0 ) + return; + + GL_SetRenderMode( kRenderTransTexture ); + + if( touch.state >= state_edit && touch_grid_enable->integer ) + { + float x; + if( touch_in_menu->integer ) + Touch_DrawTexture( 0, 0, 1, 1, cls.fillImage, 32, 32, 32, 255 ); + else + Touch_DrawTexture( 0, 0, 1, 1, cls.fillImage, 0, 0, 0, 112 ); + pglColor4ub( 0, 224, 224, 112 ); + for ( x = 0; x < 1 ; x += GRID_X ) + R_DrawStretchPic( TO_SCRN_X(x), + 0, + 1, + TO_SCRN_Y(1), + 0, 0, 1, 1, cls.fillImage ); + for ( x = 0; x < 1 ; x += GRID_Y ) + R_DrawStretchPic( 0, + TO_SCRN_Y(x), + TO_SCRN_X(1), + 1, + 0, 0, 1, 1, cls.fillImage ); + } + + Touch_DrawButtons( &touch.list_user ); + + if( touch.state >= state_edit ) + { + rgba_t color; + MakeRGBA( color, 255, 255, 255, 255 ); + if( touch.edit ) + { + float x1 = touch.edit->x1, + y1 = touch.edit->y1, + x2 = touch.edit->x2, + y2 = touch.edit->y2; + IN_TouchCheckCoords( &x1, &y1, &x2, &y2 ); + Touch_DrawTexture( x1, y1, x2, y2, cls.fillImage, 0, 255, 0, 32 ); + } + Touch_DrawTexture( 0, 0, GRID_X, GRID_Y, cls.fillImage, 255, 255, 255, 64 ); + if( touch.selection ) + { + button = touch.selection; + Touch_DrawTexture( B(x1), B(y1), B(x2), B(y2), cls.fillImage, 255, 0, 0, 64 ); + if( touch.showbuttons ) + { + if( button->flags & TOUCH_FL_HIDE ) + { + Touch_DrawTexture( 0, GRID_Y * 8, GRID_X * 2, GRID_Y * 10, touch.showtexture, 255, 255, 255, 255 ); + Touch_DrawText( GRID_X * 2.5, GRID_Y * 8.5, 0, 0, "Show", color, 1.5 ); + } + else + { + Touch_DrawTexture( 0, GRID_Y * 8, GRID_X * 2, GRID_Y * 10, touch.hidetexture, 255, 255, 255, 255 ); + Touch_DrawText( GRID_X * 2.5, GRID_Y * 8.5, 0, 0, "Hide", color, 1.5 ); + } + } + Con_DrawString( 0, TO_SCRN_Y(GRID_Y * 11), "Selection:", color ); + Con_DrawString( Con_DrawString( 0, TO_SCRN_Y(GRID_Y*12), "Name: ", color ), + TO_SCRN_Y(GRID_Y*12), B(name), color ); + Con_DrawString( Con_DrawString( 0, TO_SCRN_Y(GRID_Y*13), "Texture: ", color ), + TO_SCRN_Y(GRID_Y*13), B(texturefile), color ); + Con_DrawString( Con_DrawString( 0, TO_SCRN_Y(GRID_Y*14), "Command: ", color ), + TO_SCRN_Y(GRID_Y*14), B(command), color ); + } + if( touch.showbuttons ) + { + // close + Touch_DrawTexture( 0, GRID_Y * 2, GRID_X * 2, GRID_Y * 4, touch.closetexture, 255, 255, 255, 255 ); + //Con_DrawString( TO_SCRN_X( GRID_X * 2.5 ), TO_SCRN_Y( GRID_Y * 2.5 ), "Close", color ); + Touch_DrawText( GRID_X * 2.5, GRID_Y * 2.5, 0, 0, "Close", color, 1.5 ); + // reset + Touch_DrawTexture( 0, GRID_Y * 5, GRID_X * 2, GRID_Y * 7, touch.resettexture, 255, 255, 255, 255 ); + //Con_DrawString( TO_SCRN_X( GRID_X * 2.5 ), TO_SCRN_Y( GRID_Y * 5.5 ), "Reset", color ); + Touch_DrawText( GRID_X * 2.5, GRID_Y * 5.5, 0, 0, "Reset", color, 1.5 ); + } + } + pglColor4ub( 255, 255, 255, 255 ); + if( ( touch.move_finger != -1 ) && touch.move && touch_move_indicator->value ) + { + float width; + float height; + if( touch_joy_texture->modified ) + { + touch_joy_texture->modified = false; + touch.joytexture = GL_LoadTexture( touch_joy_texture->string, NULL, 0, TF_NOPICMIP, NULL ); + } + if( touch.move->type == touch_move ) + { + width = touch_sidezone->value; + height = touch_forwardzone->value; + } + else + { + width = (touch.move->x2 - touch.move->x1)/2; + height = (touch.move->y2 - touch.move->y1)/2; + } + pglColor4ub( 255, 255, 255, 128 ); + R_DrawStretchPic( TO_SCRN_X( touch.move_start_x - GRID_X * touch_move_indicator->value ), + TO_SCRN_Y( touch.move_start_y - GRID_Y * touch_move_indicator->value ), + TO_SCRN_X( GRID_X * 2 * touch_move_indicator->value ), TO_SCRN_Y( GRID_Y * 2 * touch_move_indicator->value ), 0, 0, 1, 1, touch.joytexture ); + pglColor4ub( 255, 255, 255, 255 ); + R_DrawStretchPic( TO_SCRN_X( touch.move_start_x + touch.side * width - GRID_X * touch_move_indicator->value ), + TO_SCRN_Y( touch.move_start_y - touch.forward * height - GRID_Y * touch_move_indicator->value ), + TO_SCRN_X( GRID_X * 2 * touch_move_indicator->value ), TO_SCRN_Y( GRID_Y * 2 * touch_move_indicator->value ), 0, 0, 1, 1, touch.joytexture ); + + } + +} + +// clear move and selection state +void IN_TouchEditClear( void ) +{ + // Allow keep move/look fingers when doing touch_removeall + //touch.move_finger = touch.look_finger = -1; + if( touch.state < state_edit ) + return; + touch.state = state_edit; + if( touch.edit ) + touch.edit->finger = -1; + touch.resize_finger = -1; + touch.edit = NULL; + touch.selection = NULL; +} + +static void Touch_EditMove( touchEventType type, int fingerID, float x, float y, float dx, float dy ) +{ + if( touch.edit->finger == fingerID ) + { + if( type == event_up ) // shutdown button move + { + touchbutton2_t *button = touch.edit; + IN_TouchCheckCoords( &B(x1), &B(y1), &B(x2), &B(y2) ); + IN_TouchEditClear(); + if( button->type == touch_command ) + touch.selection = button; + } + if( type == event_motion ) // shutdown button move + { + touch.edit->y1 += dy; + touch.edit->y2 += dy; + touch.edit->x1 += dx; + touch.edit->x2 += dx; + } + } + else + { + if( type == event_down ) // enable resizing + { + if( touch.resize_finger == -1 ) + { + touch.resize_finger = fingerID; + } + } + if( type == event_up ) // disable resizing + { + if( touch.resize_finger == fingerID ) + { + touch.resize_finger = -1; + } + } + if( type == event_motion ) // perform resizing + { + if( touch.resize_finger == fingerID ) + { + touch.edit->y2 += dy; + touch.edit->x2 += dx; + } + } + } +} + +static void Touch_Motion( touchEventType type, int fingerID, float x, float y, float dx, float dy ) +{ + // walk + if( fingerID == touch.move_finger ) + { + // check bounds + if( touch_forwardzone->value <= 0 ) + Cvar_SetFloat( "touch_forwardzone", 0.5 ); + if( touch_sidezone->value <= 0 ) + Cvar_SetFloat( "touch_sidezone", 0.3 ); + + if( !touch.move || touch.move->type == touch_move ) + { + // move relative to touch start + touch.forward = ( touch.move_start_y - y ) / touch_forwardzone->value; + touch.side = ( x - touch.move_start_x ) / touch_sidezone->value; + } + else if( touch.move->type == touch_joy ) + { + // move relative to joy center + touch.forward = ( ( touch.move->y2 + touch.move->y1 ) - y * 2 ) / ( touch.move->y2 - touch.move->y1 ) * touch_joy_radius->value; + touch.side = ( x * 2 - ( touch.move->x2 + touch.move->x1 ) ) / ( touch.move->x2 - touch.move->x1 ) * touch_joy_radius->value; + } + else if( touch.move->type == touch_dpad ) + { + // like joy, but without acceleration. useful for bhop + touch.forward = round( ( (touch.move->y2 + touch.move->y1) - y * 2 ) / ( touch.move->y2 - touch.move->y1 ) * touch_dpad_radius->value ); + touch.side = round( ( x * 2 - (touch.move->x2 + touch.move->x1) ) / ( touch.move->x2 - touch.move->x1 ) * touch_dpad_radius->value ); + } + + touch.forward = bound( -1, touch.forward, 1 ); + touch.side = bound( -1, touch.side, 1 ); + } + + // process look + if( fingerID == touch.look_finger ) + { + if( touch.precision ) + dx *= touch_precise_amount->value, dy *= touch_precise_amount->value; + + if( touch_nonlinear_look->integer ); + { + // save angle, modify only velocity + float dabs = sqrt( dx*dx+dy*dy ); + float dcos = dx/dabs; + float dsin = dy/dabs; + + if( touch_exp_mult->value > 1 ) + dabs = (exp(dabs*touch_exp_mult->value)-1)/touch_exp_mult->value; + if( touch_pow_mult->value > 1 && touch_pow_factor->value > 1 ) + dabs = pow(dabs*touch_pow_mult->value,touch_pow_factor->value)/touch_pow_mult->value; + + dx = dabs * dcos; + dy = dabs * dsin; + } + + // accumulate + touch.yaw -= dx * touch_yaw->value, touch.pitch += dy * touch_pitch->value; + } +} + + +static qboolean Touch_ButtonPress( touchbuttonlist_t *list, touchEventType type, int fingerID, float x, float y, float dx, float dy ) +{ + touchbutton2_t *button; + + // run from end(front) to start(back) + for( button = list->last; button; button = button->prev ) + { + if( type == event_down ) + { + if( ( x > button->x1 && + x < button->x2 ) && + ( y < button->y2 && + y > button->y1 ) ) + { + button->finger = fingerID; + + if( !IN_TouchIsVisible( button ) ) + continue; + + if( button->type == touch_command ) + { + char command[256]; + Q_snprintf( command, sizeof( command ), "%s\n", button->command ); + Cbuf_AddText( command ); + + if( B(flags) & TOUCH_FL_PRECISION ) + touch.precision = true; + } + if( button->type == touch_move || button->type == touch_joy || button->type == touch_dpad ) + { + if( touch.move_finger !=-1 ) + { + button->finger = touch.move_finger; + continue; + } + if( touch.look_finger == fingerID ) + { + touchbutton2_t *newbutton; + touch.move_finger = touch.look_finger = -1; + //touch.move_finger = button->finger = -1; + for( newbutton = list->first; newbutton; newbutton = newbutton->next ) + if( ( newbutton->type == touch_move ) || ( newbutton->type == touch_look ) ) newbutton->finger = -1; + MsgDev( D_NOTE, "Touch: touch_move on look finger %d!\n", fingerID ); + continue; + } + touch.move_finger = fingerID; + touch.move = button; + if( touch.move->type == touch_move ) + { + touch.move_start_x = x; + touch.move_start_y = y; + } + else if( touch.move->type == touch_joy ) + { + touch.move_start_y = (touch.move->y2 + touch.move->y1) / 2; + touch.move_start_x = (touch.move->x2 + touch.move->x1) / 2; + touch.forward = ((touch.move->y2 + touch.move->y1) - y * 2) / (touch.move->y2 - touch.move->y1); + touch.side = (x * 2 - (touch.move->x2 + touch.move->x1)) / (touch.move->x2 - touch.move->x1); + } + else if( touch.move->type == touch_dpad ) + { + touch.move_start_y = (touch.move->y2 + touch.move->y1) / 2; + touch.move_start_x = (touch.move->x2 + touch.move->x1) / 2; + touch.forward = round(((touch.move->y2 + touch.move->y1) - y * 2) / (touch.move->y2 - touch.move->y1)); + touch.side = round((x * 2 - (touch.move->x2 + touch.move->x1)) / (touch.move->x2 - touch.move->x1)); + } + } + if( button->type == touch_look ) + { + if( touch.look_finger !=-1 ) + { + button->finger = touch.look_finger; + continue; + } + if( touch.move_finger == fingerID ) + { + touchbutton2_t *newbutton; + // This is an error, try recover + touch.move_finger = touch.look_finger = -1; + for( newbutton = list->first; newbutton; newbutton = newbutton->next ) + if( ( newbutton->type == touch_move ) || ( newbutton->type == touch_look ) ) newbutton->finger = -1; + MsgDev( D_NOTE, "Touch: touch_look on move finger %d!\n", fingerID ); + continue; + } + touch.look_finger = fingerID; + } + } + } + if( type == event_up ) + { + if( fingerID == button->finger ) + { + button->finger = -1; + if( !IN_TouchIsVisible( button ) ) + continue; + + if( ( button->type == touch_command ) && ( button->command[0] == '+' ) ) + { + char command[256]; + Q_snprintf( command, sizeof( command ), "%s\n", button->command ); + command[0] = '-'; + Cbuf_AddText( command ); + if( B(flags) & TOUCH_FL_PRECISION ) + touch.precision = false; + } + if( button->type == touch_move || button->type == touch_joy || button->type == touch_dpad ) + { + touch.move_finger = -1; + touch.forward = touch.side = 0; + touch.move = NULL; + } + if( button->type == touch_look ) + { + touch.look_finger = -1; + } + } + } + } + + return false; +} + +static qboolean Touch_ButtonEdit( touchEventType type, int fingerID, float x, float y, float dx, float dy ) +{ + touchbutton2_t *button; + + // edit buttons are on y1 + if( type == event_down ) + { + if( (x < GRID_X) && (y < GRID_Y) ) + { + touch.showbuttons ^= true; + return true; + } + if( touch.showbuttons && ( x < GRID_X * 2 ) ) + { + if( ( y > GRID_Y * 2 ) && ( y < GRID_Y * 4 ) ) // close button + { + IN_TouchDisableEdit_f(); + if( touch_in_menu->integer ) + { + Cvar_Set( "touch_in_menu", "0" ); + } + else + IN_TouchWriteConfig(); + return true; + } + if( ( y > GRID_Y * 5 ) && ( y < GRID_Y * 7 ) ) // reset button + { + IN_TouchReloadConfig_f(); + return true; + } + if( ( y > GRID_Y * 8 ) && ( y < GRID_Y * 10 ) && touch.selection ) // hide button + { + touch.selection->flags ^= TOUCH_FL_HIDE; + return true; + } + } + } + + // run from end(front) to start(back) + for( button = touch.list_user.last; button; button = button->prev ) + { + if( type == event_down ) + { + if( ( x > button->x1 && + x < button->x2 ) && + ( y < button->y2 && + y > button->y1 ) ) + { + button->finger = fingerID; + + // do not edit NOEDIT buttons + if( button->flags & TOUCH_FL_NOEDIT ) + continue; + + touch.edit = button; + touch.selection = NULL; + // Make button last to bring it up + if( ( button->next ) && ( button->type == touch_command ) ) + { + if( button->prev ) + button->prev->next = button->next; + else + touch.list_user.first = button->next; + + button->next->prev = button->prev; + touch.list_user.last->next = button; + button->prev = touch.list_user.last; + button->next = NULL; + touch.list_user.last = button; + } + touch.state = state_edit_move; + return true; + } + } + if( type == event_up ) + if( fingerID == button->finger ) + button->finger = -1; + } + + if( type == event_down ) + touch.selection = NULL; + + return false; +} + +static int Touch_ControlsEvent( touchEventType type, int fingerID, float x, float y, float dx, float dy ) +{ + touchbutton2_t *button; + + if( touch.state == state_edit_move ) + { + Touch_EditMove( type, fingerID, x, y, dx, dy ); + return 1; + } + + if( touch.state == state_edit && Touch_ButtonEdit( type, fingerID, x, y, dx, dy ) ) + return true; + if( Touch_ButtonPress( &touch.list_user, type, fingerID, x, y, dx, dy ) ) + return true; + if( type == event_motion ) + Touch_Motion( type, fingerID, x, y, dx, dy ); + return true; +} + +int IN_TouchEvent( touchEventType type, int fingerID, float x, float y, float dx, float dy ) +{ + + // simulate menu mouse click + if( cls.key_dest != key_game && !touch_in_menu->integer ) + { + touch.move_finger = touch.resize_finger = touch.look_finger = -1; + // Hack for keyboard, hope it help + if( cls.key_dest == key_console || cls.key_dest == key_message ) + { + Key_EnableTextInput( true, true ); + if( cls.key_dest == key_console ) + { + static float y1 = 0; + y1 += dy; + if( dy > 0.4 ) + Con_Bottom(); + if( y1 > 0.01 ) + { + Con_PageUp( 1 ); + y1 = 0; + } + if( y1 < -0.01 ) + { + Con_PageDown( 1 ); + y1 = 0; + } + } + + // exit of console area + if( type == event_down && x < 0.1f && y > 0.9f ) + Cbuf_AddText( "escape\n" ); + } + UI_MouseMove( TO_SCRN_X(x), TO_SCRN_Y(y) ); + //MsgDev( D_NOTE, "touch %d %d\n", TO_SCRN_X(x), TO_SCRN_Y(y) ); + if( type == event_down ) + Key_Event( K_MOUSE1, true ); + if( type == event_up ) + Key_Event( K_MOUSE1, false ); + return 0; + } + + + if( VGui_IsActive() ) + { + VGui_MouseMove( TO_SCRN_X(x), TO_SCRN_Y(y) ); + + if( type != event_motion ) + VGui_KeyEvent( K_MOUSE1, type == event_down ? 1 : 0 ); + + // allow scoreboard scroll + if( host.mouse_visible && type == event_motion ) + return 0; + } + + if( !touch.initialized || (!touch_enable->integer && !touch.clientonly) ) + { +#if 0 + if( type == event_down ) + Key_Event( K_MOUSE1, true ); + if( type == event_up ) + Key_Event( K_MOUSE1, false ); + Android_AddMove( dx * scr_width->value, dy * scr_height->value ); +#endif + return 0; + } + + if( clgame.dllFuncs.pfnTouchEvent && clgame.dllFuncs.pfnTouchEvent( type, fingerID, x, y, dx, dy ) ) + return true; + + return Touch_ControlsEvent( type, fingerID, x, y, dx, dy ); +} + +void IN_TouchMove( float *forward, float *side, float *yaw, float *pitch ) +{ + *forward += touch.forward; + *side += touch.side; + *yaw += touch.yaw; + *pitch += touch.pitch; + touch.yaw = touch.pitch = 0; +} + +void IN_TouchKeyEvent( int key, int down ) +{ + int xi, yi; + float x, y; + + if( touch_enable->integer ) + return; + + if( !touch.clientonly ) + return; + + CL_GetMousePosition( &xi, &yi ); + + x = xi/SCR_W; + y = yi/SCR_H; + + Touch_ControlsEvent( !down, key == K_MOUSE1?0:1, x, y, 0, 0 ); +} + +void IN_TouchShutdown( void ) +{ + if( !touch.initialized ) + return; + IN_TouchRemoveAll_f(); + Cmd_RemoveCommand( "touch_addbutton" ); + Cmd_RemoveCommand( "touch_removebutton" ); + Cmd_RemoveCommand( "touch_enableedit" ); + Cmd_RemoveCommand( "touch_disableedit" ); + Cmd_RemoveCommand( "touch_settexture" ); + Cmd_RemoveCommand( "touch_setcolor" ); + Cmd_RemoveCommand( "touch_setcommand" ); + Cmd_RemoveCommand( "touch_setflags" ); + Cmd_RemoveCommand( "touch_show" ); + Cmd_RemoveCommand( "touch_hide" ); + Cmd_RemoveCommand( "touch_list" ); + Cmd_RemoveCommand( "touch_removeall" ); + Cmd_RemoveCommand( "touch_loaddefaults" ); + Cmd_RemoveCommand( "touch_roundall" ); + Cmd_RemoveCommand( "touch_exportconfig" ); + Cmd_RemoveCommand( "touch_set_stroke" ); + Cmd_RemoveCommand( "touch_setclientonly" ); + Cmd_RemoveCommand( "touch_reloadconfig" ); + Cmd_RemoveCommand( "touch_writeconfig" ); + Cmd_RemoveCommand( "touch_generate_code" ); + + touch.initialized = false; + Mem_FreePool( &touch.mempool ); +} +#endif diff --git a/engine/common/input.c b/engine/common/input.c index 2618bebf..d08977ae 100644 --- a/engine/common/input.c +++ b/engine/common/input.c @@ -17,100 +17,38 @@ GNU General Public License for more details. #include "input.h" #include "client.h" #include "vgui_draw.h" +#include "gl_local.h" -#define PRINTSCREEN_ID 1 -#define WND_HEADSIZE wnd_caption // some offset -#define WND_BORDER 3 // sentinel border in pixels +#ifdef XASH_SDL +#include +#endif -HICON in_mousecursor; +#ifdef _WIN32 +#include "windows.h" +#endif + +void* in_mousecursor; qboolean in_mouseactive; // false when not focus app -qboolean in_restore_spi; qboolean in_mouseinitialized; -int in_mouse_oldbuttonstate; qboolean in_mouse_suspended; +POINT in_lastvalidpos; qboolean in_mouse_savedpos; -int in_mouse_buttons; -RECT window_rect, real_rect; -POINT in_lastvalidpos; -uint in_mouse_wheel; -int wnd_caption; - -static byte scan_to_key[128] = -{ - 0,27,'1','2','3','4','5','6','7','8','9','0','-','=',K_BACKSPACE,9, - 'q','w','e','r','t','y','u','i','o','p','[',']', 13 , K_CTRL, - 'a','s','d','f','g','h','j','k','l',';','\'','`', - K_SHIFT,'\\','z','x','c','v','b','n','m',',','.','/',K_SHIFT, - '*',K_ALT,' ',K_CAPSLOCK, - K_F1,K_F2,K_F3,K_F4,K_F5,K_F6,K_F7,K_F8,K_F9,K_F10, - K_PAUSE,0,K_HOME,K_UPARROW,K_PGUP,K_KP_MINUS,K_LEFTARROW,K_KP_5, - K_RIGHTARROW,K_KP_PLUS,K_END,K_DOWNARROW,K_PGDN,K_INS,K_DEL, - 0,0,0,K_F11,K_F12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -}; - -// extra mouse buttons -static int mouse_buttons[] = +static struct inputstate_s { - MK_LBUTTON, - MK_RBUTTON, - MK_MBUTTON, - MK_XBUTTON1, - MK_XBUTTON2, - MK_XBUTTON3, - MK_XBUTTON4, - MK_XBUTTON5 -}; - -/* -======= -Host_MapKey + float lastpitch, lastyaw; +} inputstate; -Map from windows to engine keynums -======= -*/ -static int Host_MapKey( int key ) -{ - int result, modified; - qboolean is_extended = false; +extern convar_t *vid_fullscreen; +convar_t *m_enginemouse; +convar_t *m_pitch; +convar_t *m_yaw; - modified = ( key >> 16 ) & 255; - if( modified > 127 ) return 0; - - if( key & ( 1 << 24 )) - is_extended = true; - - result = scan_to_key[modified]; - - if( !is_extended ) - { - switch( result ) - { - case K_HOME: return K_KP_HOME; - case K_UPARROW: return K_KP_UPARROW; - case K_PGUP: return K_KP_PGUP; - case K_LEFTARROW: return K_KP_LEFTARROW; - case K_RIGHTARROW: return K_KP_RIGHTARROW; - case K_END: return K_KP_END; - case K_DOWNARROW: return K_KP_DOWNARROW; - case K_PGDN: return K_KP_PGDN; - case K_INS: return K_KP_INS; - case K_DEL: return K_KP_DEL; - default: return result; - } - } - else - { - switch( result ) - { - case K_PAUSE: return K_KP_NUMLOCK; - case 0x0D: return K_KP_ENTER; - case 0x2F: return K_KP_SLASH; - case 0xAF: return K_KP_PLUS; - default: return result; - } - } -} +convar_t *m_enginesens; +convar_t *m_ignore; +convar_t *cl_forwardspeed; +convar_t *cl_sidespeed; +convar_t *cl_backspeed; +convar_t *look_filter; /* =========== @@ -119,40 +57,28 @@ IN_StartupMouse */ void IN_StartupMouse( void ) { - if( host.type == HOST_DEDICATED ) return; - if( Sys_CheckParm( "-nomouse" )) return; + m_ignore = Cvar_Get( "m_ignore", DEFAULT_M_IGNORE, FCVAR_ARCHIVE , "ignore mouse events" ); + + m_enginemouse = Cvar_Get( "m_enginemouse", "0", FCVAR_ARCHIVE, "read mouse events in engine instead of client" ); + m_enginesens = Cvar_Get( "m_enginesens", "0.3", FCVAR_ARCHIVE, "mouse sensitivity, when m_enginemouse enabled" ); + m_pitch = Cvar_Get( "m_pitch", "0.022", FCVAR_ARCHIVE, "mouse pitch value" ); + m_yaw = Cvar_Get( "m_yaw", "0.022", FCVAR_ARCHIVE, "mouse yaw value" ); + look_filter = Cvar_Get( "look_filter", "0", FCVAR_ARCHIVE, "filter look events making it smoother" ); + + // You can use -nomouse argument to prevent using mouse from client + // -noenginemouse will disable all mouse input + if( Sys_CheckParm( "-noenginemouse" )) return; - in_mouse_buttons = 8; in_mouseinitialized = true; - in_mouse_wheel = RegisterWindowMessage( "MSWHEEL_ROLLMSG" ); -} - -static qboolean IN_CursorInRect( void ) -{ - POINT curpos; - - if( !in_mouseinitialized || !in_mouseactive ) - return false; - - // find mouse movement - GetCursorPos( &curpos ); - - if( curpos.x < real_rect.left + WND_BORDER ) - return false; - if( curpos.x > real_rect.right - WND_BORDER * 3 ) - return false; - if( curpos.y < real_rect.top + WND_HEADSIZE + WND_BORDER ) - return false; - if( curpos.y > real_rect.bottom - WND_BORDER * 3 ) - return false; - return true; } static void IN_ActivateCursor( void ) { if( cls.key_dest == key_menu ) { - SetCursor( in_mousecursor ); +#ifdef XASH_SDL + SDL_SetCursor( in_mousecursor ); +#endif } } @@ -175,8 +101,10 @@ void IN_MouseSavePos( void ) if( !in_mouseactive ) return; - GetCursorPos( &in_lastvalidpos ); +#ifdef XASH_SDL + SDL_GetMouseState( &in_lastvalidpos.x, &in_lastvalidpos.y ); in_mouse_savedpos = true; +#endif } /* @@ -191,7 +119,10 @@ void IN_MouseRestorePos( void ) if( !in_mouse_savedpos ) return; - SetCursorPos( in_lastvalidpos.x, in_lastvalidpos.y ); +#ifdef XASH_SDL + SDL_WarpMouseInWindow( host.hWnd, in_lastvalidpos.x, in_lastvalidpos.y ); +#endif + in_mouse_savedpos = false; } @@ -213,16 +144,46 @@ void IN_ToggleClientMouse( int newstate, int oldstate ) else if( newstate == key_game ) { // reset mouse pos, so cancel effect in game - SetCursorPos( host.window_center_x, host.window_center_y ); - clgame.dllFuncs.IN_ActivateMouse(); +#ifdef XASH_SDL + if( touch_enable->value ) + { + SDL_SetRelativeMouseMode( SDL_FALSE ); + SDL_SetWindowGrab( host.hWnd, SDL_FALSE ); + } + else + { + SDL_WarpMouseInWindow( host.hWnd, host.window_center_x, host.window_center_y ); + SDL_SetWindowGrab( host.hWnd, SDL_TRUE ); + if( clgame.dllFuncs.pfnLookEvent ) + SDL_SetRelativeMouseMode( SDL_TRUE ); + } +#endif // XASH_SDL + if( cls.initialized ) + clgame.dllFuncs.IN_ActivateMouse(); } - if( newstate == key_menu && ( !CL_IsBackgroundMap() || CL_IsBackgroundDemo( ))) + if( ( newstate == key_menu || newstate == key_console || newstate == key_message ) && ( !CL_IsBackgroundMap() || CL_IsBackgroundDemo( ))) { - in_mouseactive = false; - ClipCursor( NULL ); - ReleaseCapture(); - while( ShowCursor( true ) < 0 ); +#ifdef XASH_SDL + SDL_SetWindowGrab(host.hWnd, SDL_FALSE); + if( clgame.dllFuncs.pfnLookEvent ) + SDL_SetRelativeMouseMode( SDL_FALSE ); +#endif +#ifdef __ANDROID__ + Android_ShowMouse( true ); +#endif +#ifdef USE_EVDEV + Evdev_SetGrab( false ); +#endif + } + else + { +#ifdef __ANDROID__ + Android_ShowMouse( false ); +#endif +#ifdef USE_EVDEV + Evdev_SetGrab( true ); +#endif } } @@ -254,16 +215,16 @@ void IN_ActivateMouse( qboolean force ) { if( in_mouse_suspended ) { - ClipCursor( NULL ); - ReleaseCapture(); - while( ShowCursor( true ) < 0 ); +#ifdef XASH_SDL + SDL_ShowCursor( false ); +#endif UI_ShowCursor( false ); } } oldstate = in_mouse_suspended; - if( in_mouse_suspended && IN_CursorInRect( )) + if( in_mouse_suspended ) { in_mouse_suspended = false; in_mouseactive = false; // re-initialize mouse @@ -279,24 +240,10 @@ void IN_ActivateMouse( qboolean force ) if( cls.key_dest == key_game ) { clgame.dllFuncs.IN_ActivateMouse(); +#ifdef XASH_SDL + SDL_GetRelativeMouseState( 0, 0 ); // Reset mouse position +#endif } - - width = GetSystemMetrics( SM_CXSCREEN ); - height = GetSystemMetrics( SM_CYSCREEN ); - - GetWindowRect( host.hWnd, &window_rect ); - if( window_rect.left < 0 ) window_rect.left = 0; - if( window_rect.top < 0 ) window_rect.top = 0; - if( window_rect.right >= width ) window_rect.right = width - 1; - if( window_rect.bottom >= height - 1 ) window_rect.bottom = height - 1; - - host.window_center_x = (window_rect.right + window_rect.left) / 2; - host.window_center_y = (window_rect.top + window_rect.bottom) / 2; - SetCursorPos( host.window_center_x, host.window_center_y ); - - SetCapture( host.hWnd ); - ClipCursor( &window_rect ); - while( ShowCursor( false ) >= 0 ); } /* @@ -317,9 +264,9 @@ void IN_DeactivateMouse( void ) } in_mouseactive = false; - ClipCursor( NULL ); - ReleaseCapture(); - while( ShowCursor( true ) < 0 ); +#ifdef XASH_SDL + SDL_SetWindowGrab( host.hWnd, SDL_FALSE ); +#endif } /* @@ -335,11 +282,19 @@ void IN_MouseMove( void ) return; // find mouse movement - GetCursorPos( ¤t_pos ); - ScreenToClient( host.hWnd, ¤t_pos ); +#ifdef XASH_SDL + SDL_GetMouseState( ¤t_pos.x, ¤t_pos.y ); +#endif VGui_MouseMove( current_pos.x, current_pos.y ); + // HACKHACK: show cursor in UI, as mainui doesn't call + // platform-dependent SetCursor anymore +#ifdef XASH_SDL + if( UI_IsVisible() ) + SDL_ShowCursor( SDL_TRUE ); +#endif + // if the menu is visible, move the menu cursor UI_MouseMove( current_pos.x, current_pos.y ); @@ -351,34 +306,62 @@ void IN_MouseMove( void ) IN_MouseEvent =========== */ -void IN_MouseEvent( int mstate ) +void IN_MouseEvent( void ) { int i; if( !in_mouseinitialized || !in_mouseactive ) return; + if( m_ignore->value ) + return; + if( cls.key_dest == key_game ) { - clgame.dllFuncs.IN_MouseEvent( mstate ); +#if defined( XASH_SDL ) + static qboolean ignore; // igonre mouse warp event + int x, y; + SDL_GetMouseState(&x, &y); + if( host.mouse_visible ) + SDL_ShowCursor( SDL_TRUE ); + else + SDL_ShowCursor( SDL_FALSE ); + + if( x < host.window_center_x / 2 || + y < host.window_center_y / 2 || + x > host.window_center_x + host.window_center_x / 2 || + y > host.window_center_y + host.window_center_y / 2 ) + { + SDL_WarpMouseInWindow(host.hWnd, host.window_center_x, host.window_center_y); + ignore = 1; // next mouse event will be mouse warp + return; + } + + if ( !ignore ) + { + if( !m_enginemouse->value ) + { + // a1ba: mouse keys are now separated + // so pass 0 here + clgame.dllFuncs.IN_MouseEvent( 0 ); + } + } + else + { + SDL_GetRelativeMouseState( 0, 0 ); // reset relative state + ignore = 0; + } +#endif return; } - - // perform button actions - for( i = 0; i < in_mouse_buttons; i++ ) + else { - if( FBitSet( mstate, BIT( i )) && !FBitSet( in_mouse_oldbuttonstate, BIT( i ))) - { - Key_Event( K_MOUSE1 + i, true ); - } - - if( !FBitSet( mstate, BIT( i )) && FBitSet( in_mouse_oldbuttonstate, BIT( i ))) - { - Key_Event( K_MOUSE1 + i, false ); - } - } - - in_mouse_oldbuttonstate = mstate; +#if defined(XASH_SDL) && !defined(_WIN32) + SDL_SetRelativeMouseMode( SDL_FALSE ); + SDL_ShowCursor( SDL_TRUE ); +#endif + IN_MouseMove(); + } } /* @@ -389,6 +372,10 @@ IN_Shutdown void IN_Shutdown( void ) { IN_DeactivateMouse( ); + +#ifdef XASH_USE_EVDEV + Evdev_Shutdown(); +#endif } @@ -399,7 +386,171 @@ IN_Init */ void IN_Init( void ) { - IN_StartupMouse( ); + cl_forwardspeed = Cvar_Get( "cl_forwardspeed", "400", FCVAR_ARCHIVE | FCVAR_CLIENTDLL, "Default forward move speed" ); + cl_backspeed = Cvar_Get( "cl_backspeed", "400", FCVAR_ARCHIVE | FCVAR_CLIENTDLL, "Default back move speed" ); + cl_sidespeed = Cvar_Get( "cl_sidespeed", "400", FCVAR_ARCHIVE | FCVAR_CLIENTDLL, "Default side move speed" ); + + if( !Host_IsDedicated() ) + { + IN_StartupMouse( ); + + Joy_Init(); // common joystick support init + +#ifdef XASH_USE_EVDEV + Evdev_Init(); +#endif + } +} + +/* +================ +IN_JoyMove + +Common function for engine joystick movement + + -1 < forwardmove < 1, -1 < sidemove < 1 + +================ +*/ + +#define F (1U << 0) // Forward +#define B (1U << 1) // Back +#define L (1U << 2) // Left +#define R (1U << 3) // Right +#define T (1U << 4) // Forward stop +#define S (1U << 5) // Side stop +void IN_JoyAppendMove( usercmd_t *cmd, float forwardmove, float sidemove ) +{ + static uint moveflags = T | S; + + if( forwardmove ) cmd->forwardmove = forwardmove * cl_forwardspeed->value; + if( sidemove ) cmd->sidemove = sidemove * cl_sidespeed->value; + + if( forwardmove ) + { + moveflags &= ~T; + } + else if( !( moveflags & T ) ) + { + Cmd_ExecuteString( "-back" ); + Cmd_ExecuteString( "-forward" ); + moveflags |= T; + } + + if( sidemove ) + { + moveflags &= ~S; + } + else if( !( moveflags & S ) ) + { + Cmd_ExecuteString( "-moveleft" ); + Cmd_ExecuteString( "-moveright" ); + moveflags |= S; + } + + if ( forwardmove > 0.7 && !( moveflags & F )) + { + moveflags |= F; + Cmd_ExecuteString( "+forward" ); + } + else if ( forwardmove < 0.7 && ( moveflags & F )) + { + moveflags &= ~F; + Cmd_ExecuteString( "-forward" ); + } + + if ( forwardmove < -0.7 && !( moveflags & B )) + { + moveflags |= B; + Cmd_ExecuteString( "+back" ); + } + else if ( forwardmove > -0.7 && ( moveflags & B )) + { + moveflags &= ~B; + Cmd_ExecuteString( "-back" ); + } + + if ( sidemove > 0.9 && !( moveflags & R )) + { + moveflags |= R; + Cmd_ExecuteString( "+moveright" ); + } + else if ( sidemove < 0.9 && ( moveflags & R )) + { + moveflags &= ~R; + Cmd_ExecuteString( "-moveright" ); + } + + if ( sidemove < -0.9 && !( moveflags & L )) + { + moveflags |= L; + Cmd_ExecuteString( "+moveleft" ); + } + else if ( sidemove > -0.9 && ( moveflags & L )) + { + moveflags &= ~L; + Cmd_ExecuteString( "-moveleft" ); + } +} + +/* +================ +IN_EngineAppendMove + +Called from cl_main.c after generating command in client +================ +*/ +void IN_EngineAppendMove( float frametime, usercmd_t *cmd, qboolean active ) +{ + float forward, side, dpitch, dyaw; + + if( clgame.dllFuncs.pfnLookEvent ) + return; + + if( cls.key_dest != key_game || cl.paused || cl.intermission ) + return; + + forward = side = dpitch = dyaw = 0; + + if(active) + { + float sensitivity = ( (float)RI.fov_x / (float)90.0f ); +#if XASH_INPUT == INPUT_SDL + if( m_enginemouse->value && !m_ignore->value ) + { + int mouse_x, mouse_y; + SDL_GetRelativeMouseState( &mouse_x, &mouse_y ); + RI.viewangles[PITCH] += mouse_y * m_pitch->value * sensitivity; + RI.viewangles[YAW] -= mouse_x * m_yaw->value * sensitivity; + } +#endif +#ifdef __ANDROID__ + if( !m_ignore->value ) + { + float mouse_x, mouse_y; + Android_MouseMove( &mouse_x, &mouse_y ); + RI.viewangles[PITCH] += mouse_y * m_pitch->value * sensitivity; + RI.viewangles[YAW] -= mouse_x * m_yaw->value * sensitivity; + } +#endif + Joy_FinalizeMove( &forward, &side, &dyaw, &dpitch ); + IN_TouchMove( &forward, &side, &dyaw, &dpitch ); + IN_JoyAppendMove( cmd, forward, side ); +#ifdef USE_EVDEV + IN_EvdevMove( &dyaw, &dpitch ); +#endif + if( look_filter->value ) + { + dpitch = ( inputstate.lastpitch + dpitch ) / 2; + dyaw = ( inputstate.lastyaw + dyaw ) / 2; + inputstate.lastpitch = dpitch; + inputstate.lastyaw = dyaw; + } + + RI.viewangles[YAW] += dyaw * sensitivity; + RI.viewangles[PITCH] += dpitch * sensitivity; + RI.viewangles[PITCH] = bound( -90, RI.viewangles[PITCH], 90 ); + } } /* @@ -412,9 +563,57 @@ Called every frame, even if not generating commands void Host_InputFrame( void ) { qboolean shutdownMouse = false; + float forward = 0, side = 0, pitch = 0, yaw = 0; Sys_SendKeyEvents (); +#ifdef XASH_USE_EVDEV + IN_EvdevFrame(); +#endif + + if( clgame.dllFuncs.pfnLookEvent ) + { + int dx, dy; + +#ifndef __ANDROID__ + if( in_mouseinitialized && !m_ignore->value ) + { + SDL_GetRelativeMouseState( &dx, &dy ); + pitch += dy * m_pitch->value, yaw -= dx * m_yaw->value; //mouse speed + } +#endif + +#ifdef __ANDROID__ + if( !m_ignore->value ) + { + float mouse_x, mouse_y; + Android_MouseMove( &mouse_x, &mouse_y ); + pitch += mouse_y * m_pitch->value, yaw -= mouse_x * m_yaw->value; //mouse speed + } +#endif + + Joy_FinalizeMove( &forward, &side, &yaw, &pitch ); + IN_TouchMove( &forward, &side, &yaw, &pitch ); +#ifdef USE_EVDEV + IN_EvdevMove( &yaw, &pitch ); +#endif + if( look_filter->value ) + { + pitch = ( inputstate.lastpitch + pitch ) / 2; + yaw = ( inputstate.lastyaw + yaw ) / 2; + inputstate.lastpitch = pitch; + inputstate.lastyaw = yaw; + } + + if( cls.key_dest == key_game ) + { + clgame.dllFuncs.pfnLookEvent( yaw, pitch ); + clgame.dllFuncs.pfnMoveEvent( forward, side ); + } + } + + Cbuf_Execute (); + if( !in_mouseinitialized ) return; diff --git a/engine/common/input.h b/engine/common/input.h index 96d6758e..acd8a0d8 100644 --- a/engine/common/input.h +++ b/engine/common/input.h @@ -26,23 +26,13 @@ INPUT #include "keydefs.h" -#define WHEEL_DELTA 120 // Default value for rolling one notch -#define WM_MOUSEWHEEL ( WM_MOUSELAST + 1 )// message that will be supported by the OS -#define MK_XBUTTON1 0x0020 -#define MK_XBUTTON2 0x0040 -#define MK_XBUTTON3 0x0080 -#define MK_XBUTTON4 0x0100 -#define MK_XBUTTON5 0x0200 -#define WM_XBUTTONUP 0x020C -#define WM_XBUTTONDOWN 0x020B - // // input.c // void IN_Init( void ); void Host_InputFrame( void ); void IN_Shutdown( void ); -void IN_MouseEvent( int mstate ); +void IN_MouseEvent( void ); void IN_ActivateMouse( qboolean force ); void IN_DeactivateMouse( void ); void IN_MouseSavePos( void ); @@ -50,4 +40,73 @@ void IN_MouseRestorePos( void ); void IN_ToggleClientMouse( int newstate, int oldstate ); void IN_SetCursor( void *hCursor ); +// +// in_touch.c +// +typedef enum +{ + event_down = 0, + event_up, + event_motion +} touchEventType; + +extern convar_t *touch_enable; + +void IN_TouchDraw( void ); +void IN_TouchEditClear( void ); +void IN_TouchSetClientOnly( qboolean state ); +void IN_TouchRemoveButton( const char *name ); +void IN_TouchHideButtons( const char *name, qboolean hide ); +//void IN_TouchSetCommand( const char *name, const char *command ); +//void IN_TouchSetTexture( const char *name, const char *texture ); +//void IN_TouchSetColor( const char *name, byte *color ); +void IN_TouchAddClientButton( const char *name, const char *texture, const char *command, float x1, float y1, float x2, float y2, byte *color, int round, float aspect, int flags ); +void IN_TouchAddDefaultButton( const char *name, const char *texturefile, const char *command, float x1, float y1, float x2, float y2, byte *color, int round, float aspect, int flags ); +void IN_TouchInitConfig( void ); +void IN_TouchWriteConfig( void ); +void IN_TouchInit( void ); +void IN_TouchShutdown( void ); +void IN_TouchMove( float * forward, float *side, float *yaw, float *pitch ); +void IN_TouchResetDefaultButtons( void ); +int IN_TouchEvent( touchEventType type, int fingerID, float x, float y, float dx, float dy ); +void IN_TouchKeyEvent( int key, int down ); + +// +// in_joy.c +// +enum +{ + JOY_HAT_CENTERED = 0, + JOY_HAT_UP = BIT(0), + JOY_HAT_RIGHT = BIT(1), + JOY_HAT_DOWN = BIT(2), + JOY_HAT_LEFT = BIT(3), + JOY_HAT_RIGHTUP = JOY_HAT_RIGHT | JOY_HAT_UP, + JOY_HAT_RIGHTDOWN = JOY_HAT_RIGHT | JOY_HAT_DOWN, + JOY_HAT_LEFTUP = JOY_HAT_LEFT | JOY_HAT_UP, + JOY_HAT_LEFTDOWN = JOY_HAT_LEFT | JOY_HAT_DOWN +}; +extern convar_t *joy_found; + +qboolean Joy_IsActive( void ); +void Joy_HatMotionEvent( int id, byte hat, byte value ); +void Joy_AxisMotionEvent( int id, byte axis, short value ); +void Joy_BallMotionEvent( int id, byte ball, short xrel, short yrel ); +void Joy_ButtonEvent( int id, byte button, byte down ); +void Joy_AddEvent( int id ); +void Joy_RemoveEvent( int id ); +void Joy_FinalizeMove( float *fw, float *side, float *dpitch, float *dyaw ); +void Joy_Init( void ); +void Joy_Shutdown( void ); +void Joy_EnableTextInput(qboolean enable, qboolean force); + +// +// in_evdev.c +// +#ifdef XASH_USE_EVDEV +void Evdev_SetGrab( qboolean grab ); +void Evdev_Shutdown( void ); +void Evdev_Init( void ); +#endif // XASH_USE_EVDEV + #endif//INPUT_H diff --git a/engine/common/keys.c b/engine/common/keys.c index b56ebcce..84b68c09 100644 --- a/engine/common/keys.c +++ b/engine/common/keys.c @@ -18,12 +18,12 @@ GNU General Public License for more details. #include "client.h" #include "vgui_draw.h" -typedef struct key_s +typedef struct { qboolean down; int repeats; // if > 1, it is autorepeating const char *binding; -} key_t; +} enginekey_t; typedef struct keyname_s { @@ -32,7 +32,7 @@ typedef struct keyname_s const char *binding; // default bind } keyname_t; -key_t keys[256]; +enginekey_t keys[256]; keyname_t keynames[] = { @@ -548,15 +548,11 @@ void Key_Event( int key, qboolean down ) // console key is hardcoded, so the user can never unbind it if( key == '`' || key == '~' ) { - // we are in typing mode. So don't switch to console - if( (word)GetKeyboardLayout( 0 ) == (word)0x419 ) - { - if( cls.key_dest != key_game ) - return; - } + // we are in typing mode, so don't switch to console + if( cls.key_dest == key_message || !down ) + return; - if( !down ) return; - Con_ToggleConsole_f(); + Con_ToggleConsole_f(); return; }