1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
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)
|