From 87618f905bc3b913f1b79c5ffd5b240e62d5b9f2 Mon Sep 17 00:00:00 2001 From: Luca Tessaro Date: Sun, 17 Nov 2019 12:05:29 +0100 Subject: [PATCH 1/7] First setup for thread and dynamic event handling --- .gitignore | 1 + XInput.py | 39 +++++++++++++++++++++++++++++++++++++++ XInputThreadTest.py | 25 +++++++++++++++++++++++++ 3 files changed, 65 insertions(+) create mode 100644 XInputThreadTest.py diff --git a/.gitignore b/.gitignore index 6a80c6a..ceb1d36 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ XInput_Python.egg-info/ runreadmelang.bat *.pyc *.bat +.vscode diff --git a/XInput.py b/XInput.py index d027b67..2e2cba1 100644 --- a/XInput.py +++ b/XInput.py @@ -6,6 +6,8 @@ import time +from threading import Thread + XINPUT_DLL_NAMES = ( "XInput1_4.dll", "XInput9_1_0.dll", @@ -476,6 +478,43 @@ def get_events(): _last_states = these_states +class GamepadEventsHandler: + def on_button_event(self,event): + raise NotImplemented + + def on_stick_event(self,event): + raise NotImplemented + + def on_trigger_event(self,event): + raise NotImplemented + +class GamepadThread: + def __init__(self, events_handler, auto_start=True): + if (events_handler is None or not issubclass(type(events_handler), GamepadEventsHandler)): + raise TypeError("The event handler must be a subclass of XInput.GamepadEventsHandler") + self.handler = events_handler + if auto_start: + self.start_thread() + + def __tfun(self): # thread function + while(self.isRunning): # polling + events = get_events() + for e in events: + print(e) + + def start_thread(self): # starts the thread + self.isRunning = True + if(not hasattr(self,"__t")): + self.__t = Thread(target=self.__tfun, args=()) + self.__t.daemon = True + self.__t.start() + + def stop_thread(self): # stops the thread + self.isRunning = False + + + + if __name__ == "__main__": try: import tkinter as tk diff --git a/XInputThreadTest.py b/XInputThreadTest.py new file mode 100644 index 0000000..f961b42 --- /dev/null +++ b/XInputThreadTest.py @@ -0,0 +1,25 @@ +#/usr/bin python3 + +from XInput import * + +if __name__ == "__main__": + + class MyHandler(GamepadEventsHandler): + def on_button_event(self,event): + print(event) + + def on_stick_event(self, event): + print(event) + + def on_trigger_event(self, event): + print(event) + + handler = MyHandler() + thread = GamepadThread(handler) + + try: + while(1): + pass + + except KeyboardInterrupt: + print("\n--- User exit ---") \ No newline at end of file From ea18c37e97ab952131c1936d16057a6ff86289e7 Mon Sep 17 00:00:00 2001 From: Luca Tessaro Date: Sun, 17 Nov 2019 13:07:22 +0100 Subject: [PATCH 2/7] Events callbacks --- XInput.py | 30 ++++++++++++++++++++++-------- XInputThreadTest.py | 3 +++ 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/XInput.py b/XInput.py index 2e2cba1..1b17417 100644 --- a/XInput.py +++ b/XInput.py @@ -478,15 +478,19 @@ def get_events(): _last_states = these_states +# Event handler class to be extended to use dynamic events class GamepadEventsHandler: - def on_button_event(self,event): - raise NotImplemented + def on_button_event(self, event): + raise NotImplementedError("Method not implemented. Must be implemented in the child class") - def on_stick_event(self,event): - raise NotImplemented + def on_stick_event(self, event): + raise NotImplementedError("Method not implemented. Must be implemented in the child class") - def on_trigger_event(self,event): - raise NotImplemented + def on_trigger_event(self, event): + raise NotImplementedError("Method not implemented. Must be implemented in the child class") + + def on_connection_event(self, event): + raise NotImplementedError("Method not implemented. Must be implemented in the child class") class GamepadThread: def __init__(self, events_handler, auto_start=True): @@ -499,8 +503,18 @@ def __init__(self, events_handler, auto_start=True): def __tfun(self): # thread function while(self.isRunning): # polling events = get_events() - for e in events: - print(e) + for e in events: # filtering events + if e.type == EVENT_CONNECTED or e.type == EVENT_DISCONNECTED: + self.handler.on_connection_event(e) + elif e.type == EVENT_BUTTON_PRESSED or e.type == EVENT_BUTTON_RELEASED: + self.handler.on_button_event(e) + elif e.type == EVENT_TRIGGER_MOVED: + self.handler.on_trigger_event(e) + elif e.type == EVENT_STICK_MOVED: + self.handler.on_stick_event(e) + else: + raise ValueError("Event type not recognized") + def start_thread(self): # starts the thread self.isRunning = True diff --git a/XInputThreadTest.py b/XInputThreadTest.py index f961b42..d2859fe 100644 --- a/XInputThreadTest.py +++ b/XInputThreadTest.py @@ -13,6 +13,9 @@ def on_stick_event(self, event): def on_trigger_event(self, event): print(event) + + def on_connection_event(self, event): + print(event) handler = MyHandler() thread = GamepadThread(handler) From 1abc788afe9514c7cc3cdd44b7aad9ef80ff28b8 Mon Sep 17 00:00:00 2001 From: Luca Tessaro Date: Sun, 17 Nov 2019 13:46:32 +0100 Subject: [PATCH 3/7] Adapted the interface test to use the thread class --- XInputThreadTest.py | 194 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 181 insertions(+), 13 deletions(-) diff --git a/XInputThreadTest.py b/XInputThreadTest.py index d2859fe..eeed25a 100644 --- a/XInputThreadTest.py +++ b/XInputThreadTest.py @@ -1,28 +1,196 @@ #/usr/bin python3 -from XInput import * - if __name__ == "__main__": + from XInput import * + + try: + import tkinter as tk + except ImportError: + import Tkinter as tk + + # creates the interface + root = tk.Tk() + root.title("XInput") + canvas = tk.Canvas(root, width= 600, height = 400, bg="white") + canvas.pack() + + set_deadzone(DEADZONE_TRIGGER,10) + + class Controller: + def __init__(self, center): + self.center = center + + self.on_indicator_pos = (self.center[0], self.center[1] - 50) + + self.on_indicator = canvas.create_oval(((self.on_indicator_pos[0] - 10, self.on_indicator_pos[1] - 10), (self.on_indicator_pos[0] + 10, self.on_indicator_pos[1] + 10))) + + self.r_thumb_pos = (self.center[0] + 50, self.center[1] + 20) + + r_thumb_outline = canvas.create_oval(((self.r_thumb_pos[0] - 25, self.r_thumb_pos[1] - 25), (self.r_thumb_pos[0] + 25, self.r_thumb_pos[1] + 25))) + + r_thumb_stick_pos = self.r_thumb_pos + + self.r_thumb_stick = canvas.create_oval(((r_thumb_stick_pos[0] - 10, r_thumb_stick_pos[1] - 10), (r_thumb_stick_pos[0] + 10, r_thumb_stick_pos[1] + 10))) + + self.l_thumb_pos = (self.center[0] - 100, self.center[1] - 20) + + l_thumb_outline = canvas.create_oval(((self.l_thumb_pos[0] - 25, self.l_thumb_pos[1] - 25), (self.l_thumb_pos[0] + 25, self.l_thumb_pos[1] + 25))) + + l_thumb_stick_pos = self.l_thumb_pos + + self.l_thumb_stick = canvas.create_oval(((l_thumb_stick_pos[0] - 10, l_thumb_stick_pos[1] - 10), (l_thumb_stick_pos[0] + 10, l_thumb_stick_pos[1] + 10))) + + self.l_trigger_pos = (self.center[0] - 120, self.center[1] - 70) + + l_trigger_outline = canvas.create_rectangle(((self.l_trigger_pos[0] - 5, self.l_trigger_pos[1] - 20), (self.l_trigger_pos[0] + 5, self.l_trigger_pos[1] + 20))) + + l_trigger_index_pos = (self.l_trigger_pos[0], self.l_trigger_pos[1] - 20) + + self.l_trigger_index = canvas.create_rectangle(((l_trigger_index_pos[0] - 10, l_trigger_index_pos[1] - 5), (l_trigger_index_pos[0] + 10, l_trigger_index_pos[1] + 5))) + + self.r_trigger_pos = (self.center[0] + 120, self.center[1] - 70) + + r_trigger_outline = canvas.create_rectangle(((self.r_trigger_pos[0] - 5, self.r_trigger_pos[1] - 20), (self.r_trigger_pos[0] + 5, self.r_trigger_pos[1] + 20))) + + r_trigger_index_pos = (self.r_trigger_pos[0], self.r_trigger_pos[1] - 20) + + self.r_trigger_index = canvas.create_rectangle(((r_trigger_index_pos[0] - 10, r_trigger_index_pos[1] - 5), (r_trigger_index_pos[0] + 10, r_trigger_index_pos[1] + 5))) + + buttons_pos = (self.center[0] + 100, self.center[1] - 20) + + A_button_pos = (buttons_pos[0], buttons_pos[1] + 20) + + B_button_pos = (buttons_pos[0] + 20, buttons_pos[1]) + + Y_button_pos = (buttons_pos[0], buttons_pos[1] - 20) + + X_button_pos = (buttons_pos[0] - 20, buttons_pos[1]) + + self.A_button = canvas.create_oval(((A_button_pos[0] - 10, A_button_pos[1] - 10), (A_button_pos[0] + 10, A_button_pos[1] + 10))) + + self.B_button = canvas.create_oval(((B_button_pos[0] - 10, B_button_pos[1] - 10), (B_button_pos[0] + 10, B_button_pos[1] + 10))) + + self.Y_button = canvas.create_oval(((Y_button_pos[0] - 10, Y_button_pos[1] - 10), (Y_button_pos[0] + 10, Y_button_pos[1] + 10))) + + self.X_button = canvas.create_oval(((X_button_pos[0] - 10, X_button_pos[1] - 10), (X_button_pos[0] + 10, X_button_pos[1] + 10))) + + dpad_pos = (self.center[0] - 50, self.center[1] + 20) + + self.dpad_left = canvas.create_rectangle(((dpad_pos[0] - 30, dpad_pos[1] - 10), (dpad_pos[0] - 10, dpad_pos[1] + 10)), outline = "") + + self.dpad_up = canvas.create_rectangle(((dpad_pos[0] - 10, dpad_pos[1] - 30), (dpad_pos[0] + 10, dpad_pos[1] - 10)), outline = "") + + self.dpad_right = canvas.create_rectangle(((dpad_pos[0] + 10, dpad_pos[1] - 10), (dpad_pos[0] + 30, dpad_pos[1] + 10)), outline = "") + + self.dpad_down = canvas.create_rectangle(((dpad_pos[0] - 10, dpad_pos[1] + 10), (dpad_pos[0] + 10, dpad_pos[1] + 30)), outline = "") + + dpad_outline = canvas.create_polygon(((dpad_pos[0] - 30, dpad_pos[1] - 10), (dpad_pos[0] - 10, dpad_pos[1] - 10), (dpad_pos[0] - 10, dpad_pos[1] - 30), (dpad_pos[0] + 10, dpad_pos[1] - 30), + (dpad_pos[0] + 10, dpad_pos[1] - 10), (dpad_pos[0] + 30, dpad_pos[1] - 10), (dpad_pos[0] + 30, dpad_pos[1] + 10), (dpad_pos[0] + 10, dpad_pos[1] + 10), + (dpad_pos[0] + 10, dpad_pos[1] + 30), (dpad_pos[0] - 10, dpad_pos[1] + 30), (dpad_pos[0] - 10, dpad_pos[1] + 10), (dpad_pos[0] - 30, dpad_pos[1] + 10)), + fill = "", outline = "black") + + back_button_pos = (self.center[0] - 20, self.center[1] - 20) + + self.back_button = canvas.create_oval(((back_button_pos[0] - 5, back_button_pos[1] - 5), (back_button_pos[0] + 5, back_button_pos[1] + 5))) + + start_button_pos = (self.center[0] + 20, self.center[1] - 20) + + self.start_button = canvas.create_oval(((start_button_pos[0] - 5, start_button_pos[1] - 5), (start_button_pos[0] + 5, start_button_pos[1] + 5))) + + l_shoulder_pos = (self.center[0] - 90, self.center[1] - 70) + + self.l_shoulder = canvas.create_rectangle(((l_shoulder_pos[0] - 20, l_shoulder_pos[1] - 5), (l_shoulder_pos[0] + 20, l_shoulder_pos[1] + 10))) + + r_shoulder_pos = (self.center[0] + 90, self.center[1] - 70) + + self.r_shoulder = canvas.create_rectangle(((r_shoulder_pos[0] - 20, r_shoulder_pos[1] - 10), (r_shoulder_pos[0] + 20, r_shoulder_pos[1] + 5))) + + controllers = (Controller((150., 100.)), + Controller((450., 100.)), + Controller((150., 300.)), + Controller((450., 300.))) + + + + # Create the handler and set the events functions class MyHandler(GamepadEventsHandler): + def __init__(self, controllers, canvas): + self.controllers = controllers + self.canvas = canvas + def on_button_event(self,event): - print(event) + controller = self.controllers[event.user_index] + fill_color = "" + if event.type == EVENT_BUTTON_PRESSED: + fill_color = "red" + + if event.button == "LEFT_THUMB": + canvas.itemconfig(controller.l_thumb_stick, fill=fill_color) + elif event.button == "RIGHT_THUMB": + canvas.itemconfig(controller.r_thumb_stick, fill=fill_color) + + elif event.button == "LEFT_SHOULDER": + canvas.itemconfig(controller.l_shoulder, fill=fill_color) + elif event.button == "RIGHT_SHOULDER": + canvas.itemconfig(controller.r_shoulder, fill=fill_color) + + elif event.button == "BACK": + canvas.itemconfig(controller.back_button, fill=fill_color) + elif event.button == "START": + canvas.itemconfig(controller.start_button, fill=fill_color) + + elif event.button == "DPAD_LEFT": + canvas.itemconfig(controller.dpad_left, fill=fill_color) + elif event.button == "DPAD_RIGHT": + canvas.itemconfig(controller.dpad_right, fill=fill_color) + elif event.button == "DPAD_UP": + canvas.itemconfig(controller.dpad_up, fill=fill_color) + elif event.button == "DPAD_DOWN": + canvas.itemconfig(controller.dpad_down, fill=fill_color) + + elif event.button == "A": + canvas.itemconfig(controller.A_button, fill=fill_color) + elif event.button == "B": + canvas.itemconfig(controller.B_button, fill=fill_color) + elif event.button == "Y": + canvas.itemconfig(controller.Y_button, fill=fill_color) + elif event.button == "X": + canvas.itemconfig(controller.X_button, fill=fill_color) def on_stick_event(self, event): - print(event) + controller = self.controllers[event.user_index] + if event.stick == LEFT: + l_thumb_stick_pos = (int(round(controller.l_thumb_pos[0] + 25 * event.x,0)), int(round(controller.l_thumb_pos[1] - 25 * event.y,0))) + self.canvas.coords(controller.l_thumb_stick, (l_thumb_stick_pos[0] - 10, l_thumb_stick_pos[1] - 10, l_thumb_stick_pos[0] + 10, l_thumb_stick_pos[1] + 10)) + + elif event.stick == RIGHT: + r_thumb_stick_pos = (int(round(controller.r_thumb_pos[0] + 25 * event.x,0)), int(round(controller.r_thumb_pos[1] - 25 * event.y,0))) + self.canvas.coords(controller.r_thumb_stick, (r_thumb_stick_pos[0] - 10, r_thumb_stick_pos[1] - 10, r_thumb_stick_pos[0] + 10, r_thumb_stick_pos[1] + 10)) def on_trigger_event(self, event): - print(event) + controller = self.controllers[event.user_index] + if event.trigger == LEFT: + l_trigger_index_pos = (controller.l_trigger_pos[0], controller.l_trigger_pos[1] - 20 + int(round(40 * event.value, 0))) + self.canvas.coords(controller.l_trigger_index, (l_trigger_index_pos[0] - 10, l_trigger_index_pos[1] - 5, l_trigger_index_pos[0] + 10, l_trigger_index_pos[1] + 5)) + elif event.trigger == RIGHT: + r_trigger_index_pos = (controller.r_trigger_pos[0], controller.r_trigger_pos[1] - 20 + int(round(40 * event.value, 0))) + self.canvas.coords(controller.r_trigger_index, (r_trigger_index_pos[0] - 10, r_trigger_index_pos[1] - 5, r_trigger_index_pos[0] + 10, r_trigger_index_pos[1] + 5)) def on_connection_event(self, event): - print(event) + controller = self.controllers[event.user_index] + if event.type == EVENT_CONNECTED: + self.canvas.itemconfig(controller.on_indicator, fill="light green") + elif event.type == EVENT_DISCONNECTED: + self.canvas.itemconfig(controller.on_indicator, fill="") + else: + print("Unrecognized controller event type") - handler = MyHandler() - thread = GamepadThread(handler) - try: - while(1): - pass + handler = MyHandler(controllers, canvas) # initialize handler object + thread = GamepadThread(handler) # initialize controller thread + + root.mainloop() # run UI loop - except KeyboardInterrupt: - print("\n--- User exit ---") \ No newline at end of file +else: + raise ImportError("This is not a module. Import XInput only") \ No newline at end of file From 5cfabca7d110e9686c3fe681b270fd9f7681cc3b Mon Sep 17 00:00:00 2001 From: Luca Tessaro Date: Sun, 17 Nov 2019 15:46:55 +0100 Subject: [PATCH 4/7] First version of filters --- XInput.py | 61 ++++++++++++++++++++++++++++++++++++++++++--- XInputThreadTest.py | 4 +++ 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/XInput.py b/XInput.py index 1b17417..73f89b9 100644 --- a/XInput.py +++ b/XInput.py @@ -51,6 +51,30 @@ BATTERY_LEVEL_MEDIUM = 0x02 BATTERY_LEVEL_FULL = 0x03 +BUTTON_DPAD_UP = 0x000001 +BUTTON_DPAD_DOWN = 0x000002 +BUTTON_DPAD_LEFT = 0x000004 +BUTTON_DPAD_RIGHT = 0x000008 +BUTTON_START = 0x000010 +BUTTON_BACK = 0x000020 +BUTTON_LEFT_THUMB = 0x000040 +BUTTON_RIGHT_THUMB = 0x000080 +BUTTON_LEFT_SHOULDER = 0x000100 +BUTTON_RIGHT_SHOULDER = 0x000200 +BUTTON_A = 0x001000 +BUTTON_B = 0x002000 +BUTTON_X = 0x004000 +BUTTON_Y = 0x008000 + +STICK_LEFT = 0x010000 +STICK_RIGHT = 0x020000 +TRIGGER_LEFT = 0x040000 +TRIGGER_RIGHT = 0x080000 + +FILTER_DOWN_ONLY = 0x100000 +FILTER_UP_ONLY = 0x200000 +FILTER_NONE = 0xffffff-FILTER_DOWN_ONLY-FILTER_UP_ONLY + _battery_type_dict = {BATTERY_TYPE_DISCONNECTED : "DISCONNECTED", BATTERY_TYPE_WIRED : "WIRED", BATTERY_TYPE_ALKALINE : "ALKALINE", @@ -497,6 +521,7 @@ def __init__(self, events_handler, auto_start=True): if (events_handler is None or not issubclass(type(events_handler), GamepadEventsHandler)): raise TypeError("The event handler must be a subclass of XInput.GamepadEventsHandler") self.handler = events_handler + self.filters = [FILTER_NONE]*4 # by default none of the input is filtered (masking also up and down filter for buttons) if auto_start: self.start_thread() @@ -507,11 +532,15 @@ def __tfun(self): # thread function if e.type == EVENT_CONNECTED or e.type == EVENT_DISCONNECTED: self.handler.on_connection_event(e) elif e.type == EVENT_BUTTON_PRESSED or e.type == EVENT_BUTTON_RELEASED: - self.handler.on_button_event(e) + if not((self.filters[e.user_index] & (FILTER_DOWN_ONLY+FILTER_UP_ONLY)) and not(self.filters[e.user_index] & (FILTER_DOWN_ONLY << (e.type - EVENT_BUTTON_PRESSED)))): + if e.button_id & self.filters[e.user_index]: + self.handler.on_button_event(e) elif e.type == EVENT_TRIGGER_MOVED: - self.handler.on_trigger_event(e) + if (TRIGGER_LEFT << e.trigger) & self.filters[e.user_index]: + self.handler.on_trigger_event(e) elif e.type == EVENT_STICK_MOVED: - self.handler.on_stick_event(e) + if (STICK_LEFT << e.stick) & self.filters[e.user_index]: + self.handler.on_stick_event(e) else: raise ValueError("Event type not recognized") @@ -526,6 +555,32 @@ def start_thread(self): # starts the thread def stop_thread(self): # stops the thread self.isRunning = False + # the filter is the sum of the buttons that must be shown + # the list of possible inputs to be filtered is: + # * button values (1,2,4,8, ...) + # * 0x010000 or 65536 for left stick + # * 0x020000 or 131072 for right stick + # * 0x040000 or 262144 for left trigger + # * 0x080000 or 524288 for right trigger + # + # additional values are available to filter only one kind of events wich is only button down and only button down: + # FILTER_DOWN_ONLY + # FILTER_UP_ONLY + # + # example1: add_filter(BUTTON_X + BUTTON_Y + BUTTON_DPAD_UP) will add a filter for all players that allow events only for the X, Y and DPAD_UP buttons + # example2: add_filter(BUTTON_A + FILTER_DOWN_ONLY, [1,3]) will add the filter only for player 1 and 3 and only when the button is pressed down + # NOTE: Controller events are not maskable + def add_filter(self, filter, controller = [0,1,2,3]): + for i in controller: + if(self.filters[i] == FILTER_NONE): + self.filters[i] = filter + else: + self.filters[i] |=filter + + # remove any filter + # the "controller" attribute remove the filter only for the selected controller. By default will remove every filter + def clear_filters(self, controller = [0,1,2,3]): + self.filters[controller] = [] diff --git a/XInputThreadTest.py b/XInputThreadTest.py index eeed25a..0469c3b 100644 --- a/XInputThreadTest.py +++ b/XInputThreadTest.py @@ -190,6 +190,10 @@ def on_connection_event(self, event): handler = MyHandler(controllers, canvas) # initialize handler object thread = GamepadThread(handler) # initialize controller thread + # filters examples + thread.add_filter(BUTTON_A + BUTTON_B + STICK_LEFT + FILTER_DOWN_ONLY + TRIGGER_RIGHT + BUTTON_START) + # thread.add_filter(STICK_RIGHT,[2]) + root.mainloop() # run UI loop else: From b9a94ab24be88f31ab8d479a4c6ad0bdf6182ea9 Mon Sep 17 00:00:00 2001 From: Luca Tessaro Date: Sun, 17 Nov 2019 16:17:43 +0100 Subject: [PATCH 5/7] Moved filter to handler object and set up for multiple handlers --- XInput.py | 99 ++++++++++++++++++++++++++++----------------- XInputThreadTest.py | 28 +++++++++++-- 2 files changed, 86 insertions(+), 41 deletions(-) diff --git a/XInput.py b/XInput.py index 73f89b9..8019f7f 100644 --- a/XInput.py +++ b/XInput.py @@ -504,6 +504,9 @@ def get_events(): # Event handler class to be extended to use dynamic events class GamepadEventsHandler: + def __init__(self, filter = FILTER_NONE): + self.filters = [filter]*4 + def on_button_event(self, event): raise NotImplementedError("Method not implemented. Must be implemented in the child class") @@ -516,11 +519,42 @@ def on_trigger_event(self, event): def on_connection_event(self, event): raise NotImplementedError("Method not implemented. Must be implemented in the child class") + # the filter is the sum of the buttons that must be shown + # the list of possible inputs to be filtered is: + # * button values (1,2,4,8, ...) + # * 0x010000 or 65536 for left stick + # * 0x020000 or 131072 for right stick + # * 0x040000 or 262144 for left trigger + # * 0x080000 or 524288 for right trigger + # + # additional values are available to filter only one kind of events wich is only button down and only button down: + # FILTER_DOWN_ONLY + # FILTER_UP_ONLY + # + # example1: add_filter(BUTTON_X + BUTTON_Y + BUTTON_DPAD_UP) will add a filter for all players that allow events only for the X, Y and DPAD_UP buttons + # example2: add_filter(BUTTON_A + FILTER_DOWN_ONLY, [1,3]) will add the filter only for player 1 and 3 and only when the button is pressed down + # NOTE: Controller events are not maskable + def add_filter(self, filter, controller = [0,1,2,3]): + for i in controller: + if(self.filters[i] == FILTER_NONE): + self.filters[i] = filter + else: + self.filters[i] |=filter + + # remove any filter + # the "controller" attribute remove the filter only for the selected controller. By default will remove every filter + def clear_filters(self, controller = [0,1,2,3]): + self.filters[controller] = [] + + class GamepadThread: - def __init__(self, events_handler, auto_start=True): - if (events_handler is None or not issubclass(type(events_handler), GamepadEventsHandler)): - raise TypeError("The event handler must be a subclass of XInput.GamepadEventsHandler") - self.handler = events_handler + def __init__(self, events_handlers, auto_start=True): + if not isinstance(events_handlers, list): + events_handlers = [events_handlers] + for ev in events_handlers: + if (ev is None or not issubclass(type(ev), GamepadEventsHandler)): + raise TypeError("The event handler must be a subclass of XInput.GamepadEventsHandler") + self.handlers = events_handlers self.filters = [FILTER_NONE]*4 # by default none of the input is filtered (masking also up and down filter for buttons) if auto_start: self.start_thread() @@ -530,17 +564,21 @@ def __tfun(self): # thread function events = get_events() for e in events: # filtering events if e.type == EVENT_CONNECTED or e.type == EVENT_DISCONNECTED: - self.handler.on_connection_event(e) + for h in self.handlers: + h.on_connection_event(e) elif e.type == EVENT_BUTTON_PRESSED or e.type == EVENT_BUTTON_RELEASED: - if not((self.filters[e.user_index] & (FILTER_DOWN_ONLY+FILTER_UP_ONLY)) and not(self.filters[e.user_index] & (FILTER_DOWN_ONLY << (e.type - EVENT_BUTTON_PRESSED)))): - if e.button_id & self.filters[e.user_index]: - self.handler.on_button_event(e) + for h in self.handlers: + if not((h.filters[e.user_index] & (FILTER_DOWN_ONLY+FILTER_UP_ONLY)) and not(h.filters[e.user_index] & (FILTER_DOWN_ONLY << (e.type - EVENT_BUTTON_PRESSED)))): + if e.button_id & h.filters[e.user_index]: + h.on_button_event(e) elif e.type == EVENT_TRIGGER_MOVED: - if (TRIGGER_LEFT << e.trigger) & self.filters[e.user_index]: - self.handler.on_trigger_event(e) + for h in self.handlers: + if (TRIGGER_LEFT << e.trigger) & h.filters[e.user_index]: + h.on_trigger_event(e) elif e.type == EVENT_STICK_MOVED: - if (STICK_LEFT << e.stick) & self.filters[e.user_index]: - self.handler.on_stick_event(e) + for h in self.handlers: + if (STICK_LEFT << e.stick) & h.filters[e.user_index]: + h.on_stick_event(e) else: raise ValueError("Event type not recognized") @@ -554,33 +592,18 @@ def start_thread(self): # starts the thread def stop_thread(self): # stops the thread self.isRunning = False - - # the filter is the sum of the buttons that must be shown - # the list of possible inputs to be filtered is: - # * button values (1,2,4,8, ...) - # * 0x010000 or 65536 for left stick - # * 0x020000 or 131072 for right stick - # * 0x040000 or 262144 for left trigger - # * 0x080000 or 524288 for right trigger - # - # additional values are available to filter only one kind of events wich is only button down and only button down: - # FILTER_DOWN_ONLY - # FILTER_UP_ONLY - # - # example1: add_filter(BUTTON_X + BUTTON_Y + BUTTON_DPAD_UP) will add a filter for all players that allow events only for the X, Y and DPAD_UP buttons - # example2: add_filter(BUTTON_A + FILTER_DOWN_ONLY, [1,3]) will add the filter only for player 1 and 3 and only when the button is pressed down - # NOTE: Controller events are not maskable - def add_filter(self, filter, controller = [0,1,2,3]): - for i in controller: - if(self.filters[i] == FILTER_NONE): - self.filters[i] = filter - else: - self.filters[i] |=filter - # remove any filter - # the "controller" attribute remove the filter only for the selected controller. By default will remove every filter - def clear_filters(self, controller = [0,1,2,3]): - self.filters[controller] = [] + def add_event_handler(self, event_handler): + if (event_handler is None or not issubclass(type(event_handler), GamepadEventsHandler)): + raise TypeError("The event handler must be a subclass of XInput.GamepadEventsHandler") + self.handlers.append(event_handler) + + def remove_event_handler(self, event_handler): + try: + self.handlers.remove(event_handler) + return True + except ValueError: + return False diff --git a/XInputThreadTest.py b/XInputThreadTest.py index 0469c3b..128749f 100644 --- a/XInputThreadTest.py +++ b/XInputThreadTest.py @@ -115,7 +115,8 @@ def __init__(self, center): # Create the handler and set the events functions class MyHandler(GamepadEventsHandler): - def __init__(self, controllers, canvas): + def __init__(self, controllers, canvas, filter=FILTER_NONE): + super().__init__(filter) self.controllers = controllers self.canvas = canvas @@ -185,14 +186,35 @@ def on_connection_event(self, event): self.canvas.itemconfig(controller.on_indicator, fill="") else: print("Unrecognized controller event type") + + class MyOtherHandler(GamepadEventsHandler): + def __init__(self): + super().__init__(BUTTON_A+FILTER_DOWN_ONLY) + + def on_button_event(self, event): + print("Pressed button A") + + def on_stick_event(self, event): + pass + + def on_trigger_event(self, event): + print("Trigger LEFT") + + def on_connection_event(self, event): + pass handler = MyHandler(controllers, canvas) # initialize handler object thread = GamepadThread(handler) # initialize controller thread + handler2 = MyOtherHandler() + thread.add_event_handler(handler2) # add another handler + handler2.add_filter(TRIGGER_LEFT) + # filters examples - thread.add_filter(BUTTON_A + BUTTON_B + STICK_LEFT + FILTER_DOWN_ONLY + TRIGGER_RIGHT + BUTTON_START) - # thread.add_filter(STICK_RIGHT,[2]) + # handler.add_filter(BUTTON_A + BUTTON_B + STICK_LEFT + FILTER_DOWN_ONLY + TRIGGER_RIGHT + BUTTON_START) + # handler.add_filter(STICK_RIGHT,[2]) + # handler = MyHandler(controllers, canvas, STICK_LEFT) root.mainloop() # run UI loop From 36e15a3af905c43c7fca447b6427332b08a67398 Mon Sep 17 00:00:00 2001 From: Luca Tessaro Date: Sun, 17 Nov 2019 17:43:27 +0100 Subject: [PATCH 6/7] Updated documentation --- README.md | 101 ++++++++++++++++++++++++++++++++++++++------ README.rml | 69 +++++++++++++++++++++++++++++- README.rst | 89 +++++++++++++++++++++++++++++++++++++- XInputThreadTest.py | 2 + readmelang.py | 2 +- 5 files changed, 246 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 8d6c232..8025b3b 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ It can be inmported like this: ### Using XInput\-Python XInput\-Python provides a few functions: -`get_connected() -> (bool, bool, bool, bool)` Query which controllers are connected \(note: don't query each frame\) +`get_connected() -> (bool, bool, bool, bool)` Query which controllers are connected (note: don't query each frame) `get_state(user_index) -> State` Gets the State of the controller `user_index` @@ -32,11 +32,11 @@ XInput\-Python provides a few functions: `set_deadzone(deadzone, value) -> None` Sets the deadzone values for left/right thumb stick and triggers\. The following deadzones exist: -`XInput.DEADZONE_LEFT_THUMB` \- \(range 0 to 32767\) Left thumb stick deadzone \(default is 7849\) +`XInput.DEADZONE_LEFT_THUMB` \- (range 0 to 32767) Left thumb stick deadzone (default is 7849) -`XInput.DEADZONE_RIGHT_THUMB` \- \(range 0 to 32767\) Right thumb stick deadzone \(default is 8689\) +`XInput.DEADZONE_RIGHT_THUMB` \- (range 0 to 32767) Right thumb stick deadzone (default is 8689) -`XInput.DEADZONE_TRIGGER` \- \(range 0 to 255\) Trigger deadzone \(default is 30\) +`XInput.DEADZONE_TRIGGER` \- (range 0 to 255) Trigger deadzone (default is 30) #### Using Events You can also use the Event\-system: @@ -47,11 +47,11 @@ You can also use the Event\-system: `get_events` will return a generator that yields instances of the `Event` class\. The `Event` class always has the following members: -`Event.user_index` \(range 0 to 3\) \- the id of the controller that issued this event +`Event.user_index` (range 0 to 3) \- the id of the controller that issued this event `Event.type` \- which type of event was issued The following events exist: -`XInput.EVENT_CONNECTED == 1` \- a controller with this `user_index` was connected \(this event will even occur if the controller was connected before the script was started\) +`XInput.EVENT_CONNECTED == 1` \- a controller with this `user_index` was connected (this event will even occur if the controller was connected before the script was started) `XInput.EVENT_DISCONNECTED == 2` \- a controller with this `user_index` was disconnected @@ -89,16 +89,89 @@ The following buttons exist: **Trigger Events** All trigger related Events have the following additional members: -`Event.trigger` \(either `XInput.LEFT == 0` or `XInput.RIGHT == 1`\) \- which trigger was moved -`Event.value` \(range 0\.0 to 1\.0\) \- by how much the trigger is currently pressed +`Event.trigger` (either `XInput.LEFT == 0` or `XInput.RIGHT == 1`) \- which trigger was moved +`Event.value` (range 0\.0 to 1\.0) \- by how much the trigger is currently pressed **Stick Events** All thumb stick related Events have the following additional members: -`Event.stick` \(either `XInput.LEFT == 0` or `XInput.RIGHT == 1`\) \- which stick was moved -`Event.x` \(range \-1\.0 to 1\.0\) \- the position of the stick on the X axis -`Event.y` \(range \-1\.0 to 1\.0\) \- the position of the stick on the Y axis -`Event.value` \(range 0\.0 to 1\.0\) \- the distance of the stick from it's center position -`Event.dir` \(tuple of X and Y\) \- the direction the stick is currently pointing +`Event.stick` (either `XInput.LEFT == 0` or `XInput.RIGHT == 1`) \- which stick was moved +`Event.x` (range \-1\.0 to 1\.0) \- the position of the stick on the X axis +`Event.y` (range \-1\.0 to 1\.0) \- the position of the stick on the Y axis +`Event.value` (range 0\.0 to 1\.0) \- the distance of the stick from it's center position +`Event.dir` (tuple of X and Y) \- the direction the stick is currently pointing + +### Callback events and threading +With the `GamepadThread` class it is possible to handle asynchronous events\. +To use this feature, extend the `GamepadEventsHandler` to create one or multiple handlers and add them to the thread\. +The library will automatically check the status of the gamepad and use the appropriate callback for the triggering event\. +It is also possible to filter the inputs for every single handler\. +In case of multiple handlers it is possible to use a list of handlers as argument, as well as the `add_handler()` method and the `remove_handler()` method to remove them\. +Filters can be applied to select events of only certain buttons, trigger or stick\. Also a "button\-down" and "button\-up" filter is available\. +The available filters are: + + + BUTTON_DPAD_UP + BUTTON_DPAD_DOWN + BUTTON_DPAD_LEFT + BUTTON_DPAD_RIGHT + BUTTON_START + BUTTON_BACK + BUTTON_LEFT_THUMB + BUTTON_RIGHT_THUMB + BUTTON_LEFT_SHOULDER + BUTTON_RIGHT_SHOULDER + BUTTON_A + BUTTON_B + BUTTON_X + BUTTON_Y + + STICK_LEFT + STICK_RIGHT + TRIGGER_LEFT + TRIGGER_RIGHT + + FILTER_DOWN_ONLY + FILTER_UP_ONLY + + + +The filters can be combined by adding them together: + + + filter1 = STICK_LEFT + STICK_RIGHT + BUTTON_DPAD_DOWN + BUTTON_DPAD_UP + filter2 = BUTTON_Y + BUTTON_X + FILTER_DOWN_ONLY + + +While adding a filter is also possible to set to wich controller/s should be applied: + +`handler.add_filter(filter, controller = [1,2])` + +**Example** + + class MyHandler(GamepadEventsHandler): + def __init__(self): + ... + + def on_button_event(self, event): + # put here the code to parse every event related only to the buttons + + def on_trigger_event(self, event): + # event reserved for the two triggers + + def on_stick_event(self, event): + # event reserved for the two sticks + + def on_connection_event(self, event): + # event related to the gamepad status + + filter = STICK_LEFT + STICK_RIGHT + my_handler = MyHandler() + my_handler.add_filter = filter + my_gamepad_thread = GamepadThread(my_handler) + + +The thread will start automatically upon creation\. It is possible to stop and start it again if necessary with the two methods `start_thread()` and `stop_thread()` ### Demo -Run `XInput.py` as main \(`python XInput.py`\) to see a visual representation of the controller input\. \ No newline at end of file +Run `XInput.py` as main (`python XInput.py`) to see a visual representation of the controller input\. +Run `python XInputThreadTest.py` to test the visual representation using the asynchronous callbacks\. \ No newline at end of file diff --git a/README.rml b/README.rml index e5283a6..119e734 100644 --- a/README.rml +++ b/README.rml @@ -92,5 +92,72 @@ All thumb stick related Events have the following additional members: [code]Event.value[/code] (range 0.0 to 1.0) - the distance of the stick from it's center position [code]Event.dir[/code] (tuple of X and Y) - the direction the stick is currently pointing +[s2]Callback events and threading[/] +With the [code]GamepadThread[/code] class it is possible to handle asynchronous events. +To use this feature, extend the [code]GamepadEventsHandler[/code] to create one or multiple handlers and add them to the thread. +The library will automatically check the status of the gamepad and use the appropriate callback for the triggering event. +It is also possible to filter the inputs for every single handler. +In case of multiple handlers it is possible to use a list of handlers as argument, as well as the [code]add_handler()[/code] method and the [code]remove_handler()[/code] method to remove them. +Filters can be applied to select events of only certain buttons, trigger or stick. Also a "button-down" and "button-up" filter is available. +The available filters are: +[code] +BUTTON_DPAD_UP +BUTTON_DPAD_DOWN +BUTTON_DPAD_LEFT +BUTTON_DPAD_RIGHT +BUTTON_START +BUTTON_BACK +BUTTON_LEFT_THUMB +BUTTON_RIGHT_THUMB +BUTTON_LEFT_SHOULDER +BUTTON_RIGHT_SHOULDER +BUTTON_A +BUTTON_B +BUTTON_X +BUTTON_Y + +STICK_LEFT +STICK_RIGHT +TRIGGER_LEFT +TRIGGER_RIGHT + +FILTER_DOWN_ONLY +FILTER_UP_ONLY +[/code] + +The filters can be combined by adding them together: + +[code]filter1 = STICK_LEFT + STICK_RIGHT + BUTTON_DPAD_DOWN + BUTTON_DPAD_UP +filter2 = BUTTON_Y + BUTTON_X + FILTER_DOWN_ONLY[/code] + +While adding a filter is also possible to set to wich controller/s should be applied: + +[code]handler.add_filter(filter, controller = [1,2])[/code] + +[b]Example[/] +[code]class MyHandler(GamepadEventsHandler): + def __init__(self): + ... + + def on_button_event(self, event): + # put here the code to parse every event related only to the buttons + + def on_trigger_event(self, event): + # event reserved for the two triggers + + def on_stick_event(self, event): + # event reserved for the two sticks + + def on_connection_event(self, event): + # event related to the gamepad status + +filter = STICK_LEFT + STICK_RIGHT +my_handler = MyHandler() +my_handler.add_filter = filter +my_gamepad_thread = GamepadThread(my_handler)[/code] + +The thread will start automatically upon creation. It is possible to stop and start it again if necessary with the two methods [code]start_thread()[/code] and [code]stop_thread()[/code] + [s2]Demo[/] -Run [code]XInput.py[/code] as main ([code]python XInput.py[/code]) to see a visual representation of the controller input. \ No newline at end of file +Run [code]XInput.py[/code] as main ([code]python XInput.py[/code]) to see a visual representation of the controller input. +Run [code]python XInputThreadTest.py[/code] to test the visual representation using the asynchronous callbacks. \ No newline at end of file diff --git a/README.rst b/README.rst index 3be7836..564b9df 100644 --- a/README.rst +++ b/README.rst @@ -131,6 +131,93 @@ Using Events | :code:`Event.dir` \(tuple of X and Y\) \- the direction the stick is currently pointing | +Callback events and threading +----------------------------- +| With the :code:`GamepadThread` class it is possible to handle asynchronous events\. +| To use this feature\, extend the :code:`GamepadEventsHandler` to create one or multiple handlers and add them to the thread\. +| The library will automatically check the status of the gamepad and use the appropriate callback for the triggering event\. +| It is also possible to filter the inputs for every single handler\. +| In case of multiple handlers it is possible to use a list of handlers as argument\, as well as the :code:`add_handler()` method and the :code:`remove_handler()` method to remove them\. +| Filters can be applied to select events of only certain buttons\, trigger or stick\. Also a \"button\-down\" and \"button\-up\" filter is available\. +| The available filters are\: + + +:: + + + BUTTON_DPAD_UP + BUTTON_DPAD_DOWN + BUTTON_DPAD_LEFT + BUTTON_DPAD_RIGHT + BUTTON_START + BUTTON_BACK + BUTTON_LEFT_THUMB + BUTTON_RIGHT_THUMB + BUTTON_LEFT_SHOULDER + BUTTON_RIGHT_SHOULDER + BUTTON_A + BUTTON_B + BUTTON_X + BUTTON_Y + + STICK_LEFT + STICK_RIGHT + TRIGGER_LEFT + TRIGGER_RIGHT + + FILTER_DOWN_ONLY + FILTER_UP_ONLY + + + +| +| The filters can be combined by adding them together\: +| + + +:: + + filter1 = STICK_LEFT + STICK_RIGHT + BUTTON_DPAD_DOWN + BUTTON_DPAD_UP + filter2 = BUTTON_Y + BUTTON_X + FILTER_DOWN_ONLY + + +| +| While adding a filter is also possible to set to wich controller\/s should be applied\: +| +| :code:`handler.add_filter(filter, controller = [1,2])` +| +| **Example** + + +:: + + class MyHandler(GamepadEventsHandler): + def __init__(self): + ... + + def on_button_event(self, event): + # put here the code to parse every event related only to the buttons + + def on_trigger_event(self, event): + # event reserved for the two triggers + + def on_stick_event(self, event): + # event reserved for the two sticks + + def on_connection_event(self, event): + # event related to the gamepad status + + filter = STICK_LEFT + STICK_RIGHT + my_handler = MyHandler() + my_handler.add_filter = filter + my_gamepad_thread = GamepadThread(my_handler) + + +| +| The thread will start automatically upon creation\. It is possible to stop and start it again if necessary with the two methods :code:`start_thread()` and :code:`stop_thread()` +| + Demo ---- -| Run :code:`XInput.py` as main \(:code:`python XInput.py`\) to see a visual representation of the controller input\. \ No newline at end of file +| Run :code:`XInput.py` as main \(:code:`python XInput.py`\) to see a visual representation of the controller input\. +| Run :code:`python XInputThreadTest.py` to test the visual representation using the asynchronous callbacks\. \ No newline at end of file diff --git a/XInputThreadTest.py b/XInputThreadTest.py index 128749f..cb6841f 100644 --- a/XInputThreadTest.py +++ b/XInputThreadTest.py @@ -218,5 +218,7 @@ def on_connection_event(self, event): root.mainloop() # run UI loop + # can run other stuff here + else: raise ImportError("This is not a module. Import XInput only") \ No newline at end of file diff --git a/readmelang.py b/readmelang.py index 7addf3c..f848f7a 100644 --- a/readmelang.py +++ b/readmelang.py @@ -22,7 +22,7 @@ INLINE_CODE = 2**14 def format_md(text): - for char in "\\`*{}[]()#+-.!_": + for char in "\\`*{}[]#+-.!_": if not char in text: continue text = text.replace(char, "\\"+char) return text From b390756ab3c51343616ed281694a8850efa43aae Mon Sep 17 00:00:00 2001 From: Zuzu-Typ Date: Sat, 23 Nov 2019 16:07:12 +0100 Subject: [PATCH 7/7] Updated threading implementation Also updated documentation and moved test to external file --- README.md | 37 ++- README.rml | 35 ++- README.rst | 41 ++-- XInput.py | 556 ++++++++++++++++++-------------------------- XInputTest.py | 207 +++++++++++++++++ XInputThreadTest.py | 59 +++-- 6 files changed, 514 insertions(+), 421 deletions(-) create mode 100644 XInputTest.py diff --git a/README.md b/README.md index 8025b3b..db306cf 100644 --- a/README.md +++ b/README.md @@ -102,11 +102,11 @@ All thumb stick related Events have the following additional members: ### Callback events and threading With the `GamepadThread` class it is possible to handle asynchronous events\. -To use this feature, extend the `GamepadEventsHandler` to create one or multiple handlers and add them to the thread\. +To use this feature, extend the `EventHandler` to create one or multiple handlers and add them to the thread\. The library will automatically check the status of the gamepad and use the appropriate callback for the triggering event\. It is also possible to filter the inputs for every single handler\. In case of multiple handlers it is possible to use a list of handlers as argument, as well as the `add_handler()` method and the `remove_handler()` method to remove them\. -Filters can be applied to select events of only certain buttons, trigger or stick\. Also a "button\-down" and "button\-up" filter is available\. +Filters can be applied to select events of only certain buttons, trigger or stick\. Also a "pressed\-only" and "released\-only" filter is available for buttons\. The available filters are: @@ -130,8 +130,8 @@ The available filters are: TRIGGER_LEFT TRIGGER_RIGHT - FILTER_DOWN_ONLY - FILTER_UP_ONLY + FILTER_PRESSED_ONLY + FILTER_RELEASED_ONLY @@ -139,39 +139,38 @@ The filters can be combined by adding them together: filter1 = STICK_LEFT + STICK_RIGHT + BUTTON_DPAD_DOWN + BUTTON_DPAD_UP - filter2 = BUTTON_Y + BUTTON_X + FILTER_DOWN_ONLY + filter2 = BUTTON_Y + BUTTON_X + FILTER_PRESSED_ONLY -While adding a filter is also possible to set to wich controller/s should be applied: +The filter can be applied using add\_filter: + + + handler.add_filter(filter) -`handler.add_filter(filter, controller = [1,2])` **Example** - class MyHandler(GamepadEventsHandler): - def __init__(self): - ... - - def on_button_event(self, event): + class MyHandler(EventHandler): + def process_button_event(self, event): # put here the code to parse every event related only to the buttons - def on_trigger_event(self, event): + def process_trigger_event(self, event): # event reserved for the two triggers - def on_stick_event(self, event): + def process_stick_event(self, event): # event reserved for the two sticks - def on_connection_event(self, event): + def process_connection_event(self, event): # event related to the gamepad status filter = STICK_LEFT + STICK_RIGHT my_handler = MyHandler() - my_handler.add_filter = filter + my_handler.add_filter(filter) my_gamepad_thread = GamepadThread(my_handler) -The thread will start automatically upon creation\. It is possible to stop and start it again if necessary with the two methods `start_thread()` and `stop_thread()` +The thread will start automatically upon creation\. It is possible to stop and start it again if necessary with the two methods `start()` and `stop()` ### Demo -Run `XInput.py` as main (`python XInput.py`) to see a visual representation of the controller input\. -Run `python XInputThreadTest.py` to test the visual representation using the asynchronous callbacks\. \ No newline at end of file +Run `XInputTest.py` to see a visual representation of the controller input\. +Run `XInputThreadTest.py` to test the visual representation using the asynchronous callbacks\. \ No newline at end of file diff --git a/README.rml b/README.rml index 119e734..319651d 100644 --- a/README.rml +++ b/README.rml @@ -94,11 +94,11 @@ All thumb stick related Events have the following additional members: [s2]Callback events and threading[/] With the [code]GamepadThread[/code] class it is possible to handle asynchronous events. -To use this feature, extend the [code]GamepadEventsHandler[/code] to create one or multiple handlers and add them to the thread. +To use this feature, extend the [code]EventHandler[/code] to create one or multiple handlers and add them to the thread. The library will automatically check the status of the gamepad and use the appropriate callback for the triggering event. It is also possible to filter the inputs for every single handler. In case of multiple handlers it is possible to use a list of handlers as argument, as well as the [code]add_handler()[/code] method and the [code]remove_handler()[/code] method to remove them. -Filters can be applied to select events of only certain buttons, trigger or stick. Also a "button-down" and "button-up" filter is available. +Filters can be applied to select events of only certain buttons, trigger or stick. Also a "pressed-only" and "released-only" filter is available for buttons. The available filters are: [code] BUTTON_DPAD_UP @@ -121,43 +121,40 @@ STICK_RIGHT TRIGGER_LEFT TRIGGER_RIGHT -FILTER_DOWN_ONLY -FILTER_UP_ONLY +FILTER_PRESSED_ONLY +FILTER_RELEASED_ONLY [/code] The filters can be combined by adding them together: [code]filter1 = STICK_LEFT + STICK_RIGHT + BUTTON_DPAD_DOWN + BUTTON_DPAD_UP -filter2 = BUTTON_Y + BUTTON_X + FILTER_DOWN_ONLY[/code] +filter2 = BUTTON_Y + BUTTON_X + FILTER_PRESSED_ONLY[/code] -While adding a filter is also possible to set to wich controller/s should be applied: +The filter can be applied using add_filter: -[code]handler.add_filter(filter, controller = [1,2])[/code] +[code]handler.add_filter(filter)[/code] [b]Example[/] -[code]class MyHandler(GamepadEventsHandler): - def __init__(self): - ... - - def on_button_event(self, event): +[code]class MyHandler(EventHandler): + def process_button_event(self, event): # put here the code to parse every event related only to the buttons - def on_trigger_event(self, event): + def process_trigger_event(self, event): # event reserved for the two triggers - def on_stick_event(self, event): + def process_stick_event(self, event): # event reserved for the two sticks - def on_connection_event(self, event): + def process_connection_event(self, event): # event related to the gamepad status filter = STICK_LEFT + STICK_RIGHT my_handler = MyHandler() -my_handler.add_filter = filter +my_handler.add_filter(filter) my_gamepad_thread = GamepadThread(my_handler)[/code] -The thread will start automatically upon creation. It is possible to stop and start it again if necessary with the two methods [code]start_thread()[/code] and [code]stop_thread()[/code] +The thread will start automatically upon creation. It is possible to stop and start it again if necessary with the two methods [code]start()[/code] and [code]stop()[/code] [s2]Demo[/] -Run [code]XInput.py[/code] as main ([code]python XInput.py[/code]) to see a visual representation of the controller input. -Run [code]python XInputThreadTest.py[/code] to test the visual representation using the asynchronous callbacks. \ No newline at end of file +Run [code]XInputTest.py[/code] to see a visual representation of the controller input. +Run [code]XInputThreadTest.py[/code] to test the visual representation using the asynchronous callbacks. \ No newline at end of file diff --git a/README.rst b/README.rst index 564b9df..eb40773 100644 --- a/README.rst +++ b/README.rst @@ -134,11 +134,11 @@ Using Events Callback events and threading ----------------------------- | With the :code:`GamepadThread` class it is possible to handle asynchronous events\. -| To use this feature\, extend the :code:`GamepadEventsHandler` to create one or multiple handlers and add them to the thread\. +| To use this feature\, extend the :code:`EventHandler` to create one or multiple handlers and add them to the thread\. | The library will automatically check the status of the gamepad and use the appropriate callback for the triggering event\. | It is also possible to filter the inputs for every single handler\. | In case of multiple handlers it is possible to use a list of handlers as argument\, as well as the :code:`add_handler()` method and the :code:`remove_handler()` method to remove them\. -| Filters can be applied to select events of only certain buttons\, trigger or stick\. Also a \"button\-down\" and \"button\-up\" filter is available\. +| Filters can be applied to select events of only certain buttons\, trigger or stick\. Also a \"pressed\-only\" and \"released\-only\" filter is available for buttons\. | The available filters are\: @@ -165,8 +165,8 @@ Callback events and threading TRIGGER_LEFT TRIGGER_RIGHT - FILTER_DOWN_ONLY - FILTER_UP_ONLY + FILTER_PRESSED_ONLY + FILTER_RELEASED_ONLY @@ -178,46 +178,49 @@ Callback events and threading :: filter1 = STICK_LEFT + STICK_RIGHT + BUTTON_DPAD_DOWN + BUTTON_DPAD_UP - filter2 = BUTTON_Y + BUTTON_X + FILTER_DOWN_ONLY + filter2 = BUTTON_Y + BUTTON_X + FILTER_PRESSED_ONLY | -| While adding a filter is also possible to set to wich controller\/s should be applied\: +| The filter can be applied using add\_filter\: | -| :code:`handler.add_filter(filter, controller = [1,2])` + + +:: + + handler.add_filter(filter) + + | | **Example** :: - class MyHandler(GamepadEventsHandler): - def __init__(self): - ... - - def on_button_event(self, event): + class MyHandler(EventHandler): + def process_button_event(self, event): # put here the code to parse every event related only to the buttons - def on_trigger_event(self, event): + def process_trigger_event(self, event): # event reserved for the two triggers - def on_stick_event(self, event): + def process_stick_event(self, event): # event reserved for the two sticks - def on_connection_event(self, event): + def process_connection_event(self, event): # event related to the gamepad status filter = STICK_LEFT + STICK_RIGHT my_handler = MyHandler() - my_handler.add_filter = filter + my_handler.add_filter(filter) my_gamepad_thread = GamepadThread(my_handler) | -| The thread will start automatically upon creation\. It is possible to stop and start it again if necessary with the two methods :code:`start_thread()` and :code:`stop_thread()` +| The thread will start automatically upon creation\. It is possible to stop and start it again if necessary with the two methods :code:`start()` and :code:`stop()` | Demo ---- -| Run :code:`XInput.py` as main \(:code:`python XInput.py`\) to see a visual representation of the controller input\. -| Run :code:`python XInputThreadTest.py` to test the visual representation using the asynchronous callbacks\. \ No newline at end of file +| Run :code:`XInputTest.py` to see a visual representation of the controller input\. +| Run :code:`XInputThreadTest.py` to test the visual representation using the asynchronous callbacks\. \ No newline at end of file diff --git a/XInput.py b/XInput.py index 8019f7f..9c08def 100644 --- a/XInput.py +++ b/XInput.py @@ -6,8 +6,10 @@ import time -from threading import Thread +from threading import Thread, Lock + +# loading the DLL # XINPUT_DLL_NAMES = ( "XInput1_4.dll", "XInput9_1_0.dll", @@ -27,18 +29,21 @@ if not libXInput: raise IOError("XInput library was not found.") -WORD = ctypes.c_ushort -BYTE = ctypes.c_ubyte -SHORT = ctypes.c_short -DWORD = ctypes.c_ulong +#/loading the DLL # + +# defining static global variables # +WORD = ctypes.c_ushort +BYTE = ctypes.c_ubyte +SHORT = ctypes.c_short +DWORD = ctypes.c_ulong -ERROR_SUCCESS = 0 -ERROR_BAD_ARGUMENTS = 160 -ERROR_DEVICE_NOT_CONNECTED = 1167 +ERROR_SUCCESS = 0 +ERROR_BAD_ARGUMENTS = 160 +ERROR_DEVICE_NOT_CONNECTED = 1167 -XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE = 7849 +XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE = 7849 XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE = 8689 -XINPUT_GAMEPAD_TRIGGER_THRESHOLD = 30 +XINPUT_GAMEPAD_TRIGGER_THRESHOLD = 30 BATTERY_DEVTYPE_GAMEPAD = 0x00 BATTERY_TYPE_DISCONNECTED = 0x00 @@ -71,21 +76,28 @@ TRIGGER_LEFT = 0x040000 TRIGGER_RIGHT = 0x080000 -FILTER_DOWN_ONLY = 0x100000 -FILTER_UP_ONLY = 0x200000 -FILTER_NONE = 0xffffff-FILTER_DOWN_ONLY-FILTER_UP_ONLY +FILTER_PRESSED_ONLY = 0x100000 +FILTER_RELEASED_ONLY = 0x200000 +FILTER_NONE = 0xffffff-FILTER_PRESSED_ONLY-FILTER_RELEASED_ONLY -_battery_type_dict = {BATTERY_TYPE_DISCONNECTED : "DISCONNECTED", - BATTERY_TYPE_WIRED : "WIRED", - BATTERY_TYPE_ALKALINE : "ALKALINE", - BATTERY_TYPE_NIMH : "NIMH", - BATTERY_TYPE_UNKNOWN : "UNKNOWN"} +DEADZONE_LEFT_THUMB = 0 +DEADZONE_RIGHT_THUMB = 1 +DEADZONE_TRIGGER = 2 -_battery_level_dict = {BATTERY_LEVEL_EMPTY : "EMPTY", - BATTERY_LEVEL_LOW : "LOW", - BATTERY_LEVEL_MEDIUM : "MEDIUM", - BATTERY_LEVEL_FULL : "FULL"} +DEADZONE_DEFAULT = -1 +EVENT_CONNECTED = 1 +EVENT_DISCONNECTED = 2 +EVENT_BUTTON_PRESSED = 3 +EVENT_BUTTON_RELEASED = 4 +EVENT_TRIGGER_MOVED = 5 +EVENT_STICK_MOVED = 6 + +LEFT = 0 +RIGHT = 1 +#/defining static global variables # + +# defining XInput compatible structures # class XINPUT_GAMEPAD(Structure): _fields_ = [("wButtons", WORD), ("bLeftTrigger", BYTE), @@ -130,7 +142,19 @@ def XInputSetState(dwUserIndex, vibration): def XInputGetBatteryInformation(dwUserIndex, devType, batteryInformation): return libXInput.XInputGetBatteryInformation(dwUserIndex, devType, ctypes.byref(batteryInformation)) +#/defining XInput compatible structures # +# defining file-local variables # +_battery_type_dict = {BATTERY_TYPE_DISCONNECTED : "DISCONNECTED", + BATTERY_TYPE_WIRED : "WIRED", + BATTERY_TYPE_ALKALINE : "ALKALINE", + BATTERY_TYPE_NIMH : "NIMH", + BATTERY_TYPE_UNKNOWN : "UNKNOWN"} + +_battery_level_dict = {BATTERY_LEVEL_EMPTY : "EMPTY", + BATTERY_LEVEL_LOW : "LOW", + BATTERY_LEVEL_MEDIUM : "MEDIUM", + BATTERY_LEVEL_FULL : "FULL"} _last_states = (State(), State(), State(), State()) @@ -140,12 +164,6 @@ def XInputGetBatteryInformation(dwUserIndex, devType, batteryInformation): _last_checked = 0 -DEADZONE_LEFT_THUMB = 0 -DEADZONE_RIGHT_THUMB = 1 -DEADZONE_TRIGGER = 2 - -DEADZONE_DEFAULT = -1 - _deadzones = [{DEADZONE_RIGHT_THUMB : XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE, DEADZONE_LEFT_THUMB : XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, DEADZONE_TRIGGER : XINPUT_GAMEPAD_TRIGGER_THRESHOLD}, @@ -159,6 +177,24 @@ def XInputGetBatteryInformation(dwUserIndex, devType, batteryInformation): DEADZONE_LEFT_THUMB : XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, DEADZONE_TRIGGER : XINPUT_GAMEPAD_TRIGGER_THRESHOLD}] +_button_dict = {0x0001 : "DPAD_UP", + 0x0002 : "DPAD_DOWN", + 0x0004 : "DPAD_LEFT", + 0x0008 : "DPAD_RIGHT", + 0x0010 : "START", + 0x0020 : "BACK", + 0x0040 : "LEFT_THUMB", + 0x0080 : "RIGHT_THUMB", + 0x0100 : "LEFT_SHOULDER", + 0x0200 : "RIGHT_SHOULDER", + 0x1000 : "A", + 0x2000 : "B", + 0x4000 : "X", + 0x8000 : "Y", + } +#/defining file-local variables # + +# defining custom classes and methods # class XInputNotConnectedError(Exception): pass @@ -166,6 +202,13 @@ class XInputBadArgumentError(ValueError): pass def set_deadzone(dzone, value): + """Sets the deadzone to . +Any raw value retruned by the respective stick or trigger +will be clamped to 0 if it's lower than . +The supported deadzones are: +DEADZONE_RIGHT_THUMB (default value is 8689, max is 32767) +DEADZONE_LEFT_THUMB (default value is 7849, max is 32767) +DEADZONE_TRIGGER (default value is 30, max is 255 )""" global XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE, XINPUT_GAMEPAD_TRIGGER_THRESHOLD assert dzone >= 0 and dzone <= 2, "invalid deadzone" @@ -191,6 +234,10 @@ def set_deadzone(dzone, value): else: XINPUT_GAMEPAD_TRIGGER_THRESHOLD = value def get_connected(): + """get_connected() -> (bool, bool, bool, bool) +Returns wether or not the controller at each index is +connected. +You shouldn't check this too frequently.""" state = XINPUT_STATE() out = [False] * 4 for i in range(4): @@ -199,6 +246,8 @@ def get_connected(): return tuple(out) def get_state(user_index): + """get_state(int) -> XINPUT_STATE +Returns the raw state of the controller.""" state = XINPUT_STATE() res = XInputGetState(user_index, state) if res == ERROR_DEVICE_NOT_CONNECTED: @@ -212,11 +261,17 @@ def get_state(user_index): return state def get_battery_information(user_index): + """get_battery_information(int) -> (str, str) +Returns the battery information for controller . +The return value is formatted as (, )""" battery_information = XINPUT_BATTERY_INFORMATION() XInputGetBatteryInformation(user_index, BATTERY_DEVTYPE_GAMEPAD, battery_information) return (_battery_type_dict[battery_information.BatteryType], _battery_level_dict[battery_information.BatteryLevel]) def set_vibration(user_index, left_speed, right_speed): + """Sets the vibration motor speed for controller . +The speed ranges from 0.0 to 1.0 (float values) or +0 to 65535 (int values).""" if type(left_speed) == float and left_speed <= 1.0: left_speed = (round(65535 * left_speed, 0)) @@ -231,6 +286,10 @@ def set_vibration(user_index, left_speed, right_speed): return XInputSetState(user_index, vibration) == 0 def get_button_values(state): + """get_button_values(XINPUT_STATE) -> dict +Returns a dict with string keys and boolean values, +representing the button and it's value respectively. +You can get the required state using get_state()""" wButtons = state.Gamepad.wButtons return {"DPAD_UP" : bool(wButtons & 0x0001), "DPAD_DOWN" : bool(wButtons & 0x0002), @@ -249,6 +308,9 @@ def get_button_values(state): } def get_trigger_values(state): + """get_trigger_values(XINPUT_STATE) -> (float, float) +Returns the normalized left and right trigger values. +You can get the required state using get_state()""" LT = state.Gamepad.bLeftTrigger RT = state.Gamepad.bRightTrigger @@ -270,6 +332,10 @@ def get_trigger_values(state): return (normLT, normRT) def get_thumb_values(state): + """get_thumb_values(XINPUT_STATE) -> ((float, float), (float, float)) +Returns the normalized left and right thumb stick values, +represented as X and Y values. +You can get the required state using get_state()""" LX = state.Gamepad.sThumbLX LY = state.Gamepad.sThumbLY RX = state.Gamepad.sThumbRX @@ -317,31 +383,9 @@ def get_thumb_values(state): return ((normLX * normMagL, normLY * normMagL), (normRX * normMagR, normRY * normMagR)) -_button_dict = {0x0001 : "DPAD_UP", - 0x0002 : "DPAD_DOWN", - 0x0004 : "DPAD_LEFT", - 0x0008 : "DPAD_RIGHT", - 0x0010 : "START", - 0x0020 : "BACK", - 0x0040 : "LEFT_THUMB", - 0x0080 : "RIGHT_THUMB", - 0x0100 : "LEFT_SHOULDER", - 0x0200 : "RIGHT_SHOULDER", - 0x1000 : "A", - 0x2000 : "B", - 0x4000 : "X", - 0x8000 : "Y", - } -EVENT_CONNECTED = 1 -EVENT_DISCONNECTED = 2 -EVENT_BUTTON_PRESSED = 3 -EVENT_BUTTON_RELEASED = 4 -EVENT_TRIGGER_MOVED = 5 -EVENT_STICK_MOVED = 6 -LEFT = 0 -RIGHT = 1 + class Event: def __init__(self, user_index, type_): @@ -352,6 +396,11 @@ def __str__(self): return str(self.__dict__) def get_events(): + """get_events() -> generator +Returns a generator that yields events for each change that +occured since this function was last called. +Each event has a and associated. +The other variables vary.""" global _last_states, _connected, _last_checked, _button_dict, _last_norm_values this_time = time.time() these_states = (State(), State(), State(), State()) @@ -501,316 +550,157 @@ def get_events(): _last_norm_values[3] = out _last_states = these_states - -# Event handler class to be extended to use dynamic events -class GamepadEventsHandler: - def __init__(self, filter = FILTER_NONE): - self.filters = [filter]*4 - def on_button_event(self, event): +class EventHandler: + def __init__(self, *controllers, filter = FILTER_NONE): + self.set_controllers(*controllers) + + self.filter = filter + + def process_button_event(self, event): raise NotImplementedError("Method not implemented. Must be implemented in the child class") - def on_stick_event(self, event): + def process_stick_event(self, event): raise NotImplementedError("Method not implemented. Must be implemented in the child class") - def on_trigger_event(self, event): + def process_trigger_event(self, event): raise NotImplementedError("Method not implemented. Must be implemented in the child class") - def on_connection_event(self, event): + def process_connection_event(self, event): raise NotImplementedError("Method not implemented. Must be implemented in the child class") - # the filter is the sum of the buttons that must be shown - # the list of possible inputs to be filtered is: - # * button values (1,2,4,8, ...) - # * 0x010000 or 65536 for left stick - # * 0x020000 or 131072 for right stick - # * 0x040000 or 262144 for left trigger - # * 0x080000 or 524288 for right trigger - # - # additional values are available to filter only one kind of events wich is only button down and only button down: - # FILTER_DOWN_ONLY - # FILTER_UP_ONLY - # - # example1: add_filter(BUTTON_X + BUTTON_Y + BUTTON_DPAD_UP) will add a filter for all players that allow events only for the X, Y and DPAD_UP buttons - # example2: add_filter(BUTTON_A + FILTER_DOWN_ONLY, [1,3]) will add the filter only for player 1 and 3 and only when the button is pressed down - # NOTE: Controller events are not maskable - def add_filter(self, filter, controller = [0,1,2,3]): - for i in controller: - if(self.filters[i] == FILTER_NONE): - self.filters[i] = filter - else: - self.filters[i] |=filter - - # remove any filter - # the "controller" attribute remove the filter only for the selected controller. By default will remove every filter - def clear_filters(self, controller = [0,1,2,3]): - self.filters[controller] = [] + def add_controller(self, user_index): + """Adds a given controller to the ones that are processed""" + assert 0 <= user_index <= 3, "controllers must have a user_index between 0 and 3" -class GamepadThread: - def __init__(self, events_handlers, auto_start=True): - if not isinstance(events_handlers, list): - events_handlers = [events_handlers] - for ev in events_handlers: - if (ev is None or not issubclass(type(ev), GamepadEventsHandler)): - raise TypeError("The event handler must be a subclass of XInput.GamepadEventsHandler") - self.handlers = events_handlers - self.filters = [FILTER_NONE]*4 # by default none of the input is filtered (masking also up and down filter for buttons) - if auto_start: - self.start_thread() + self.controllers.add(user_index) - def __tfun(self): # thread function - while(self.isRunning): # polling - events = get_events() - for e in events: # filtering events - if e.type == EVENT_CONNECTED or e.type == EVENT_DISCONNECTED: - for h in self.handlers: - h.on_connection_event(e) - elif e.type == EVENT_BUTTON_PRESSED or e.type == EVENT_BUTTON_RELEASED: - for h in self.handlers: - if not((h.filters[e.user_index] & (FILTER_DOWN_ONLY+FILTER_UP_ONLY)) and not(h.filters[e.user_index] & (FILTER_DOWN_ONLY << (e.type - EVENT_BUTTON_PRESSED)))): - if e.button_id & h.filters[e.user_index]: - h.on_button_event(e) - elif e.type == EVENT_TRIGGER_MOVED: - for h in self.handlers: - if (TRIGGER_LEFT << e.trigger) & h.filters[e.user_index]: - h.on_trigger_event(e) - elif e.type == EVENT_STICK_MOVED: - for h in self.handlers: - if (STICK_LEFT << e.stick) & h.filters[e.user_index]: - h.on_stick_event(e) - else: - raise ValueError("Event type not recognized") - + def set_controllers(self, *controllers): + """Sets the controllers that are processed""" + if not controllers: + raise ValueError("You need to specify at least one controller") + + for user_index in controllers: + assert 0 <= user_index <= 3, "controllers must have a user_index between 0 and 3" - def start_thread(self): # starts the thread - self.isRunning = True - if(not hasattr(self,"__t")): - self.__t = Thread(target=self.__tfun, args=()) - self.__t.daemon = True - self.__t.start() + self.controllers = set(controllers) - def stop_thread(self): # stops the thread - self.isRunning = False - - def add_event_handler(self, event_handler): - if (event_handler is None or not issubclass(type(event_handler), GamepadEventsHandler)): - raise TypeError("The event handler must be a subclass of XInput.GamepadEventsHandler") - self.handlers.append(event_handler) + def remove_controller(self, user_index): + """Removes a given controller from the ones that are processed""" + assert 0 <= user_index <= 3, "controllers must have a user_index between 0 and 3" - def remove_event_handler(self, event_handler): + assert len(self.controllers) >= 2, "you have to keep at least one controller" + try: - self.handlers.remove(event_handler) + self.controllers.remove(user_index) return True - except ValueError: + except KeyError: return False + def has_controller(self, user_index): + """Checks, wether or not this handler handles controller """ + assert 0 <= user_index <= 3, "controllers must have a user_index between 0 and 3" - -if __name__ == "__main__": - try: - import tkinter as tk - except ImportError: - import Tkinter as tk - - root = tk.Tk() - root.title("XInput") - canvas = tk.Canvas(root, width= 600, height = 400, bg="white") - canvas.pack() + return user_index in self.controllers - set_deadzone(DEADZONE_TRIGGER,10) - - class Controller: - def __init__(self, center): - self.center = center + def set_filter(self, filter_): + """Applies a new filter mask to this handler. +A filter can be any combination of filters, such as +(BUTTON_A | BUTTON_B) to only get events for buttons A and B or +(FILTER_RELEASED_ONLY | BUTTON_Y) to get an event when Y is released.""" + self.filter = filter_ + + # remove any filter + # the "controller" attribute remove the filter only for the selected controller. By default will remove every filter + def clear_filter(self): + """Removes all filters""" + self.filter = FILTER_NONE - self.on_indicator_pos = (self.center[0], self.center[1] - 50) - self.on_indicator = canvas.create_oval(((self.on_indicator_pos[0] - 10, self.on_indicator_pos[1] - 10), (self.on_indicator_pos[0] + 10, self.on_indicator_pos[1] + 10))) +class GamepadThread: + def __init__(self, *event_handlers, auto_start=True): + for event_handler in event_handlers: + if (event_handler is None or not issubclass(type(event_handler), EventHandler)): + raise TypeError("The event handler must be a subclass of XInput.EventHandler") - self.r_thumb_pos = (self.center[0] + 50, self.center[1] + 20) - - r_thumb_outline = canvas.create_oval(((self.r_thumb_pos[0] - 25, self.r_thumb_pos[1] - 25), (self.r_thumb_pos[0] + 25, self.r_thumb_pos[1] + 25))) - - r_thumb_stick_pos = self.r_thumb_pos - - self.r_thumb_stick = canvas.create_oval(((r_thumb_stick_pos[0] - 10, r_thumb_stick_pos[1] - 10), (r_thumb_stick_pos[0] + 10, r_thumb_stick_pos[1] + 10))) - - self.l_thumb_pos = (self.center[0] - 100, self.center[1] - 20) - - l_thumb_outline = canvas.create_oval(((self.l_thumb_pos[0] - 25, self.l_thumb_pos[1] - 25), (self.l_thumb_pos[0] + 25, self.l_thumb_pos[1] + 25))) - - l_thumb_stick_pos = self.l_thumb_pos - - self.l_thumb_stick = canvas.create_oval(((l_thumb_stick_pos[0] - 10, l_thumb_stick_pos[1] - 10), (l_thumb_stick_pos[0] + 10, l_thumb_stick_pos[1] + 10))) - - self.l_trigger_pos = (self.center[0] - 120, self.center[1] - 70) - - l_trigger_outline = canvas.create_rectangle(((self.l_trigger_pos[0] - 5, self.l_trigger_pos[1] - 20), (self.l_trigger_pos[0] + 5, self.l_trigger_pos[1] + 20))) - - l_trigger_index_pos = (self.l_trigger_pos[0], self.l_trigger_pos[1] - 20) - - self.l_trigger_index = canvas.create_rectangle(((l_trigger_index_pos[0] - 10, l_trigger_index_pos[1] - 5), (l_trigger_index_pos[0] + 10, l_trigger_index_pos[1] + 5))) - - self.r_trigger_pos = (self.center[0] + 120, self.center[1] - 70) - - r_trigger_outline = canvas.create_rectangle(((self.r_trigger_pos[0] - 5, self.r_trigger_pos[1] - 20), (self.r_trigger_pos[0] + 5, self.r_trigger_pos[1] + 20))) - - r_trigger_index_pos = (self.r_trigger_pos[0], self.r_trigger_pos[1] - 20) - - self.r_trigger_index = canvas.create_rectangle(((r_trigger_index_pos[0] - 10, r_trigger_index_pos[1] - 5), (r_trigger_index_pos[0] + 10, r_trigger_index_pos[1] + 5))) - - buttons_pos = (self.center[0] + 100, self.center[1] - 20) - - A_button_pos = (buttons_pos[0], buttons_pos[1] + 20) - - B_button_pos = (buttons_pos[0] + 20, buttons_pos[1]) - - Y_button_pos = (buttons_pos[0], buttons_pos[1] - 20) - - X_button_pos = (buttons_pos[0] - 20, buttons_pos[1]) - - self.A_button = canvas.create_oval(((A_button_pos[0] - 10, A_button_pos[1] - 10), (A_button_pos[0] + 10, A_button_pos[1] + 10))) - - self.B_button = canvas.create_oval(((B_button_pos[0] - 10, B_button_pos[1] - 10), (B_button_pos[0] + 10, B_button_pos[1] + 10))) + self.handlers = set(event_handlers) - self.Y_button = canvas.create_oval(((Y_button_pos[0] - 10, Y_button_pos[1] - 10), (Y_button_pos[0] + 10, Y_button_pos[1] + 10))) + self.lock = Lock() - self.X_button = canvas.create_oval(((X_button_pos[0] - 10, X_button_pos[1] - 10), (X_button_pos[0] + 10, X_button_pos[1] + 10))) - - dpad_pos = (self.center[0] - 50, self.center[1] + 20) - - self.dpad_left = canvas.create_rectangle(((dpad_pos[0] - 30, dpad_pos[1] - 10), (dpad_pos[0] - 10, dpad_pos[1] + 10)), outline = "") - - self.dpad_up = canvas.create_rectangle(((dpad_pos[0] - 10, dpad_pos[1] - 30), (dpad_pos[0] + 10, dpad_pos[1] - 10)), outline = "") - - self.dpad_right = canvas.create_rectangle(((dpad_pos[0] + 10, dpad_pos[1] - 10), (dpad_pos[0] + 30, dpad_pos[1] + 10)), outline = "") - - self.dpad_down = canvas.create_rectangle(((dpad_pos[0] - 10, dpad_pos[1] + 10), (dpad_pos[0] + 10, dpad_pos[1] + 30)), outline = "") - - dpad_outline = canvas.create_polygon(((dpad_pos[0] - 30, dpad_pos[1] - 10), (dpad_pos[0] - 10, dpad_pos[1] - 10), (dpad_pos[0] - 10, dpad_pos[1] - 30), (dpad_pos[0] + 10, dpad_pos[1] - 30), - (dpad_pos[0] + 10, dpad_pos[1] - 10), (dpad_pos[0] + 30, dpad_pos[1] - 10), (dpad_pos[0] + 30, dpad_pos[1] + 10), (dpad_pos[0] + 10, dpad_pos[1] + 10), - (dpad_pos[0] + 10, dpad_pos[1] + 30), (dpad_pos[0] - 10, dpad_pos[1] + 30), (dpad_pos[0] - 10, dpad_pos[1] + 10), (dpad_pos[0] - 30, dpad_pos[1] + 10)), - fill = "", outline = "black") - - back_button_pos = (self.center[0] - 20, self.center[1] - 20) - - self.back_button = canvas.create_oval(((back_button_pos[0] - 5, back_button_pos[1] - 5), (back_button_pos[0] + 5, back_button_pos[1] + 5))) - - start_button_pos = (self.center[0] + 20, self.center[1] - 20) - - self.start_button = canvas.create_oval(((start_button_pos[0] - 5, start_button_pos[1] - 5), (start_button_pos[0] + 5, start_button_pos[1] + 5))) - - l_shoulder_pos = (self.center[0] - 90, self.center[1] - 70) - - self.l_shoulder = canvas.create_rectangle(((l_shoulder_pos[0] - 20, l_shoulder_pos[1] - 5), (l_shoulder_pos[0] + 20, l_shoulder_pos[1] + 10))) + self.queued_new_handlers = [] + self.queued_removed_handlers = [] + + if auto_start: + self.start() - r_shoulder_pos = (self.center[0] + 90, self.center[1] - 70) + def __tfun(self): # thread function + while(self.running): # polling + self.lock.acquire() + for new_handler in self.queued_new_handlers: + self.handlers.add(new_handler) + + for removed_handler in self.queued_removed_handlers: + if removed_handler in self.handlers: + self.handlers.remove(removed_handler) + self.queued_new_handlers.clear() + self.queued_removed_handlers.clear() + self.lock.release() + + events = get_events() + for event in events: # filtering events + if event.type == EVENT_CONNECTED or event.type == EVENT_DISCONNECTED: + for handler in self.handlers: + if handler.has_controller(event.user_index): + handler.process_connection_event(event) + + elif event.type == EVENT_BUTTON_PRESSED or event.type == EVENT_BUTTON_RELEASED: + for handler in self.handlers: + if handler.has_controller(event.user_index): + if not((handler.filter & (FILTER_PRESSED_ONLY+FILTER_RELEASED_ONLY)) and not(handler.filter & (FILTER_PRESSED_ONLY << (event.type - EVENT_BUTTON_PRESSED)))): + if event.button_id & handler.filter: + handler.process_button_event(event) + elif event.type == EVENT_TRIGGER_MOVED: + for handler in self.handlers: + if handler.has_controller(event.user_index): + if (TRIGGER_LEFT << event.trigger) & handler.filter: + handler.process_trigger_event(event) + elif event.type == EVENT_STICK_MOVED: + for handler in self.handlers: + if handler.has_controller(event.user_index): + if (STICK_LEFT << event.stick) & handler.filter: + handler.process_stick_event(event) + else: + raise ValueError("Event type not recognized") + - self.r_shoulder = canvas.create_rectangle(((r_shoulder_pos[0] - 20, r_shoulder_pos[1] - 10), (r_shoulder_pos[0] + 20, r_shoulder_pos[1] + 5))) + def start(self): # starts the thread + self.running = True + if(not hasattr(self,"__thread")): + self.__thread = Thread(target=self.__tfun, args=()) + self.__thread.daemon = True + self.__thread.start() - controllers = (Controller((150., 100.)), - Controller((450., 100.)), - Controller((150., 300.)), - Controller((450., 300.))) + def stop(self): # stops the thread + self.running = False + self.__thread.join() + + def add_event_handler(self, event_handler): + if (event_handler is None or not issubclass(type(event_handler), EventHandler)): + raise TypeError("The event handler must be a subclass of XInput.EventHandler") + self.lock.acquire() + self.queued_new_handlers.append(event_handler) + self.lock.release() - while 1: - events = get_events() - for event in events: - controller = controllers[event.user_index] - if event.type == EVENT_CONNECTED: - canvas.itemconfig(controller.on_indicator, fill="light green") - - elif event.type == EVENT_DISCONNECTED: - canvas.itemconfig(controller.on_indicator, fill="") - - elif event.type == EVENT_STICK_MOVED: - if event.stick == LEFT: - l_thumb_stick_pos = (int(round(controller.l_thumb_pos[0] + 25 * event.x,0)), int(round(controller.l_thumb_pos[1] - 25 * event.y,0))) - canvas.coords(controller.l_thumb_stick, (l_thumb_stick_pos[0] - 10, l_thumb_stick_pos[1] - 10, l_thumb_stick_pos[0] + 10, l_thumb_stick_pos[1] + 10)) - - elif event.stick == RIGHT: - r_thumb_stick_pos = (int(round(controller.r_thumb_pos[0] + 25 * event.x,0)), int(round(controller.r_thumb_pos[1] - 25 * event.y,0))) - canvas.coords(controller.r_thumb_stick, (r_thumb_stick_pos[0] - 10, r_thumb_stick_pos[1] - 10, r_thumb_stick_pos[0] + 10, r_thumb_stick_pos[1] + 10)) - - elif event.type == EVENT_TRIGGER_MOVED: - if event.trigger == LEFT: - l_trigger_index_pos = (controller.l_trigger_pos[0], controller.l_trigger_pos[1] - 20 + int(round(40 * event.value, 0))) - canvas.coords(controller.l_trigger_index, (l_trigger_index_pos[0] - 10, l_trigger_index_pos[1] - 5, l_trigger_index_pos[0] + 10, l_trigger_index_pos[1] + 5)) - elif event.trigger == RIGHT: - r_trigger_index_pos = (controller.r_trigger_pos[0], controller.r_trigger_pos[1] - 20 + int(round(40 * event.value, 0))) - canvas.coords(controller.r_trigger_index, (r_trigger_index_pos[0] - 10, r_trigger_index_pos[1] - 5, r_trigger_index_pos[0] + 10, r_trigger_index_pos[1] + 5)) - - elif event.type == EVENT_BUTTON_PRESSED: - if event.button == "LEFT_THUMB": - canvas.itemconfig(controller.l_thumb_stick, fill="red") - elif event.button == "RIGHT_THUMB": - canvas.itemconfig(controller.r_thumb_stick, fill="red") - - elif event.button == "LEFT_SHOULDER": - canvas.itemconfig(controller.l_shoulder, fill="red") - elif event.button == "RIGHT_SHOULDER": - canvas.itemconfig(controller.r_shoulder, fill="red") - - elif event.button == "BACK": - canvas.itemconfig(controller.back_button, fill="red") - elif event.button == "START": - canvas.itemconfig(controller.start_button, fill="red") - - elif event.button == "DPAD_LEFT": - canvas.itemconfig(controller.dpad_left, fill="red") - elif event.button == "DPAD_RIGHT": - canvas.itemconfig(controller.dpad_right, fill="red") - elif event.button == "DPAD_UP": - canvas.itemconfig(controller.dpad_up, fill="red") - elif event.button == "DPAD_DOWN": - canvas.itemconfig(controller.dpad_down, fill="red") - - elif event.button == "A": - canvas.itemconfig(controller.A_button, fill="red") - elif event.button == "B": - canvas.itemconfig(controller.B_button, fill="red") - elif event.button == "Y": - canvas.itemconfig(controller.Y_button, fill="red") - elif event.button == "X": - canvas.itemconfig(controller.X_button, fill="red") - - elif event.type == EVENT_BUTTON_RELEASED: - if event.button == "LEFT_THUMB": - canvas.itemconfig(controller.l_thumb_stick, fill="") - elif event.button == "RIGHT_THUMB": - canvas.itemconfig(controller.r_thumb_stick, fill="") - - elif event.button == "LEFT_SHOULDER": - canvas.itemconfig(controller.l_shoulder, fill="") - elif event.button == "RIGHT_SHOULDER": - canvas.itemconfig(controller.r_shoulder, fill="") - - elif event.button == "BACK": - canvas.itemconfig(controller.back_button, fill="") - elif event.button == "START": - canvas.itemconfig(controller.start_button, fill="") - - elif event.button == "DPAD_LEFT": - canvas.itemconfig(controller.dpad_left, fill="") - elif event.button == "DPAD_RIGHT": - canvas.itemconfig(controller.dpad_right, fill="") - elif event.button == "DPAD_UP": - canvas.itemconfig(controller.dpad_up, fill="") - elif event.button == "DPAD_DOWN": - canvas.itemconfig(controller.dpad_down, fill="") - - elif event.button == "A": - canvas.itemconfig(controller.A_button, fill="") - elif event.button == "B": - canvas.itemconfig(controller.B_button, fill="") - elif event.button == "Y": - canvas.itemconfig(controller.Y_button, fill="") - elif event.button == "X": - canvas.itemconfig(controller.X_button, fill="") - - try: - root.update() - except tk.TclError: - break + def remove_event_handler(self, event_handler): + if (event_handler is None or not issubclass(type(event_handler), EventHandler)): + raise TypeError("The event handler must be a subclass of XInput.EventHandler") + self.lock.acquire() + self.queued_removed_handlers.append(event_handler) + self.lock.release() + + def __del__(self): + if hasattr(self, "__thread"): + self.stop() +#/defining custom classes and methods # diff --git a/XInputTest.py b/XInputTest.py new file mode 100644 index 0000000..316c0a8 --- /dev/null +++ b/XInputTest.py @@ -0,0 +1,207 @@ +from XInput import * + +try: + import tkinter as tk +except ImportError: + import Tkinter as tk + +root = tk.Tk() +root.title("XInput") +canvas = tk.Canvas(root, width= 600, height = 400, bg="white") +canvas.pack() + +set_deadzone(DEADZONE_TRIGGER,10) + +class Controller: + def __init__(self, center): + self.center = center + + self.on_indicator_pos = (self.center[0], self.center[1] - 50) + + self.on_indicator = canvas.create_oval(((self.on_indicator_pos[0] - 10, self.on_indicator_pos[1] - 10), (self.on_indicator_pos[0] + 10, self.on_indicator_pos[1] + 10))) + + self.r_thumb_pos = (self.center[0] + 50, self.center[1] + 20) + + r_thumb_outline = canvas.create_oval(((self.r_thumb_pos[0] - 25, self.r_thumb_pos[1] - 25), (self.r_thumb_pos[0] + 25, self.r_thumb_pos[1] + 25))) + + r_thumb_stick_pos = self.r_thumb_pos + + self.r_thumb_stick = canvas.create_oval(((r_thumb_stick_pos[0] - 10, r_thumb_stick_pos[1] - 10), (r_thumb_stick_pos[0] + 10, r_thumb_stick_pos[1] + 10))) + + self.l_thumb_pos = (self.center[0] - 100, self.center[1] - 20) + + l_thumb_outline = canvas.create_oval(((self.l_thumb_pos[0] - 25, self.l_thumb_pos[1] - 25), (self.l_thumb_pos[0] + 25, self.l_thumb_pos[1] + 25))) + + l_thumb_stick_pos = self.l_thumb_pos + + self.l_thumb_stick = canvas.create_oval(((l_thumb_stick_pos[0] - 10, l_thumb_stick_pos[1] - 10), (l_thumb_stick_pos[0] + 10, l_thumb_stick_pos[1] + 10))) + + self.l_trigger_pos = (self.center[0] - 120, self.center[1] - 70) + + l_trigger_outline = canvas.create_rectangle(((self.l_trigger_pos[0] - 5, self.l_trigger_pos[1] - 20), (self.l_trigger_pos[0] + 5, self.l_trigger_pos[1] + 20))) + + l_trigger_index_pos = (self.l_trigger_pos[0], self.l_trigger_pos[1] - 20) + + self.l_trigger_index = canvas.create_rectangle(((l_trigger_index_pos[0] - 10, l_trigger_index_pos[1] - 5), (l_trigger_index_pos[0] + 10, l_trigger_index_pos[1] + 5))) + + self.r_trigger_pos = (self.center[0] + 120, self.center[1] - 70) + + r_trigger_outline = canvas.create_rectangle(((self.r_trigger_pos[0] - 5, self.r_trigger_pos[1] - 20), (self.r_trigger_pos[0] + 5, self.r_trigger_pos[1] + 20))) + + r_trigger_index_pos = (self.r_trigger_pos[0], self.r_trigger_pos[1] - 20) + + self.r_trigger_index = canvas.create_rectangle(((r_trigger_index_pos[0] - 10, r_trigger_index_pos[1] - 5), (r_trigger_index_pos[0] + 10, r_trigger_index_pos[1] + 5))) + + buttons_pos = (self.center[0] + 100, self.center[1] - 20) + + A_button_pos = (buttons_pos[0], buttons_pos[1] + 20) + + B_button_pos = (buttons_pos[0] + 20, buttons_pos[1]) + + Y_button_pos = (buttons_pos[0], buttons_pos[1] - 20) + + X_button_pos = (buttons_pos[0] - 20, buttons_pos[1]) + + self.A_button = canvas.create_oval(((A_button_pos[0] - 10, A_button_pos[1] - 10), (A_button_pos[0] + 10, A_button_pos[1] + 10))) + + self.B_button = canvas.create_oval(((B_button_pos[0] - 10, B_button_pos[1] - 10), (B_button_pos[0] + 10, B_button_pos[1] + 10))) + + self.Y_button = canvas.create_oval(((Y_button_pos[0] - 10, Y_button_pos[1] - 10), (Y_button_pos[0] + 10, Y_button_pos[1] + 10))) + + self.X_button = canvas.create_oval(((X_button_pos[0] - 10, X_button_pos[1] - 10), (X_button_pos[0] + 10, X_button_pos[1] + 10))) + + dpad_pos = (self.center[0] - 50, self.center[1] + 20) + + self.dpad_left = canvas.create_rectangle(((dpad_pos[0] - 30, dpad_pos[1] - 10), (dpad_pos[0] - 10, dpad_pos[1] + 10)), outline = "") + + self.dpad_up = canvas.create_rectangle(((dpad_pos[0] - 10, dpad_pos[1] - 30), (dpad_pos[0] + 10, dpad_pos[1] - 10)), outline = "") + + self.dpad_right = canvas.create_rectangle(((dpad_pos[0] + 10, dpad_pos[1] - 10), (dpad_pos[0] + 30, dpad_pos[1] + 10)), outline = "") + + self.dpad_down = canvas.create_rectangle(((dpad_pos[0] - 10, dpad_pos[1] + 10), (dpad_pos[0] + 10, dpad_pos[1] + 30)), outline = "") + + dpad_outline = canvas.create_polygon(((dpad_pos[0] - 30, dpad_pos[1] - 10), (dpad_pos[0] - 10, dpad_pos[1] - 10), (dpad_pos[0] - 10, dpad_pos[1] - 30), (dpad_pos[0] + 10, dpad_pos[1] - 30), + (dpad_pos[0] + 10, dpad_pos[1] - 10), (dpad_pos[0] + 30, dpad_pos[1] - 10), (dpad_pos[0] + 30, dpad_pos[1] + 10), (dpad_pos[0] + 10, dpad_pos[1] + 10), + (dpad_pos[0] + 10, dpad_pos[1] + 30), (dpad_pos[0] - 10, dpad_pos[1] + 30), (dpad_pos[0] - 10, dpad_pos[1] + 10), (dpad_pos[0] - 30, dpad_pos[1] + 10)), + fill = "", outline = "black") + + back_button_pos = (self.center[0] - 20, self.center[1] - 20) + + self.back_button = canvas.create_oval(((back_button_pos[0] - 5, back_button_pos[1] - 5), (back_button_pos[0] + 5, back_button_pos[1] + 5))) + + start_button_pos = (self.center[0] + 20, self.center[1] - 20) + + self.start_button = canvas.create_oval(((start_button_pos[0] - 5, start_button_pos[1] - 5), (start_button_pos[0] + 5, start_button_pos[1] + 5))) + + l_shoulder_pos = (self.center[0] - 90, self.center[1] - 70) + + self.l_shoulder = canvas.create_rectangle(((l_shoulder_pos[0] - 20, l_shoulder_pos[1] - 5), (l_shoulder_pos[0] + 20, l_shoulder_pos[1] + 10))) + + r_shoulder_pos = (self.center[0] + 90, self.center[1] - 70) + + self.r_shoulder = canvas.create_rectangle(((r_shoulder_pos[0] - 20, r_shoulder_pos[1] - 10), (r_shoulder_pos[0] + 20, r_shoulder_pos[1] + 5))) + +controllers = (Controller((150., 100.)), + Controller((450., 100.)), + Controller((150., 300.)), + Controller((450., 300.))) + +while 1: + events = get_events() + for event in events: + controller = controllers[event.user_index] + if event.type == EVENT_CONNECTED: + canvas.itemconfig(controller.on_indicator, fill="light green") + + elif event.type == EVENT_DISCONNECTED: + canvas.itemconfig(controller.on_indicator, fill="") + + elif event.type == EVENT_STICK_MOVED: + if event.stick == LEFT: + l_thumb_stick_pos = (int(round(controller.l_thumb_pos[0] + 25 * event.x,0)), int(round(controller.l_thumb_pos[1] - 25 * event.y,0))) + canvas.coords(controller.l_thumb_stick, (l_thumb_stick_pos[0] - 10, l_thumb_stick_pos[1] - 10, l_thumb_stick_pos[0] + 10, l_thumb_stick_pos[1] + 10)) + + elif event.stick == RIGHT: + r_thumb_stick_pos = (int(round(controller.r_thumb_pos[0] + 25 * event.x,0)), int(round(controller.r_thumb_pos[1] - 25 * event.y,0))) + canvas.coords(controller.r_thumb_stick, (r_thumb_stick_pos[0] - 10, r_thumb_stick_pos[1] - 10, r_thumb_stick_pos[0] + 10, r_thumb_stick_pos[1] + 10)) + + elif event.type == EVENT_TRIGGER_MOVED: + if event.trigger == LEFT: + l_trigger_index_pos = (controller.l_trigger_pos[0], controller.l_trigger_pos[1] - 20 + int(round(40 * event.value, 0))) + canvas.coords(controller.l_trigger_index, (l_trigger_index_pos[0] - 10, l_trigger_index_pos[1] - 5, l_trigger_index_pos[0] + 10, l_trigger_index_pos[1] + 5)) + elif event.trigger == RIGHT: + r_trigger_index_pos = (controller.r_trigger_pos[0], controller.r_trigger_pos[1] - 20 + int(round(40 * event.value, 0))) + canvas.coords(controller.r_trigger_index, (r_trigger_index_pos[0] - 10, r_trigger_index_pos[1] - 5, r_trigger_index_pos[0] + 10, r_trigger_index_pos[1] + 5)) + + elif event.type == EVENT_BUTTON_PRESSED: + if event.button == "LEFT_THUMB": + canvas.itemconfig(controller.l_thumb_stick, fill="red") + elif event.button == "RIGHT_THUMB": + canvas.itemconfig(controller.r_thumb_stick, fill="red") + + elif event.button == "LEFT_SHOULDER": + canvas.itemconfig(controller.l_shoulder, fill="red") + elif event.button == "RIGHT_SHOULDER": + canvas.itemconfig(controller.r_shoulder, fill="red") + + elif event.button == "BACK": + canvas.itemconfig(controller.back_button, fill="red") + elif event.button == "START": + canvas.itemconfig(controller.start_button, fill="red") + + elif event.button == "DPAD_LEFT": + canvas.itemconfig(controller.dpad_left, fill="red") + elif event.button == "DPAD_RIGHT": + canvas.itemconfig(controller.dpad_right, fill="red") + elif event.button == "DPAD_UP": + canvas.itemconfig(controller.dpad_up, fill="red") + elif event.button == "DPAD_DOWN": + canvas.itemconfig(controller.dpad_down, fill="red") + + elif event.button == "A": + canvas.itemconfig(controller.A_button, fill="red") + elif event.button == "B": + canvas.itemconfig(controller.B_button, fill="red") + elif event.button == "Y": + canvas.itemconfig(controller.Y_button, fill="red") + elif event.button == "X": + canvas.itemconfig(controller.X_button, fill="red") + + elif event.type == EVENT_BUTTON_RELEASED: + if event.button == "LEFT_THUMB": + canvas.itemconfig(controller.l_thumb_stick, fill="") + elif event.button == "RIGHT_THUMB": + canvas.itemconfig(controller.r_thumb_stick, fill="") + + elif event.button == "LEFT_SHOULDER": + canvas.itemconfig(controller.l_shoulder, fill="") + elif event.button == "RIGHT_SHOULDER": + canvas.itemconfig(controller.r_shoulder, fill="") + + elif event.button == "BACK": + canvas.itemconfig(controller.back_button, fill="") + elif event.button == "START": + canvas.itemconfig(controller.start_button, fill="") + + elif event.button == "DPAD_LEFT": + canvas.itemconfig(controller.dpad_left, fill="") + elif event.button == "DPAD_RIGHT": + canvas.itemconfig(controller.dpad_right, fill="") + elif event.button == "DPAD_UP": + canvas.itemconfig(controller.dpad_up, fill="") + elif event.button == "DPAD_DOWN": + canvas.itemconfig(controller.dpad_down, fill="") + + elif event.button == "A": + canvas.itemconfig(controller.A_button, fill="") + elif event.button == "B": + canvas.itemconfig(controller.B_button, fill="") + elif event.button == "Y": + canvas.itemconfig(controller.Y_button, fill="") + elif event.button == "X": + canvas.itemconfig(controller.X_button, fill="") + + try: + root.update() + except tk.TclError: + break diff --git a/XInputThreadTest.py b/XInputThreadTest.py index cb6841f..b99d464 100644 --- a/XInputThreadTest.py +++ b/XInputThreadTest.py @@ -114,14 +114,9 @@ def __init__(self, center): # Create the handler and set the events functions - class MyHandler(GamepadEventsHandler): - def __init__(self, controllers, canvas, filter=FILTER_NONE): - super().__init__(filter) - self.controllers = controllers - self.canvas = canvas - - def on_button_event(self,event): - controller = self.controllers[event.user_index] + class MyHandler(EventHandler): + def process_button_event(self,event): + controller = controllers[event.user_index] fill_color = "" if event.type == EVENT_BUTTON_PRESSED: fill_color = "red" @@ -159,57 +154,57 @@ def on_button_event(self,event): elif event.button == "X": canvas.itemconfig(controller.X_button, fill=fill_color) - def on_stick_event(self, event): - controller = self.controllers[event.user_index] + def process_stick_event(self, event): + controller = controllers[event.user_index] if event.stick == LEFT: l_thumb_stick_pos = (int(round(controller.l_thumb_pos[0] + 25 * event.x,0)), int(round(controller.l_thumb_pos[1] - 25 * event.y,0))) - self.canvas.coords(controller.l_thumb_stick, (l_thumb_stick_pos[0] - 10, l_thumb_stick_pos[1] - 10, l_thumb_stick_pos[0] + 10, l_thumb_stick_pos[1] + 10)) + canvas.coords(controller.l_thumb_stick, (l_thumb_stick_pos[0] - 10, l_thumb_stick_pos[1] - 10, l_thumb_stick_pos[0] + 10, l_thumb_stick_pos[1] + 10)) elif event.stick == RIGHT: r_thumb_stick_pos = (int(round(controller.r_thumb_pos[0] + 25 * event.x,0)), int(round(controller.r_thumb_pos[1] - 25 * event.y,0))) - self.canvas.coords(controller.r_thumb_stick, (r_thumb_stick_pos[0] - 10, r_thumb_stick_pos[1] - 10, r_thumb_stick_pos[0] + 10, r_thumb_stick_pos[1] + 10)) + canvas.coords(controller.r_thumb_stick, (r_thumb_stick_pos[0] - 10, r_thumb_stick_pos[1] - 10, r_thumb_stick_pos[0] + 10, r_thumb_stick_pos[1] + 10)) - def on_trigger_event(self, event): - controller = self.controllers[event.user_index] + def process_trigger_event(self, event): + controller = controllers[event.user_index] if event.trigger == LEFT: l_trigger_index_pos = (controller.l_trigger_pos[0], controller.l_trigger_pos[1] - 20 + int(round(40 * event.value, 0))) - self.canvas.coords(controller.l_trigger_index, (l_trigger_index_pos[0] - 10, l_trigger_index_pos[1] - 5, l_trigger_index_pos[0] + 10, l_trigger_index_pos[1] + 5)) + canvas.coords(controller.l_trigger_index, (l_trigger_index_pos[0] - 10, l_trigger_index_pos[1] - 5, l_trigger_index_pos[0] + 10, l_trigger_index_pos[1] + 5)) elif event.trigger == RIGHT: r_trigger_index_pos = (controller.r_trigger_pos[0], controller.r_trigger_pos[1] - 20 + int(round(40 * event.value, 0))) - self.canvas.coords(controller.r_trigger_index, (r_trigger_index_pos[0] - 10, r_trigger_index_pos[1] - 5, r_trigger_index_pos[0] + 10, r_trigger_index_pos[1] + 5)) + canvas.coords(controller.r_trigger_index, (r_trigger_index_pos[0] - 10, r_trigger_index_pos[1] - 5, r_trigger_index_pos[0] + 10, r_trigger_index_pos[1] + 5)) - def on_connection_event(self, event): - controller = self.controllers[event.user_index] + def process_connection_event(self, event): + controller = controllers[event.user_index] if event.type == EVENT_CONNECTED: - self.canvas.itemconfig(controller.on_indicator, fill="light green") + canvas.itemconfig(controller.on_indicator, fill="light green") elif event.type == EVENT_DISCONNECTED: - self.canvas.itemconfig(controller.on_indicator, fill="") + canvas.itemconfig(controller.on_indicator, fill="") else: print("Unrecognized controller event type") - class MyOtherHandler(GamepadEventsHandler): - def __init__(self): - super().__init__(BUTTON_A+FILTER_DOWN_ONLY) + class MyOtherHandler(EventHandler): + def __init__(self, *controllers): + super().__init__(*controllers, filter=BUTTON_A+FILTER_PRESSED_ONLY) - def on_button_event(self, event): + def process_button_event(self, event): print("Pressed button A") - def on_stick_event(self, event): + def process_stick_event(self, event): pass - def on_trigger_event(self, event): + def process_trigger_event(self, event): print("Trigger LEFT") - def on_connection_event(self, event): + def process_connection_event(self, event): pass - handler = MyHandler(controllers, canvas) # initialize handler object + handler = MyHandler(0, 1, 2, 3) # initialize handler object thread = GamepadThread(handler) # initialize controller thread - handler2 = MyOtherHandler() + handler2 = MyOtherHandler(1) thread.add_event_handler(handler2) # add another handler - handler2.add_filter(TRIGGER_LEFT) + handler2.set_filter(handler2.filter+TRIGGER_LEFT) # filters examples # handler.add_filter(BUTTON_A + BUTTON_B + STICK_LEFT + FILTER_DOWN_ONLY + TRIGGER_RIGHT + BUTTON_START) @@ -218,7 +213,9 @@ def on_connection_event(self, event): root.mainloop() # run UI loop + thread.stop() + # can run other stuff here else: - raise ImportError("This is not a module. Import XInput only") \ No newline at end of file + raise ImportError("This is not a module. Import XInput only")