Merge lp:~jeff-licquia/pystromo/saitek into lp:pystromo

Proposed by Jeff Licquia
Status: Needs review
Proposed branch: lp:~jeff-licquia/pystromo/saitek
Merge into: lp:pystromo
Diff against target: 442 lines (+325/-8)
4 files modified
config/saitek-cyborg-cmdunit.map (+236/-0)
lib/constants.py (+15/-0)
lib/devices.py (+70/-7)
pystromo-remap.py (+4/-1)
To merge this branch: bzr merge lp:~jeff-licquia/pystromo/saitek
Reviewer Review Type Date Requested Status
Raumkraut Needs Fixing
Review via email: mp+41413@code.launchpad.net

Description of the change

This branch pulls in the changes from Shane McCormack to support the Saitek Cyborg Command Unit gamepad (with some updates from me).

To post a comment you must log in.
Revision history for this message
Raumkraut (raumkraut) wrote :

Thanks for bringing this to my attention, Jeff.
It's not going to get merged in though, as the features have been implemented in the wrong places:
- Parsing config files should be (and is!) done before an InputDevice is instantiated.
- Saving application-state information shouldn't really be done by the InputDevice either. And it's probably a bad idea to write config data every time an LED changes.
- Plus some nomenclature issues. :)

And I have to ask: why were some buttons renamed to "TRIGGER_HAPPY##", aside from the amusement factor? :)

review: Needs Information
Revision history for this message
Jeff Licquia (jeff-licquia) wrote :

On 11/23/2010 07:48 AM, Raumkraut wrote:
> Review: Needs Information
> Thanks for bringing this to my attention, Jeff.
> It's not going to get merged in though, as the features have been implemented in the wrong places:
> - Parsing config files should be (and is!) done before an InputDevice is instantiated.
> - Saving application-state information shouldn't really be done by the InputDevice either. And it's probably a bad idea to write config data every time an LED changes.
> - Plus some nomenclature issues. :)

OK. I'll refactor and resend. Any hints on the nomenclature issues?

> And I have to ask: why were some buttons renamed to "TRIGGER_HAPPY##", aside from the amusement factor? :)

It's from linux/input.h; here's the commit log for when this came in:

commit cf2f765f1896064e34c6f0f2ef896ff058dd5c06
Author: Jiri Kosina <email address hidden>
Date: Mon Jan 4 12:20:56 2010 +0100

     HID: handle joysticks with large number of buttons

     Current HID code doesn't properly handle HID joysticks which have
     larger number of buttons than what fits into current range reserved
     for BTN_JOYSTICK.

     One such joystick reported to not work properly is Saitek X52 Pro
     Flight System.

     We can't extend the range to fit more buttons in, because of backwards
     compatibility reasons.

     Therefore this patch introduces a new BTN_TRIGGER_HAPPY range, and
     uses these to map the buttons which are over BTN_JOYSTICK limit.

     Acked-by: Dmitry Torokhov <email address hidden> [for the
input.h part]
     Signed-off-by: Jiri Kosina <email address hidden>

Revision history for this message
Raumkraut (raumkraut) wrote :

Right, further clarification:
- The events to use as additional LEDs should be passed in to InputDevices on instantiation. Actually, it looks like the value is already implicitly passed in with the **params. (The **params were originally intended solely to identify the devices to match against, so 'leds' may be better as another argument, I'm not sure)
- The LED state should probably only be saved on exit, which I think just means moving that code to the end of pystromo-remap.py.
- Nomenclature:
- - saitek.map should be named more specifically for the device.
- - The location of the save-file should probably not be user-specified, but in constants.py. Following XDG probably means ~/.local/pystromo/{device-id}.state (because we're storing the "state" of the device). If there's no device-id available (I forget), "{vendor}:{product}.state" probably covers 99.99% of cases.
- - Plus any other variables named after device manufacturers. ;)
- - And if the Czech wants to go all TRIGGER_HAPPY, I guess we should let him. :)

Also: `print` statements should probably be removed/commented from anything in the 'lib' directory, once you're happy that it's working.

Phew! It probably took me more time to write that, than it will for you to refactor the code. :D

review: Needs Fixing
lp:~jeff-licquia/pystromo/saitek updated
66. By Jeff Licquia

First pass at refactoring according to suggestions from upstream.

Changes:

 - References to "saitek" in variable names purged.

 - Instead of configuring the saved state file, force it to a standard
   location (currently ~/.local/pystromo, set in constants.py), with a
   standard filename.

 - Instead of saving on every LED state change, save when pystromo-remap
   exits. State is also only saved if needed (i.e. we read state on startup
   and/or we detected a state change).

67. By Jeff Licquia

Include further changes in key mapping for LED "buttons".

68. By Jeff Licquia

Fix bug in InputDevice.save_state(), and remove debug statements.

69. By Jeff Licquia

Renamed sample file and gave it a better device ID.

70. By Jeff Licquia

Get rid of print statements in lib dir.

Revision history for this message
Jeff Licquia (jeff-licquia) wrote :

Hello again! Sorry for taking so long, but here's the refactored changes. I tried to do everything you asked for. Specifics:

> - The events to use as additional LEDs should be passed in to InputDevices on
> instantiation. Actually, it looks like the value is already implicitly passed
> in with the **params. (The **params were originally intended solely to
> identify the devices to match against, so 'leds' may be better as another
> argument, I'm not sure)

"leds" is now its own argument.

> - The LED state should probably only be saved on exit, which I think just
> means moving that code to the end of pystromo-remap.py.

Done, sorta. The save code was moved to its own method for InputDevice, which is called at the end of pystromo-remap.py.

> - Nomenclature:
> - - saitek.map should be named more specifically for the device.

Done.

> - - The location of the save-file should probably not be user-specified, but
> in constants.py. Following XDG probably means ~/.local/pystromo/{device-
> id}.state (because we're storing the "state" of the device). If there's no
> device-id available (I forget), "{vendor}:{product}.state" probably covers
> 99.99% of cases.

This is done exactly as requested.

> - - Plus any other variables named after device manufacturers. ;)

Think I got them all.

> - - And if the Czech wants to go all TRIGGER_HAPPY, I guess we should let him.
> :)

It turned out I needed to add a few more; the switch events moved along with some of the buttons.

> Also: `print` statements should probably be removed/commented from anything in
> the 'lib' directory, once you're happy that it's working.

Think I got them all.

Unmerged revisions

70. By Jeff Licquia

Get rid of print statements in lib dir.

69. By Jeff Licquia

Renamed sample file and gave it a better device ID.

68. By Jeff Licquia

Fix bug in InputDevice.save_state(), and remove debug statements.

67. By Jeff Licquia

Include further changes in key mapping for LED "buttons".

66. By Jeff Licquia

First pass at refactoring according to suggestions from upstream.

Changes:

 - References to "saitek" in variable names purged.

 - Instead of configuring the saved state file, force it to a standard
   location (currently ~/.local/pystromo, set in constants.py), with a
   standard filename.

 - Instead of saving on every LED state change, save when pystromo-remap
   exits. State is also only saved if needed (i.e. we read state on startup
   and/or we detected a state change).

65. By Jeff Licquia

Updated key constants for buttons F1-F4 and 21 on Saitek.

64. By Shane Mc Cormack

Pull in Saitek gamepad changes from Shane McCormack.

Original repo: git://git.soren.co.uk/shane/pystromo.git/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'config/saitek-cyborg-cmdunit.map'
--- config/saitek-cyborg-cmdunit.map 1970-01-01 00:00:00 +0000
+++ config/saitek-cyborg-cmdunit.map 2011-01-30 05:34:10 +0000
@@ -0,0 +1,236 @@
1# Saitek Cyborg Command Unit gamepad
2[Device:saitek-cyborg-command-unit]
3vendor=0x06a3
4product=0x80c1
5leds=BTN_TRIGGER_HAPPY22, BTN_TRIGGER_HAPPY23, BTN_TRIGGER_HAPPY24
6
7################################################################################
8# Green #
9################################################################################
10[map:saitek-cyborg-command-unit + BTN_TRIGGER_HAPPY22]
11### F1
12#BTN_TRIGGER_HAPPY17:
13### F2
14#BTN_TRIGGER_HAPPY18:
15### F3
16#BTN_TRIGGER_HAPPY19:
17### F4
18#BTN_TRIGGER_HAPPY20:
19
20### 1
21#BTN_TRIGGER:
22### W
23#BTN_THUMB:
24### 3
25#BTN_THUMB2:
26### 4
27#BTN_TOP:
28
29### A
30#BTN_TOP2:
31### S
32#BTN_PINKIE:
33### D
34#BTN_BASE:
35### 8
36#BTN_BASE2:
37
38### 9
39#BTN_BASE3:
40### 10
41#BTN_BASE4:
42### 11
43#BTN_BASE5:
44
45### 12
46#BTN_BASE6:
47### 13
48#BTN_7:
49### 14
50#BTN_8:
51
52### 15
53#BTN_DEAD2:
54### 16
55#BTN_DEAD:
56### 21
57#BTN_TRIGGER_HAPPY21:
58
59### X Axis
60#ABS_X:
61### Y Axis
62#ABS_Y:
63
64
65################################################################################
66# Yellow #
67################################################################################
68[map:saitek-cyborg-command-unit + BTN_TRIGGER_HAPPY23]
69### F1
70#BTN_TRIGGER_HAPPY17:
71### F2
72#BTN_TRIGGER_HAPPY18:
73### F3
74#BTN_TRIGGER_HAPPY19:
75### F4
76#BTN_TRIGGER_HAPPY20:
77
78### 1
79#BTN_TRIGGER:
80### W
81#BTN_THUMB:
82### 3
83#BTN_THUMB2:
84### 4
85#BTN_TOP:
86
87### A
88#BTN_TOP2:
89### S
90#BTN_PINKIE:
91### D
92#BTN_BASE:
93### 8
94#BTN_BASE2:
95
96### 9
97#BTN_BASE3:
98### 10
99#BTN_BASE4:
100### 11
101#BTN_BASE5:
102
103### 12
104#BTN_BASE6:
105### 13
106#BTN_7:
107### 14
108#BTN_8:
109
110### 15
111#BTN_DEAD2:
112### 16
113#BTN_DEAD:
114### 21
115#BTN_TRIGGER_HAPPY21:
116
117### X Axis
118#ABS_X:
119### Y Axis
120#ABS_Y:
121
122
123################################################################################
124# Red #
125################################################################################
126[map:saitek-cyborg-command-unit + BTN_TRIGGER_HAPPY24]
127### F1
128#BTN_TRIGGER_HAPPY17:
129### F2
130#BTN_TRIGGER_HAPPY18:
131### F3
132#BTN_TRIGGER_HAPPY19:
133### F4
134#BTN_TRIGGER_HAPPY20:
135
136### 1
137#BTN_TRIGGER:
138### W
139#BTN_THUMB:
140### 3
141#BTN_THUMB2:
142### 4
143#BTN_TOP:
144
145### A
146#BTN_TOP2:
147### S
148#BTN_PINKIE:
149### D
150#BTN_BASE:
151### 8
152#BTN_BASE2:
153
154### 9
155#BTN_BASE3:
156### 10
157#BTN_BASE4:
158### 11
159#BTN_BASE5:
160
161### 12
162#BTN_BASE6:
163### 13
164#BTN_7:
165### 14
166#BTN_8:
167
168### 15
169#BTN_DEAD2:
170### 16
171#BTN_DEAD:
172### 21
173#BTN_TRIGGER_HAPPY21:
174
175### X Axis
176#ABS_X:
177### Y Axis
178#ABS_Y:
179
180
181################################################################################
182# All #
183################################################################################
184[map:saitek-cyborg-command-unit]
185### F1
186#BTN_TRIGGER_HAPPY17:
187### F2
188#BTN_TRIGGER_HAPPY18:
189### F3
190#BTN_TRIGGER_HAPPY19:
191### F4
192#BTN_TRIGGER_HAPPY20:
193
194### 1
195#BTN_TRIGGER:
196### W
197#BTN_THUMB:
198### 3
199#BTN_THUMB2:
200### 4
201#BTN_TOP:
202
203### A
204#BTN_TOP2:
205### S
206#BTN_PINKIE:
207### D
208#BTN_BASE:
209### 8
210#BTN_BASE2:
211
212### 9
213#BTN_BASE3:
214### 10
215#BTN_BASE4:
216### 11
217#BTN_BASE5:
218
219### 12
220#BTN_BASE6:
221### 13
222#BTN_7:
223### 14
224#BTN_8:
225
226### 15
227#BTN_DEAD2:
228### 16
229#BTN_DEAD:
230### 21
231#BTN_TRIGGER_HAPPY21:
232
233### X Axis
234#ABS_X:
235### Y Axis
236#ABS_Y:
0237
=== modified file 'lib/constants.py'
--- lib/constants.py 2007-10-01 13:58:18 +0000
+++ lib/constants.py 2011-01-30 05:34:10 +0000
@@ -15,6 +15,10 @@
15# The first one which exists is the one which is used.15# The first one which exists is the one which is used.
16UINPUT_DEVICES = ['/dev/uinput', '/dev/input/uinput', '/dev/misc/uinput']16UINPUT_DEVICES = ['/dev/uinput', '/dev/input/uinput', '/dev/misc/uinput']
1717
18# Path to the device state file, if used. This is relative to the home
19# directory.
20STATE_FILE_PATH = "~/.local/pystromo"
21
1822
1923
20## I don't know any other bus values!24## I don't know any other bus values!
@@ -299,6 +303,9 @@
299 0x129: 'BTN_BASE4',303 0x129: 'BTN_BASE4',
300 0x12a: 'BTN_BASE5',304 0x12a: 'BTN_BASE5',
301 0x12b: 'BTN_BASE6',305 0x12b: 'BTN_BASE6',
306 0x12c: 'BTN_BASE7',
307 0x12d: 'BTN_BASE8',
308 0x12e: 'BTN_DEAD2',
302 0x12f: 'BTN_DEAD',309 0x12f: 'BTN_DEAD',
303 0x130: 'BTN_A',310 0x130: 'BTN_A',
304 0x131: 'BTN_B',311 0x131: 'BTN_B',
@@ -326,6 +333,14 @@
326 0x14a: 'BTN_TOUCH',333 0x14a: 'BTN_TOUCH',
327 0x14b: 'BTN_STYLUS',334 0x14b: 'BTN_STYLUS',
328 0x14c: 'BTN_STYLUS2',335 0x14c: 'BTN_STYLUS2',
336 0x2d0: 'BTN_TRIGGER_HAPPY17',
337 0x2d1: 'BTN_TRIGGER_HAPPY18',
338 0x2d2: 'BTN_TRIGGER_HAPPY19',
339 0x2d3: 'BTN_TRIGGER_HAPPY20',
340 0x2d4: 'BTN_TRIGGER_HAPPY21',
341 0x2d5: 'BTN_TRIGGER_HAPPY22',
342 0x2d6: 'BTN_TRIGGER_HAPPY23',
343 0x2d7: 'BTN_TRIGGER_HAPPY24',
329 },344 },
330 # EV_REL345 # EV_REL
331 0x02: {346 0x02: {
332347
=== modified file 'lib/devices.py'
--- lib/devices.py 2008-10-13 22:00:38 +0000
+++ lib/devices.py 2011-01-30 05:34:10 +0000
@@ -9,6 +9,8 @@
9import events9import events
10import mapping10import mapping
11import constants as const11import constants as const
12import ConfigParser
13import os
1214
13# This is a list of all the devices which have been created.15# This is a list of all the devices which have been created.
14# We use this for the receive() function.16# We use this for the receive() function.
@@ -59,7 +61,7 @@
59 busy = property(fget=_getBusy)61 busy = property(fget=_getBusy)
60 62
61 63
62 def __init__ (self, keymap, id=None, output=None, **params):64 def __init__ (self, keymap, id=None, output=None, leds=None, **params):
63 """65 """
64 If not specified, the id will be populated by the device's66 If not specified, the id will be populated by the device's
65 detected 'name' parameter (if any).67 detected 'name' parameter (if any).
@@ -90,9 +92,18 @@
90 # at the same time...92 # at the same time...
91 self._starting = set()93 self._starting = set()
92 94
95 # Some gamepads use button events to indicate the state of
96 # a switch on the pad. Instead of recording individual
97 # events, we should maintain an internal sense of the
98 # switch state which can be used in key definitions.
99 if leds:
100 self._switch_buttons = leds.replace(' ', '').split(',');
101 else:
102 self._switch_buttons = []
93 103
94 # Do stuff with device nodes104 # Do stuff with device nodes
95 nodes = ioctl.matchInputNodes (**params)105 # nodes = ioctl.matchInputNodes (**params)
106 nodes = ioctl.matchInputNodes (vendor=params['vendor'], product=params['product'])
96 self._nodes = nodes107 self._nodes = nodes
97 for node in nodes:108 for node in nodes:
98 # Gimme gimme gimme!109 # Gimme gimme gimme!
@@ -129,7 +140,29 @@
129 # Shortcuts140 # Shortcuts
130 self._remap = lambda chord, **kwargs: self.keymap.remap(chord, device=self.id, **kwargs)141 self._remap = lambda chord, **kwargs: self.keymap.remap(chord, device=self.id, **kwargs)
131 self._inMap = lambda chord: self.keymap.deviceContains(chord, device=self.id)142 self._inMap = lambda chord: self.keymap.deviceContains(chord, device=self.id)
132 143
144 # State file?
145 self._led_state = None
146 self._state_fn = None
147 if self.id:
148 self._state_fn = self.id + ".state"
149 elif "vendor" in params and "product" in params:
150 self._state_fn = "%s:%s.state" % (params["vendor"], params["product"])
151
152 if self._state_fn:
153 state_path = os.path.expanduser(os.path.join(const.STATE_FILE_PATH, self._state_fn))
154 if os.path.exists(state_path):
155 savefile = ConfigParser.ConfigParser()
156 savefile.read(state_path)
157 if savefile.has_section('lastled'):
158 try:
159 stringCode = savefile.get('lastled', 'stringCode')
160 eventValue = savefile.get('lastled', 'eventValue')
161 self._led_state = (stringCode, eventValue)
162 key = mapping.Key(stringCode, eventValue, eventValue)
163 self._changeMode(key)
164 except KeyError:
165 pass
133 166
134 def __repr__ (self):167 def __repr__ (self):
135 params = (self.__class__.__name__, str(self.id), len(self._nodes))168 params = (self.__class__.__name__, str(self.id), len(self._nodes))
@@ -141,6 +174,28 @@
141 Returns a list of all the device nodes associated with this object.174 Returns a list of all the device nodes associated with this object.
142 """175 """
143 return self._nodes176 return self._nodes
177
178
179 def save_state(self):
180 """
181 Saves LED state for the device, if supported.
182 """
183 if self._state_fn and self._led_state:
184 (stringCode, eventValue) = self._led_state
185 savefile = ConfigParser.ConfigParser()
186
187 state_dir = os.path.expanduser(const.STATE_FILE_PATH)
188 if not os.path.exists(state_dir):
189 os.makedirs(state_dir)
190
191 state_path = os.path.join(state_dir, self._state_fn)
192 if os.path.exists(state_path):
193 savefile.read(state_path)
194 if not savefile.has_section('lastled'):
195 savefile.add_section('lastled')
196 savefile.set('lastled', 'stringCode', stringCode)
197 savefile.set('lastled', 'eventValue', eventValue)
198 savefile.write(open(state_path, 'wb'));
144 199
145 200
146 def read (self, node=None):201 def read (self, node=None):
@@ -188,7 +243,7 @@
188 events.append(event)243 events.append(event)
189 244
190 bytesToRead = eventSize245 bytesToRead = eventSize
191 data = ''246 data = ""
192 247
193 248
194 return events249 return events
@@ -208,6 +263,12 @@
208 ## Most likely a SYN packet. We see a lot of those.263 ## Most likely a SYN packet. We see a lot of those.
209 return False264 return False
210 265
266 # Some game pads send BTN events for a change in LED mode
267 # instead of EV_LED events, handle that nicely here if the
268 # appropriate config settings are set.
269 if (event.type == const.EV_KEY and stringCode in self._switch_buttons):
270 event.type = const.EV_LED
271
211 if event.type == const.EV_KEY and self._inMap(key):272 if event.type == const.EV_KEY and self._inMap(key):
212 if event.value == const.KEYDOWN:273 if event.value == const.KEYDOWN:
213 self._startEvent (key)274 self._startEvent (key)
@@ -239,8 +300,11 @@
239 300
240 301
241 elif event.type == const.EV_LED:302 elif event.type == const.EV_LED:
242 # We've received a notification of a change in LED status303 # Change in LED status
243 self._changeMode (key)304 if event.value == const.KEYDOWN:
305 self._led_state = (stringCode, event.value)
306 self._changeMode(key)
307
244 308
245 else:309 else:
246 # Don't have any specific rules for this type310 # Don't have any specific rules for this type
@@ -258,7 +322,6 @@
258 if cycle not in queue:322 if cycle not in queue:
259 queue[cycle] = cycle.getIter()323 queue[cycle] = cycle.getIter()
260 324
261
262 def _startEvent (self, key, modes=None):325 def _startEvent (self, key, modes=None):
263 """326 """
264 Starts whatever needs to be started with the additional327 Starts whatever needs to be started with the additional
265328
=== modified file 'pystromo-remap.py'
--- pystromo-remap.py 2008-10-13 22:00:38 +0000
+++ pystromo-remap.py 2011-01-30 05:34:10 +0000
@@ -211,7 +211,10 @@
211 # End the loop only if all the devices have finished.211 # End the loop only if all the devices have finished.
212 if not len(busyDevices):212 if not len(busyDevices):
213 looping = False213 looping = False
214 214
215# Save our state, if needed
216for device in inputs:
217 device.save_state()
215218
216# Destroy and close our output219# Destroy and close our output
217output.close()220output.close()

Subscribers

People subscribed via source and target branches

to all changes: