/* 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" #if XASH_USE_EVDEV #include "common.h" #include "input.h" #include "client.h" #include #include #include #include #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 \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: IN_MWheelEvent( ev.value ); 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( m_ignore.value ) 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_AddRestrictedCommand ("evdev_open", Evdev_OpenDevice_f, "Open event device"); Cmd_AddRestrictedCommand ("evdev_close", Evdev_CloseDevice_f, "Close event device"); Cmd_AddRestrictedCommand ("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