aboutsummaryrefslogtreecommitdiff
path: root/pyjstest.py
diff options
context:
space:
mode:
Diffstat (limited to 'pyjstest.py')
-rwxr-xr-xpyjstest.py429
1 files changed, 429 insertions, 0 deletions
diff --git a/pyjstest.py b/pyjstest.py
new file mode 100755
index 0000000..dc7e08d
--- /dev/null
+++ b/pyjstest.py
@@ -0,0 +1,429 @@
+#!/usr/bin/env python3
+# File: pyjstest.py
+# Location: https://bgstack15.ddns.net/git/pyjstest/
+# Author: bgstack15
+# SPDX-License-Identifier: GPL-3.0
+# Startdate: 2022-06-29
+# Title: Pyjstest
+# Package: pyjstest
+# Purpose: Display inputs of one connected gamepad
+# History:
+# Usage:
+# Adjust config.py and then run!
+# Reference:
+# https://stackoverflow.com/a/50572903
+# https://pysdl2.readthedocs.io/en/0.9.11/tutorial/pong.html
+# Improve:
+# display text of axes values? https://github.com/caerus706/Python3-pySDL2-examples/blob/master/text.py
+# Documentation: See README.md
+import ctypes, time, random
+from sdl2 import *
+import sdl2.ext, sys
+
+from config import colors, CONFIG
+
+class SoftwareRenderer(sdl2.ext.SoftwareSpriteRenderSystem):
+ def __init__(self, window):
+ super(SoftwareRenderer, self).__init__(window)
+ def render(self, components):
+ sdl2.ext.fill(self.surface, colors["BKGD"])
+ super(SoftwareRenderer, self).render(components)
+
+class MovementSystem(sdl2.ext.Applicator):
+ def __init__(self, minx, miny, maxx, maxy):
+ super(MovementSystem, self).__init__()
+ self.componenttypes = Velocity, sdl2.ext.Sprite
+ self.minx = minx
+ self.miny = miny
+ self.maxx = maxx
+ self.maxy = maxy
+ def process(self, world, componentsets):
+ for velocity, sprite in componentsets:
+ swidth, sheight = sprite.size
+ sprite.x += velocity.vx
+ sprite.y += velocity.vy
+ sprite.x = max(self.minx, sprite.x)
+ sprite.y = max(self.miny, sprite.y)
+ pmaxx = sprite.x + swidth
+ pmaxy = sprite.y + sheight
+ if pmaxx > self.maxx:
+ sprite.x = self.maxx - swidth
+ if pmaxy > self.maxy:
+ sprite.y = self.maxy - sheight
+
+class Velocity(object):
+ def __init__(self):
+ super(Velocity, self).__init__()
+ self.vx = 0
+ self.vy = 0
+
+class Rectangle(sdl2.ext.Entity):
+ def __init__(self, world, sprite, posx=0, posy=0):
+ self.world = world
+ self.sprite = sprite
+ self.sprite.position = posx, posy
+ self.velocity = Velocity()
+
+class TwoAxisBackground(sdl2.ext.Entity):
+ def __init__(self, world, factory, color, posx=0, posy=0, sizex=100, sizey=100):
+ self.world = world
+ self.sprite = factory.from_color(color, size=(sizex, sizey))
+ self.sprite.position = posx, posy
+
+class Button(object):
+ def __init__(self, world, factory, color_on, color_off, posx, posy, size, button, relx=0, rely=0, owner = None):
+ self.world = world
+ self.factory = factory
+ self.color_on = color_on
+ self.color_off = color_off
+ self.posx = posx
+ self.posy = posy
+ self.size = size
+ self.object = Rectangle(self.world, self.factory.from_color(self.color_off, size=(self.size, self.size)), self.posx, self.posy)
+ self.status = False
+ self.button = button
+ self.type = "button"
+ self.relx = relx
+ self.rely = rely
+ self.owner = owner # either None, or an Analog
+ def on(self):
+ if not self.status:
+ self.object.sprite = self.factory.from_color(self.color_on, size=(self.size,self.size))
+ if self.owner is None:
+ self.object.sprite.position = self.posx+self.relx, self.posy+self.rely
+ self.status = True
+ def off(self):
+ if self.status:
+ self.object.sprite = self.factory.from_color(self.color_off, size=(self.size,self.size))
+ if self.owner is None:
+ self.object.sprite.position = self.posx+self.relx, self.posy+self.rely
+ self.status = False
+
+class Dpad(object):
+ """ Represent simple UDLR position of an analog stick. One Dpad object per direction per axis excluding off. """
+ def __init__(self, world, factory, color_on, color_off, posx, posy, size, axis, direction):
+ self.world = world
+ self.factory = factory
+ self.color_on = color_on
+ self.color_off = color_off
+ self.posx = posx
+ self.posy = posy
+ self.size = size
+ self.type = "dpad"
+ self.axis = axis
+ self.direction = direction
+ self.object = Rectangle(self.world, self.factory.from_color(self.color_off, size=(self.size,self.size)), self.posx, self.posy)
+ self.status = False
+ def positive(self, axis):
+ #print(f"Dpad axis {axis} moved positive.")
+ self.on() if "positive" == self.direction else self.off()
+ def negative(self, axis):
+ #print(f"Dpad axis {axis} moved negative.")
+ self.on() if "negative" == self.direction else self.off()
+ def middle(self, axis):
+ self.off()
+ def on(self):
+ if not self.status:
+ #self.object = Rectangle(self.world, self.factory.from_color(self.color_on, size=(self.size,self.size)), self.posx, self.posy)
+ self.object.sprite = self.factory.from_color(self.color_on, size=(self.size,self.size))
+ self.object.sprite.position = self.posx, self.posy
+ self.status = True
+ def off(self):
+ if self.status:
+ #self.object = Rectangle(self.world, self.factory.from_color(self.color_off, size=(self.size,self.size)), self.posx, self.posy)
+ self.object.sprite = self.factory.from_color(self.color_off, size=(self.size,self.size))
+ self.object.sprite.position = self.posx, self.posy
+ self.status = False
+ def react(self, config, axis, value):
+ #print(f"Dpad axis {axis} value {value}")
+ if value >= config["dpad_axes"]["min"][axis] and value < config["dpad_axes"]["mid_min"][axis]:
+ self.negative(axis)
+ elif value >= config["dpad_axes"]["mid_min"][axis] and value <= config["dpad_axes"]["mid_max"][axis]:
+ self.middle(axis)
+ elif value > config["dpad_axes"]["mid_max"][axis] and value <= config["dpad_axes"]["max"][axis]:
+ self.positive(axis)
+ else:
+ print(f"Warning! For Dpad {self} axis {axis}, value {value} is undefined behavior.")
+
+class Analog(object):
+ def __init__(self, world, factory, color_on, color_off, centerx, centery, indicator_size, size, axes, orientations, buttons):
+ self.world = world
+ self.factory = factory
+ self.color_on = color_on
+ self.color_off = color_off
+ self.posx = centerx
+ self.posy = centery
+ self.size = size
+ self.type = "analog"
+ self.axes = axes
+ self.indicator_size = indicator_size
+ self.orientations = orientations
+ self.buttons_in = buttons # a list of ["color_on", "color_off", button_id]
+ self.ulx = int(self.posx-(size/2))
+ self.uly = int(self.posy-(size/2))
+ self.lrx = int(self.posx+(size/2))
+ self.lry = int(self.posy+(size/2))
+ self.background = TwoAxisBackground(self.world, self.factory, self.color_off, self.ulx, self.uly, size, size)
+ self.indicator = Rectangle(self.world, self.factory.from_color(self.color_on, size=(self.indicator_size,self.indicator_size)), self.posx, self.posy)
+ #self.movement = MovementSystem(self.ulx, self.uly, self.lrx, self.lry)
+ #self.world.add_system(self.movement)
+ self.buttons = []
+ for b in buttons:
+ self.buttons.append(Button(self.world,self.factory,colors[b[0]],colors[b[1]],self.posx,self.posy,b[4],b[5],b[2],b[3],self))
+ def react(self, config, axis, value):
+ #print(f"Updating with {axis}, {value}")
+ _max = config["dpad_axes"]["max"][axis]
+ _min = config["dpad_axes"]["min"][axis]
+ percent = (value - _min) / (_max - _min) * 100
+ current_position = self.indicator.sprite.position # (x, y)
+ newx = current_position[0]
+ newy = current_position[1]
+ # X is axes[0]
+ if axis == self.axes[0]:
+ newx = int((self.ulx+(self.size*percent/100))-(self.indicator_size/2))
+ elif axis == self.axes[1]:
+ newy = int((self.uly+(self.size*percent/100))-(self.indicator_size/2))
+ self.indicator.sprite.position = newx, newy
+ for b in self.buttons:
+ b.object.sprite.position = (newx+b.relx), (newy+b.rely)
+ return newx, newy
+
+class Hat(object):
+ def __init__(self, world, factory, color_on, color_off, posx, posy, size, hat, off, up, right, down, left):
+ self.world = world
+ self.factory = factory
+ self.color_on = color_on
+ self.color_off = color_off
+ self.hat = hat
+ self.basex = posx
+ self.basey = posy
+ self.size = size
+ self.type = "hat"
+ # center, up, right, down, left
+ self.posx = [0, 0, 20, 0, -20]
+ self.posy = [0, -20, 0, 20, 0]
+ self.box = []
+ for i in range(0,5):
+ self.box.append(Rectangle(self.world, self.factory.from_color(self.color_off, size=(self.size,self.size)), self.basex+self.posx[i], self.basey+self.posy[i]))
+ # array of binary values that will describe the current hat position
+ self.binary = [off, up, right, down, left]
+ def react(self, value):
+ print(f"Hat {self.hat} value {value}")
+ possible = [0,1,2,3,4]
+ on = []
+ if value == self.binary[0]:
+ on.append(0)
+ for r in range(1,5): # 1 to 4
+ if value & self.binary[r]:
+ on.append(r)
+ # evaluate all the options
+ off = possible
+ for option in on:
+ off.remove(option)
+ #print(f"Hat options on: {on}")
+ #print(f"Hat options off: {off}")
+ x = 0
+ while x < 5:
+ self.box = []
+ if x in on:
+ self.box.append(Rectangle(self.world, self.factory.from_color(self.color_on, size=(self.size,self.size)), self.basex+self.posx[x], self.basey+self.posy[x]))
+ else:
+ self.box.append(Rectangle(self.world, self.factory.from_color(self.color_off, size=(self.size,self.size)), self.basex+self.posx[x], self.basey+self.posy[x]))
+ x = x + 1
+
+# color_on, color_off, x,y, size, axis, direction
+#["GRAY", "DARK_GRAY", 60, 40, 20, 2, 1],
+class Trigger(object):
+ """ Direction is not actually implemented. """
+ def __init__(self, world, factory, renderer, color_on, color_off, color_bkgd, x1, y1, x2, y2, size, axis, direction):
+ self.world = world
+ self.factory = factory
+ self.color_on = color_on
+ self.color_off = color_off
+ self.color_bkgd = color_bkgd
+ self.axis = axis
+ self.x1 = x1
+ self.y1 = y1
+ self.x2 = x2
+ self.y2 = y2
+ self.boxx1 = min(x1,x2)
+ self.boxy1 = min(y1,y2)
+ self.boxx2 = max(x1,x2)
+ self.boxy2 = max(y1,y2)
+ self.linex1 = x1 - self.boxx1
+ self.liney1 = y1 - self.boxy1
+ self.linex2 = x2 - self.boxx1
+ self.liney2 = y2 - self.boxy1
+ self.type = "trigger"
+ self.indicator_size = size
+ # This line makes rectangle the minimum size so the drawn line actually displays.
+ self.rectangle_size = max((self.boxx2-self.boxx1),1),max((self.boxy2-self.boxy1),1)
+ print(f"Drawing rectangle, {self.color_bkgd}, size ({self.rectangle_size}), starting ({self.boxx1},{self.boxy1})")
+ self.background = Rectangle(self.world, self.factory.from_color(self.color_bkgd, size=self.rectangle_size), self.boxx1, self.boxy1)
+
+ print(f"Drawing line color {self.color_off}, from ({self.linex1}, {self.liney1}) to ({self.linex2},{self.liney2})")
+ sdl2.ext.draw.line(self.background.sprite, self.color_off, (self.linex1, self.liney1, self.linex2, self.liney2))
+
+ self.indicator = Rectangle(self.world, self.factory.from_color(self.color_on, size=(self.indicator_size,self.indicator_size)), self.x1, self.y1)
+ def react(self, config, axis, value):
+ """ This one-axis trigger will move in X,Y dimensions based on the line drawn. """
+ print(f"Axis {axis} value {value}")
+ _max = config["dpad_axes"]["max"][axis]
+ _min = config["dpad_axes"]["min"][axis]
+ percent = (value - _min) / (_max - _min) * 100
+ current_position = self.indicator.sprite.position # (x, y)
+ newx = int((self.x2 - self.x1)*percent/100-(self.indicator_size/2) + self.x1) +1
+ newy = int((self.y2 - self.y1)*percent/100-(self.indicator_size/2) + self.y1) +1
+ self.indicator.sprite.position = newx, newy
+
+def find_joystick_config_by_alias(config, name):
+ """
+ Given CONFIG and a joystick name, see if that joystick name shows up as either a ruleset or alias for a ruleset, and then return the proper ruleset name.
+ """
+ if name in config:
+ print(f"DEBUG (fjcba): Found name {name} in config.")
+ return name
+ else:
+ for c in config:
+ if "aliases" in config[c]:
+ if name in config[c]["aliases"]:
+ print(f"DEBUG (fjcba): Found name {c} that matches alias {name}.")
+ return c
+ print(f"DEBUG (fjcba): oops! Found nothing that matches {name}.")
+ return None
+
+def run():
+ world = sdl2.ext.World()
+ gamepads = []
+ #joystick = Joystick()
+ SDL_Init(SDL_INIT_JOYSTICK)
+ sdl2.ext.init()
+ joystick = ""
+ # load resources/ directory
+ #RESOURCES = sdl2.ext.Resources(__file__,"resources")
+ factory = sdl2.ext.SpriteFactory(sdl2.ext.SOFTWARE)
+ #sprite = factory.from_image(RESOURCES.get_path("gamepad.png"))
+
+ input_array = []
+ #if "buttons" in CONFIG[joystick]:
+ # for b in CONFIG[joystick]["buttons"]:
+ # input_array.append(button(world,factory,colors[b[0]],colors[b[1]],b[2],b[3],b[4]))
+ #if "dpads" in CONFIG[joystick]:
+ # for b in CONFIG[joystick]["dpads"]:
+ # input_array.append(Dpad(world,factory,colors[b[0]],colors[b[1]],b[2],b[3],b[4],b[5]))
+ #if "hats" in CONFIG[joystick]:
+ # for b in CONFIG[joystick]["hats"]:
+ # input_array.append(Hat(world, factory, colors[b[0]], colors[b[1]], b[2], b[3], b[4], b[5],b[6],b[7],b[8],b[9]))
+ #window = sdl2.ext.Window("Pyjoy", size=CONFIG[joystick]["window"])
+ #window = sdl2.ext.Window("Pyjoy", size=(100,100), flags = SDL_WINDOW_RESIZABLE)
+ window = sdl2.ext.Window("Pyjoy", size=(100,100))
+ window.show()
+
+ spriterenderer = SoftwareRenderer(window)
+ world.add_system(spriterenderer)
+ running = True
+ status = {}
+ while running:
+ event = SDL_Event()
+ while SDL_PollEvent(ctypes.byref(event)) != 0:
+ if event.type == SDL_JOYDEVICEADDED:
+ #self.device = SDL_JoystickOpen(event.jdevice.which)
+ joystick = SDL_JoystickName(SDL_JoystickOpen(event.jdevice.which)).decode('UTF-8')
+ print(f"Device: {joystick}")
+ config_name = find_joystick_config_by_alias(CONFIG,joystick)
+ window.title = config_name
+ if config_name:
+ joystick = config_name
+ print(f"Working with controller type {config_name}")
+ #window.size = CONFIG[joystick]["window"]
+ SDL_SetWindowSize(window.window,CONFIG[joystick]["window"][0],CONFIG[joystick]["window"][1])
+ window.refresh()
+ #window.get_surface()
+ # Use a temporary list, to avoid the "Set size changed during iteration."
+ temp_list = []
+ for i in world.entities:
+ temp_list.append(i)
+ for i in temp_list:
+ i.delete()
+ world.remove_system(spriterenderer)
+ spriterenderer = SoftwareRenderer(window)
+ world.add_system(spriterenderer)
+ input_array = []
+ if "buttons" in CONFIG[joystick]:
+ for b in CONFIG[joystick]["buttons"]:
+ input_array.append(Button(world,factory,colors[b[0]],colors[b[1]],b[2],b[3],b[4],b[5]))
+ if "dpads" in CONFIG[joystick]:
+ for b in CONFIG[joystick]["dpads"]:
+ input_array.append(Dpad(world,factory,colors[b[0]],colors[b[1]],b[2],b[3],b[4],b[5],b[6]))
+ if "hats" in CONFIG[joystick]:
+ for b in CONFIG[joystick]["hats"]:
+ input_array.append(Hat(world, factory, colors[b[0]], colors[b[1]], b[2], b[3], b[4], b[5],b[6],b[7],b[8],b[9],b[10]))
+ if "analogs" in CONFIG[joystick]:
+ for b in CONFIG[joystick]["analogs"]:
+ a = Analog(world, factory, colors[b[0]], colors[b[1]], b[2], b[3], b[4], b[5], b[6], b[7], b[8])
+ input_array.append(a)
+ for b in a.buttons:
+ input_array.append(b)
+ if "triggers" in CONFIG[joystick]:
+ for b in CONFIG[joystick]["triggers"]:
+ input_array.append(Trigger(world, factory, spriterenderer, colors[b[0]], colors[b[1]], colors[b[2]], b[3], b[4], b[5], b[6], b[7], b[8], b[9]))
+ #spriterenderer = SoftwareRenderer(window)
+ #world.add_system(spriterenderer)
+ #return 0
+ elif event.type == SDL_JOYAXISMOTION:
+ #self.axis[event.jaxis.axis] = event.jaxis.value
+ #print(event.jaxis.axis,event.jaxis.value)
+ status[event.jaxis.axis] = event.jaxis.value
+ axis = event.jaxis.axis
+ value = event.jaxis.value
+ for q in input_array:
+ if "dpad" == q.type and q.axis == axis:
+ q.react(CONFIG[joystick], axis, value)
+ if "analog" == q.type and axis in q.axes:
+ response = q.react(CONFIG[joystick], axis, value)
+ #print(f"Coord of indicator: {response}")
+ if "trigger" == q.type and axis == q.axis:
+ q.react(CONFIG[joystick], axis, value)
+ elif event.type == SDL_JOYHATMOTION:
+ #print(f"hat {event.jhat.hat},{event.jhat.value}")
+ for q in input_array:
+ if "hat" == q.type and q.hat == event.jhat.hat:
+ q.react(event.jhat.value)
+ elif event.type == SDL_JOYBUTTONDOWN:
+ #self.button[event.jbutton.button] = True
+ print(f"{event.jbutton.button} down")
+ b = event.jbutton.button
+ for q in input_array:
+ if "button" == q.type and b == q.button:
+ q.on()
+ elif event.type == SDL_JOYBUTTONUP:
+ #self.button[event.jbutton.button] = False
+ print(f"{event.jbutton.button} up")
+ b = event.jbutton.button
+ for q in input_array:
+ if "button" == q.type and b == q.button:
+ q.off()
+ elif event.type == sdl2.SDL_QUIT:
+ window.close()
+ print("quitting...")
+ running = False
+ break
+ else:
+ # file /usr/lib/python3/dist-packages/sdl2/events.py
+ # 1536 = SDL_JOYAXISMOTION but gets thrown separately too?
+ # 1024 = SDL_MOUSEMOTION
+ #if event.type not in [1536, 1538, 1024]:
+ print(f"Unknown event {event.type}")
+ result = "\r"
+ for s in sorted(status):
+ result += f"{s}: {status[s]:06} "
+ print(result, end=" ")
+ world.process()
+ return 0
+
+if __name__ == "__main__":
+ running_main = True
+ while running_main:
+ if -1 == run():
+ running_main = False
+ sys.exit(0)
bgstack15