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/README.md b/README.md index 8d6c232..db306cf 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,88 @@ 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 `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 "pressed\-only" and "released\-only" filter is available for buttons\. +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_PRESSED_ONLY + FILTER_RELEASED_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_PRESSED_ONLY + + +The filter can be applied using add\_filter: + + + handler.add_filter(filter) + + +**Example** + + class MyHandler(EventHandler): + def process_button_event(self, event): + # put here the code to parse every event related only to the buttons + + def process_trigger_event(self, event): + # event reserved for the two triggers + + def process_stick_event(self, event): + # event reserved for the two sticks + + 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_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()` and `stop()` ### 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 `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 e5283a6..319651d 100644 --- a/README.rml +++ b/README.rml @@ -92,5 +92,69 @@ 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]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 "pressed-only" and "released-only" filter is available for buttons. +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_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_PRESSED_ONLY[/code] + +The filter can be applied using add_filter: + +[code]handler.add_filter(filter)[/code] + +[b]Example[/] +[code]class MyHandler(EventHandler): + def process_button_event(self, event): + # put here the code to parse every event related only to the buttons + + def process_trigger_event(self, event): + # event reserved for the two triggers + + def process_stick_event(self, event): + # event reserved for the two sticks + + 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_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()[/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. \ 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 3be7836..eb40773 100644 --- a/README.rst +++ b/README.rst @@ -131,6 +131,96 @@ 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:`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 \"pressed\-only\" and \"released\-only\" filter is available for buttons\. +| 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_PRESSED_ONLY + FILTER_RELEASED_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_PRESSED_ONLY + + +| +| The filter can be applied using add\_filter\: +| + + +:: + + handler.add_filter(filter) + + +| +| **Example** + + +:: + + class MyHandler(EventHandler): + def process_button_event(self, event): + # put here the code to parse every event related only to the buttons + + def process_trigger_event(self, event): + # event reserved for the two triggers + + def process_stick_event(self, event): + # event reserved for the two sticks + + 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_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()` and :code:`stop()` +| + 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:`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 d027b67..9c08def 100644 --- a/XInput.py +++ b/XInput.py @@ -6,6 +6,10 @@ import time +from threading import Thread, Lock + + +# loading the DLL # XINPUT_DLL_NAMES = ( "XInput1_4.dll", "XInput9_1_0.dll", @@ -25,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 @@ -49,17 +56,48 @@ BATTERY_LEVEL_MEDIUM = 0x02 BATTERY_LEVEL_FULL = 0x03 -_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"} - +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_PRESSED_ONLY = 0x100000 +FILTER_RELEASED_ONLY = 0x200000 +FILTER_NONE = 0xffffff-FILTER_PRESSED_ONLY-FILTER_RELEASED_ONLY + +DEADZONE_LEFT_THUMB = 0 +DEADZONE_RIGHT_THUMB = 1 +DEADZONE_TRIGGER = 2 + +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), @@ -104,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()) @@ -114,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}, @@ -133,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 @@ -140,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" @@ -165,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): @@ -173,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: @@ -186,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)) @@ -205,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), @@ -223,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 @@ -244,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 @@ -291,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_): @@ -326,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()) @@ -475,211 +550,157 @@ def get_events(): _last_norm_values[3] = out _last_states = these_states - -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() - 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 = "") +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") - self.dpad_up = canvas.create_rectangle(((dpad_pos[0] - 10, dpad_pos[1] - 30), (dpad_pos[0] + 10, dpad_pos[1] - 10)), outline = "") + def process_stick_event(self, event): + raise NotImplementedError("Method not implemented. Must be implemented in the child class") - self.dpad_right = canvas.create_rectangle(((dpad_pos[0] + 10, dpad_pos[1] - 10), (dpad_pos[0] + 30, dpad_pos[1] + 10)), outline = "") + def process_trigger_event(self, event): + raise NotImplementedError("Method not implemented. Must be implemented in the child class") - self.dpad_down = canvas.create_rectangle(((dpad_pos[0] - 10, dpad_pos[1] + 10), (dpad_pos[0] + 10, dpad_pos[1] + 30)), outline = "") + def process_connection_event(self, event): + raise NotImplementedError("Method not implemented. Must be implemented in the child class") - 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) + 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" - 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))) + self.controllers.add(user_index) - start_button_pos = (self.center[0] + 20, self.center[1] - 20) + 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" - 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))) + self.controllers = set(controllers) - l_shoulder_pos = (self.center[0] - 90, self.center[1] - 70) + 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" - 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))) + assert len(self.controllers) >= 2, "you have to keep at least one controller" + + try: + self.controllers.remove(user_index) + return True + except KeyError: + return False - r_shoulder_pos = (self.center[0] + 90, self.center[1] - 70) + 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" - 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))) + return user_index in self.controllers + + 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 + + +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.handlers = set(event_handlers) - controllers = (Controller((150., 100.)), - Controller((450., 100.)), - Controller((150., 300.)), - Controller((450., 300.))) + self.lock = Lock() - 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") + self.queued_new_handlers = [] + self.queued_removed_handlers = [] + + if auto_start: + self.start() + + 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) - elif event.type == EVENT_DISCONNECTED: - canvas.itemconfig(controller.on_indicator, fill="") + 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") - 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 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() + + 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() + + 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 new file mode 100644 index 0000000..b99d464 --- /dev/null +++ b/XInputThreadTest.py @@ -0,0 +1,221 @@ +#/usr/bin python3 + +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(EventHandler): + def process_button_event(self,event): + controller = 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 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))) + 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)) + + 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))) + 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)) + + def process_connection_event(self, event): + 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="") + else: + print("Unrecognized controller event type") + + class MyOtherHandler(EventHandler): + def __init__(self, *controllers): + super().__init__(*controllers, filter=BUTTON_A+FILTER_PRESSED_ONLY) + + def process_button_event(self, event): + print("Pressed button A") + + def process_stick_event(self, event): + pass + + def process_trigger_event(self, event): + print("Trigger LEFT") + + def process_connection_event(self, event): + pass + + + handler = MyHandler(0, 1, 2, 3) # initialize handler object + thread = GamepadThread(handler) # initialize controller thread + + handler2 = MyOtherHandler(1) + thread.add_event_handler(handler2) # add another handler + 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) + # handler.add_filter(STICK_RIGHT,[2]) + # handler = MyHandler(controllers, canvas, STICK_LEFT) + + root.mainloop() # run UI loop + + thread.stop() + + # can run other stuff here + +else: + raise ImportError("This is not a module. Import XInput only") 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