tradedangerous 12.7.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- py.typed +1 -0
- trade.py +49 -0
- tradedangerous/__init__.py +43 -0
- tradedangerous/cache.py +1381 -0
- tradedangerous/cli.py +136 -0
- tradedangerous/commands/TEMPLATE.py +74 -0
- tradedangerous/commands/__init__.py +244 -0
- tradedangerous/commands/buildcache_cmd.py +102 -0
- tradedangerous/commands/buy_cmd.py +427 -0
- tradedangerous/commands/commandenv.py +372 -0
- tradedangerous/commands/exceptions.py +94 -0
- tradedangerous/commands/export_cmd.py +150 -0
- tradedangerous/commands/import_cmd.py +222 -0
- tradedangerous/commands/local_cmd.py +243 -0
- tradedangerous/commands/market_cmd.py +207 -0
- tradedangerous/commands/nav_cmd.py +252 -0
- tradedangerous/commands/olddata_cmd.py +270 -0
- tradedangerous/commands/parsing.py +221 -0
- tradedangerous/commands/rares_cmd.py +298 -0
- tradedangerous/commands/run_cmd.py +1521 -0
- tradedangerous/commands/sell_cmd.py +262 -0
- tradedangerous/commands/shipvendor_cmd.py +60 -0
- tradedangerous/commands/station_cmd.py +68 -0
- tradedangerous/commands/trade_cmd.py +181 -0
- tradedangerous/commands/update_cmd.py +67 -0
- tradedangerous/corrections.py +55 -0
- tradedangerous/csvexport.py +234 -0
- tradedangerous/db/__init__.py +27 -0
- tradedangerous/db/adapter.py +192 -0
- tradedangerous/db/config.py +107 -0
- tradedangerous/db/engine.py +259 -0
- tradedangerous/db/lifecycle.py +332 -0
- tradedangerous/db/locks.py +208 -0
- tradedangerous/db/orm_models.py +500 -0
- tradedangerous/db/paths.py +113 -0
- tradedangerous/db/utils.py +661 -0
- tradedangerous/edscupdate.py +565 -0
- tradedangerous/edsmupdate.py +474 -0
- tradedangerous/formatting.py +210 -0
- tradedangerous/fs.py +156 -0
- tradedangerous/gui.py +1146 -0
- tradedangerous/mapping.py +133 -0
- tradedangerous/mfd/__init__.py +103 -0
- tradedangerous/mfd/saitek/__init__.py +3 -0
- tradedangerous/mfd/saitek/directoutput.py +678 -0
- tradedangerous/mfd/saitek/x52pro.py +195 -0
- tradedangerous/misc/checkpricebounds.py +287 -0
- tradedangerous/misc/clipboard.py +49 -0
- tradedangerous/misc/coord64.py +83 -0
- tradedangerous/misc/csvdialect.py +57 -0
- tradedangerous/misc/derp-sentinel.py +35 -0
- tradedangerous/misc/diff-system-csvs.py +159 -0
- tradedangerous/misc/eddb.py +81 -0
- tradedangerous/misc/eddn.py +349 -0
- tradedangerous/misc/edsc.py +437 -0
- tradedangerous/misc/edsm.py +121 -0
- tradedangerous/misc/importeddbstats.py +54 -0
- tradedangerous/misc/prices-json-exp.py +179 -0
- tradedangerous/misc/progress.py +194 -0
- tradedangerous/plugins/__init__.py +249 -0
- tradedangerous/plugins/edcd_plug.py +371 -0
- tradedangerous/plugins/eddblink_plug.py +861 -0
- tradedangerous/plugins/edmc_batch_plug.py +133 -0
- tradedangerous/plugins/spansh_plug.py +2647 -0
- tradedangerous/prices.py +211 -0
- tradedangerous/submit-distances.py +422 -0
- tradedangerous/templates/Added.csv +37 -0
- tradedangerous/templates/Category.csv +17 -0
- tradedangerous/templates/RareItem.csv +143 -0
- tradedangerous/templates/TradeDangerous.sql +338 -0
- tradedangerous/tools.py +40 -0
- tradedangerous/tradecalc.py +1302 -0
- tradedangerous/tradedb.py +2320 -0
- tradedangerous/tradeenv.py +313 -0
- tradedangerous/tradeenv.pyi +109 -0
- tradedangerous/tradeexcept.py +131 -0
- tradedangerous/tradeorm.py +183 -0
- tradedangerous/transfers.py +192 -0
- tradedangerous/utils.py +243 -0
- tradedangerous/version.py +16 -0
- tradedangerous-12.7.6.dist-info/METADATA +106 -0
- tradedangerous-12.7.6.dist-info/RECORD +87 -0
- tradedangerous-12.7.6.dist-info/WHEEL +5 -0
- tradedangerous-12.7.6.dist-info/entry_points.txt +3 -0
- tradedangerous-12.7.6.dist-info/licenses/LICENSE +373 -0
- tradedangerous-12.7.6.dist-info/top_level.txt +2 -0
- tradegui.py +24 -0
|
@@ -0,0 +1,678 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DirectOutput.py - Saitek DirectOutput.dll Python Wrapper
|
|
3
|
+
|
|
4
|
+
Version: 0.3
|
|
5
|
+
Author: Oliver "kfsone" Smith <oliver@kfs.org> 2014
|
|
6
|
+
Original Author: Frazzle
|
|
7
|
+
|
|
8
|
+
Description: Python wrapper class for DirectOutput functions.
|
|
9
|
+
|
|
10
|
+
This module consists of two classes - DirectOutput and DirectOutputDevice
|
|
11
|
+
|
|
12
|
+
DirectOutput directly calls C functions within DirectOutput.dll to allow Python control of the Saitek X52 Pro MFD and LEDs. Implemented as a class to allow sharing of dll object amongst functions
|
|
13
|
+
|
|
14
|
+
DirectOutputDevice is a wrapper around DirectOutput which automates setup and persists the device handle across functions. This class can be directly called or inherited to control an individual device (eg. X52 Pro)
|
|
15
|
+
|
|
16
|
+
Thanks to Spksh and ellF for the C# version of the wrapper which was very helpful in implementing this.
|
|
17
|
+
Thanks to Frazzle for the first Python version (no-longer compatible with the saitek driver).
|
|
18
|
+
|
|
19
|
+
Example Usage:
|
|
20
|
+
|
|
21
|
+
device = DirectOutputDevice()
|
|
22
|
+
device.AddPage(0, "Test", True)
|
|
23
|
+
device.SetString(0, 0, "Test String")
|
|
24
|
+
|
|
25
|
+
import time, sys
|
|
26
|
+
while True:
|
|
27
|
+
try:
|
|
28
|
+
time.sleep(1)
|
|
29
|
+
except:
|
|
30
|
+
#This is used to catch Ctrl+C, calling finish method is *very* important to de-initalize device.
|
|
31
|
+
device.finish()
|
|
32
|
+
sys.exit()
|
|
33
|
+
|
|
34
|
+
Saitek appear to periodically change the DLLs functions. At time of writing,
|
|
35
|
+
these are the functions listed in the DLL:
|
|
36
|
+
|
|
37
|
+
DirectOutput_Initialize
|
|
38
|
+
DirectOutput_Deinitialize
|
|
39
|
+
DirectOutput_AddPage
|
|
40
|
+
DirectOutput_DeleteFile
|
|
41
|
+
DirectOutput_DisplayFile
|
|
42
|
+
DirectOutput_Enumerate
|
|
43
|
+
DirectOutput_GetDeviceInstance
|
|
44
|
+
DirectOutput_GetDeviceType
|
|
45
|
+
DirectOutput_RegisterDeviceCallback
|
|
46
|
+
DirectOutput_RegisterPageCallback
|
|
47
|
+
DirectOutput_RegisterSoftButtonCallback
|
|
48
|
+
DirectOutput_RemovePage
|
|
49
|
+
DirectOutput_SaveFile
|
|
50
|
+
DirectOutput_SendServerFile
|
|
51
|
+
DirectOutput_SendServerMsg
|
|
52
|
+
DirectOutput_SetImage
|
|
53
|
+
DirectOutput_SetImageFromFile
|
|
54
|
+
DirectOutput_SetLed
|
|
55
|
+
DirectOutput_SetProfile
|
|
56
|
+
DirectOutput_SetString
|
|
57
|
+
|
|
58
|
+
DirectOutput_StartServer
|
|
59
|
+
DirectOutput_CloseServer
|
|
60
|
+
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
from tradedangerous.mfd import MissingDeviceError
|
|
64
|
+
|
|
65
|
+
import ctypes
|
|
66
|
+
import ctypes.wintypes
|
|
67
|
+
import logging
|
|
68
|
+
import os
|
|
69
|
+
import platform
|
|
70
|
+
import sys
|
|
71
|
+
import time
|
|
72
|
+
|
|
73
|
+
S_OK = 0x00000000
|
|
74
|
+
E_HANDLE = 0x80070006
|
|
75
|
+
E_INVALIDARG = 0x80070057
|
|
76
|
+
E_OUTOFMEMORY = 0x8007000E
|
|
77
|
+
E_PAGENOTACTIVE = -0xfbffff # Something munges it from it's actual value
|
|
78
|
+
E_BUFFERTOOSMALL = -0xfc0000
|
|
79
|
+
E_NOTIMPL = 0x80004001
|
|
80
|
+
ERROR_DEV_NOT_EXIST = 55
|
|
81
|
+
|
|
82
|
+
SOFTBUTTON_SELECT = 0x00000001
|
|
83
|
+
SOFTBUTTON_UP = 0x00000002
|
|
84
|
+
SOFTBUTTON_DOWN = 0x00000004
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class DirectOutput:
|
|
88
|
+
def __init__(self, dll_path):
|
|
89
|
+
"""
|
|
90
|
+
Creates python object to interact with DirecOutput.dll
|
|
91
|
+
|
|
92
|
+
Required Arguments:
|
|
93
|
+
dll_path -- String containing DirectOutput.dll location.
|
|
94
|
+
|
|
95
|
+
"""
|
|
96
|
+
logging.debug("DirectOutput.__init__")
|
|
97
|
+
self.DirectOutputDLL = ctypes.WinDLL(dll_path)
|
|
98
|
+
|
|
99
|
+
def Initialize(self, application_name):
|
|
100
|
+
"""
|
|
101
|
+
Function to call DirectOutput_Initialize
|
|
102
|
+
|
|
103
|
+
Required Arguments:
|
|
104
|
+
application_name -- String representing name of applicaiton - must be unique per-application
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
S_OK: The call completed sucesfully
|
|
108
|
+
E_OUTOFMEMORY: There was insufficient memory to complete this call.
|
|
109
|
+
E_INVALIDARG: The argument is invalid
|
|
110
|
+
E_HANDLE: The DirectOutputManager process could not be found
|
|
111
|
+
|
|
112
|
+
"""
|
|
113
|
+
logging.debug("DirectOutput.Initialize")
|
|
114
|
+
return self.DirectOutputDLL.DirectOutput_Initialize(ctypes.wintypes.LPWSTR(application_name))
|
|
115
|
+
|
|
116
|
+
def Deinitialize(self):
|
|
117
|
+
"""
|
|
118
|
+
Direct function call to DirectOutput_Deinitialize
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
S_OK: The call completed successfully.
|
|
122
|
+
E_HANDLE: DirectOutput was not initialized or was already deinitialized.
|
|
123
|
+
"""
|
|
124
|
+
logging.debug("DirectOutput.Deinitialize")
|
|
125
|
+
return self.DirectOutputDLL.DirectOutput_Deinitialize()
|
|
126
|
+
|
|
127
|
+
def RegisterDeviceCallback(self, function):
|
|
128
|
+
"""
|
|
129
|
+
Registers self.DeviceCallback to be called when devices get registered
|
|
130
|
+
|
|
131
|
+
Required Arugments:
|
|
132
|
+
function -- Function to call when a device registers
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
S_OK: The call completed successfully
|
|
136
|
+
E_HANDLE: DirectOutput was not initialized.
|
|
137
|
+
|
|
138
|
+
"""
|
|
139
|
+
logging.debug("DirectOutput.RegisterDeviceCallback")
|
|
140
|
+
return self.DirectOutputDLL.DirectOutput_RegisterDeviceCallback(function, 0)
|
|
141
|
+
|
|
142
|
+
def Enumerate(self, function):
|
|
143
|
+
"""
|
|
144
|
+
Direct call to DirectOutput_Enumerate
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
S_OK: The call completed successfully
|
|
148
|
+
E_HANDLE: DirectOutput was not initialized.
|
|
149
|
+
|
|
150
|
+
"""
|
|
151
|
+
logging.debug("DirectOutput.Enumerate")
|
|
152
|
+
return self.DirectOutputDLL.DirectOutput_Enumerate(function, 0)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def RegisterSoftButtonCallback(self, device_handle, function):
|
|
156
|
+
"""
|
|
157
|
+
Registers a function to be called when a soft button changes
|
|
158
|
+
|
|
159
|
+
Required Arugments:
|
|
160
|
+
device_handle -- ID of device
|
|
161
|
+
function -- Function to call when a soft button changes
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
S_OK: The call completed successfully.
|
|
165
|
+
E_HANDLE: The device handle specified is invalid.
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
logging.debug("DirectOutput.RegisterSoftButtonCallback({}, {})".format(device_handle, function))
|
|
169
|
+
return self.DirectOutputDLL.DirectOutput_RegisterSoftButtonCallback(ctypes.wintypes.HANDLE(device_handle), function, 0)
|
|
170
|
+
|
|
171
|
+
def RegisterPageCallback(self, device_handle, function):
|
|
172
|
+
"""
|
|
173
|
+
Registers a function to be called when page changes
|
|
174
|
+
|
|
175
|
+
Required Arugments:
|
|
176
|
+
device_handle -- ID of device
|
|
177
|
+
function -- Function to call when a page changes
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
S_OK: The call completed successfully.
|
|
181
|
+
E_HANDLE: The device handle specified is invalid.
|
|
182
|
+
"""
|
|
183
|
+
logging.debug("DirectOutput.RegisterPageCallback({}, {})".format(device_handle,function))
|
|
184
|
+
return self.DirectOutputDLL.DirectOutput_RegisterPageCallback(ctypes.wintypes.HANDLE(device_handle), function, 0)
|
|
185
|
+
|
|
186
|
+
def SetProfile(self, device_handle, profile):
|
|
187
|
+
"""
|
|
188
|
+
Sets the profile used on the device.
|
|
189
|
+
|
|
190
|
+
Required Arguments:
|
|
191
|
+
device_handle -- ID of device
|
|
192
|
+
profile -- full path of the profile to activate. passing None will clear the profile.
|
|
193
|
+
"""
|
|
194
|
+
logging.debug("DirectOutput.SetProfile({}, {})".format(device_handle, profile))
|
|
195
|
+
if profile:
|
|
196
|
+
return self.DirectOutputDLL.DirectOutput_SetProfile(ctypes.wintypes.HANDLE(device_handle), len(profile), ctypes.wintypes.LPWSTR(profile))
|
|
197
|
+
else:
|
|
198
|
+
return self.DirectOutputDLL.DirectOutput_SetProfile(ctypes.wintypes.HANDLE(device_handle), 0, 0)
|
|
199
|
+
|
|
200
|
+
def AddPage(self, device_handle, page, name, active):
|
|
201
|
+
"""
|
|
202
|
+
Adds a page to the MFD
|
|
203
|
+
|
|
204
|
+
Required Arguments:
|
|
205
|
+
device_handle -- ID of device
|
|
206
|
+
page -- page ID to add
|
|
207
|
+
name -- String specifying page name
|
|
208
|
+
active -- True if page is to become the active page, if False this will not change the active page
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
S_OK: The call completed successfully.
|
|
212
|
+
E_OUTOFMEMORY: Insufficient memory to complete the request.
|
|
213
|
+
E_INVALIDARG: The dwPage parameter already exists.
|
|
214
|
+
E_HANDLE: The device handle specified is invalid.
|
|
215
|
+
|
|
216
|
+
"""
|
|
217
|
+
logging.debug("DirectOutput.AddPage({}, {}, {}, {})".format(device_handle, page, name, active))
|
|
218
|
+
return self.DirectOutputDLL.DirectOutput_AddPage(ctypes.wintypes.HANDLE(device_handle), page, active)
|
|
219
|
+
|
|
220
|
+
def RemovePage(self, device_handle, page):
|
|
221
|
+
"""
|
|
222
|
+
Removes a page from the MFD
|
|
223
|
+
|
|
224
|
+
Required Arguments:
|
|
225
|
+
device_handle -- ID of device
|
|
226
|
+
page -- page ID to remove
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
S_OK: The call completed successfully.
|
|
230
|
+
E_INVALIDARG: The dwPage argument does not reference a valid page id.
|
|
231
|
+
E_HANDLE: The device handle specified is invalid.
|
|
232
|
+
|
|
233
|
+
"""
|
|
234
|
+
logging.debug("DirectOutput.RemovePage({}, {})".format(device_handle, page))
|
|
235
|
+
return self.DirectOutputDLL.DirectOutput_RemovePage(ctypes.wintypes.HANDLE(device_handle), page)
|
|
236
|
+
|
|
237
|
+
def SetLed(self, device_handle, page, led, value):
|
|
238
|
+
"""
|
|
239
|
+
Sets LED state on a given page
|
|
240
|
+
|
|
241
|
+
Required Arguments:
|
|
242
|
+
device_handle -- ID of device
|
|
243
|
+
page -- page number
|
|
244
|
+
led -- ID of LED to change
|
|
245
|
+
value -- value to set LED (1 = on, 0 = off)
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
S_OK: The call completes successfully.
|
|
249
|
+
E_INVALIDARG: The dwPage argument does not reference a valid page id, or the dwLed argument does not specifiy a valid LED id.
|
|
250
|
+
E_HANDLE: The device handle specified is invalid
|
|
251
|
+
|
|
252
|
+
"""
|
|
253
|
+
logging.debug("DirectOutput.SetLed({}, {}, {}, {})".format(device_handle, page, led, value))
|
|
254
|
+
return self.DirectOutputDLL.DirectOutput_SetLed(ctypes.wintypes.HANDLE(device_handle), page, led, value)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def SetString(self, device_handle, page, line, string):
|
|
258
|
+
"""
|
|
259
|
+
Sets a string to display on the MFD
|
|
260
|
+
|
|
261
|
+
Required Arguments:
|
|
262
|
+
device_handle -- ID of device
|
|
263
|
+
page -- the ID of the page to add the string to
|
|
264
|
+
line -- the line to display the string on (0 = top, 1 = middle, 2 = bottom)
|
|
265
|
+
string -- the string to display
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
S_OK: The call completes successfully.
|
|
269
|
+
E_INVALIDARG: The dwPage argument does not reference a valid page id, or the dwString argument does not reference a valid string id.
|
|
270
|
+
E_OUTOFMEMORY: Insufficient memory to complete the request.
|
|
271
|
+
E_HANDLE: The device handle specified is invalid.
|
|
272
|
+
|
|
273
|
+
"""
|
|
274
|
+
logging.debug("DirectOutput.SetString({}, {}, {}, {})".format(device_handle, page, line, string))
|
|
275
|
+
return self.DirectOutputDLL.DirectOutput_SetString(ctypes.wintypes.HANDLE(device_handle), page, line, len(string), ctypes.wintypes.LPWSTR(string))
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
class DirectOutputDevice:
|
|
279
|
+
|
|
280
|
+
class Buttons:
|
|
281
|
+
|
|
282
|
+
select, up, down = False, False, False
|
|
283
|
+
|
|
284
|
+
def __init__(self, bitmask):
|
|
285
|
+
self.bitmask = bitmask
|
|
286
|
+
if bitmask == 1:
|
|
287
|
+
self.select = True
|
|
288
|
+
elif bitmask == 2:
|
|
289
|
+
self.up = True
|
|
290
|
+
elif bitmask == 3:
|
|
291
|
+
self.up = True
|
|
292
|
+
self.select = True
|
|
293
|
+
elif bitmask == 4:
|
|
294
|
+
self.down = True
|
|
295
|
+
elif bitmask == 5:
|
|
296
|
+
self.down = True
|
|
297
|
+
self.select = True
|
|
298
|
+
elif bitmask == 6:
|
|
299
|
+
self.up = True
|
|
300
|
+
self.down = True
|
|
301
|
+
elif bitmask == 7:
|
|
302
|
+
self.up = True
|
|
303
|
+
self.down = True
|
|
304
|
+
self.select = True
|
|
305
|
+
|
|
306
|
+
def __repr__(self):
|
|
307
|
+
return "Select: "+str(self.select)+" Up: "+str(self.up)+" Down: "+str(self.down)
|
|
308
|
+
|
|
309
|
+
application_name = "GenericDevice"
|
|
310
|
+
device_handle = None
|
|
311
|
+
direct_output = None
|
|
312
|
+
debug_level = 0
|
|
313
|
+
|
|
314
|
+
def __init__(self, debug_level=0, name=None):
|
|
315
|
+
"""
|
|
316
|
+
Initialises device, creates internal state (device_handle) and registers callbacks.
|
|
317
|
+
|
|
318
|
+
"""
|
|
319
|
+
|
|
320
|
+
logging.info("DirectOutputDevice.__init__")
|
|
321
|
+
|
|
322
|
+
prog_dir = os.environ["ProgramFiles"]
|
|
323
|
+
if platform.machine().endswith('86'):
|
|
324
|
+
# 32-bit machine, nothing to worry about
|
|
325
|
+
pass
|
|
326
|
+
elif platform.machine().endswith('64'):
|
|
327
|
+
# 64-bit machine, are we a 32-bit python?
|
|
328
|
+
if platform.architecture()[0] == '32bit':
|
|
329
|
+
prog_dir = os.environ["ProgramFiles(x86)"]
|
|
330
|
+
dll_path = os.path.join(prog_dir, "Logitech\\DirectOutput\\DirectOutput.dll")
|
|
331
|
+
|
|
332
|
+
self.application_name = name or DirectOutputDevice.application_name
|
|
333
|
+
self.debug_level = debug_level
|
|
334
|
+
|
|
335
|
+
try:
|
|
336
|
+
logging.debug("DirectOutputDevice -> DirectOutput: {}".format(dll_path))
|
|
337
|
+
self.direct_output = DirectOutput(dll_path)
|
|
338
|
+
logging.debug("direct_output = {}".format(self.direct_output))
|
|
339
|
+
except WindowsError as e:
|
|
340
|
+
logging.warning("DLLError: {}: {}".format(dll_path, e.winerror))
|
|
341
|
+
raise DLLError(e.winerror) from None
|
|
342
|
+
|
|
343
|
+
result = self.direct_output.Initialize(self.application_name)
|
|
344
|
+
if result != S_OK:
|
|
345
|
+
logging.warning("direct_output.Initialize returned {}".format(result))
|
|
346
|
+
raise DirectOutputError(result)
|
|
347
|
+
|
|
348
|
+
logging.info("Creating callback closures.")
|
|
349
|
+
self.onDevice_closure = self._OnDeviceClosure()
|
|
350
|
+
logging.debug("onDevice_closure is {}".format(self.onDevice_closure))
|
|
351
|
+
self.onEnumerate_closure = self._OnEnumerateClosure()
|
|
352
|
+
logging.debug("onEnumerate_closure is {}".format(self.onEnumerate_closure))
|
|
353
|
+
self.onPage_closure = self._OnPageClosure()
|
|
354
|
+
logging.debug("onPage_closure is {}".format(self.onPage_closure))
|
|
355
|
+
self.onSoftButton_closure = self._OnSoftButtonClosure()
|
|
356
|
+
logging.debug("onSoftButton_closure is {}".format(self.onSoftButton_closure))
|
|
357
|
+
|
|
358
|
+
result = self.direct_output.RegisterDeviceCallback(self.onDevice_closure)
|
|
359
|
+
if result != S_OK:
|
|
360
|
+
logging.warning("RegisterDeviceCallback failed: {}".format(result))
|
|
361
|
+
self.finish()
|
|
362
|
+
raise DirectOutputError(result)
|
|
363
|
+
|
|
364
|
+
result = self.direct_output.Enumerate(self.onEnumerate_closure)
|
|
365
|
+
if result != S_OK:
|
|
366
|
+
logging.warning("Enumerate failed: {}".format(result))
|
|
367
|
+
self.finish()
|
|
368
|
+
raise DirectOutputError(result)
|
|
369
|
+
|
|
370
|
+
if not self.device_handle:
|
|
371
|
+
logging.warning("No device handle")
|
|
372
|
+
self.finish()
|
|
373
|
+
raise MissingDeviceError()
|
|
374
|
+
|
|
375
|
+
result = self.direct_output.RegisterSoftButtonCallback(self.device_handle, self.onSoftButton_closure)
|
|
376
|
+
if result != S_OK:
|
|
377
|
+
logging.warning("RegisterSoftButtonCallback failed")
|
|
378
|
+
self.finish()
|
|
379
|
+
raise DirectOutputError(result)
|
|
380
|
+
|
|
381
|
+
result = self.direct_output.RegisterPageCallback(self.device_handle, self.onPage_closure)
|
|
382
|
+
if result != S_OK:
|
|
383
|
+
logging.warning("RegisterPageCallback failed")
|
|
384
|
+
self.finish()
|
|
385
|
+
raise DirectOutputError(result)
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def __del__(self, *args, **kwargs):
|
|
389
|
+
logging.debug("DirectOutputDevice.__del__")
|
|
390
|
+
self.finish()
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def finish(self):
|
|
394
|
+
"""
|
|
395
|
+
De-initializes DLL. Must be called before program exit
|
|
396
|
+
|
|
397
|
+
"""
|
|
398
|
+
if self.direct_output:
|
|
399
|
+
logging.info("DirectOutputDevice deinitializing")
|
|
400
|
+
self.direct_output.Deinitialize()
|
|
401
|
+
self.direct_output = None
|
|
402
|
+
else:
|
|
403
|
+
logging.debug("nothing to do in finish()")
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def _OnDeviceClosure(self):
|
|
407
|
+
"""
|
|
408
|
+
Returns a pointer to function that calls self._OnDevice method. This allows class methods to be called from within DirectOutput.dll
|
|
409
|
+
|
|
410
|
+
http://stackoverflow.com/questions/7259794/how-can-i-get-methods-to-work-as-callbacks-with-python-ctypes
|
|
411
|
+
|
|
412
|
+
"""
|
|
413
|
+
OnDevice_Proto = ctypes.WINFUNCTYPE(None, ctypes.c_void_p, ctypes.c_bool, ctypes.c_void_p)
|
|
414
|
+
|
|
415
|
+
def func(hDevice, bAdded, pvContext):
|
|
416
|
+
logging.info("device callback closure func: {}, {}, {}".format(hDevice, bAdded, pvContext))
|
|
417
|
+
self._OnDevice(hDevice, bAdded, pvContext)
|
|
418
|
+
|
|
419
|
+
return OnDevice_Proto(func)
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def _OnEnumerateClosure(self):
|
|
423
|
+
"""
|
|
424
|
+
Returns a pointer to function that calls self._OnEnumerate method. This allows class methods to be called from within DirectOutput.dll
|
|
425
|
+
|
|
426
|
+
http://stackoverflow.com/questions/7259794/how-can-i-get-methods-to-work-as-callbacks-with-python-ctypes
|
|
427
|
+
|
|
428
|
+
"""
|
|
429
|
+
OnEnumerate_Proto = ctypes.WINFUNCTYPE(None, ctypes.c_void_p, ctypes.c_void_p)
|
|
430
|
+
|
|
431
|
+
def func(hDevice, pvContext):
|
|
432
|
+
logging.info("enumerate callback closure func: {}, {}".format(hDevice, pvContext))
|
|
433
|
+
self._OnEnumerate(hDevice, pvContext)
|
|
434
|
+
|
|
435
|
+
return OnEnumerate_Proto(func)
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def _OnPageClosure(self):
|
|
439
|
+
"""
|
|
440
|
+
Returns a pointer to function that calls self._OnPage method. This allows class methods to be called from within DirectOutput.dll
|
|
441
|
+
|
|
442
|
+
http://stackoverflow.com/questions/7259794/how-can-i-get-methods-to-work-as-callbacks-with-python-ctypes
|
|
443
|
+
|
|
444
|
+
"""
|
|
445
|
+
OnPage_Proto = ctypes.WINFUNCTYPE(None, ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.c_bool, ctypes.c_void_p)
|
|
446
|
+
|
|
447
|
+
def func(hDevice, dwPage, bActivated, pvContext):
|
|
448
|
+
logging.info("page callback closure: {}, {}, {}, {}".format(hDevice, dwPage, bActivated, pvContext))
|
|
449
|
+
self._OnPage(hDevice, dwPage, bActivated, pvContext)
|
|
450
|
+
|
|
451
|
+
return OnPage_Proto(func)
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def _OnSoftButtonClosure(self):
|
|
455
|
+
"""
|
|
456
|
+
Returns a pointer to function that calls self._OnSoftButton method. This allows class methods to be called from within DirectOutput.dll
|
|
457
|
+
|
|
458
|
+
http://stackoverflow.com/questions/7259794/how-can-i-get-methods-to-work-as-callbacks-with-python-ctypes
|
|
459
|
+
|
|
460
|
+
"""
|
|
461
|
+
OnSoftButton_Proto = ctypes.WINFUNCTYPE(None, ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.c_void_p)
|
|
462
|
+
|
|
463
|
+
def func(hDevice, dwButtons, pvContext):
|
|
464
|
+
logging.info("soft button callback closure: {}, {}, {}".format(hDevice, dwButtons, pvContext))
|
|
465
|
+
self._OnSoftButton(hDevice, dwButtons, pvContext)
|
|
466
|
+
|
|
467
|
+
return OnSoftButton_Proto(func)
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
def _OnDevice(self, hDevice, bAdded, pvContext):
|
|
471
|
+
"""
|
|
472
|
+
Internal function to register device handle
|
|
473
|
+
|
|
474
|
+
"""
|
|
475
|
+
if not bAdded:
|
|
476
|
+
raise NotImplementedError("Received a message that a device went away.")
|
|
477
|
+
if self.device_handle and self.device_handle != hDevice:
|
|
478
|
+
raise IndexError("Too many Saitek devices present")
|
|
479
|
+
logging.info("_OnDevice")
|
|
480
|
+
self.device_handle = hDevice
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def _OnEnumerate(self, hDevice, pvContext):
|
|
484
|
+
"""
|
|
485
|
+
Internal function to process a device enumeration
|
|
486
|
+
|
|
487
|
+
"""
|
|
488
|
+
logging.info("_OnEnumerate")
|
|
489
|
+
self._OnDevice(hDevice, True, pvContext)
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
def _OnPage(self, hDevice, dwPage, bActivated, pvContext):
|
|
493
|
+
"""
|
|
494
|
+
Method called when page changes. Calls self.OnPage to hide hDevice and pvContext from end-user
|
|
495
|
+
|
|
496
|
+
"""
|
|
497
|
+
logging.info("_OnPage")
|
|
498
|
+
self.OnPage(dwPage, bActivated)
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
def _OnSoftButton(self, hDevice, dwButtons, pvContext):
|
|
502
|
+
"""
|
|
503
|
+
Method called when soft button changes. Calls self.OnSoftButton to hide hDevice and pvContext from end-user. Also hides change of page softbutton and press-up.
|
|
504
|
+
|
|
505
|
+
"""
|
|
506
|
+
logging.info("_OnSoftButton")
|
|
507
|
+
self.OnSoftButton(self.Buttons(dwButtons))
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
def OnPage(self, page, activated):
|
|
511
|
+
"""
|
|
512
|
+
Method called when a page changes. This should be overwritten by inheriting class
|
|
513
|
+
|
|
514
|
+
Required Arguments:
|
|
515
|
+
page -- page_id passed to AddPage
|
|
516
|
+
activated -- true if this page has become the active page, false if this page was the active page
|
|
517
|
+
|
|
518
|
+
"""
|
|
519
|
+
logging.info("OnPage({}, {})".format(page, activated))
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
def OnSoftButton(self, buttons):
|
|
523
|
+
"""
|
|
524
|
+
Method called when a soft button changes. This should be overwritten by inheriting class
|
|
525
|
+
|
|
526
|
+
Required Arguments:
|
|
527
|
+
buttons - Buttons object representing button state
|
|
528
|
+
|
|
529
|
+
"""
|
|
530
|
+
logging.info("OnSoftButton({})".format(buttons))
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
def SetProfile(self, profile):
|
|
534
|
+
"""
|
|
535
|
+
Sets the profile used on the device.
|
|
536
|
+
|
|
537
|
+
Required Arguments:
|
|
538
|
+
device_handle -- ID of device
|
|
539
|
+
profile -- full path of the profile to activate. passing None will clear the profile.
|
|
540
|
+
"""
|
|
541
|
+
logging.debug("SetProfile({})".format(profile))
|
|
542
|
+
return self.direct_output.SetProfile(self.device_handle, profile)
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
def AddPage(self, page, name, active):
|
|
546
|
+
"""
|
|
547
|
+
Adds a page to the MFD. If overriden by a derived class, you should
|
|
548
|
+
call super().AddPage(*args, **kwargs)
|
|
549
|
+
|
|
550
|
+
Required Arguments:
|
|
551
|
+
page -- page ID to add
|
|
552
|
+
name -- String specifying page name
|
|
553
|
+
active -- True if page is to become the active page, if False this will not change the active page
|
|
554
|
+
|
|
555
|
+
"""
|
|
556
|
+
logging.info("AddPage({}, {}, {})".format(page, name, active))
|
|
557
|
+
self.direct_output.AddPage(self.device_handle, page, name, active)
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
def RemovePage(self, page):
|
|
561
|
+
"""
|
|
562
|
+
Removes a page from the MFD
|
|
563
|
+
|
|
564
|
+
Required Arguments:
|
|
565
|
+
page -- page ID to remove
|
|
566
|
+
|
|
567
|
+
"""
|
|
568
|
+
logging.info("RemovePage({})".format(page))
|
|
569
|
+
result = self.direct_output.RemovePage(self.device_handle, page)
|
|
570
|
+
if result != S_OK:
|
|
571
|
+
logging.error("RemovePage failed: {}".format(result))
|
|
572
|
+
self.finish()
|
|
573
|
+
raise DirectOutputError(result)
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
def SetString(self, page, line, string):
|
|
577
|
+
"""
|
|
578
|
+
Sets a string to display on the MFD
|
|
579
|
+
|
|
580
|
+
Required Arguments:
|
|
581
|
+
page -- the ID of the page to add the string to
|
|
582
|
+
line -- the line to display the string on (0 = top, 1 = middle, 2 = bottom)
|
|
583
|
+
string -- the string to display
|
|
584
|
+
"""
|
|
585
|
+
logging.debug("SetString({}, {}, {})".format(page, line, string))
|
|
586
|
+
result = self.direct_output.SetString(self.device_handle, page, line, string)
|
|
587
|
+
if result != S_OK:
|
|
588
|
+
logging.warning("SetString failed: {}".format(result))
|
|
589
|
+
self.finish()
|
|
590
|
+
raise DirectOutputError(result)
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
def SetLed(self, page, led, value):
|
|
594
|
+
"""
|
|
595
|
+
Sets LED state on a given page
|
|
596
|
+
|
|
597
|
+
Required Arguments:
|
|
598
|
+
page -- page number
|
|
599
|
+
led -- ID of LED to change
|
|
600
|
+
value -- value to set LED (1 = on, 0 = off)
|
|
601
|
+
|
|
602
|
+
"""
|
|
603
|
+
logging.debug("SetLed({}, {}, {})".format(page, led, value))
|
|
604
|
+
result = self.direct_output.SetLed(self.device_handle, page, led, value)
|
|
605
|
+
if result != S_OK:
|
|
606
|
+
logging.warning("SetLed failed: {}".format(result))
|
|
607
|
+
self.finish()
|
|
608
|
+
raise DirectOutputError(result)
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
class DeviceNotFoundError(Exception):
|
|
612
|
+
|
|
613
|
+
def __str__(self):
|
|
614
|
+
return "No Compatible Device Found"
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
class DLLError(Exception):
|
|
618
|
+
|
|
619
|
+
def __init__(self, error_code):
|
|
620
|
+
self.error_code = error_code
|
|
621
|
+
if error_code == 126:
|
|
622
|
+
self.msg = "specified file does not exist"
|
|
623
|
+
elif error_code == 193:
|
|
624
|
+
self.msg = "possible 32/64 bit mismatch between Python interpreter and DLL. Make sure you have installed both the 32- and 64-bit driver from Saitek's website"
|
|
625
|
+
else:
|
|
626
|
+
self.msg = "unspecified error"
|
|
627
|
+
|
|
628
|
+
def __str__(self):
|
|
629
|
+
return "Unable to load DirectOutput.dll - "+self.msg
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
class DirectOutputError(Exception):
|
|
633
|
+
Errors = {
|
|
634
|
+
E_HANDLE : "Invalid device handle specified.",
|
|
635
|
+
E_INVALIDARG : "An argument is invalid, and I don't mean it has a poorly leg.",
|
|
636
|
+
E_OUTOFMEMORY : "Download more RAM.",
|
|
637
|
+
E_PAGENOTACTIVE : "Page not active, stupid page.",
|
|
638
|
+
E_BUFFERTOOSMALL : "Buffer used was too small. Use a bigger buffer. See also E_OUTOFMEMORY.",
|
|
639
|
+
E_NOTIMPL : "Feature not implemented, allegedly"
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
def __init__(self, error_code):
|
|
643
|
+
self.error_code = error_code
|
|
644
|
+
if error_code in self.Errors:
|
|
645
|
+
self.msg = self.Errors[error_code]
|
|
646
|
+
else:
|
|
647
|
+
self.msg = "Unspecified DirectOutput Error - "+str(hex(error_code))
|
|
648
|
+
|
|
649
|
+
def __str__(self):
|
|
650
|
+
return self.msg
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
if __name__ == '__main__':
|
|
654
|
+
# If you want it to go to a file?
|
|
655
|
+
# logging.basicConfig(filename='directoutput.log', filemode='w', level=logging.DEBUG, format='%(asctime)s %(name)s [%(filename)s:%(lineno)d] %(message)s')
|
|
656
|
+
# If you want less verbose logging?
|
|
657
|
+
# logging.basicConfig(level=logging.INFO, format='%(asctime)s %(name)s [%(filename)s:%(lineno)d] %(message)s')
|
|
658
|
+
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(name)s [%(filename)s:%(lineno)d] %(message)s')
|
|
659
|
+
|
|
660
|
+
device = DirectOutputDevice(debug_level=1)
|
|
661
|
+
print("Device initialized")
|
|
662
|
+
|
|
663
|
+
device.AddPage(0, "Test", True)
|
|
664
|
+
print("Test Page added")
|
|
665
|
+
|
|
666
|
+
device.SetString(0, 0, "Test String")
|
|
667
|
+
print("Test String added")
|
|
668
|
+
|
|
669
|
+
device.AddPage(1, "Other", False)
|
|
670
|
+
device.AddPage(2, "Another", False)
|
|
671
|
+
|
|
672
|
+
while True:
|
|
673
|
+
try:
|
|
674
|
+
time.sleep(1)
|
|
675
|
+
except: # noqa: E722
|
|
676
|
+
# This is used to catch Ctrl+C, calling finish method is *very* important to de-initalize device.
|
|
677
|
+
device.finish()
|
|
678
|
+
sys.exit()
|