xash3d-fwgs/engine/platform/linux/in_evdev.c
Gleb Mazovetskiy 5e0a0765ce Trim all trailing whitespace
The `.editorconfig` file in this repo is configured to trim all trailing
whitespace regardless of whether the line is modified.

Trims all trailing whitespace in the repository to make the codebase easier
to work with in editors that respect `.editorconfig`.

`git blame` becomes less useful on these lines but it already isn't very useful.

Commands:

```
find . -type f -name '*.h' -exec sed --in-place 's/[[:space:]]\+$//' {} \+
find . -type f -name '*.c' -exec sed --in-place 's/[[:space:]]\+$//' {} \+
```
2021-01-04 20:55:10 +03:00

479 lines
10 KiB
C

/*
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.
*/
#include "platform/platform.h"
#ifdef XASH_USE_EVDEV
#include "common.h"
#include "input.h"
#include "client.h"
#include <fcntl.h>
#include <errno.h>
#include <linux/input.h>
#include <dirent.h>
#define MAX_EVDEV_DEVICES 5
struct evdev_s
{
int initialized, devices;
int fds[MAX_EVDEV_DEVICES];
string paths[MAX_EVDEV_DEVICES];
qboolean grab;
double grabtime;
float x, y;
qboolean chars;
qboolean shift;
} evdev;
static convar_t *evdev_keydebug;
static int KeycodeFromEvdev(int keycode, int value)
{
switch (keycode) {
case KEY_0: return '0';
case KEY_1: return '1';
case KEY_2: return '2';
case KEY_3: return '3';
case KEY_4: return '4';
case KEY_5: return '5';
case KEY_6: return '6';
case KEY_7: return '7';
case KEY_8: return '8';
case KEY_9: return '9';
case KEY_BACKSPACE: return K_BACKSPACE;
case KEY_ENTER: return K_ENTER;
case KEY_ESC: return K_ESCAPE;
case KEY_KP0: return K_KP_INS;
case KEY_KP1: return K_KP_END;
case KEY_KP2: return K_KP_DOWNARROW;
case KEY_KP3: return K_KP_PGDN;
case KEY_KP4: return K_KP_LEFTARROW;
case KEY_KP5: return K_KP_5;
case KEY_KP6: return K_KP_RIGHTARROW;
case KEY_KP7: return K_KP_HOME;
case KEY_KP8: return K_KP_UPARROW;
case KEY_KP9: return K_KP_PGUP;
case KEY_KPDOT: return K_KP_DEL;
case KEY_KPENTER: return K_KP_ENTER;
case KEY_Q: return 'q';
case KEY_W: return 'w';
case KEY_E: return 'e';
case KEY_R: return 'r';
case KEY_T: return 't';
case KEY_Y: return 'y';
case KEY_U: return 'u';
case KEY_I: return 'i';
case KEY_O: return 'o';
case KEY_P: return 'p';
case KEY_A: return 'a';
case KEY_S: return 's';
case KEY_D: return 'd';
case KEY_F: return 'f';
case KEY_G: return 'g';
case KEY_H: return 'h';
case KEY_J: return 'j';
case KEY_K: return 'k';
case KEY_L: return 'l';
case KEY_Z: return 'z';
case KEY_X: return 'x';
case KEY_C: return 'c';
case KEY_V: return 'v';
case KEY_B: return 'b';
case KEY_N: return 'n';
case KEY_M: return 'm';
case KEY_LEFTBRACE: return '[';
case KEY_RIGHTBRACE: return ']';
case KEY_MINUS: return '-';
case KEY_EQUAL: return '=';
case KEY_TAB: return K_TAB;
case KEY_SEMICOLON: return ';';
case KEY_APOSTROPHE: return '\'';
case KEY_GRAVE: return '`';
case KEY_BACKSLASH: return '\\';
case KEY_COMMA: return ',';
case KEY_DOT: return '.';
case KEY_SLASH: return '/';
case KEY_SPACE: return K_SPACE;
case KEY_KPASTERISK: return '*';
case KEY_RIGHTCTRL:
case KEY_LEFTCTRL:
return K_CTRL;
case KEY_RIGHTSHIFT:
case KEY_LEFTSHIFT:
return K_SHIFT;
case KEY_LEFT: return K_LEFTARROW;
case KEY_RIGHT: return K_RIGHTARROW;
case KEY_UP: return K_UPARROW;
case KEY_DOWN: return K_DOWNARROW;
case BTN_LEFT: return K_MOUSE1;
case BTN_RIGHT: return K_MOUSE2;
case BTN_MIDDLE: return K_MOUSE3;
case KEY_POWER: return K_ESCAPE;
case KEY_VOLUMEDOWN: return K_PGDN;
case KEY_VOLUMEUP: return K_PGUP;
case KEY_PLAYPAUSE: return K_ENTER;
default:
break;
}
return 0;
}
static void Evdev_CheckPermissions( void )
{
#if XASH_ANDROID
system( "su 0 chmod 664 /dev/input/event*" );
#endif
}
void Evdev_Setup( void )
{
if( evdev.initialized )
return;
#if XASH_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 ) )
{
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;
Con_Printf( "Opened device %s\n", path );
#if XASH_INPUT == INPUT_EVDEV
if( Sys_CheckParm( "-grab" ) )
ioctl( evdev.fds[i], EVIOCGRAB, (void*) 1 );
#endif
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 )
{
Con_Printf( "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 ) )
{
Con_Printf( "device %s already open!\n", path );
return;
}
}
ret = open( path, O_RDONLY | O_NONBLOCK );
if( ret < 0 )
{
Con_Reportf( S_ERROR "Could not open input device %s: %s\n", path, strerror( errno ) );
return;
}
Con_Printf( "Input device #%d: %s opened sucessfully\n", evdev.devices, path );
evdev.fds[evdev.devices] = ret;
Q_strncpy( evdev.paths[evdev.devices++], path, MAX_STRING );
#if XASH_INPUT == INPUT_EVDEV
if( Sys_CheckParm( "-grab" ) )
ioctl( evdev.fds[i], EVIOCGRAB, (void*) 1 );
#endif
}
void Evdev_OpenDevice_f( void )
{
if( Cmd_Argc() < 2 )
Con_Printf( S_USAGE "evdev_opendevice <path>\n" );
Evdev_OpenDevice( Cmd_Argv( 1 ) );
}
/*
===========
Evdev_CloseDevice_f
===========
*/
void Evdev_CloseDevice_f ( void )
{
uint i;
const 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 )
{
Con_Printf( "Device %s is not open\n", arg );
return;
}
close( evdev.fds[i] );
evdev.devices--;
Con_Printf( "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 ( void )
{
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 || XASH_INPUT == INPUT_EVDEV ) )
{
int key = KeycodeFromEvdev( ev.code, ev.value );
if( CVAR_TO_BOOL(evdev_keydebug) )
Con_Printf( "key %d %d %d\n", ev.code, key, ev.value );
Key_Event( key , ev.value );
if( evdev.chars && ev.value )
{
if( key >= 32 && key < 127 )
{
if( evdev.shift )
{
key = Key_ToUpper( key );
}
CL_CharEvent( key );
}
}
if( key == K_SHIFT )
evdev.shift = ev.value;
}
}
if( evdev.grab && evdev.grabtime <= host.realtime )
{
ioctl( evdev.fds[i], EVIOCGRAB, (void*) 1 );
Key_ClearStates();
}
if( CVAR_TO_BOOL(m_ignore) )
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 )
{
// grab only if evdev is secondary input source
#if XASH_INPUT != INPUT_EVDEV
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;
#endif
}
void IN_EvdevMove( float *yaw, float *pitch )
{
*yaw += evdev.x;
*pitch += evdev.y;
evdev.x = evdev.y = 0.0f;
}
#if XASH_INPUT == INPUT_EVDEV
void Platform_EnableTextInput( qboolean enable )
{
evdev.chars = enable;
evdev.shift = false;
}
void Platfrom_MouseMove( float *yaw, float *pitch )
{
// already catched in IN_EvdevMove
}
#endif
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");
#if XASH_INPUT == INPUT_EVDEV
Evdev_Autodetect_f();
#endif
}
void Evdev_Shutdown( void )
{
int i;
Cmd_RemoveCommand( "evdev_open" );
Cmd_RemoveCommand( "evdev_close" );
Cmd_RemoveCommand( "evdev_autodetect" );
evdev_keydebug = Cvar_Get( "evdev_keydebug", "0", 0, "print key events to console" );
for( i = 0; i < evdev.devices; i++ )
{
ioctl( evdev.fds[i], EVIOCGRAB, (void*) 0 );
close( evdev.fds[i] );
evdev.fds[i] = -1;
}
evdev.devices = 0;
}
#endif // XASH_USE_EVDEV