diff options
-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) |