psychopy 2024.1.4__py3-none-any.whl → 2024.2.0__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.
Potentially problematic release.
This version of psychopy might be problematic. Click here for more details.
- psychopy/.DS_Store +0 -0
- psychopy/CHANGELOG.txt +206 -0
- psychopy/GIT_SHA +1 -0
- psychopy/VERSION +1 -0
- psychopy/__init__.py +77 -15
- psychopy/app/Resources/classic/plugin16.png +0 -0
- psychopy/app/Resources/classic/plugin16@2x.png +0 -0
- psychopy/app/Resources/dark/plugin16.png +0 -0
- psychopy/app/Resources/dark/plugin16@2x.png +0 -0
- psychopy/app/Resources/light/plugin16.png +0 -0
- psychopy/app/Resources/light/plugin16@2x.png +0 -0
- psychopy/app/__init__.py +76 -2
- psychopy/app/_psychopyApp.py +126 -101
- psychopy/app/builder/builder.py +14 -10
- psychopy/app/builder/dialogs/__init__.py +8 -8
- psychopy/app/builder/dialogs/dlgsConditions.py +12 -13
- psychopy/app/builder/dialogs/paramCtrls.py +24 -57
- psychopy/app/builder/validators.py +2 -2
- psychopy/app/coder/codeEditorBase.py +8 -8
- psychopy/app/coder/coder.py +4 -4
- psychopy/app/connections/sendusage.py +2 -2
- psychopy/app/connections/updates.py +9 -9
- psychopy/app/dialogs.py +34 -2
- psychopy/app/idle.py +31 -0
- psychopy/app/jobs.py +21 -3
- psychopy/app/linuxconfig/__init__.py +9 -0
- psychopy/app/locale/ar_001/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ar_001/LC_MESSAGE/messages.po +4602 -2540
- psychopy/app/locale/es_CO/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/es_CO/LC_MESSAGE/messages.po +56 -54
- psychopy/app/locale/es_ES/LC_MESSAGE/messages.po +53 -43
- psychopy/app/locale/es_US/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/es_US/LC_MESSAGE/messages.po +56 -54
- psychopy/app/locale/ja_JP/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ja_JP/LC_MESSAGE/messages.po +1011 -942
- psychopy/app/locale/pt_PT/LC_MESSAGE/messages.po +9415 -5
- psychopy/app/pavlovia_ui/_base.py +33 -3
- psychopy/app/pavlovia_ui/search.py +0 -1
- psychopy/app/plugin_manager/dialog.py +104 -51
- psychopy/app/plugin_manager/packages.py +5 -0
- psychopy/app/plugin_manager/plugins.py +145 -67
- psychopy/app/preferencesDlg.py +8 -8
- psychopy/app/psychopyApp.py +11 -5
- psychopy/app/ribbon.py +124 -14
- psychopy/app/runner/runner.py +6 -1
- psychopy/app/stdout/stdOutRich.py +27 -11
- psychopy/app/themes/icons.py +52 -2
- psychopy/assets/__init__.py +0 -0
- psychopy/assets/click.png +0 -0
- psychopy/assets/clicknext.png +0 -0
- psychopy/assets/next.png +0 -0
- psychopy/assets/psychopy.ico +0 -0
- psychopy/assets/psychopy.png +0 -0
- psychopy/assets/templates/__init__.py +0 -0
- psychopy/assets/touch.png +0 -0
- psychopy/assets/touchnext.png +0 -0
- psychopy/assets/window.ico +0 -0
- psychopy/changes/2023.1.0.md +9 -0
- psychopy/changes/2024.1.0.md +16 -0
- psychopy/changes/__init__.py +0 -0
- psychopy/clock.py +2 -2
- psychopy/colors.py +2 -1
- psychopy/compatibility.py +53 -1
- psychopy/contrib/.DS_Store +0 -0
- psychopy/contrib/configobj/__init__.py +10 -8
- psychopy/data/__init__.py +3 -2
- psychopy/data/base.py +5 -5
- psychopy/data/experiment.py +130 -4
- psychopy/data/routine.py +56 -0
- psychopy/data/staircase.py +2 -2
- psychopy/data/trial.py +559 -97
- psychopy/data/utils.py +56 -21
- psychopy/demos/.DS_Store +0 -0
- psychopy/demos/builder/.DS_Store +0 -0
- psychopy/demos/builder/Design Templates/.DS_Store +0 -0
- psychopy/demos/builder/Experiments/.DS_Store +0 -0
- psychopy/demos/builder/Feature Demos/.DS_Store +0 -0
- psychopy/demos/builder/Feature Demos/buttonBox/buttonBoxDemo.psyexp +375 -0
- psychopy/demos/builder/Feature Demos/buttonBox/readme.md +5 -0
- psychopy/demos/builder/Feature Demos/pilotMode/pilotMode.psyexp +433 -0
- psychopy/demos/builder/Feature Demos/pilotMode/readme.md +7 -0
- psychopy/demos/builder/Hardware/.DS_Store +0 -0
- psychopy/demos/builder/Helper Tools/.DS_Store +0 -0
- psychopy/demos/coder/.DS_Store +0 -0
- psychopy/demos/coder/hardware/testSoundLatency.py +2 -2
- psychopy/demos/coder/iohub/.DS_Store +0 -0
- psychopy/demos/coder/misc/hdf5_2_csv +33 -0
- psychopy/event.py +30 -29
- psychopy/experiment/.DS_Store +0 -0
- psychopy/experiment/_experiment.py +6 -6
- psychopy/experiment/components/.DS_Store +0 -0
- psychopy/experiment/components/__init__.py +6 -3
- psychopy/experiment/components/_base.py +286 -131
- psychopy/experiment/components/aperture/.DS_Store +0 -0
- psychopy/experiment/components/brush/.DS_Store +0 -0
- psychopy/experiment/components/button/.DS_Store +0 -0
- psychopy/experiment/components/button/__init__.py +5 -1
- psychopy/experiment/components/buttonBox/.DS_Store +0 -0
- psychopy/experiment/components/camera/.DS_Store +0 -0
- psychopy/experiment/components/code/.DS_Store +0 -0
- psychopy/experiment/components/dots/.DS_Store +0 -0
- psychopy/experiment/components/eyetracker_record/.DS_Store +0 -0
- psychopy/experiment/components/eyetracker_record/__init__.py +92 -30
- psychopy/experiment/components/form/.DS_Store +0 -0
- psychopy/experiment/components/form/__init__.py +6 -2
- psychopy/experiment/components/grating/.DS_Store +0 -0
- psychopy/experiment/components/grating/__init__.py +14 -3
- psychopy/experiment/components/image/.DS_Store +0 -0
- psychopy/experiment/components/image/__init__.py +14 -3
- psychopy/experiment/components/joyButtons/.DS_Store +0 -0
- psychopy/experiment/components/joystick/.DS_Store +0 -0
- psychopy/experiment/components/keyboard/.DS_Store +0 -0
- psychopy/experiment/components/keyboard/__init__.py +22 -10
- psychopy/experiment/components/microphone/.DS_Store +0 -0
- psychopy/experiment/components/microphone/__init__.py +59 -39
- psychopy/experiment/components/mouse/.DS_Store +0 -0
- psychopy/experiment/components/mouse/__init__.py +44 -29
- psychopy/experiment/components/movie/.DS_Store +0 -0
- psychopy/experiment/components/movie/__init__.py +1 -1
- psychopy/experiment/components/panorama/.DS_Store +0 -0
- psychopy/experiment/components/parallelOut/.DS_Store +0 -0
- psychopy/experiment/components/patch/.DS_Store +0 -0
- psychopy/experiment/components/polygon/.DS_Store +0 -0
- psychopy/experiment/components/polygon/__init__.py +26 -6
- psychopy/experiment/components/progress/.DS_Store +0 -0
- psychopy/experiment/components/ratingScale/.DS_Store +0 -0
- psychopy/experiment/components/resourceManager/.DS_Store +0 -0
- psychopy/experiment/components/roi/.DS_Store +0 -0
- psychopy/experiment/components/roi/__init__.py +5 -0
- psychopy/experiment/components/routineSettings/.DS_Store +0 -0
- psychopy/experiment/components/routineSettings/__init__.py +57 -10
- psychopy/experiment/components/serialOut/.DS_Store +0 -0
- psychopy/experiment/components/settings/.DS_Store +0 -0
- psychopy/experiment/components/settings/__init__.py +117 -42
- psychopy/experiment/components/slider/.DS_Store +0 -0
- psychopy/experiment/components/sound/.DS_Store +0 -0
- psychopy/experiment/components/sound/__init__.py +54 -19
- psychopy/experiment/components/static/.DS_Store +0 -0
- psychopy/experiment/components/static/__init__.py +1 -1
- psychopy/experiment/components/text/.DS_Store +0 -0
- psychopy/experiment/components/text/__init__.py +28 -3
- psychopy/experiment/components/textbox/.DS_Store +0 -0
- psychopy/experiment/components/textbox/__init__.py +12 -2
- psychopy/experiment/components/unknown/.DS_Store +0 -0
- psychopy/experiment/components/unknown/__init__.py +1 -2
- psychopy/experiment/components/unknownPlugin/.DS_Store +0 -0
- psychopy/experiment/components/unknownPlugin/__init__.py +2 -2
- psychopy/experiment/components/variable/.DS_Store +0 -0
- psychopy/experiment/flow.py +11 -4
- psychopy/experiment/loops.py +85 -37
- psychopy/experiment/params.py +74 -32
- psychopy/experiment/py2js_transpiler.py +8 -1
- psychopy/experiment/routines/.DS_Store +0 -0
- psychopy/experiment/routines/_base.py +102 -22
- psychopy/experiment/routines/counterbalance/.DS_Store +0 -0
- psychopy/experiment/routines/counterbalance/__init__.py +5 -1
- psychopy/experiment/routines/eyetracker_calibrate/.DS_Store +0 -0
- psychopy/experiment/routines/eyetracker_validate/.DS_Store +0 -0
- psychopy/experiment/routines/pavlovia_survey/.DS_Store +0 -0
- psychopy/experiment/routines/photodiodeValidator/.DS_Store +0 -0
- psychopy/experiment/routines/photodiodeValidator/__init__.py +6 -5
- psychopy/experiment/routines/unknown/.DS_Store +0 -0
- psychopy/gui/wxgui.py +4 -4
- psychopy/hardware/.DS_Store +0 -0
- psychopy/hardware/__init__.py +1 -1
- psychopy/hardware/base.py +12 -0
- psychopy/hardware/camera/__init__.py +1 -15
- psychopy/hardware/cedrus.py +10 -11
- psychopy/hardware/crs/colorcal.py +13 -22
- psychopy/hardware/crs/optical.py +10 -20
- psychopy/hardware/emulator.py +17 -14
- psychopy/hardware/eyetracker.py +42 -118
- psychopy/hardware/gammasci.py +4 -15
- psychopy/hardware/keyboard.py +102 -10
- psychopy/hardware/listener.py +3 -0
- psychopy/hardware/microphone.py +148 -18
- psychopy/hardware/minolta.py +8 -15
- psychopy/hardware/photodiode.py +191 -16
- psychopy/hardware/photometer/__init__.py +11 -19
- psychopy/hardware/pr.py +8 -15
- psychopy/hardware/speaker.py +39 -4
- psychopy/info.py +0 -71
- psychopy/iohub/.DS_Store +0 -0
- psychopy/iohub/__init__.py +1 -1
- psychopy/iohub/client/__init__.py +30 -20
- psychopy/iohub/client/keyboard.py +24 -24
- psychopy/iohub/datastore/__init__.py +2 -2
- psychopy/iohub/datastore/util.py +2 -2
- psychopy/iohub/default_config.yaml +1 -1
- psychopy/iohub/devices/.DS_Store +0 -0
- psychopy/iohub/devices/__init__.py +112 -25
- psychopy/iohub/devices/deviceConfigValidation.py +2 -1
- psychopy/iohub/devices/experiment/default_experiment.yaml +12 -1
- psychopy/iohub/devices/experiment/supported_config_settings.yaml +5 -1
- psychopy/iohub/devices/eyetracker/.DS_Store +0 -0
- psychopy/iohub/devices/eyetracker/__init__.py +46 -0
- psychopy/iohub/devices/eyetracker/calibration/procedure.py +2 -2
- psychopy/iohub/devices/eyetracker/hw/gazepoint/__init__.py +14 -2
- psychopy/iohub/devices/eyetracker/hw/mouse/eyetracker.py +3 -4
- psychopy/iohub/server.py +2 -2
- psychopy/iohub/start_iohub_process.py +3 -0
- psychopy/iohub/util/__init__.py +62 -70
- psychopy/layout.py +5 -5
- psychopy/logging.py +8 -1
- psychopy/microphone.py +10 -37
- psychopy/platform_specific/__init__.py +0 -2
- psychopy/platform_specific/darwin.py +1 -3
- psychopy/platform_specific/linux.py +31 -33
- psychopy/platform_specific/win32.py +38 -13
- psychopy/plugins/__init__.py +148 -116
- psychopy/plugins/util.py +39 -0
- psychopy/preferences/Darwin.spec +4 -2
- psychopy/preferences/FreeBSD.spec +4 -2
- psychopy/preferences/Linux.spec +4 -2
- psychopy/preferences/Windows.spec +4 -2
- psychopy/preferences/baseNoArch.spec +4 -2
- psychopy/preferences/preferences.py +47 -24
- psychopy/projects/pavlovia.py +47 -4
- psychopy/scripts/psyexpCompile.py +0 -4
- psychopy/session.py +153 -21
- psychopy/sound/__init__.py +31 -21
- psychopy/sound/_base.py +20 -3
- psychopy/sound/audioclip.py +320 -33
- psychopy/sound/backend_ptb.py +47 -58
- psychopy/sound/backend_pygame.py +1 -1
- psychopy/sound/backend_pysound.py +6 -15
- psychopy/sound/transcribe.py +53 -0
- psychopy/tests/.DS_Store +0 -0
- psychopy/tests/data/.DS_Store +0 -0
- psychopy/tests/data/TestUnknownPluginComponent_load_resave.psyexp +135 -0
- psychopy/tests/data/Test_textbox/test_ori_0_bottom right.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_0_center.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_0_top left.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_120_bottom right.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_120_center.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_120_top left.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_180_bottom right.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_180_center.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_180_top left.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_240_bottom right.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_240_center.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_240_top left.png +0 -0
- psychopy/tests/data/correctScript/.DS_Store +0 -0
- psychopy/tests/data/test_components/testClearKeyboard/testClearKeyboard.psyexp +200 -0
- psychopy/tests/data/test_session/.DS_Store +0 -0
- psychopy/tests/data/test_session/root/testFutureTrials/testFutureTrials.psyexp +155 -0
- psychopy/tests/data/test_session/root/testTrialNav/trialNav.psyexp +158 -0
- psychopy/tests/test_app/.DS_Store +0 -0
- psychopy/tests/test_app/conftest.py +2 -2
- psychopy/tests/test_app/test_speed.py +4 -1
- psychopy/tests/test_data/test_TrialHandler2.py +146 -1
- psychopy/tests/test_experiment/.DS_Store +0 -0
- psychopy/tests/test_experiment/needs_wx/genComponsTemplate.py +3 -3
- psychopy/tests/test_experiment/needs_wx/test_components.py +2 -2
- psychopy/tests/test_experiment/test_components/test_KeyboardComponent.py +28 -0
- psychopy/tests/test_experiment/test_components/test_UnknownPluginComponent.py +27 -0
- psychopy/tests/test_experiment/test_components/test_base_components.py +58 -0
- psychopy/tests/test_experiment/test_py2js.py +1 -1
- psychopy/tests/test_hardware/test_keyboard.py +31 -0
- psychopy/tests/test_hardware/test_ports.py +1 -11
- psychopy/tests/test_liaison/test_Liaison.py +47 -0
- psychopy/tests/test_misc/test_core.py +5 -0
- psychopy/tests/test_session/test_Session.py +5 -1
- psychopy/tests/test_tools/test_versionchooser.py +39 -8
- psychopy/tests/test_visual/test_all_stimuli.py +0 -97
- psychopy/tests/test_visual/test_image.py +6 -5
- psychopy/tests/test_visual/test_textbox.py +36 -0
- psychopy/tests/utils.py +4 -0
- psychopy/tools/filetools.py +1 -1
- psychopy/tools/pkgtools.py +160 -137
- psychopy/tools/versionchooser.py +10 -10
- psychopy/tools/wizard.py +3 -3
- psychopy/visual/.DS_Store +0 -0
- psychopy/visual/backends/pygletbackend.py +24 -13
- psychopy/visual/basevisual.py +5 -11
- psychopy/visual/button.py +2 -14
- psychopy/visual/helpers.py +5 -5
- psychopy/visual/line.py +1 -2
- psychopy/visual/movie2.py +7 -816
- psychopy/visual/movie3.py +7 -589
- psychopy/visual/movies/__init__.py +8 -11
- psychopy/visual/movies/frame.py +5 -2
- psychopy/visual/movies/players/ffpyplayer_player.py +5 -2
- psychopy/visual/noise.py +8 -7
- psychopy/visual/patch.py +7 -16
- psychopy/visual/radial.py +9 -7
- psychopy/visual/ratingscale.py +8 -1415
- psychopy/visual/secondorder.py +10 -9
- psychopy/visual/shape.py +7 -2
- psychopy/visual/text.py +1 -1
- psychopy/visual/textbox2/textbox2.py +28 -5
- {psychopy-2024.1.4.dist-info → psychopy-2024.2.0.dist-info}/METADATA +8 -13
- {psychopy-2024.1.4.dist-info → psychopy-2024.2.0.dist-info}/RECORD +307 -213
- {psychopy-2024.1.4.dist-info → psychopy-2024.2.0.dist-info}/WHEEL +1 -1
- psychopy/app/Resources/click.png +0 -0
- psychopy/app/Resources/next.png +0 -0
- psychopy/experiment/components/patch/__init__.py +0 -121
- psychopy/experiment/components/patch/classic/patch.png +0 -0
- psychopy/experiment/components/patch/dark/patch.png +0 -0
- psychopy/experiment/components/patch/dark/patch@2x.png +0 -0
- psychopy/experiment/components/patch/light/patch.png +0 -0
- psychopy/experiment/components/patch/light/patch@2x.png +0 -0
- psychopy/experiment/components/ratingScale/__init__.py +0 -337
- psychopy/experiment/components/ratingScale/classic/ratingscale.png +0 -0
- psychopy/experiment/components/ratingScale/classic/ratingscale@2x.png +0 -0
- psychopy/experiment/components/ratingScale/dark/ratingScale@2x.png +0 -0
- psychopy/experiment/components/ratingScale/dark/ratingscale.png +0 -0
- psychopy/experiment/components/ratingScale/light/ratingScale@2x.png +0 -0
- psychopy/experiment/components/ratingScale/light/ratingscale.png +0 -0
- psychopy/platform_specific/posix.py +0 -16
- psychopy/tests/test_sound/test_microphone.py +0 -217
- psychopy/tests/test_visual/test_ratingScale.py +0 -299
- /psychopy/{app/Resources → assets}/Psychopy Window Favicon@16w.png +0 -0
- /psychopy/{app/Resources → assets}/Psychopy Window Favicon@32w.png +0 -0
- /psychopy/{app/Resources → assets}/USB-C.png +0 -0
- /psychopy/{app/Resources → assets}/USB.png +0 -0
- /psychopy/{app/Resources → assets}/creditCard.png +0 -0
- /psychopy/{app/Resources → assets}/default.mp3 +0 -0
- /psychopy/{app/Resources → assets}/default.mp4 +0 -0
- /psychopy/{app/Resources → assets}/default.png +0 -0
- /psychopy/{app/Resources → assets/templates}/instruct1.png +0 -0
- /psychopy/{app/Resources → assets/templates}/instruct2.png +0 -0
- {psychopy-2024.1.4.dist-info → psychopy-2024.2.0.dist-info}/entry_points.txt +0 -0
- {psychopy-2024.1.4.dist-info → psychopy-2024.2.0.dist-info}/licenses/AUTHORS.md +0 -0
- {psychopy-2024.1.4.dist-info → psychopy-2024.2.0.dist-info}/licenses/LICENSE +0 -0
psychopy/hardware/speaker.py
CHANGED
|
@@ -1,13 +1,32 @@
|
|
|
1
1
|
from psychopy.hardware import BaseDevice
|
|
2
|
-
from psychopy.sound import setDevice, getDevices
|
|
2
|
+
from psychopy.sound import setDevice, getDevices, backend
|
|
3
|
+
from psychopy import logging
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
class SpeakerDevice(BaseDevice):
|
|
6
7
|
def __init__(self, index):
|
|
7
|
-
|
|
8
|
+
profiles = self.getAvailableDevices()
|
|
9
|
+
|
|
10
|
+
# if index is default (-1), setup a default device index
|
|
8
11
|
if not isinstance(index, (int, float)) or index < 0:
|
|
9
|
-
|
|
10
|
-
|
|
12
|
+
index = profiles[0]['index'] # initialize as the first device
|
|
13
|
+
|
|
14
|
+
# check if a default device is already set and update index
|
|
15
|
+
if hasattr(backend, 'defaultOutput'):
|
|
16
|
+
defaultDevice = backend.defaultOutput
|
|
17
|
+
if isinstance(defaultDevice, (int, float)):
|
|
18
|
+
# if a default device index is set, use it
|
|
19
|
+
index = defaultDevice
|
|
20
|
+
elif isinstance(defaultDevice, str):
|
|
21
|
+
# if a default device is set by name, find it
|
|
22
|
+
for profile in profiles:
|
|
23
|
+
if profile['deviceName'] == defaultDevice:
|
|
24
|
+
index = profile['index']
|
|
25
|
+
|
|
26
|
+
available_index = [profile['index'] for profile in profiles]
|
|
27
|
+
if index < 0 or index not in available_index:
|
|
28
|
+
logging.error("No speaker device found with index %d" % index)
|
|
29
|
+
|
|
11
30
|
# store index
|
|
12
31
|
self.index = index
|
|
13
32
|
# set global device (best we can do for now)
|
|
@@ -40,6 +59,22 @@ class SpeakerDevice(BaseDevice):
|
|
|
40
59
|
|
|
41
60
|
return self.index == index
|
|
42
61
|
|
|
62
|
+
def testDevice(self):
|
|
63
|
+
"""
|
|
64
|
+
Play a simple sound to check whether this device is working.
|
|
65
|
+
"""
|
|
66
|
+
from psychopy.sound import Sound
|
|
67
|
+
import time
|
|
68
|
+
# create a basic sound
|
|
69
|
+
snd = Sound(
|
|
70
|
+
speaker=self.index,
|
|
71
|
+
value="A"
|
|
72
|
+
)
|
|
73
|
+
# play the sound for 1s
|
|
74
|
+
snd.play()
|
|
75
|
+
time.sleep(1)
|
|
76
|
+
snd.stop()
|
|
77
|
+
|
|
43
78
|
@staticmethod
|
|
44
79
|
def getAvailableDevices():
|
|
45
80
|
devices = []
|
psychopy/info.py
CHANGED
|
@@ -650,77 +650,6 @@ def _getHashGitHead(gdir='.'):
|
|
|
650
650
|
return '(unknown branch)'
|
|
651
651
|
|
|
652
652
|
|
|
653
|
-
def _getSvnVersion(filename):
|
|
654
|
-
"""Tries to discover the svn version (revision #) for a file.
|
|
655
|
-
|
|
656
|
-
Not thoroughly tested; untested on Windows Vista, Win 7, FreeBSD
|
|
657
|
-
|
|
658
|
-
:Author:
|
|
659
|
-
- 2010 written by Jeremy Gray
|
|
660
|
-
"""
|
|
661
|
-
if not (os.path.exists(filename) and
|
|
662
|
-
os.path.isdir(os.path.join(os.path.dirname(filename), '.svn'))):
|
|
663
|
-
return None, None, None
|
|
664
|
-
svnRev, svnLastChangedRev, svnUrl = None, None, None
|
|
665
|
-
if (sys.platform in ('darwin', 'freebsd') or
|
|
666
|
-
sys.platform.startswith('linux')):
|
|
667
|
-
try:
|
|
668
|
-
# expects a filename, not dir
|
|
669
|
-
svninfo = shellCall(['svn', 'info', filename])
|
|
670
|
-
except Exception:
|
|
671
|
-
svninfo = ''
|
|
672
|
-
for line in svninfo.splitlines():
|
|
673
|
-
if line.startswith('URL:'):
|
|
674
|
-
svnUrl = line.split()[1]
|
|
675
|
-
elif line.startswith('Revision: '):
|
|
676
|
-
svnRev = line.split()[1]
|
|
677
|
-
elif line.startswith('Last Changed Rev'):
|
|
678
|
-
svnLastChangedRev = line.split()[3]
|
|
679
|
-
else:
|
|
680
|
-
# worked for me on Win XP sp2 with TortoiseSVN (SubWCRev.exe)
|
|
681
|
-
try:
|
|
682
|
-
stdout = shellCall(['subwcrev', filename])
|
|
683
|
-
except Exception:
|
|
684
|
-
stdout = ''
|
|
685
|
-
for line in stdout.splitlines():
|
|
686
|
-
if line.startswith('Last committed at revision'):
|
|
687
|
-
svnRev = line.split()[4]
|
|
688
|
-
elif line.startswith('Updated to revision'):
|
|
689
|
-
svnLastChangedRev = line.split()[3]
|
|
690
|
-
return svnRev, svnLastChangedRev, svnUrl
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
def _getHgVersion(filename):
|
|
694
|
-
"""Tries to discover the mercurial (hg) parent and id of a file.
|
|
695
|
-
|
|
696
|
-
Not thoroughly tested; untested on Windows Vista, Win 7, FreeBSD
|
|
697
|
-
|
|
698
|
-
:Author:
|
|
699
|
-
- 2010 written by Jeremy Gray
|
|
700
|
-
"""
|
|
701
|
-
dirname = os.path.dirname
|
|
702
|
-
if (not os.path.exists(filename) or
|
|
703
|
-
not os.path.isdir(os.path.join(dirname(filename), '.hg'))):
|
|
704
|
-
return None
|
|
705
|
-
try:
|
|
706
|
-
hgParentLines, err = shellCall(['hg', 'parents', filename],
|
|
707
|
-
stderr=True)
|
|
708
|
-
changeset = hgParentLines.splitlines()[0].split()[-1]
|
|
709
|
-
except Exception:
|
|
710
|
-
changeset = ''
|
|
711
|
-
try:
|
|
712
|
-
hgID, err = shellCall(['hg', 'id', '-nibt', dirname(filename)],
|
|
713
|
-
stderr=True)
|
|
714
|
-
except Exception:
|
|
715
|
-
if err:
|
|
716
|
-
hgID = ''
|
|
717
|
-
|
|
718
|
-
if len(hgID) or len(changeset):
|
|
719
|
-
return hgID.strip() + ' | parent: ' + changeset.strip()
|
|
720
|
-
else:
|
|
721
|
-
return None
|
|
722
|
-
|
|
723
|
-
|
|
724
653
|
def _getUserNameUID():
|
|
725
654
|
"""Return user name, UID.
|
|
726
655
|
|
psychopy/iohub/.DS_Store
ADDED
|
Binary file
|
psychopy/iohub/__init__.py
CHANGED
|
@@ -26,7 +26,7 @@ try:
|
|
|
26
26
|
_DATA_STORE_AVAILABLE = True
|
|
27
27
|
except ImportError:
|
|
28
28
|
print2err('WARNING: pytables package not found. ',
|
|
29
|
-
'ioHub functionality will be disabled.')
|
|
29
|
+
'ioHub hdf5 datastore functionality will be disabled.')
|
|
30
30
|
except Exception:
|
|
31
31
|
printExceptionDetailsToStdErr()
|
|
32
32
|
|
|
@@ -249,7 +249,7 @@ class ioHubConnection():
|
|
|
249
249
|
mouse=hub.devices.mouse
|
|
250
250
|
mouse_position = mouse.getPosition()
|
|
251
251
|
|
|
252
|
-
print
|
|
252
|
+
print('mouse position: ', mouse_position)
|
|
253
253
|
|
|
254
254
|
# Returns something like:
|
|
255
255
|
# >> mouse position: [-211.0, 371.0]
|
|
@@ -264,9 +264,10 @@ class ioHubConnection():
|
|
|
264
264
|
ioHubConfig)
|
|
265
265
|
|
|
266
266
|
if ioHubConnection.ACTIVE_CONNECTION is not None:
|
|
267
|
-
raise RuntimeError('An existing ioHubConnection is already open.'
|
|
268
|
-
'
|
|
269
|
-
'to access it; or use
|
|
267
|
+
raise RuntimeError('An existing ioHubConnection is already open. Use '
|
|
268
|
+
'iohub.client.ioHubConnection.getActiveConnection() '
|
|
269
|
+
'to access it; or use '
|
|
270
|
+
'iohub.ioHubConnection.getActiveConnection().quit() '
|
|
270
271
|
'to close it.')
|
|
271
272
|
Computer.psychopy_process = psutil.Process()
|
|
272
273
|
|
|
@@ -417,13 +418,19 @@ class ioHubConnection():
|
|
|
417
418
|
None
|
|
418
419
|
|
|
419
420
|
"""
|
|
420
|
-
if device_label
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
self.
|
|
425
|
-
|
|
426
|
-
|
|
421
|
+
if device_label and isinstance(device_label, str):
|
|
422
|
+
device_label = device_label.lower()
|
|
423
|
+
if device_label == 'all':
|
|
424
|
+
self.allEvents = []
|
|
425
|
+
self._sendToHubServer(('RPC', 'clearEventBuffer', [True, ]))
|
|
426
|
+
try:
|
|
427
|
+
self.getDevice('keyboard')._clearLocalEvents()
|
|
428
|
+
except:
|
|
429
|
+
pass
|
|
430
|
+
else:
|
|
431
|
+
d = self.devices.getDevice(device_label)
|
|
432
|
+
if d:
|
|
433
|
+
d.clearEvents()
|
|
427
434
|
elif device_label in [None, '', False]:
|
|
428
435
|
self.allEvents = []
|
|
429
436
|
self._sendToHubServer(('RPC', 'clearEventBuffer', [False, ]))
|
|
@@ -432,9 +439,8 @@ class ioHubConnection():
|
|
|
432
439
|
except:
|
|
433
440
|
pass
|
|
434
441
|
else:
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
d.clearEvents()
|
|
442
|
+
raise ValueError(
|
|
443
|
+
'Invalid device_label value: {}'.format(device_label))
|
|
438
444
|
|
|
439
445
|
def sendMessageEvent(self, text, category='', offset=0.0, sec_time=None):
|
|
440
446
|
"""
|
|
@@ -1085,8 +1091,12 @@ class ioHubConnection():
|
|
|
1085
1091
|
dev_cls_name = dev_cls_name[cls_name_start + 1:]
|
|
1086
1092
|
else:
|
|
1087
1093
|
dev_mod_pth = '{0}{1}'.format(dev_mod_pth, dev_name)
|
|
1088
|
-
|
|
1089
|
-
|
|
1094
|
+
# try to import EyeTracker class from given path
|
|
1095
|
+
try:
|
|
1096
|
+
dev_import_result = import_device(dev_mod_pth, dev_cls_name)
|
|
1097
|
+
except ModuleNotFoundError:
|
|
1098
|
+
# if not found, try importing from root (may have entry point)
|
|
1099
|
+
dev_import_result = import_device("psychopy.iohub.devices", dev_cls_name)
|
|
1090
1100
|
dev_cls, dev_cls_name, evt_cls_list = dev_import_result
|
|
1091
1101
|
|
|
1092
1102
|
DeviceConstants.addClassMapping(dev_cls)
|
|
@@ -1114,9 +1124,7 @@ class ioHubConnection():
|
|
|
1114
1124
|
if local_class:
|
|
1115
1125
|
d = local_class(self, dev_cls_name, dev_config)
|
|
1116
1126
|
else:
|
|
1117
|
-
|
|
1118
|
-
full_device_class_name = full_device_class_name.replace('eyetracker.EyeTracker', 'EyeTracker')
|
|
1119
|
-
d = ioHubDeviceView(self, full_device_class_name, dev_cls_name, dev_config)
|
|
1127
|
+
d = ioHubDeviceView(self, dev_mod_pth + "." + dev_cls_name, dev_cls_name, dev_config)
|
|
1120
1128
|
|
|
1121
1129
|
self.devices.addDevice(name, d)
|
|
1122
1130
|
return d
|
|
@@ -1311,7 +1319,9 @@ class ioHubConnection():
|
|
|
1311
1319
|
self.udp_client.sendTo(('STOP_IOHUB_SERVER',))
|
|
1312
1320
|
self.udp_client.close()
|
|
1313
1321
|
if Computer.iohub_process:
|
|
1314
|
-
|
|
1322
|
+
# This wait() used to have timeout=5, removing it to allow
|
|
1323
|
+
# sufficient time for all iohub devices to be closed.
|
|
1324
|
+
r = Computer.iohub_process.wait()
|
|
1315
1325
|
print('ioHub Server Process Completed With Code: ', r)
|
|
1316
1326
|
except TimeoutError:
|
|
1317
1327
|
print('Warning: TimeoutExpired, Killing ioHub Server process.')
|
|
@@ -11,12 +11,13 @@ from ..util import win32MessagePump
|
|
|
11
11
|
from ..devices.keyboard import KeyboardInputEvent
|
|
12
12
|
from ..constants import EventConstants, KeyboardConstants
|
|
13
13
|
|
|
14
|
-
#pylint: disable=protected-access
|
|
14
|
+
# pylint: disable=protected-access
|
|
15
15
|
|
|
16
16
|
getTime = Computer.getTime
|
|
17
17
|
kb_cls_attr_names = KeyboardInputEvent.CLASS_ATTRIBUTE_NAMES
|
|
18
18
|
kb_mod_codes2labels = KeyboardConstants._modifierCodes2Labels
|
|
19
19
|
|
|
20
|
+
|
|
20
21
|
class KeyboardEvent(ioEvent):
|
|
21
22
|
"""
|
|
22
23
|
Base class for KeyboardPress and KeyboardRelease events.
|
|
@@ -42,13 +43,13 @@ class KeyboardEvent(ioEvent):
|
|
|
42
43
|
def __init__(self, ioe_array):
|
|
43
44
|
super(KeyboardEvent, self).__init__(ioe_array)
|
|
44
45
|
for aname, aindex, in list(self._attrib_index.items()):
|
|
45
|
-
setattr(self, '_%s'%aname, ioe_array[aindex])
|
|
46
|
+
setattr(self, '_%s' % aname, ioe_array[aindex])
|
|
46
47
|
self._modifiers = kb_mod_codes2labels(self._modifiers)
|
|
47
48
|
|
|
48
49
|
@property
|
|
49
50
|
def key(self):
|
|
50
51
|
return self._key
|
|
51
|
-
|
|
52
|
+
|
|
52
53
|
@property
|
|
53
54
|
def char(self):
|
|
54
55
|
"""The unicode value of the keyboard event, if available. This field is
|
|
@@ -87,7 +88,7 @@ class KeyboardEvent(ioEvent):
|
|
|
87
88
|
def __str__(self):
|
|
88
89
|
pstr = ioEvent.__str__(self)
|
|
89
90
|
return '{}, key: {} char: {}, modifiers: {}'.format(pstr, self.key,
|
|
90
|
-
|
|
91
|
+
self.char, self.modifiers)
|
|
91
92
|
|
|
92
93
|
def __eq__(self, v):
|
|
93
94
|
if isinstance(v, KeyboardEvent):
|
|
@@ -113,8 +114,8 @@ class KeyboardRelease(KeyboardEvent):
|
|
|
113
114
|
|
|
114
115
|
def __init__(self, ioe_array):
|
|
115
116
|
super(KeyboardRelease, self).__init__(ioe_array)
|
|
116
|
-
#self._duration = ioe_array[self._attrib_index['duration']]
|
|
117
|
-
#self._press_event_id = ioe_array[self._attrib_index['press_event_id']]
|
|
117
|
+
# self._duration = ioe_array[self._attrib_index['duration']]
|
|
118
|
+
# self._press_event_id = ioe_array[self._attrib_index['press_event_id']]
|
|
118
119
|
|
|
119
120
|
@property
|
|
120
121
|
def duration(self):
|
|
@@ -153,47 +154,47 @@ class KeyboardRelease(KeyboardEvent):
|
|
|
153
154
|
class Keyboard(ioHubDeviceView):
|
|
154
155
|
"""The Keyboard device provides access to KeyboardPress and KeyboardRelease
|
|
155
156
|
events as well as the current keyboard state.
|
|
156
|
-
|
|
157
|
+
|
|
157
158
|
Examples:
|
|
158
159
|
|
|
159
160
|
A. Print all keyboard events received for 5 seconds::
|
|
160
|
-
|
|
161
|
+
|
|
161
162
|
from psychopy.iohub import launchHubServer
|
|
162
163
|
from psychopy.core import getTime
|
|
163
|
-
|
|
164
|
+
|
|
164
165
|
# Start the ioHub process. 'io' can now be used during the
|
|
165
166
|
# experiment to access iohub devices and read iohub device events.
|
|
166
167
|
io = launchHubServer()
|
|
167
|
-
|
|
168
|
+
|
|
168
169
|
keyboard = io.devices.keyboard
|
|
169
|
-
|
|
170
|
+
|
|
170
171
|
# Check for and print any Keyboard events received for 5 seconds.
|
|
171
172
|
stime = getTime()
|
|
172
173
|
while getTime()-stime < 5.0:
|
|
173
174
|
for e in keyboard.getEvents():
|
|
174
175
|
print(e)
|
|
175
|
-
|
|
176
|
+
|
|
176
177
|
# Stop the ioHub Server
|
|
177
178
|
io.quit()
|
|
178
|
-
|
|
179
|
+
|
|
179
180
|
B. Wait for a keyboard press event (max of 5 seconds)::
|
|
180
|
-
|
|
181
|
+
|
|
181
182
|
from psychopy.iohub import launchHubServer
|
|
182
183
|
from psychopy.core import getTime
|
|
183
|
-
|
|
184
|
+
|
|
184
185
|
# Start the ioHub process. 'io' can now be used during the
|
|
185
186
|
# experiment to access iohub devices and read iohub device events.
|
|
186
187
|
io = launchHubServer()
|
|
187
|
-
|
|
188
|
+
|
|
188
189
|
keyboard = io.devices.keyboard
|
|
189
|
-
|
|
190
|
+
|
|
190
191
|
# Wait for a key keypress event ( max wait of 5 seconds )
|
|
191
192
|
presses = keyboard.waitForPresses(maxWait=5.0)
|
|
192
|
-
|
|
193
|
+
|
|
193
194
|
print(presses)
|
|
194
|
-
|
|
195
|
+
|
|
195
196
|
# Stop the ioHub Server
|
|
196
|
-
io.quit()
|
|
197
|
+
io.quit()
|
|
197
198
|
"""
|
|
198
199
|
KEY_PRESS = EventConstants.KEYBOARD_PRESS
|
|
199
200
|
KEY_RELEASE = EventConstants.KEYBOARD_RELEASE
|
|
@@ -228,8 +229,8 @@ class Keyboard(ioHubDeviceView):
|
|
|
228
229
|
"""
|
|
229
230
|
kb_state = self.getCurrentDeviceState()
|
|
230
231
|
|
|
231
|
-
events = {int(k):v for k,v in list(kb_state.get('events').items())}
|
|
232
|
-
pressed_keys = {int(k):v for k,v in list(kb_state.get('pressed_keys',{}).items())}
|
|
232
|
+
events = {int(k): v for k, v in list(kb_state.get('events').items())}
|
|
233
|
+
pressed_keys = {int(k): v for k, v in list(kb_state.get('pressed_keys', {}).items())}
|
|
233
234
|
|
|
234
235
|
self._reporting = kb_state.get('reporting_events')
|
|
235
236
|
self._pressed_keys.clear()
|
|
@@ -289,11 +290,10 @@ class Keyboard(ioHubDeviceView):
|
|
|
289
290
|
self._reporting = self.enableEventReporting(r)
|
|
290
291
|
return self._reporting
|
|
291
292
|
|
|
292
|
-
|
|
293
293
|
def clearEvents(self, event_type=None, filter_id=None):
|
|
294
294
|
self._clearLocalEvents(event_type)
|
|
295
295
|
return self._clearEventsRPC(event_type=event_type,
|
|
296
|
-
|
|
296
|
+
filter_id=filter_id)
|
|
297
297
|
|
|
298
298
|
def getKeys(self, keys=None, chars=None, ignoreKeys=None, mods=None, duration=None,
|
|
299
299
|
etype=None, clear=True):
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import os
|
|
8
8
|
import atexit
|
|
9
9
|
import numpy as np
|
|
10
|
-
from
|
|
10
|
+
from packaging.version import Version
|
|
11
11
|
from ..server import DeviceEvent
|
|
12
12
|
from ..constants import EventConstants
|
|
13
13
|
from ..errors import ioHubError, printExceptionDetailsToStdErr, print2err
|
|
@@ -15,7 +15,7 @@ from ..errors import ioHubError, printExceptionDetailsToStdErr, print2err
|
|
|
15
15
|
import tables
|
|
16
16
|
from tables import parameters, StringCol, UInt32Col, UInt16Col, NoSuchNodeError
|
|
17
17
|
|
|
18
|
-
if
|
|
18
|
+
if Version(tables.__version__) < Version('3'):
|
|
19
19
|
from tables import openFile as open_file
|
|
20
20
|
|
|
21
21
|
create_table = "createTable"
|
psychopy/iohub/datastore/util.py
CHANGED
|
@@ -12,10 +12,10 @@ import numpy
|
|
|
12
12
|
|
|
13
13
|
from ..errors import print2err
|
|
14
14
|
|
|
15
|
-
from
|
|
15
|
+
from packaging.version import Version
|
|
16
16
|
import tables
|
|
17
17
|
|
|
18
|
-
if
|
|
18
|
+
if Version(tables.__version__) < Version('3'):
|
|
19
19
|
from tables import openFile as open_file
|
|
20
20
|
|
|
21
21
|
walk_groups = "walkGroups"
|
|
Binary file
|
|
@@ -6,9 +6,13 @@
|
|
|
6
6
|
import collections
|
|
7
7
|
import copy
|
|
8
8
|
import os
|
|
9
|
+
import importlib
|
|
9
10
|
from collections import deque
|
|
10
11
|
from operator import itemgetter
|
|
11
12
|
|
|
13
|
+
import sys
|
|
14
|
+
from psychopy.plugins.util import getEntryPoints
|
|
15
|
+
|
|
12
16
|
import numpy as np
|
|
13
17
|
|
|
14
18
|
from .computer import Computer
|
|
@@ -137,7 +141,8 @@ class ioObject(metaclass=ioObjectMetaClass):
|
|
|
137
141
|
rpcList.append(d)
|
|
138
142
|
return rpcList
|
|
139
143
|
|
|
140
|
-
|
|
144
|
+
|
|
145
|
+
# ########## Base Abstract Device that all other Devices inherit from ##########
|
|
141
146
|
|
|
142
147
|
|
|
143
148
|
class Device(ioObject):
|
|
@@ -264,7 +269,7 @@ class Device(ioObject):
|
|
|
264
269
|
None
|
|
265
270
|
|
|
266
271
|
Returns:
|
|
267
|
-
(dict): The dictionary of the device configuration settings used
|
|
272
|
+
(dict): The dictionary of the device configuration settings used
|
|
268
273
|
to create the device.
|
|
269
274
|
|
|
270
275
|
"""
|
|
@@ -278,14 +283,23 @@ class Device(ioObject):
|
|
|
278
283
|
contents.
|
|
279
284
|
|
|
280
285
|
Args:
|
|
281
|
-
event_type_id (int): If specified, provides the ioHub DeviceEvent ID for which events
|
|
286
|
+
event_type_id (int): If specified, provides the ioHub DeviceEvent ID for which events
|
|
287
|
+
should be returned for. Events that have occurred but do not match the event ID
|
|
288
|
+
specified are ignored. Event type ID's can be accessed via the EventConstants class;
|
|
289
|
+
all available event types are class attributes of EventConstants.
|
|
282
290
|
|
|
283
|
-
clearEvents (int): Can be used to indicate if the events being returned should also be
|
|
291
|
+
clearEvents (int): Can be used to indicate if the events being returned should also be
|
|
292
|
+
removed from the device event buffer. True (the default) indicates to remove events
|
|
293
|
+
being returned. False results in events being left in the device event buffer.
|
|
284
294
|
|
|
285
|
-
asType (str): Optional kwarg giving the object type to return events as. Valid values
|
|
295
|
+
asType (str): Optional kwarg giving the object type to return events as. Valid values
|
|
296
|
+
are 'namedtuple' (the default), 'dict', 'list', or 'object'.
|
|
286
297
|
|
|
287
298
|
Returns:
|
|
288
|
-
(list): New events that the ioHub has received since the last getEvents() or clearEvents()
|
|
299
|
+
(list): New events that the ioHub has received since the last getEvents() or clearEvents()
|
|
300
|
+
call to the device. Events are ordered by the ioHub time of each event, older event at
|
|
301
|
+
index 0. The event object type is determined by the asType parameter passed to the method.
|
|
302
|
+
By default a namedtuple object is returned for each event.
|
|
289
303
|
|
|
290
304
|
"""
|
|
291
305
|
self._iohub_server.processDeviceEvents()
|
|
@@ -321,11 +335,13 @@ class Device(ioObject):
|
|
|
321
335
|
call_proc_events=False)
|
|
322
336
|
else:
|
|
323
337
|
if filter_id:
|
|
324
|
-
[currentEvents.extend(
|
|
325
|
-
|
|
338
|
+
[currentEvents.extend(
|
|
339
|
+
[fe for fe in event if fe[
|
|
340
|
+
DeviceEvent.EVENT_FILTER_ID_INDEX] == filter_id]
|
|
341
|
+
) for event in list(self._iohub_event_buffer.values())]
|
|
326
342
|
else:
|
|
327
|
-
[currentEvents.extend(
|
|
328
|
-
for
|
|
343
|
+
[currentEvents.extend(event)
|
|
344
|
+
for event in list(self._iohub_event_buffer.values())]
|
|
329
345
|
|
|
330
346
|
if clearEvents is True and len(currentEvents) > 0:
|
|
331
347
|
self.clearEvents(filter_id=filter_id, call_proc_events=False)
|
|
@@ -384,7 +400,10 @@ class Device(ioObject):
|
|
|
384
400
|
|
|
385
401
|
|
|
386
402
|
Args:
|
|
387
|
-
enabled (bool): True (default) == Start to report device events to the ioHub Process.
|
|
403
|
+
enabled (bool): True (default) == Start to report device events to the ioHub Process.
|
|
404
|
+
False == Stop Reporting Events to the ioHub Process. Most Device types automatically
|
|
405
|
+
start sending events to the ioHUb Process, however some devices like the EyeTracker and
|
|
406
|
+
AnlogInput device's do not. The setting to control this behavior is 'auto_report_events'
|
|
388
407
|
|
|
389
408
|
Returns:
|
|
390
409
|
bool: The current reporting state.
|
|
@@ -516,14 +535,14 @@ class Device(ioObject):
|
|
|
516
535
|
if self.isReportingEvents():
|
|
517
536
|
self._native_event_buffer.append(e)
|
|
518
537
|
|
|
519
|
-
def _addEventListener(self,
|
|
538
|
+
def _addEventListener(self, event, eventTypeIDs):
|
|
520
539
|
for ei in eventTypeIDs:
|
|
521
|
-
self._event_listeners.setdefault(ei, []).append(
|
|
540
|
+
self._event_listeners.setdefault(ei, []).append(event)
|
|
522
541
|
|
|
523
|
-
def _removeEventListener(self,
|
|
542
|
+
def _removeEventListener(self, event):
|
|
524
543
|
for etypelisteners in list(self._event_listeners.values()):
|
|
525
|
-
if
|
|
526
|
-
etypelisteners.remove(
|
|
544
|
+
if event in etypelisteners:
|
|
545
|
+
etypelisteners.remove(event)
|
|
527
546
|
|
|
528
547
|
def _getEventListeners(self, forEventType):
|
|
529
548
|
return self._event_listeners.get(forEventType, [])
|
|
@@ -652,10 +671,16 @@ class Device(ioObject):
|
|
|
652
671
|
Since any callbacks should take as little time to process as possible,
|
|
653
672
|
a two stage approach is used to turn a native device event into an ioHub
|
|
654
673
|
Device event representation:
|
|
655
|
-
#. This method is called by the native device interface as a callback, providing the necessary
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
#. The
|
|
674
|
+
#. This method is called by the native device interface as a callback, providing the necessary
|
|
675
|
+
# information to be able to create an ioHub event. As little processing should be done in this
|
|
676
|
+
# method as possible.
|
|
677
|
+
#. The data passed to this method, along with the time the callback was called, are passed as a
|
|
678
|
+
# tuple to the Device classes _addNativeEventToBuffer method.
|
|
679
|
+
#. During the ioHub Servers event processing routine, any new native events that have been added
|
|
680
|
+
# to the ioHub Server using the _addNativeEventToBuffer method are passed individually to the
|
|
681
|
+
# _getIOHubEventObject method, which must also be implemented by the given Device subclass.
|
|
682
|
+
#. The _getIOHubEventObject method is responsible for the actual conversion of the native event
|
|
683
|
+
# representation to the required ioHub Event representation for the accociated event type.
|
|
659
684
|
|
|
660
685
|
Args:
|
|
661
686
|
args(tuple): tuple of non keyword arguments passed to the callback.
|
|
@@ -703,7 +728,8 @@ class Device(ioObject):
|
|
|
703
728
|
def __del__(self):
|
|
704
729
|
self._close()
|
|
705
730
|
|
|
706
|
-
|
|
731
|
+
|
|
732
|
+
# ########## Base Device Event that all other Device Events inherit from ##
|
|
707
733
|
|
|
708
734
|
|
|
709
735
|
class DeviceEvent(ioObject):
|
|
@@ -920,16 +946,77 @@ class DeviceEvent(ioObject):
|
|
|
920
946
|
@classmethod
|
|
921
947
|
def createEventAsNamedTuple(cls, valueList):
|
|
922
948
|
return cls.namedTupleClass(*valueList)
|
|
949
|
+
|
|
950
|
+
|
|
923
951
|
#
|
|
924
952
|
# Import Devices and DeviceEvents
|
|
925
953
|
#
|
|
926
954
|
|
|
927
955
|
|
|
928
|
-
|
|
956
|
+
def importDeviceModule(modulePath):
|
|
957
|
+
"""
|
|
958
|
+
Resolve an import string to import the module for a particular device.
|
|
959
|
+
|
|
960
|
+
Will iteratively check plugin entry points too.
|
|
961
|
+
|
|
962
|
+
Parameters
|
|
963
|
+
----------
|
|
964
|
+
modulePath : str
|
|
965
|
+
Import path for the requested module
|
|
966
|
+
|
|
967
|
+
Return
|
|
968
|
+
------
|
|
969
|
+
types.ModuleType
|
|
970
|
+
Requested module
|
|
971
|
+
|
|
972
|
+
Raises
|
|
973
|
+
------
|
|
974
|
+
ModuleNotFoundError
|
|
975
|
+
If module doesn't exist, will raise this error.
|
|
976
|
+
"""
|
|
977
|
+
module = None
|
|
978
|
+
try:
|
|
979
|
+
# try importing as is (this was the only way prior to plugins)
|
|
980
|
+
module = importlib.import_module(modulePath)
|
|
981
|
+
except ModuleNotFoundError:
|
|
982
|
+
# get entry point groups targeting iohub.devices
|
|
983
|
+
entryPoints = getEntryPoints("psychopy.iohub.devices", submodules=True, flatten=False)
|
|
984
|
+
# iterate through found groups
|
|
985
|
+
for group in entryPoints:
|
|
986
|
+
# skip irrelevant groups
|
|
987
|
+
if not modulePath.startswith(group):
|
|
988
|
+
continue
|
|
989
|
+
# get the module of the entry point group
|
|
990
|
+
module_group = importlib.import_module(group)
|
|
991
|
+
# get the entry point target module(s)
|
|
992
|
+
for ep in entryPoints[group]:
|
|
993
|
+
module_name = ep.name
|
|
994
|
+
ep_target = ep.load()
|
|
995
|
+
# bind each entry point module to the existing module tree
|
|
996
|
+
setattr(module_group, module_name, ep_target)
|
|
997
|
+
sys.modules[group + '.' + module_name] = ep_target
|
|
998
|
+
|
|
999
|
+
# re-try importing the module
|
|
1000
|
+
try:
|
|
1001
|
+
module = importlib.import_module(modulePath)
|
|
1002
|
+
except ModuleNotFoundError:
|
|
1003
|
+
pass
|
|
1004
|
+
|
|
1005
|
+
# raise error if all import options failed
|
|
1006
|
+
if module is None:
|
|
1007
|
+
raise ModuleNotFoundError(
|
|
1008
|
+
f"Could not find module `{modulePath}`. Tried importing directly "
|
|
1009
|
+
f"and iteratively using entry points."
|
|
1010
|
+
)
|
|
1011
|
+
|
|
1012
|
+
return module
|
|
929
1013
|
|
|
930
1014
|
|
|
931
1015
|
def import_device(module_path, device_class_name):
|
|
932
|
-
module
|
|
1016
|
+
# get module from module_path
|
|
1017
|
+
module = importDeviceModule(module_path)
|
|
1018
|
+
|
|
1019
|
+
# get device class from module
|
|
933
1020
|
device_class = getattr(module, device_class_name)
|
|
934
1021
|
|
|
935
1022
|
setattr(sys.modules[__name__], device_class_name, device_class)
|
|
@@ -940,8 +1027,7 @@ def import_device(module_path, device_class_name):
|
|
|
940
1027
|
event_constant_string = convertCamelToSnake(
|
|
941
1028
|
event_class_name[:-5], False)
|
|
942
1029
|
|
|
943
|
-
|
|
944
|
-
event_class = getattr(event_module, event_class_name)
|
|
1030
|
+
event_class = getattr(module, event_class_name)
|
|
945
1031
|
|
|
946
1032
|
event_class.DEVICE_PARENT = device_class
|
|
947
1033
|
|
|
@@ -951,6 +1037,7 @@ def import_device(module_path, device_class_name):
|
|
|
951
1037
|
|
|
952
1038
|
return device_class, device_class_name, event_classes
|
|
953
1039
|
|
|
1040
|
+
|
|
954
1041
|
try:
|
|
955
1042
|
if getattr(sys.modules[__name__], 'Display', None) is None:
|
|
956
1043
|
display_class, device_class_name, event_classes = import_device('psychopy.iohub.devices.display', 'Display')
|