diff options
author | Stany MARCEL <stanypub@gmail.com> | 2016-12-02 14:04:30 +0300 |
---|---|---|
committer | Stany MARCEL <stanypub@gmail.com> | 2016-12-02 14:04:30 +0300 |
commit | 0c439095333dfed5aa36e594ef6840008128b36f (patch) | |
tree | 82ce7dba68d8a987b595327be73ee49059c5fbe9 | |
parent | 82e84c5cd1773f3236f09cd1a22c7ab363b72008 (diff) |
Close issue #47 close uinput and turn off controller
A long press (>2s) to the steam button close and deleate the uinput device
The device is created on first event to prevent it to be created at daemon
restart
When the daeomon is stopped the controller should also be powered off
Signed-off-by: Stany MARCEL <stanypub@gmail.com>
-rw-r--r-- | README.md | 6 | ||||
-rwxr-xr-x | scripts/sc-desktop.py | 5 | ||||
-rwxr-xr-x | scripts/sc-mixed.py | 6 | ||||
-rwxr-xr-x | scripts/sc-xbox.py | 8 | ||||
-rw-r--r-- | src/__init__.py | 32 | ||||
-rw-r--r-- | src/daemon.py | 4 | ||||
-rw-r--r-- | src/events.py | 18 | ||||
-rw-r--r-- | src/uinput.c | 2 | ||||
-rw-r--r-- | src/uinput.py | 65 |
9 files changed, 118 insertions, 28 deletions
@@ -57,7 +57,7 @@ KERNEL=="uinput", MODE="0660", GROUP="games", OPTIONS+="static_node=uinput" Other test tools are installed: - `sc-dump.py` : Dump raw message from the controller. - `sc-gyro-plot.py` : Plot curves from gyro data (require pyqtgraph and pyside installed). - - `sc-test-cmsg.py` : Permit to send control message to the contoller. For example: + - `sc-test-cmsg.py` : Permit to send control message to the contoller. For example: `echo 8f07005e 015e01f4 01000000 | sc-test-cmsg.py` will make the controller beep. - `vdf2json.py` : Convert Steam VDF file to JSON. - `json2vdf.py` : Convert back JSON to VDF file. @@ -99,6 +99,10 @@ Other test tools are installed: - `87153284 03180000 31020008 07000707 00300000 2f010000 00000000 00000000` +### Stop Controller + - `9f046f66 66210000 ...` + + ## Control Messages formats ### Haptic feedback format: diff --git a/scripts/sc-desktop.py b/scripts/sc-desktop.py index fcbc51c..9eff430 100755 --- a/scripts/sc-desktop.py +++ b/scripts/sc-desktop.py @@ -30,6 +30,8 @@ from steamcontroller.uinput import Keys from steamcontroller.daemon import Daemon +import gc + def evminit(): evm = EventMapper() evm.setPadMouse(Pos.RIGHT) @@ -68,6 +70,9 @@ class SCDaemon(Daemon): evm = evminit() sc = SteamController(callback=evm.process) sc.run() + del sc + del evm + gc.collect() if __name__ == '__main__': import argparse diff --git a/scripts/sc-mixed.py b/scripts/sc-mixed.py index 6cfebce..4a9c3c0 100755 --- a/scripts/sc-mixed.py +++ b/scripts/sc-mixed.py @@ -35,6 +35,7 @@ from steamcontroller.uinput import \ Axes from steamcontroller.daemon import Daemon +import gc def set_evm_pad(evm): evm.setStickAxes(Axes.ABS_X, Axes.ABS_Y) @@ -87,8 +88,6 @@ def set_evm_desktop(evm): evm.setButtonAction(SCButtons.LPAD, Keys.BTN_MIDDLE) evm.setButtonAction(SCButtons.RPAD, Keys.KEY_SPACE) - - pad = True def toggle_callback(evm, btn, pressed): global pad @@ -110,6 +109,9 @@ class SCDaemon(Daemon): evm = evminit() sc = SteamController(callback=evm.process) sc.run() + del sc + del evm + gc.collect() if __name__ == '__main__': import argparse diff --git a/scripts/sc-xbox.py b/scripts/sc-xbox.py index d25668b..814c8bc 100755 --- a/scripts/sc-xbox.py +++ b/scripts/sc-xbox.py @@ -35,6 +35,8 @@ from steamcontroller.uinput import \ Axes from steamcontroller.daemon import Daemon +import gc + def evminit(): evm = EventMapper() @@ -67,6 +69,9 @@ class SCDaemon(Daemon): evm = evminit() sc = SteamController(callback=evm.process) sc.run() + del sc + del evm + gc.collect() if __name__ == '__main__': import argparse @@ -92,7 +97,8 @@ if __name__ == '__main__': evm = evminit() sc = SteamController(callback=evm.process) sc.run() + except KeyboardInterrupt: - return + pass _main() diff --git a/src/__init__.py b/src/__init__.py index eebb073..1bf111e 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -24,7 +24,8 @@ import struct from enum import IntEnum from threading import Timer -import time +from time import time + VENDOR_ID = 0x28de PRODUCT_ID = [0x1102, 0x1142, 0x1142, 0x1142, 0x1142] @@ -65,6 +66,10 @@ STEAM_CONTROLER_FORMAT = [ _FORMATS, _NAMES = zip(*STEAM_CONTROLER_FORMAT) +EXITCMD = struct.pack('>' + 'I' * 2, + 0x9f046f66, + 0x66210000) + SteamControllerInput = namedtuple('SteamControllerInput', ' '.join([x for x in _NAMES if not x.startswith('ukn_')])) SCI_NULL = SteamControllerInput._make(struct.unpack('<' + ''.join(_FORMATS), b'\x00' * 64)) @@ -159,6 +164,7 @@ class SteamController(object): setting.getProtocol() == 0 and number == i+1): self._handle.claimInterface(number) + self._number = number claimed = True except usb1.USBErrorBusy: claimed = False @@ -188,7 +194,7 @@ class SteamController(object): self._timer = None self._tup = None - self._lastusb = time.time() + self._lastusb = time() # Disable Haptic auto feedback @@ -205,10 +211,16 @@ class SteamController(object): 0x2f010000)) self._ctx.handleEvents() - - def __del__(self): + def _close(self): if self._handle: + self._sendControl(EXITCMD) + self._handle.releaseInterface(self._number) + self._handle.resetDevice() self._handle.close() + self._handle = None + + def __del__(self): + self._close() def _sendControl(self, data, timeout=0): @@ -221,6 +233,9 @@ class SteamController(object): data=data + zeros, timeout=timeout) + def addExit(self): + self._cmsg.insert(0, EXITCMD) + def addFeedback(self, position, amplitude=128, period=0, count=1): """ Add haptic feedback to be send on next usb tick @@ -252,20 +267,18 @@ class SteamController(object): if self._tup is None: return - self._lastusb = time.time() + self._lastusb = time() if isinstance(self._cb_args, (list, tuple)): self._cb(self, self._tup, *self._cb_args) else: self._cb(self, self._tup) - - self._period = HPERIOD def _callbackTimer(self): - d = time.time() - self._lastusb + d = time() - self._lastusb self._timer.cancel() if d > DURATION: @@ -295,7 +308,8 @@ class SteamController(object): if len(self._cmsg) > 0: cmsg = self._cmsg.pop() self._sendControl(cmsg) - + if cmsg == EXITCMD: + break except usb1.USBErrorInterrupted: pass diff --git a/src/daemon.py b/src/daemon.py index 029f360..964c765 100644 --- a/src/daemon.py +++ b/src/daemon.py @@ -10,6 +10,8 @@ import atexit import signal import syslog import psutil +import traceback +import gc class Daemon(object): """A generic daemon class. @@ -96,6 +98,8 @@ class Daemon(object): self.run() except Exception as e: # pylint: disable=W0703 syslog.syslog(syslog.LOG_ERR, '{}: {!s}'.format(os.path.basename(sys.argv[0]), e)) + syslog.syslog(syslog.LOG_ERR, traceback.format_exc()) + gc.collect() else: syslog.syslog(syslog.LOG_INFO, '{}: steam client is runing'.format(os.path.basename(sys.argv[0]))) time.sleep(2) diff --git a/src/events.py b/src/events.py index bca43b3..ecfdb89 100644 --- a/src/events.py +++ b/src/events.py @@ -27,6 +27,7 @@ events """ +from time import time from math import sqrt from enum import IntEnum @@ -40,6 +41,8 @@ import steamcontroller.uinput as sui from collections import deque +EXIT_PRESS_DURATION = 2.0 + class Pos(IntEnum): """Specify witch pad or trig is used""" RIGHT = 0 @@ -118,7 +121,13 @@ class EventMapper(object): self._trig_axes_callbacks = [None, None] self._moved = [0, 0] + self._steam_pressed_time = 0.0 + def __del__(self): + if hasattr(self, '_uip') and self._uip: + for u in self._uip: + del u + self._uip = [] def process(self, sc, sci): """ @@ -188,17 +197,26 @@ class EventMapper(object): else: return False + # Manage long steam press to exit + if btn_add & SCButtons.STEAM == SCButtons.STEAM: + self._steam_pressed_time = time() + if (sci.buttons & SCButtons.STEAM == SCButtons.STEAM and + time() - self._steam_pressed_time > EXIT_PRESS_DURATION): + sc.addExit() + # Manage buttons for btn, (mode, ev) in self._btn_map.items(): if mode is None: continue + if btn & btn_add: if mode is Modes.CALLBACK: ev(self, btn, True) else: _keypressed(mode, ev) elif btn & btn_rem: + if mode is Modes.CALLBACK: ev(self, btn, False) else: diff --git a/src/uinput.c b/src/uinput.c index a2d5e33..e28110a 100644 --- a/src/uinput.c +++ b/src/uinput.c @@ -208,6 +208,6 @@ void uinput_syn(int fd) void uinput_destroy(int fd) { - ioctl(fd, UI_DEV_DESTROY); + ioctl(fd, UI_DEV_DESTROY, 0); close(fd); } diff --git a/src/uinput.py b/src/uinput.py index 537af2b..8ab8d25 100644 --- a/src/uinput.py +++ b/src/uinput.py @@ -22,8 +22,10 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. + import os import ctypes +import _ctypes import time from math import pi, copysign, sqrt from enum import IntEnum @@ -191,6 +193,13 @@ class UInput(object): self._a, self._amin, self._amax, self._afuzz, self._aflat = zip(*axes) self._r = rels + self.vendor = vendor + self.product = product + self.name = name + self.keyboard = keyboard + self._fd = None + + def createDevice(self): possible_paths = [] for extension in get_so_extensions(): possible_paths.append( @@ -214,6 +223,7 @@ class UInput(object): '\n'.join(possible_paths) ) ) + self._lib = ctypes.CDLL(lib) c_k = (ctypes.c_uint16 * len(self._k))(*self._k) @@ -223,11 +233,11 @@ class UInput(object): c_afuzz = (ctypes.c_int32 * len(self._afuzz))(*self._afuzz) c_aflat = (ctypes.c_int32 * len(self._aflat))(*self._aflat) c_r = (ctypes.c_uint16 * len(self._r))(*self._r) - c_vendor = ctypes.c_uint16(vendor) - c_product = ctypes.c_uint16(product) - c_keyboard = ctypes.c_int(keyboard) + c_vendor = ctypes.c_uint16(self.vendor) + c_product = ctypes.c_uint16(self.product) + c_keyboard = ctypes.c_int(self.keyboard) - c_name = ctypes.c_char_p(name) + c_name = ctypes.c_char_p(self.name) self._fd = self._lib.uinput_init(ctypes.c_int(len(self._k)), c_k, ctypes.c_int(len(self._a)), @@ -251,6 +261,10 @@ class UInput(object): @param int axis key or btn event (KEY_* or BTN_*) @param int val event value """ + + if self._fd == None: + self.createDevice() + self._lib.uinput_key(self._fd, ctypes.c_uint16(key), ctypes.c_int32(val)) @@ -263,6 +277,10 @@ class UInput(object): @param int axis abs event (ABS_*) @param int val event value """ + + if self._fd == None: + self.createDevice() + self._lib.uinput_abs(self._fd, ctypes.c_uint16(axis), ctypes.c_int32(val)) @@ -274,6 +292,10 @@ class UInput(object): @param int rel rel event (REL_*) @param int val event value """ + + if self._fd == None: + self.createDevice() + self._lib.uinput_rel(self._fd, ctypes.c_uint16(rel), ctypes.c_int32(val)) @@ -284,6 +306,10 @@ class UInput(object): @param int val scan event value (scancode) """ + + if self._fd == None: + self.createDevice() + self._lib.uinput_scan(self._fd, ctypes.c_int32(val)) @@ -291,6 +317,10 @@ class UInput(object): """ Generate a syn event """ + + if self._fd == None: + self.createDevice() + self._lib.uinput_syn(self._fd) @@ -302,6 +332,9 @@ class UInput(object): @param int period period is ms """ + if self._fd == None: + self.createDevice() + self._lib.uinput_set_delay_period(self._fd, ctypes.c_int32(delay), ctypes.c_int32(period)) @@ -317,8 +350,12 @@ class UInput(object): def __del__(self): - if self._lib: + + if self._lib and self._fd: self._lib.uinput_destroy(self._fd) + self._fd = None + _ctypes.dlclose(self._lib._handle) + self._lib = None class Gamepad(UInput): @@ -430,9 +467,9 @@ class Mouse(UInput): self._radscale = (degree * pi / 180) / ampli self._mass = mass self._friction = friction - self._r = r - self._I = (2 * self._mass * self._r**2) / 5.0 - self._a = self._r * self._friction / self._I + self._rad = r + self._I = (2 * self._mass * self._rad**2) / 5.0 + self._acc = self._rad * self._friction / self._I self._xvel_dq = deque(maxlen=mean_len) self._yvel_dq = deque(maxlen=mean_len) @@ -465,8 +502,8 @@ class Mouse(UInput): self._scr_mass = mass self._scr_friction = friction self._scr_r = r - self._scr_I = (2 * self._mass * self._r**2) / 5.0 - self._scr_a = self._r * self._friction / self._I + self._scr_I = (2 * self._mass * self._rad**2) / 5.0 + self._scr_a = self._rad * self._friction / self._I self._scr_xvel_dq = deque(maxlen=mean_len) self._scr_yvel_dq = deque(maxlen=mean_len) @@ -528,11 +565,11 @@ class Mouse(UInput): _hyp = sqrt((self._xvel**2) + (self._yvel**2)) if _hyp != 0.0: - _ax = self._a * (abs(self._xvel) / _hyp) - _ay = self._a * (abs(self._yvel) / _hyp) + _ax = self._acc * (abs(self._xvel) / _hyp) + _ay = self._acc * (abs(self._yvel) / _hyp) else: - _ax = self._a - _ay = self._a + _ax = self._acc + _ay = self._acc # Cap friction desceleration _dvx = min(abs(self._xvel), _ax * dt) |