summaryrefslogtreecommitdiff
path: root/external/plyer
diff options
context:
space:
mode:
Diffstat (limited to 'external/plyer')
-rw-r--r--external/plyer/CHANGELOG.md939
-rw-r--r--external/plyer/LICENSE19
-rw-r--r--external/plyer/README.rst38
-rw-r--r--external/plyer/__init__.py63
-rw-r--r--external/plyer/__init__.pycbin0 -> 1254 bytes
-rw-r--r--external/plyer/compat.py34
-rw-r--r--external/plyer/facades/__init__.py28
-rw-r--r--external/plyer/facades/__init__.pycbin0 -> 1522 bytes
-rw-r--r--external/plyer/facades/accelerometer.py36
-rw-r--r--external/plyer/facades/accelerometer.pycbin0 -> 2335 bytes
-rw-r--r--external/plyer/facades/audio.py56
-rw-r--r--external/plyer/facades/audio.pycbin0 -> 3053 bytes
-rw-r--r--external/plyer/facades/battery.py22
-rw-r--r--external/plyer/facades/battery.pycbin0 -> 1404 bytes
-rw-r--r--external/plyer/facades/camera.py43
-rw-r--r--external/plyer/facades/camera.pycbin0 -> 2296 bytes
-rw-r--r--external/plyer/facades/compass.py37
-rw-r--r--external/plyer/facades/compass.pycbin0 -> 2215 bytes
-rw-r--r--external/plyer/facades/email.py23
-rw-r--r--external/plyer/facades/email.pycbin0 -> 1447 bytes
-rw-r--r--external/plyer/facades/filechooser.py53
-rw-r--r--external/plyer/facades/filechooser.pycbin0 -> 3367 bytes
-rw-r--r--external/plyer/facades/gps.py67
-rw-r--r--external/plyer/facades/gps.pycbin0 -> 3208 bytes
-rw-r--r--external/plyer/facades/gyroscope.py37
-rw-r--r--external/plyer/facades/gyroscope.pycbin0 -> 2226 bytes
-rw-r--r--external/plyer/facades/irblaster.py59
-rw-r--r--external/plyer/facades/irblaster.pycbin0 -> 3340 bytes
-rw-r--r--external/plyer/facades/notification.py26
-rw-r--r--external/plyer/facades/notification.pycbin0 -> 1581 bytes
-rw-r--r--external/plyer/facades/orientation.py45
-rw-r--r--external/plyer/facades/orientation.pycbin0 -> 2726 bytes
-rw-r--r--external/plyer/facades/sms.py19
-rw-r--r--external/plyer/facades/sms.pycbin0 -> 1051 bytes
-rw-r--r--external/plyer/facades/tts.py16
-rw-r--r--external/plyer/facades/tts.pycbin0 -> 1035 bytes
-rw-r--r--external/plyer/facades/uniqueid.py29
-rw-r--r--external/plyer/facades/uniqueid.pycbin0 -> 1498 bytes
-rw-r--r--external/plyer/facades/vibrator.py53
-rw-r--r--external/plyer/facades/vibrator.pycbin0 -> 3205 bytes
-rw-r--r--external/plyer/platforms/__init__.py0
-rw-r--r--external/plyer/platforms/android/__init__.py12
-rw-r--r--external/plyer/platforms/android/accelerometer.py74
-rw-r--r--external/plyer/platforms/android/audio.py58
-rw-r--r--external/plyer/platforms/android/battery.py34
-rw-r--r--external/plyer/platforms/android/camera.py59
-rw-r--r--external/plyer/platforms/android/compass.py74
-rw-r--r--external/plyer/platforms/android/email.py40
-rw-r--r--external/plyer/platforms/android/gps.py79
-rw-r--r--external/plyer/platforms/android/gyroscope.py74
-rw-r--r--external/plyer/platforms/android/irblaster.py54
-rw-r--r--external/plyer/platforms/android/notification.py37
-rw-r--r--external/plyer/platforms/android/orientation.py43
-rw-r--r--external/plyer/platforms/android/sms.py25
-rw-r--r--external/plyer/platforms/android/tts.py24
-rw-r--r--external/plyer/platforms/android/uniqueid.py16
-rw-r--r--external/plyer/platforms/android/vibrator.py48
-rw-r--r--external/plyer/platforms/ios/__init__.py0
-rw-r--r--external/plyer/platforms/ios/accelerometer.py34
-rw-r--r--external/plyer/platforms/ios/battery.py36
-rw-r--r--external/plyer/platforms/ios/compass.py31
-rw-r--r--external/plyer/platforms/ios/email.py41
-rw-r--r--external/plyer/platforms/ios/gps.py45
-rw-r--r--external/plyer/platforms/ios/gyroscope.py31
-rw-r--r--external/plyer/platforms/ios/tts.py35
-rw-r--r--external/plyer/platforms/ios/uniqueid.py17
-rw-r--r--external/plyer/platforms/linux/__init__.py0
-rw-r--r--external/plyer/platforms/linux/accelerometer.py36
-rw-r--r--external/plyer/platforms/linux/battery.py46
-rw-r--r--external/plyer/platforms/linux/email.py36
-rw-r--r--external/plyer/platforms/linux/filechooser.py249
-rw-r--r--external/plyer/platforms/linux/notification.py52
-rw-r--r--external/plyer/platforms/linux/tts.py25
-rw-r--r--external/plyer/platforms/linux/uniqueid.py30
-rw-r--r--external/plyer/platforms/macosx/__init__.py0
-rw-r--r--external/plyer/platforms/macosx/accelerometer.py25
-rw-r--r--external/plyer/platforms/macosx/battery.py47
-rw-r--r--external/plyer/platforms/macosx/email.py38
-rw-r--r--external/plyer/platforms/macosx/filechooser.py108
-rw-r--r--external/plyer/platforms/macosx/libs/__init__.py0
-rw-r--r--external/plyer/platforms/macosx/libs/osx_motion_sensor.py118
-rw-r--r--external/plyer/platforms/macosx/notification.py27
-rw-r--r--external/plyer/platforms/macosx/tts.py25
-rw-r--r--external/plyer/platforms/macosx/uniqueid.py32
-rw-r--r--external/plyer/platforms/win/__init__.py0
-rw-r--r--external/plyer/platforms/win/battery.py21
-rw-r--r--external/plyer/platforms/win/email.py34
-rw-r--r--external/plyer/platforms/win/filechooser.py112
-rw-r--r--external/plyer/platforms/win/libs/__init__.py0
-rw-r--r--external/plyer/platforms/win/libs/balloontip.py145
-rw-r--r--external/plyer/platforms/win/libs/batterystatus.py13
-rw-r--r--external/plyer/platforms/win/libs/win_api_defs.py200
-rw-r--r--external/plyer/platforms/win/notification.py13
-rw-r--r--external/plyer/platforms/win/tts.py16
-rw-r--r--external/plyer/platforms/win/uniqueid.py21
-rw-r--r--external/plyer/tools/pep8checker/pep8.py1956
-rw-r--r--external/plyer/tools/pep8checker/pep8base.html70
-rw-r--r--external/plyer/tools/pep8checker/pep8kivy.py109
-rw-r--r--external/plyer/tools/pep8checker/pre-commit.githook78
-rw-r--r--external/plyer/utils.py135
-rw-r--r--external/plyer/utils.pycbin0 -> 5806 bytes
101 files changed, 6580 insertions, 0 deletions
diff --git a/external/plyer/CHANGELOG.md b/external/plyer/CHANGELOG.md
new file mode 100644
index 0000000..20f5bcf
--- /dev/null
+++ b/external/plyer/CHANGELOG.md
@@ -0,0 +1,939 @@
+Changelog
+=========
+
+%%version%% (unreleased)
+------------------------
+
+- Update __init__.py. [dessant]
+
+- Update copyright year. [dessant]
+
+- Fix windows filechooser using `mode` instead of `self.mode` [gabriel
+ pettier]
+
+- Merge pull request #144 from thegrymek/plyer-audio. [dessant]
+
+ Plyer audio for android with facade and example
+
+- Update docs facade with permissions. [andrzej.grymkowski]
+
+- Improving docs. [andrzej.grymkowski]
+
+- Corrected "" in docstring. [Andrzej Grymkowski]
+
+- Flake8. [Andrzej Grymkowski]
+
+- Update readme. [Andrzej Grymkowski]
+
+- Removing temporary files. [Andrzej Grymkowski]
+
+- Moved example class to facades. [Andrzej Grymkowski]
+
+- Impoved example. [andrzej.grymkowski]
+
+- Added almost working example. [Andrzej Grymkowski]
+
+- Initial commit. [Andrzej Grymkowski]
+
+- Merge pull request #147 from kivy/stylefix. [dessant]
+
+ style fixes
+
+- Style fixes. [dessant]
+
+- Merge pull request #146 from kivy/dessant-patch-1. [dessant]
+
+ add pydev files to gitignore
+
+- Add pydev files to gitignore. [dessant]
+
+- Merge pull request #145 from thegrymek/plyer-style-guide-update.
+ [dessant]
+
+ Plyer style guide update
+
+- Another improvements. [andrzej.grymkowski]
+
+- Corrected win filechooser. [andrzej.grymkowski]
+
+- Update for docs. [andrzej.grymkowski]
+
+- Improved styles in files. [andrzej.grymkowski]
+
+- Updated kivy pep8 and corrected some files. [andrzej.grymkowski]
+
+- Merge pull request #143 from kivy/dessant-patch-1. [dessant]
+
+ fix versionchanged tag
+
+- Fix versionchanged tag. [dessant]
+
+- Merge pull request #133 from aron-bordin/master. [dessant]
+
+ fix #107 - Use Android_ID instead of IMEI
+
+- Fix #107 - Use Android_ID instead of IMEI. [Aron Bordin]
+
+- Merge pull request #140 from thegrymek/plyer-android4.0-email.
+ [dessant]
+
+ update info about support email for android<4.0
+
+- Update info to readme about support email on android<4.0.
+ [andrzej.grymkowski]
+
+- Merge pull request #135 from thegrymek/inclement-orientation.
+ [dessant]
+
+ Inclement orientation
+
+- Adding tag versionadded to facade. [andrzej.grymkowski]
+
+- Fix for import facade orientation. [Andrzej Grymkowski]
+
+- Added to readme supported platforms platform android < 4.0. [Andrzej
+ Grymkowski]
+
+- Removing red functions label. [Andrzej Grymkowski]
+
+- Corrected readme. [Andrzej Grymkowski]
+
+- Updated orientation. [Andrzej Grymkowski]
+
+- Merge pull request #139 from thegrymek/plyer-add-package-facades-
+ setuptools. [dessant]
+
+ added plyer.facade to setuptools package
+
+- Added plyer.facade to setuptools package. [Andrzej Grymkowski]
+
+- Merge pull request #138 from thegrymek/plyer-split-facades. [dessant]
+
+ splitted facades
+
+- Changed order. [Andrzej Grymkowski]
+
+- Spliting end. [Andrzej Grymkowski]
+
+- Spliting end. [Andrzej Grymkowski]
+
+- First part. [Andrzej Grymkowski]
+
+- Merge pull request #129 from thegrymek/android-vibrator-sdk-9.
+ [dessant]
+
+ vibrator for android v < 4.0
+
+- Added defaults. [Andrzej Grymkowski]
+
+- Removing files tests. [Andrzej Grymkowski]
+
+- A. [Andrzej Grymkowski]
+
+- Merging. [Andrzej Grymkowski]
+
+- Replace """ in docstrings" [Andrzej Grymkowski]
+
+- Added exist for sdk < 11. [Andrzej Grymkowski]
+
+- Added unsupported exception. [Andrzej Grymkowski]
+
+- Removed unnessesary docstrings. [Andrzej Grymkowski]
+
+- Conflicts main. [Andrzej Grymkowski]
+
+- Updated document. [Andrzej Grymkowski]
+
+- Vibrator for android v < 4.0, pep257 and flake8. [Andrzej Grymkowski]
+
+- Updated document. [Andrzej Grymkowski]
+
+- Remove unnessesary docstring. [Andrzej Grymkowski]
+
+- Merging. [Andrzej Grymkowski]
+
+- Merge branch 'new_branch_name' [Andrzej Grymkowski]
+
+- Merge. [Andrzej Grymkowski]
+
+- Remove unused variables. [thegrymek]
+
+- PEP8 and typo fixes in MacOS X file chooser. [Robert Jerovsek]
+
+- Removed unused imports and refactor. [Andrzej Grymkowski]
+
+- Pep8 - removed unused imports and variables. [Andrzej Grymkowski]
+
+- Make pep8 compatible. [laltin]
+
+- Responds to issue 109 https://github.com/kivy/plyer/issues/109.
+ [albericc]
+
+- As. [Andrzej Grymkowski]
+
+- Added first structure. [Andrzej Grymkowski]
+
+- Merge pull request #1 from kivy/master. [thegrymek]
+
+ sync pull request
+
+- Merge pull request #134 from thegrymek/patch-1. [dessant]
+
+ remove unused variables
+
+- Remove unused variables. [thegrymek]
+
+- Merge pull request #110 from AlbericC/issue_109. [dessant]
+
+ responds to issue 109 https://github.com/kivy/plyer/issues/109
+
+- Responds to issue 109 https://github.com/kivy/plyer/issues/109.
+ [albericc]
+
+- Merge pull request #121 from laltin/pep8_fix. [Mathieu Virbel]
+
+ Pep8 fix
+
+- Make pep8 compatible. [laltin]
+
+- Merge pull request #1 from kivy/master. [Lütfi Altın]
+
+ sync with origin
+
+- Merge pull request #122 from thegrymek/pep8-removed-unused-imports-
+ and-variables. [Mathieu Virbel]
+
+ pep8 - removed unused imports and variables
+
+- Removed unused imports and refactor. [Andrzej Grymkowski]
+
+- Pep8 - removed unused imports and variables. [Andrzej Grymkowski]
+
+- Merge pull request #123 from robertjerovsek/master. [Mathieu Virbel]
+
+ PEP8 and typo fixes in MacOS X file chooser.
+
+- PEP8 and typo fixes in MacOS X file chooser. [Robert Jerovsek]
+
+- Merge pull request #106 from Davideddu/filechooser. [Mathieu Virbel]
+
+ Add file chooser facade and support for Linux and Windows
+
+- Fix filter conversion on Mac OS X. [Davide Depau]
+
+- Add experimental support for Mac OS X. [Davide Depau]
+
+- Fix inheritance issue on windows filechooser implementation. [Davide
+ Depau]
+
+- Add filechooser facade and support for Linux and Windows. [Davide
+ Depau]
+
+- Merge pull request #119 from trivedigaurav/use-environ-lang. [Mathieu
+ Virbel]
+
+ Use environ to change LANG to 'C' while calling shell processes
+
+- Use environ to change LANG to 'C' while calling shell processes.
+ [gtrivedi]
+
+- Merge pull request #120 from pspchucky/master. [Mathieu Virbel]
+
+ add video recoding to Camera facade and camera.py
+
+- Update camera.py. [pspchucky]
+
+- Add video capture support to facades.py. [pspchucky]
+
+- Merge pull request #118 from kived/irblaster-facade. [Akshay Arora]
+
+ add IrBlaster facade and Android implementation
+
+- Add IrBlaster facade and Android implementation. [Ryan Pessa]
+
+- Update README.rst. [Mathieu Virbel]
+
+- Merge pull request #116 from laltin/ios_gps. [Mathieu Virbel]
+
+ iOS GPS support
+
+- Cleanup & comment. [laltin]
+
+- First working version. [laltin]
+
+- Merge pull request #115 from kivy/fixes_114. [Mathieu Virbel]
+
+ use environ to change LANG to 'C' while calling lshw
+
+- Use environ to change LANG to 'C' while calling lshw. [gabriel
+ pettier]
+
+ fixes #114
+
+- Merge pull request #117 from JimmyStavros/patch-1. [Mathieu Virbel]
+
+ Android gps.py: fixed location provider cycling
+
+- Android gps.py: fixed location provider cycling. [JimmyStavros]
+
+ Issue was first brought to light here: https://github.com/kivy/plyer/issues/54
+
+ GPS module was cycling through "gps" three times instead of all location services.
+
+- Fixes hashCode overflow. Latest kivy/pyjnius master have already
+ hashCode/equals/toString implementation, and the hashCode is fixed
+ with overflow. Just dont try to implement it here. Closes #103. Ref
+ kivy/pyjnius#146. [Mathieu Virbel]
+
+- Bump to 1.2.4-dev. [Mathieu Virbel]
+
+1.2.3 (2015-01-27)
+------------------
+
+- Bump to 1.2.3. [Mathieu Virbel]
+
+- Setup.py: add changelog into the description + fix rst issue. [Mathieu
+ Virbel]
+
+- Bump to 1.2.3-dev. [Mathieu Virbel]
+
+1.2.2 (2015-01-27)
+------------------
+
+- Bump to 1.2.2. [Mathieu Virbel]
+
+- Update the version to the next dev (missing from the last release)
+ [Mathieu Virbel]
+
+- Merge branch 'master' of ssh://github.com/kivy/plyer. [Mathieu Virbel]
+
+- Merge pull request #102 from helenst/sys-platform-linux-check.
+ [Mathieu Virbel]
+
+ Linux platform check made compatible with python 3.3+ (Fixes #58)
+
+- Linux platform check made compatible with python 3.3+ (Fixes #58)
+ [Helen ST]
+
+- Add initial changelog. [Mathieu Virbel]
+
+- Plyer: fix androidd notification. Closes #93 (credits to @kashifpk)
+ [Mathieu Virbel]
+
+- Android/notification: implement a switch to allow usage of API < 16.
+ [Mathieu Virbel]
+
+1.2.1 (2014-08-19)
+------------------
+
+- Merge pull request #92 from dessant/patch-2. [trivedigaurav]
+
+ fix print statement
+
+- Fix print statement. [dessant]
+
+- SMS Manager is supported since Android 1.6. [trivedigaurav]
+
+- Merge pull request #90 from trivedigaurav/ios_uuid. [trivedigaurav]
+
+ iOS UUID facade
+
+- IOS UUID facade. [gtrivedi]
+
+- Merge pull request #86 from trivedigaurav/ios_battery. [trivedigaurav]
+
+ iOS Battery
+
+- Change get_status to get_state. [gtrivedi]
+
+- Clean up. [gtrivedi]
+
+- Add battery.py. [gtrivedi]
+
+- IOS Battery Facade. [gtrivedi]
+
+- Update compass.py. [trivedigaurav]
+
+- Update gyroscope.py. [trivedigaurav]
+
+- Fix typo. [trivedigaurav]
+
+- Fix typo. [trivedigaurav]
+
+- Fix style. [gtrivedi]
+
+- Merge branch 'trivedigaurav-ios_tts' [gtrivedi]
+
+- Fixing README confict and merge master. [gtrivedi]
+
+- Merge pull request #88 from trivedigaurav/ios_email. [trivedigaurav]
+
+ iOS Email Facade
+
+- Add email.py. [gtrivedi]
+
+- Merge branch 'master' of https://github.com/kivy/plyer into ios_email.
+ [gtrivedi]
+
+- Merge pull request #89 from trivedigaurav/fix_make. [trivedigaurav]
+
+ Removing build_ext from plyer
+
+- Removing build_ext from plyer. [gtrivedi]
+
+- Update accelerometer.py. [trivedigaurav]
+
+- Python 3 compat. [trivedigaurav]
+
+- Python 3 compat. [trivedigaurav]
+
+- Python 3 compat. [trivedigaurav]
+
+- IOS Email Facade. [gtrivedi]
+
+- Merge pull request #82 from trivedigaurav/sensors_start_none.
+ [trivedigaurav]
+
+ Fix Android enable and disable. Return (None, None, None) until sensor data is available
+
+- Fix sensor enable-disable. [gtrivedi]
+
+- Merge branch 'master' of https://github.com/kivy/plyer into
+ sensors_start_none. [gtrivedi]
+
+- Merge pull request #68 from trivedigaurav/linux_accel. [trivedigaurav]
+
+ Linux accelerometer facade
+
+- Linux accelerometer facade. [gtrivedi]
+
+- Fix style error. [trivedigaurav]
+
+- Merge pull request #85 from trivedigaurav/battery_ischarging.
+ [trivedigaurav]
+
+ Change connected to isCharging
+
+- Change connected to isCharging. [gtrivedi]
+
+- Merge pull request #80 from ChrisCole42/patch-2. [trivedigaurav]
+
+ Update compass.py
+
+- Update compass.py. [ChrisCole42]
+
+ copied the missing definitions across from accelerometer.py
+
+- Merge pull request #79 from trivedigaurav/where_is. [trivedigaurav]
+
+ Use whereis_exe to check for binaries
+
+- Remove trailing newline. [gtrivedi]
+
+- Add whereis_exe import. [gtrivedi]
+
+- Remove trailing newline. [gtrivedi]
+
+- Use whereis_exe to check for binaries. [gtrivedi]
+
+- Fix style errors. [gtrivedi]
+
+- Copy values atomically. [gtrivedi]
+
+- Return None untill data is available. [gtrivedi]
+
+- Merge pull request #77 from ChrisCole42/patch-1. [trivedigaurav]
+
+ Update compass.py
+
+- Update compass.py. [ChrisCole42]
+
+ fix "global name is not defined" error in AndroidCompass.__init__()
+
+- Merge pull request #75 from trivedigaurav/maintenance. [trivedigaurav]
+
+ Maintenance merge
+
+- Remove extra make command. [gtrivedi]
+
+- Merge branch 'master' of https://github.com/kivy/plyer into
+ maintenance. [gtrivedi]
+
+ Conflicts:
+ plyer/facades.py
+
+- Changed battery Xs to correct columns (ios -> win) [Alexander Taylor]
+
+- Really did fix battery formatting in readme. [Alexander Taylor]
+
+- Fixed battery formatting in readme. [Alexander Taylor]
+
+- Merge pull request #74 from dessant/patch-1. [Akshay Arora]
+
+ facade docstring revision
+
+- Facade docstring revision. [dessant]
+
+- Merge pull request #73 from trivedigaurav/battery_info. [Akshay Arora]
+
+ Query Battery info/status
+
+- Merge pull request #71 from trivedigaurav/master. [trivedigaurav]
+
+ Revert "Activity was imported twice"
+
+- Revert "Activity was imported twice" [gtrivedi]
+
+ This reverts commit a0600929774c1e90c7dc43043ff87b5ea84213b4.
+
+- Activity was imported twice. [trivedigaurav]
+
+- Fix style errors after merging with master. [gtrivedi]
+
+- Fix styles errors in libs/ [gtrivedi]
+
+- Don't ignore lib folder. [gtrivedi]
+
+- Fix style errors. [gtrivedi]
+
+- Fix docstrings. [gtrivedi]
+
+- Fix typo and style. [gtrivedi]
+
+- Copy pep8 style checker from Kivy. [gtrivedi]
+
+- Add Makefile. [gtrivedi]
+
+- Add windows implementation. [gtrivedi]
+
+- Fix android implemenation typos. [gtrivedi]
+
+- Battery facade. [gtrivedi]
+
+- Merge branch 'master' of https://github.com/kivy/plyer into
+ battery_info. [gtrivedi]
+
+ Conflicts:
+ plyer/__init__.py
+
+- Merge pull request #70 from trivedigaurav/master. [trivedigaurav]
+
+ Fix tabbing
+
+- Merge pull request #69 from trivedigaurav/gyroscope_fix.
+ [trivedigaurav]
+
+ Gyroscope facade proxy declarations
+
+- Gyroscope facade proxy declarations. [gtrivedi]
+
+- Merge pull request #67 from trivedigaurav/patch-1. [Akshay Arora]
+
+ Update README.rst
+
+- Update README.rst. [trivedigaurav]
+
+ We have merged commits for the email facades on desktops.
+
+- Merge branch 'master' of https://github.com/trivedigaurav/plyer into
+ battery_info. [gtrivedi]
+
+- Fix tabbing. [trivedigaurav]
+
+ Not sure how this passed through.
+
+- Battery facade. [gtrivedi]
+
+- Typo. [Mathieu Virbel]
+
+- Ios: gyroscope is also supported now. [Mathieu Virbel]
+
+1.2.0 (2014-06-24)
+------------------
+
+- Bump to 1.2.0, and mark new classes to 1.2.0. [Mathieu Virbel]
+
+- Merge master. [Mathieu Virbel]
+
+- Remove dependency on Hardware.java. [gtrivedi]
+
+- IOS and Android implementations. [gtrivedi]
+
+- Gyroscope facade. [gtrivedi]
+
+- Add gyroscope in README. [gtrivedi]
+
+- Merge branch 'master' of ssh://github.com/kivy/plyer. [Mathieu Virbel]
+
+- Merge pull request #49 from Davideddu/macosx_email. [Mathieu Virbel]
+
+ add Mac OS X email support
+
+- Add Mac OS X email support. [Davide Depau]
+
+- Merge pull request #48 from Davideddu/windows_email. [Mathieu Virbel]
+
+ add Windows email support
+
+- Catch exception if no email client is installed on Windows. [Davide
+ Depau]
+
+- Add Windows email support. [Davide Depau]
+
+- Merge pull request #47 from Davideddu/linux_email. [Mathieu Virbel]
+
+ added Linux email support
+
+- Revert previous commit, use xdg-open to open email client. [Davide
+ Depau]
+
+- Use xdg-email by default, fallback to xdg-open + mailto uri. [Davide
+ Depau]
+
+- Added Linux email support. [Davide Depau]
+
+- Merge master. [Mathieu Virbel]
+
+- Remove gyroscope files. [gtrivedi]
+
+- Fix typos introduced while copying. [gtrivedi]
+
+- Fix typo. [gtrivedi]
+
+- Fix tabbing. [gtrivedi]
+
+- IOS Magnetometer. [gtrivedi]
+
+- Merge branch 'master' of ssh://github.com/kivy/plyer. [Mathieu Virbel]
+
+- Merge pull request #63 from trivedigaurav/pyjnius_accel. [Mathieu
+ Virbel]
+
+ Switched to pyjnius
+
+- Make values assignment atomic. [gtrivedi]
+
+- Switched to pyjnius. [gtrivedi]
+
+- Merge master. [Mathieu Virbel]
+
+- Plyer Unique ID facade. [gtrivedi]
+
+- Merge master. [Mathieu Virbel]
+
+- Update README. [trivedigaurav]
+
+- Merge branch 'android_compass' of
+ https://github.com/trivedigaurav/plyer into android_compass.
+ [gtrivedi]
+
+- Update buildozer.spec. [trivedigaurav]
+
+- Pyjnius compass. [gtrivedi]
+
+- Update buildozer.spec. [gtrivedi]
+
+- Plyer compass facade. [gtrivedi]
+
+- Merge pull request #52 from mihaineacsu/sms. [Mathieu Virbel]
+
+ Added sms facade, example and android implementation
+
+- Updated sms components. [mihaineacsu]
+
+- Fix components of the sms facade. [mihaineacsu]
+
+- Update README with sms feature. [mihaineacsu]
+
+- Update plyer init. [mihaineacsu]
+
+- Add sms example. [mihaineacsu]
+
+- Add initial android sms implementation. [mihaineacsu]
+
+- Add sms facade. [mihaineacsu]
+
+- Merge pull request #55 from trivedigaurav/osx_accel. [Mathieu Virbel]
+
+ Using sudden motion sensor as accelerometer on OSX
+
+- Check accelerometer in OSX. [gtrivedi]
+
+- LGPL notice no longer required. [gtrivedi]
+
+- Fix tabs to spaces. [gtrivedi]
+
+- Switched to ctypes. [gtrivedi]
+
+- Remove osx libs in setup.py. [gtrivedi]
+
+- Added LGPL.txt for unimotion.c. [gtrivedi]
+
+- Added libs/ folder in macosx. [gtrivedi]
+
+- OSX Accel facade using unimotion. [gtrivedi]
+
+- Merge pull request #62 from trivedigaurav/patch-2. [Mathieu Virbel]
+
+ Update README
+
+- Update README. [trivedigaurav]
+
+- Merge pull request #56 from trivedigaurav/patch-1. [Akshay Arora]
+
+ Update README
+
+- Update README. [trivedigaurav]
+
+- Remove buildozer db. [Mathieu Virbel]
+
+- Merge pull request #46 from matham/master. [akshayaurora]
+
+ Add compat module, remove decoding of strings in notification
+
+- Explicitly declare HANDLE since ctypes in py3 doesn't seem to have it.
+ [Matthew Einhorn]
+
+- Make py3 compat. [Matthew Einhorn]
+
+- Decode textinput input before sending to notification. [Matthew
+ Einhorn]
+
+- Add compat module, remove decoding of strings in notification, do
+ direct inmport instead of relative import. [Matthew Einhorn]
+
+- Removed unused import. [Ben Rousch]
+
+- Merge pull request #6 from inclement/vibrate. [Alexander Taylor]
+
+ Added Vibrator facade and android implementation
+
+- Renamed vibrate_pattern to just pattern. [Alexander Taylor]
+
+- Fixed typo in vibrator proxy comment. [Alexander Taylor]
+
+- Added vibrator example and buildozer.spec. [Alexander Taylor]
+
+- Added AndroidVibrator implementation. [Alexander Taylor]
+
+- Added Vibrator facade. [Alexander Taylor]
+
+- Merge pull request #18 from matham/ctypes-notify-window. [Ben Rousch]
+
+ Changes notify to use ctypes instead of win32gui so we could use unicode.
+
+- Use unique id. [Matthew Einhorn]
+
+- Use count instead of self to generate different classes. [Matthew
+ Einhorn]
+
+- Add notify_close method and always display some systray icon. [Matthew
+ Einhorn]
+
+- Update to use ctypes instead of win32gui so we could use unicode.
+ [Matthew Einhorn]
+
+- Add windows ctypes api defs file. [Matthew Einhorn]
+
+- Merge pull request #39 from trivedigaurav/accelerometer_example. [Ben
+ Rousch]
+
+ Created an accelerometer example. Uses garden graph to plot the values
+
+- Removed unused variable. [gtrivedi]
+
+- Added a simple accelerometer example. [gtrivedi]
+
+- Corrected README for buildozer commands. [gtrivedi]
+
+- Updated README to reflect path changes. [gtrivedi]
+
+- Moved the graph example to examples/accelerometer/using_graph.
+ [gtrivedi]
+
+- Updated README to include information about garden.graph. [gtrivedi]
+
+- Added screenshot of the app running on Android 4.3. [gtrivedi]
+
+- Added README on how to build. [gtrivedi]
+
+- Cleaned up a bit. Newlines. Removed dangling commented lines.
+ [gtrivedi]
+
+- Fixed ordering in the try catch block. It fails at the correct line in
+ the try block if the accelerometer is not present. [gtrivedi]
+
+- Fixed android permissions. Doesn't require any. [gtrivedi]
+
+- Changed popup error message to state that the problem is with the
+ current platform. [gtrivedi]
+
+- Removed a test file not a part of the example. [gtrivedi]
+
+- Created an accelerometer example. Uses garden graph to plot the
+ values. [gtrivedi]
+
+- Added examples README. [Ben Rousch]
+
+- Merge pull request #38 from trivedigaurav/tts_example. [Ben Rousch]
+
+ Shows an error popup if there is no TTS
+
+- Added newlines at the end of files. [gtrivedi]
+
+- Shows an error popup if there is no TTS capability. [gtrivedi]
+
+- Merge pull request #37 from trivedigaurav/tts_example. [akshayaurora]
+
+ Text to Speech Example
+
+- Removed an empty line. [gtrivedi]
+
+- Created an example application for the text to speech (tts) facade.
+ [gtrivedi]
+
+- Merge pull request #11 from kivy/notification_windows_icon.
+ [akshayaurora]
+
+ User-specified icon support for Windows notifications
+
+- Notification: introduce `timeout` [qua-non]
+
+- User-specified icon support for Windows notifications. [Ben Rousch]
+
+- Merge pull request #15 from voen/patch-1. [Ben Rousch]
+
+ readme typo corrected
+
+- Readme typo corrected. [voen]
+
+- Merge pull request #10 from kivy/dbus_notify. [akshayaurora]
+
+ Introduce dbus notification
+
+- Removed attempts at using app_icon in Android notification. [Ben
+ Rousch]
+
+- Improve notification example. [qua-non]
+
+- Partial example notification example. [Ben Rousch]
+
+- Introduce dbus notification. [qua-non]
+
+- Fix plyer android.activity import. [Mathieu Virbel]
+
+- Fixed whereis_exe for windows. Fixed espeak TTS for windows. [Ben
+ Rousch]
+
+- Merge pull request #5 from inclement/sendemail. [Mathieu Virbel]
+
+ Added an email facade and basic android implementation
+
+- Removed unnecessary import from email. [Alexander Taylor]
+
+- Pep8 fixes. [Alexander Taylor]
+
+- Added email facade example. [Alexander Taylor]
+
+- Added email to README. [Alexander Taylor]
+
+- Completed email facade and android implementation. [Alexander Taylor]
+
+- Added email facade. [Alexander Taylor]
+
+- Added simple buildozer.spec for gps example. [Alexander Taylor]
+
+- Add missing super() constructor in IosAccelerometer. [Mathieu Virbel]
+
+- Ios: add support for accelerometer on iOS (and motivate brousch again)
+ [Mathieu Virbel]
+
+- Add MANIFEST to include LICENSE and README. bump to 1.1.2. [Mathieu
+ Virbel]
+
+- Bump to 1.1.1. [Mathieu Virbel]
+
+- Fix setup for pip. [Mathieu Virbel]
+
+- Update readme. [Mathieu Virbel]
+
+- Setup.py: fix readme. [Mathieu Virbel]
+
+- Update readme. [Mathieu Virbel]
+
+- Gps: add versionadded. [Mathieu Virbel]
+
+- Fix documentation version. [Mathieu Virbel]
+
+- Gps: update documentation. [Mathieu Virbel]
+
+- Update setup.py to correctly include win. [Mathieu Virbel]
+
+- Merge branch 'master' of ssh://github.com/kivy/plyer. [Mathieu Virbel]
+
+- Add a basic grid to show supported features per os. [tshirtman]
+
+- Custom icon specification for android notification. [Ben Rousch]
+
+- Even more elegant Android notification code. [Ben Rousch]
+
+- Less hacky getPackageName from Android. [Ben Rousch]
+
+- Fixed hasattr bug. Added default icon support for Android
+ notifications. [Ben Rousch]
+
+- PEPed up balloontip. Fixed no 2nd notify bug. Fixed blocking
+ notification bug. [Ben Rousch]
+
+- Added notifications for Linux via notify-send. [Ben Rousch]
+
+- Fixed name of Windows platform, fixed Windows notify. [Ben Rousch]
+
+- Add GPS/android support for plyer. [Mathieu Virbel]
+
+- Add setup.py. [Mathieu Virbel]
+
+- Plyer is now under MIT license. [Mathieu Virbel]
+
+- Fixed incorrect Android tTS return type. [Ben Rousch]
+
+- Merge pull request #1 from kivy/tts. [Ben Rousch]
+
+ TTS!
+
+- Changed NotImplemented exception to NotImplementedError. [Ben Rousch]
+
+- Added missing () to TTS returns. [Ben Rousch]
+
+- Fixed missed TextToSpeech, deleted old files. [Ben Rousch]
+
+- Changed TextToSpeech to TTS. Returning TTS instead of raising
+ NotImplemented. [Ben Rousch]
+
+- Added text_to_speech for Android, Linux, OSX, and Windows. [Ben
+ Rousch]
+
+- Ensure the documentation will find plyer. [Mathieu Virbel]
+
+- Rework how implementation works, and start documentation. [Mathieu
+ Virbel]
+
+- First version of plyer, including accelerometer (android), camera
+ (android) and notification (android, osx). api is not stabilized.
+ [Mathieu Virbel]
+
+- Merge branch 'master' of github.com:kivy/plyer. [tshirtman]
+
+ Conflicts:
+ readme.md
+
+- Update readme.md. [Gabriel Pettier]
+
+- Rename to plyer, and uses plateform() from kivy utils. [tshirtman]
+
+- Add android/desktop/ios modules, and auto import from them.
+ [tshirtman]
+
+- Initial commit, created simple readme. [tshirtman]
+
+
diff --git a/external/plyer/LICENSE b/external/plyer/LICENSE
new file mode 100644
index 0000000..b336a3a
--- /dev/null
+++ b/external/plyer/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2010-2015 Kivy Team and other contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/external/plyer/README.rst b/external/plyer/README.rst
new file mode 100644
index 0000000..56860db
--- /dev/null
+++ b/external/plyer/README.rst
@@ -0,0 +1,38 @@
+Plyer
+=====
+
+Plyer is a platform-independent api to use features commonly found on various
+platforms, notably mobile ones, in Python.
+
+How
+---
+
+Plyer tries not to reinvent the wheel, and will call for external libraries to
+implement the api in the easiest way, depending on the current platform.
+
+- on python-for-android, pyjnius is used
+- on kivy-ios, pyobjus is used
+- on windows/mac/linux, commonly found libraries and programs will be used
+
+Support
+-------
+
+================================== ============= ============= === ======= === =====
+Platform Android < 4.0 Android > 4.0 iOS Windows OSX Linux
+================================== ============= ============= === ======= === =====
+Accelerometer X X X X X
+Camera (taking picture) X X
+GPS X X X
+Notifications X X X X X
+Text to speech X X X X X X
+Email (open mail client) X X X X X X
+Vibrator X X
+Sms (send messages) X X
+Compass X X X
+Unique ID (IMEI or SN) X X X X X X
+Gyroscope X X X
+Battery X X X X X X
+Native file chooser X X X
+Orientation X X
+Audio recording X X
+================================== ============= ============= === ======= === =====
diff --git a/external/plyer/__init__.py b/external/plyer/__init__.py
new file mode 100644
index 0000000..f8e35e5
--- /dev/null
+++ b/external/plyer/__init__.py
@@ -0,0 +1,63 @@
+'''
+Plyer
+=====
+
+'''
+
+__all__ = ('accelerometer', 'audio', 'battery', 'camera', 'compass', 'email',
+ 'filechooser', 'gps', 'gyroscope', 'irblaster', 'orientation',
+ 'notification', 'sms', 'tts', 'uniqueid', 'vibrator')
+
+__version__ = '1.2.4'
+
+
+from plyer import facades
+from plyer.utils import Proxy
+
+#: Accelerometer proxy to :class:`plyer.facades.Accelerometer`
+accelerometer = Proxy('accelerometer', facades.Accelerometer)
+
+#: Audio proxy to :class:`plyer.facades.Audio`
+audio = Proxy('audio', facades.Audio)
+
+#: Battery proxy to :class:`plyer.facades.Battery`
+battery = Proxy('battery', facades.Battery)
+
+#: Compass proxy to :class:`plyer.facades.Compass`
+compass = Proxy('compass', facades.Compass)
+
+#: Camera proxy to :class:`plyer.facades.Camera`
+camera = Proxy('camera', facades.Camera)
+
+#: Email proxy to :class:`plyer.facades.Email`
+email = Proxy('email', facades.Email)
+
+#: FileChooser proxy to :class:`plyer.facades.FileChooser`
+filechooser = Proxy('filechooser', facades.FileChooser)
+
+#: GPS proxy to :class:`plyer.facades.GPS`
+gps = Proxy('gps', facades.GPS)
+
+#: Gyroscope proxy to :class:`plyer.facades.Gyroscope`
+gyroscope = Proxy('gyroscope', facades.Gyroscope)
+
+#: IrBlaster proxy to :class:`plyer.facades.IrBlaster`
+irblaster = Proxy('irblaster', facades.IrBlaster)
+
+#: Orientation proxy to :class:`plyer.facades.Orientation`
+orientation = Proxy('orientation', facades.Orientation)
+
+#: Notification proxy to :class:`plyer.facades.Notification`
+notification = Proxy('notification', facades.Notification)
+
+#: Sms proxy to :class:`plyer.facades.Sms`
+sms = Proxy('sms', facades.Sms)
+
+#: TTS proxy to :class:`plyer.facades.TTS`
+tts = Proxy('tts', facades.TTS)
+
+#: UniqueID proxy to :class:`plyer.facades.UniqueID`
+uniqueid = Proxy('uniqueid', facades.UniqueID)
+
+#: Vibrator proxy to :class:`plyer.facades.Vibrator`
+vibrator = Proxy('vibrator', facades.Vibrator)
diff --git a/external/plyer/__init__.pyc b/external/plyer/__init__.pyc
new file mode 100644
index 0000000..d78a630
--- /dev/null
+++ b/external/plyer/__init__.pyc
Binary files differ
diff --git a/external/plyer/compat.py b/external/plyer/compat.py
new file mode 100644
index 0000000..fee64da
--- /dev/null
+++ b/external/plyer/compat.py
@@ -0,0 +1,34 @@
+'''
+Compatibility module for Python 2.7 and > 3.3
+=============================================
+'''
+
+__all__ = ('PY2', 'string_types', 'queue', 'iterkeys',
+ 'itervalues', 'iteritems')
+
+import sys
+try:
+ import queue
+except ImportError:
+ import Queue as queue
+
+#: True if Python 2 intepreter is used
+PY2 = sys.version_info[0] == 2
+
+#: String types that can be used for checking if a object is a string
+string_types = None
+text_type = None
+if PY2:
+ string_types = basestring
+ text_type = unicode
+else:
+ string_types = text_type = str
+
+if PY2:
+ iterkeys = lambda d: d.iterkeys()
+ itervalues = lambda d: d.itervalues()
+ iteritems = lambda d: d.iteritems()
+else:
+ iterkeys = lambda d: iter(d.keys())
+ itervalues = lambda d: iter(d.values())
+ iteritems = lambda d: iter(d.items())
diff --git a/external/plyer/facades/__init__.py b/external/plyer/facades/__init__.py
new file mode 100644
index 0000000..e5560c0
--- /dev/null
+++ b/external/plyer/facades/__init__.py
@@ -0,0 +1,28 @@
+'''
+Facades
+=======
+
+Interface of all the features available.
+
+'''
+
+__all__ = ('Accelerometer', 'Audio', 'Battery', 'Camera', 'Compass', 'Email',
+ 'FileChooser', 'GPS', 'Gyroscope', 'IrBlaster', 'Orientation',
+ 'Notification', 'Sms', 'TTS', 'UniqueID', 'Vibrator')
+
+from plyer.facades.accelerometer import Accelerometer
+from plyer.facades.audio import Audio
+from plyer.facades.battery import Battery
+from plyer.facades.camera import Camera
+from plyer.facades.compass import Compass
+from plyer.facades.email import Email
+from plyer.facades.filechooser import FileChooser
+from plyer.facades.gps import GPS
+from plyer.facades.gyroscope import Gyroscope
+from plyer.facades.irblaster import IrBlaster
+from plyer.facades.orientation import Orientation
+from plyer.facades.notification import Notification
+from plyer.facades.sms import Sms
+from plyer.facades.tts import TTS
+from plyer.facades.uniqueid import UniqueID
+from plyer.facades.vibrator import Vibrator
diff --git a/external/plyer/facades/__init__.pyc b/external/plyer/facades/__init__.pyc
new file mode 100644
index 0000000..8058b7a
--- /dev/null
+++ b/external/plyer/facades/__init__.pyc
Binary files differ
diff --git a/external/plyer/facades/accelerometer.py b/external/plyer/facades/accelerometer.py
new file mode 100644
index 0000000..e8146bf
--- /dev/null
+++ b/external/plyer/facades/accelerometer.py
@@ -0,0 +1,36 @@
+class Accelerometer(object):
+ '''Accelerometer facade.
+ '''
+
+ @property
+ def acceleration(self):
+ '''Property that returns values of the current acceleration
+ sensors, as a (x, y, z) tuple. Returns (None, None, None)
+ if no data is currently available.
+ '''
+ return self.get_acceleration()
+
+ def enable(self):
+ '''Activate the accelerometer sensor. Throws an error if the
+ hardware is not available or not implemented on.
+ '''
+ self._enable()
+
+ def disable(self):
+ '''Disable the accelerometer sensor.
+ '''
+ self._disable()
+
+ def get_acceleration(self):
+ return self._get_acceleration()
+
+ # private
+
+ def _enable(self):
+ raise NotImplementedError()
+
+ def _disable(self):
+ raise NotImplementedError()
+
+ def _get_acceleration(self):
+ raise NotImplementedError()
diff --git a/external/plyer/facades/accelerometer.pyc b/external/plyer/facades/accelerometer.pyc
new file mode 100644
index 0000000..c646a98
--- /dev/null
+++ b/external/plyer/facades/accelerometer.pyc
Binary files differ
diff --git a/external/plyer/facades/audio.py b/external/plyer/facades/audio.py
new file mode 100644
index 0000000..5678841
--- /dev/null
+++ b/external/plyer/facades/audio.py
@@ -0,0 +1,56 @@
+class Audio(object):
+ '''Audio Facade.
+
+ Used for recording audio.
+ Use method `start` to start record and `stop` for stop recording.
+ To hear what you have just recorded, use method `play`.
+
+ `status` will tell you about current job of the Audio.
+
+ Default path for recording is set in platform implementation.
+
+ .. note::
+ On Android the `RECORD_AUDIO` permission is needed.
+ '''
+
+ state = 'ready'
+ _file_path = ''
+
+ def __init__(self, file_path):
+ super(Audio, self).__init__()
+ self._file_path = file_path
+
+ def start(self):
+ '''Start record.'''
+ self._start()
+ self.state = 'recording'
+
+ def _start(self):
+ raise NotImplementedError()
+
+ def stop(self):
+ '''Stop record.'''
+ self._stop()
+ self.state = 'ready'
+
+ def _stop(self):
+ raise NotImplementedError()
+
+ def play(self):
+ '''Play current recording.'''
+ self._play()
+ self.state = 'playing'
+
+ def _play(self):
+ raise NotImplementedError()
+
+ @property
+ def file_path(self):
+ return self._file_path
+
+ @file_path.setter
+ def file_path(self, location):
+ '''Location of the recording.'''
+ assert isinstance(location, (basestring, unicode)), \
+ 'Location must be string or unicode'
+ self._file_path = location
diff --git a/external/plyer/facades/audio.pyc b/external/plyer/facades/audio.pyc
new file mode 100644
index 0000000..9b79fcf
--- /dev/null
+++ b/external/plyer/facades/audio.pyc
Binary files differ
diff --git a/external/plyer/facades/battery.py b/external/plyer/facades/battery.py
new file mode 100644
index 0000000..7cd48c7
--- /dev/null
+++ b/external/plyer/facades/battery.py
@@ -0,0 +1,22 @@
+class Battery(object):
+ '''Battery info facade.'''
+
+ @property
+ def status(self):
+ '''Property that contains a dict with the following fields:
+ * **isCharging** *(bool)*: Battery is charging
+ * **percentage** *(float)*: Battery charge remaining
+
+ .. warning::
+ If any of the fields is not readable, it is set as
+ None.
+ '''
+ return self.get_state()
+
+ def get_state(self):
+ return self._get_state()
+
+ #private
+
+ def _get_state(self):
+ raise NotImplementedError()
diff --git a/external/plyer/facades/battery.pyc b/external/plyer/facades/battery.pyc
new file mode 100644
index 0000000..07f25d7
--- /dev/null
+++ b/external/plyer/facades/battery.pyc
Binary files differ
diff --git a/external/plyer/facades/camera.py b/external/plyer/facades/camera.py
new file mode 100644
index 0000000..5300ede
--- /dev/null
+++ b/external/plyer/facades/camera.py
@@ -0,0 +1,43 @@
+class Camera(object):
+ '''Camera facade.
+ '''
+
+ def take_picture(self, filename, on_complete):
+ '''Ask the OS to capture a picture, and store it at filename.
+
+ When the capture is done, on_complete will be called with the filename
+ as an argument. If the callback returns True, the filename will be
+ unlinked.
+
+ :param filename: Name of the image file
+ :param on_complete: Callback that will be called when the operation is
+ done
+
+ :type filename: str
+ :type on_complete: callable
+ '''
+ self._take_picture(filename=filename, on_complete=on_complete)
+
+ def take_video(self, filename, on_complete):
+ '''Ask the OS to capture a video, and store it at filename.
+
+ When the capture is done, on_complete will be called with the filename
+ as an argument. If the callback returns True, the filename will be
+ unlinked.
+
+ :param filename: Name of the video file
+ :param on_complete: Callback that will be called when the operation is
+ done
+
+ :type filename: str
+ :type on_complete: callable
+ '''
+ self._take_video(filename=filename, on_complete=on_complete)
+
+ # private
+
+ def _take_picture(self, **kwargs):
+ raise NotImplementedError()
+
+ def _take_video(self, **kwargs):
+ raise NotImplementedError()
diff --git a/external/plyer/facades/camera.pyc b/external/plyer/facades/camera.pyc
new file mode 100644
index 0000000..5146deb
--- /dev/null
+++ b/external/plyer/facades/camera.pyc
Binary files differ
diff --git a/external/plyer/facades/compass.py b/external/plyer/facades/compass.py
new file mode 100644
index 0000000..aed4bbf
--- /dev/null
+++ b/external/plyer/facades/compass.py
@@ -0,0 +1,37 @@
+class Compass(object):
+ '''Compass facade.
+
+ .. versionadded:: 1.2.0
+ '''
+
+ @property
+ def orientation(self):
+ '''Property that returns values of the current compass
+ (magnetic field) sensors, as a (x, y, z) tuple.
+ Returns (None, None, None) if no data is currently available.
+ '''
+ return self.get_orientation()
+
+ def enable(self):
+ '''Activate the compass sensor.
+ '''
+ self._enable()
+
+ def disable(self):
+ '''Disable the compass sensor.
+ '''
+ self._disable()
+
+ def get_orientation(self):
+ return self._get_orientation()
+
+ # private
+
+ def _enable(self):
+ raise NotImplementedError()
+
+ def _disable(self):
+ raise NotImplementedError()
+
+ def _get_orientation(self):
+ raise NotImplementedError()
diff --git a/external/plyer/facades/compass.pyc b/external/plyer/facades/compass.pyc
new file mode 100644
index 0000000..af2e244
--- /dev/null
+++ b/external/plyer/facades/compass.pyc
Binary files differ
diff --git a/external/plyer/facades/email.py b/external/plyer/facades/email.py
new file mode 100644
index 0000000..6056a16
--- /dev/null
+++ b/external/plyer/facades/email.py
@@ -0,0 +1,23 @@
+class Email(object):
+ '''Email facade.'''
+
+ def send(self, recipient=None, subject=None, text=None,
+ create_chooser=None):
+ '''Open an email client message send window, prepopulated with the
+ given arguments.
+
+ :param recipient: Recipient of the message (str)
+ :param subject: Subject of the message (str)
+ :param text: Main body of the message (str)
+ :param create_chooser: Whether to display a program chooser to
+ handle the message (bool)
+
+ .. note:: create_chooser is only supported on Android
+ '''
+ self._send(recipient=recipient, subject=subject, text=text,
+ create_chooser=create_chooser)
+
+ # private
+
+ def _send(self, **kwargs):
+ raise NotImplementedError()
diff --git a/external/plyer/facades/email.pyc b/external/plyer/facades/email.pyc
new file mode 100644
index 0000000..b1ee2bf
--- /dev/null
+++ b/external/plyer/facades/email.pyc
Binary files differ
diff --git a/external/plyer/facades/filechooser.py b/external/plyer/facades/filechooser.py
new file mode 100644
index 0000000..040e9bb
--- /dev/null
+++ b/external/plyer/facades/filechooser.py
@@ -0,0 +1,53 @@
+class FileChooser(object):
+ '''Native filechooser dialog facade.
+
+ open_file, save_file and choose_dir accept a number of arguments
+ listed below. They return either a list of paths (normally
+ absolute), or None if no file was selected or the operation was
+ canceled and no result is available.
+
+ Arguments:
+ * **path** *(string or None)*: a path that will be selected
+ by default, or None
+ * **multiple** *(bool)*: True if you want the dialog to
+ allow multiple file selection. (Note: Windows doesn't
+ support multiple directory selection)
+ * **filters** *(iterable)*: either a list of wildcard patterns
+ or of sequences that contain the name of the filter and any
+ number of wildcards that will be grouped under that name
+ (e.g. [["Music", "*mp3", "*ogg", "*aac"], "*jpg", "*py"])
+ * **preview** *(bool)*: True if you want the file chooser to
+ show a preview of the selected file, if supported by the
+ back-end.
+ * **title** *(string or None)*: The title of the file chooser
+ window, or None for the default title.
+ * **icon** *(string or None)*: Path to the icon of the file
+ chooser window (where supported), or None for the back-end's
+ default.
+ * **show_hidden** *(bool)*: Force showing hidden files (currently
+ supported only on Windows)
+
+ Important: these methods will return only after user interaction.
+ Use threads or you will stop the mainloop if your app has one.
+ '''
+
+ def _file_selection_dialog(self, **kwargs):
+ raise NotImplementedError()
+
+ def open_file(self, *args, **kwargs):
+ """Open the file chooser in "open" mode.
+ """
+ return self._file_selection_dialog(mode="open", *args, **kwargs)
+
+ def save_file(self, *args, **kwargs):
+ """Open the file chooser in "save" mode. Confirmation will be asked
+ when a file with the same name already exists.
+ """
+ return self._file_selection_dialog(mode="save", *args, **kwargs)
+
+ def choose_dir(self, *args, **kwargs):
+ """Open the directory chooser. Note that on Windows this is very
+ limited. Consider writing your own chooser if you target that
+ platform and are planning on using unsupported features.
+ """
+ return self._file_selection_dialog(mode="dir", *args, **kwargs)
diff --git a/external/plyer/facades/filechooser.pyc b/external/plyer/facades/filechooser.pyc
new file mode 100644
index 0000000..70c0f61
--- /dev/null
+++ b/external/plyer/facades/filechooser.pyc
Binary files differ
diff --git a/external/plyer/facades/gps.py b/external/plyer/facades/gps.py
new file mode 100644
index 0000000..09f7e4b
--- /dev/null
+++ b/external/plyer/facades/gps.py
@@ -0,0 +1,67 @@
+class GPS(object):
+ '''GPS facade.
+
+ .. versionadded:: 1.1
+
+ You need to set a `on_location` callback with the :meth:`configure` method.
+ This callback will receive a couple of keywords / values, that might be
+ different depending of their availability on the targeted platform.
+ Lat and lon are always available.
+
+ - lat: latitude of the last location, in degrees
+ - lon: longitude of the last location, in degrees
+ - speed: speed of the user, in meters/second over ground
+ - bearing: bearing in degrees
+ - altitude: altitude in meters above the sea level
+
+ Here is an example of the usage of gps::
+
+ from plyer import gps
+
+ def print_locations(**kwargs):
+ print 'lat: {lat}, lon: {lon}'.format(**kwargs)
+
+ gps.configure(on_location=print_locations)
+ gps.start()
+ # later
+ gps.stop()
+ '''
+
+ def configure(self, on_location, on_status=None):
+ '''Configure the GPS object. This method should be called before
+ :meth:`start`.
+
+ :param on_location: Function to call when receiving a new location
+ :param on_status: Function to call when a status message is received
+ :type on_location: callable, multiples keys/value will be passed.
+ :type on_status: callable, args are "message-type", "status"
+
+ .. warning::
+
+ The `on_location` and `on_status` callables might be called from
+ another thread than the thread used for creating the GPS object.
+ '''
+ self.on_location = on_location
+ self.on_status = on_status
+ self._configure()
+
+ def start(self):
+ '''Start the GPS location updates.
+ '''
+ self._start()
+
+ def stop(self):
+ '''Stop the GPS location updates.
+ '''
+ self._stop()
+
+ # private
+
+ def _configure(self):
+ raise NotImplementedError()
+
+ def _start(self):
+ raise NotImplementedError()
+
+ def _stop(self):
+ raise NotImplementedError()
diff --git a/external/plyer/facades/gps.pyc b/external/plyer/facades/gps.pyc
new file mode 100644
index 0000000..175cf9e
--- /dev/null
+++ b/external/plyer/facades/gps.pyc
Binary files differ
diff --git a/external/plyer/facades/gyroscope.py b/external/plyer/facades/gyroscope.py
new file mode 100644
index 0000000..31ec1ae
--- /dev/null
+++ b/external/plyer/facades/gyroscope.py
@@ -0,0 +1,37 @@
+class Gyroscope(object):
+ '''Gyroscope facade.
+
+ .. versionadded:: 1.2.0
+ '''
+
+ @property
+ def orientation(self):
+ '''Property that returns values of the current Gyroscope sensors, as
+ a (x, y, z) tuple. Returns (None, None, None) if no data is currently
+ available.
+ '''
+ return self.get_orientation()
+
+ def enable(self):
+ '''Activate the Gyroscope sensor.
+ '''
+ self._enable()
+
+ def disable(self):
+ '''Disable the Gyroscope sensor.
+ '''
+ self._disable()
+
+ def get_orientation(self):
+ return self._get_orientation()
+
+ # private
+
+ def _enable(self):
+ raise NotImplementedError()
+
+ def _disable(self):
+ raise NotImplementedError()
+
+ def _get_orientation(self):
+ raise NotImplementedError()
diff --git a/external/plyer/facades/gyroscope.pyc b/external/plyer/facades/gyroscope.pyc
new file mode 100644
index 0000000..c6989c6
--- /dev/null
+++ b/external/plyer/facades/gyroscope.pyc
Binary files differ
diff --git a/external/plyer/facades/irblaster.py b/external/plyer/facades/irblaster.py
new file mode 100644
index 0000000..0e15a07
--- /dev/null
+++ b/external/plyer/facades/irblaster.py
@@ -0,0 +1,59 @@
+class IrBlaster(object):
+ '''Infrared blaster facade.'''
+
+ @staticmethod
+ def periods_to_microseconds(frequency, pattern):
+ '''Convert a pattern from period counts to microseconds.
+ '''
+ period = 1000000. / frequency
+ return [period * x for x in pattern]
+
+ @staticmethod
+ def microseconds_to_periods(frequency, pattern):
+ '''Convert a pattern from microseconds to period counts.
+ '''
+ period = 1000000. / frequency
+ return [x / period for x in pattern]
+
+ @property
+ def frequencies(self):
+ '''Property which contains a list of frequency ranges
+ supported by the device in the form:
+
+ [(from1, to1),
+ (from2, to2),
+ ...
+ (fromN, toN)]
+ '''
+ return self.get_frequencies()
+
+ def get_frequencies(self):
+ return self._get_frequencies()
+
+ def _get_frequencies(self):
+ raise NotImplementedError()
+
+ def transmit(self, frequency, pattern, mode='period'):
+ '''Transmit an IR sequence.
+
+ :parameters:
+ `frequency`: int
+ Carrier frequency for the IR transmission.
+ `pattern`: list[int]
+ Burst pair pattern to transmit.
+ `mode`: str, defaults to 'period'
+ Specifies the format of the pattern values.
+ Can be 'period' or 'microseconds'.
+ '''
+ return self._transmit(frequency, pattern, mode)
+
+ def _transmit(self, frequency, pattern, mode):
+ raise NotImplementedError()
+
+ def exists(self):
+ '''Check if the device has an infrared emitter.
+ '''
+ return self._exists()
+
+ def _exists(self):
+ raise NotImplementedError()
diff --git a/external/plyer/facades/irblaster.pyc b/external/plyer/facades/irblaster.pyc
new file mode 100644
index 0000000..74c9d71
--- /dev/null
+++ b/external/plyer/facades/irblaster.pyc
Binary files differ
diff --git a/external/plyer/facades/notification.py b/external/plyer/facades/notification.py
new file mode 100644
index 0000000..3a477e9
--- /dev/null
+++ b/external/plyer/facades/notification.py
@@ -0,0 +1,26 @@
+class Notification(object):
+ '''Notification facade.
+ '''
+
+ def notify(self, title='', message='', app_name='', app_icon='',
+ timeout=10):
+ '''Send a notification.
+
+ :param title: Title of the notification
+ :param message: Message of the notification
+ :param app_name: Name of the app launching this notification
+ :param app_icon: Icon to be displayed along with the message
+ :param timeout: time to display the message for, defaults to 10
+ :type title: str
+ :type message: str
+ :type app_name: str
+ :type app_icon: str
+ :type timeout: int
+ '''
+ self._notify(title=title, message=message, app_icon=app_icon,
+ app_name=app_name, timeout=timeout)
+
+ # private
+
+ def _notify(self, **kwargs):
+ raise NotImplementedError("No usable implementation found!")
diff --git a/external/plyer/facades/notification.pyc b/external/plyer/facades/notification.pyc
new file mode 100644
index 0000000..8a31ae3
--- /dev/null
+++ b/external/plyer/facades/notification.pyc
Binary files differ
diff --git a/external/plyer/facades/orientation.py b/external/plyer/facades/orientation.py
new file mode 100644
index 0000000..e58e723
--- /dev/null
+++ b/external/plyer/facades/orientation.py
@@ -0,0 +1,45 @@
+class Orientation(object):
+ '''Orientation facade.
+
+ .. note::
+ These settings are generally guidelines, the operating
+ system may choose to ignore them, or they may be overridden by
+ other system components.
+
+ .. versionadded:: 1.2.4
+ '''
+
+ def set_landscape(self, reverse=False):
+ '''Rotate the app to a landscape orientation.
+
+ :param reverse: If True, uses the opposite of the natural
+ orientation.
+ '''
+ self._set_landscape(reverse=reverse)
+
+ def _set_landscape(self, **kwargs):
+ raise NotImplementedError()
+
+ def set_portrait(self, reverse=False):
+ '''Rotate the app to a portrait orientation.
+
+ :param reverse: If True, uses the opposite of the natural
+ orientation.
+ '''
+ self._set_portrait(reverse=reverse)
+
+ def _set_portrait(self, **kwargs):
+ raise NotImplementedError()
+
+ def set_sensor(self, mode='any'):
+ '''Rotate freely following sensor information from the device.
+
+ :param mode: The rotation mode, should be one of 'any' (rotate
+ to any orientation), 'landscape' (choose nearest
+ landscape mode) or 'portrait' (choose nearest
+ portrait mode). Defaults to 'any'.
+ '''
+ self._set_sensor(mode=mode)
+
+ def _set_sensor(self, **kwargs):
+ raise NotImplementedError()
diff --git a/external/plyer/facades/orientation.pyc b/external/plyer/facades/orientation.pyc
new file mode 100644
index 0000000..23deae8
--- /dev/null
+++ b/external/plyer/facades/orientation.pyc
Binary files differ
diff --git a/external/plyer/facades/sms.py b/external/plyer/facades/sms.py
new file mode 100644
index 0000000..ce4f299
--- /dev/null
+++ b/external/plyer/facades/sms.py
@@ -0,0 +1,19 @@
+class Sms(object):
+ '''Sms facade.
+
+ .. note::
+
+ On Android your app needs the SEND_SMS permission in order to
+ send sms messages.
+
+ .. versionadded:: 1.2.0
+
+ '''
+
+ def send(self, recipient, message):
+ self._send(recipient=recipient, message=message)
+
+ # private
+
+ def _send(self, **kwargs):
+ raise NotImplementedError()
diff --git a/external/plyer/facades/sms.pyc b/external/plyer/facades/sms.pyc
new file mode 100644
index 0000000..7294345
--- /dev/null
+++ b/external/plyer/facades/sms.pyc
Binary files differ
diff --git a/external/plyer/facades/tts.py b/external/plyer/facades/tts.py
new file mode 100644
index 0000000..91c1a76
--- /dev/null
+++ b/external/plyer/facades/tts.py
@@ -0,0 +1,16 @@
+class TTS(object):
+ '''TextToSpeech facade.
+ '''
+
+ def speak(self, message=''):
+ '''Use text to speech capabilities to speak the message.
+
+ :param message: What to speak
+ :type message: str
+ '''
+ self._speak(message=message)
+
+ # private
+
+ def _speak(self, **kwargs):
+ raise NotImplementedError()
diff --git a/external/plyer/facades/tts.pyc b/external/plyer/facades/tts.pyc
new file mode 100644
index 0000000..d5d395f
--- /dev/null
+++ b/external/plyer/facades/tts.pyc
Binary files differ
diff --git a/external/plyer/facades/uniqueid.py b/external/plyer/facades/uniqueid.py
new file mode 100644
index 0000000..d916f01
--- /dev/null
+++ b/external/plyer/facades/uniqueid.py
@@ -0,0 +1,29 @@
+class UniqueID(object):
+ '''UniqueID facade.
+
+ Returns the following depending on the platform:
+
+ * **Android**: Android ID
+ * **OS X**: Serial number of the device
+ * **Linux**: Serial number using lshw
+ * **Windows**: MachineGUID from regkey
+
+ .. versionadded:: 1.2.0
+
+ .. versionchanged:: 1.2.4
+ On Android returns Android ID instead of IMEI.
+ '''
+
+ @property
+ def id(self):
+ '''Property that returns the unique id of the platform.
+ '''
+ return self.get_uid()
+
+ def get_uid(self):
+ return self._get_uid()
+
+ # private
+
+ def _get_uid(self, **kwargs):
+ raise NotImplementedError()
diff --git a/external/plyer/facades/uniqueid.pyc b/external/plyer/facades/uniqueid.pyc
new file mode 100644
index 0000000..2d4c2ff
--- /dev/null
+++ b/external/plyer/facades/uniqueid.pyc
Binary files differ
diff --git a/external/plyer/facades/vibrator.py b/external/plyer/facades/vibrator.py
new file mode 100644
index 0000000..94fe9aa
--- /dev/null
+++ b/external/plyer/facades/vibrator.py
@@ -0,0 +1,53 @@
+class Vibrator(object):
+ '''Vibration facade.
+
+ .. note::
+ On Android your app needs the VIBRATE permission to
+ access the vibrator.
+ '''
+
+ def vibrate(self, time=1):
+ '''Ask the vibrator to vibrate for the given period.
+
+ :param time: Time to vibrate for, in seconds. Default is 1.
+ '''
+ self._vibrate(time=time)
+
+ def _vibrate(self, **kwargs):
+ raise NotImplementedError()
+
+ def pattern(self, pattern=(0, 1), repeat=-1):
+ '''Ask the vibrator to vibrate with the given pattern, with an
+ optional repeat.
+
+ :param pattern: Pattern to vibrate with. Should be a list of
+ times in seconds. The first number is how long to wait
+ before vibrating, and subsequent numbers are times to
+ vibrate and not vibrate alternately.
+ Defaults to ``[0, 1]``.
+
+ :param repeat: Index at which to repeat the pattern. When the
+ vibration pattern reaches this index, it will start again
+ from the beginning. Defaults to ``-1``, which means no
+ repeat.
+ '''
+ self._pattern(pattern=pattern, repeat=repeat)
+
+ def _pattern(self, **kwargs):
+ raise NotImplementedError()
+
+ def exists(self):
+ '''Check if the device has a vibrator. Returns True or
+ False.
+ '''
+ return self._exists()
+
+ def _exists(self, **kwargs):
+ raise NotImplementedError()
+
+ def cancel(self):
+ '''Cancels any current vibration, and stops the vibrator.'''
+ self._cancel()
+
+ def _cancel(self, **kwargs):
+ raise NotImplementedError()
diff --git a/external/plyer/facades/vibrator.pyc b/external/plyer/facades/vibrator.pyc
new file mode 100644
index 0000000..cec7d7f
--- /dev/null
+++ b/external/plyer/facades/vibrator.pyc
Binary files differ
diff --git a/external/plyer/platforms/__init__.py b/external/plyer/platforms/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/external/plyer/platforms/__init__.py
diff --git a/external/plyer/platforms/android/__init__.py b/external/plyer/platforms/android/__init__.py
new file mode 100644
index 0000000..6b565a3
--- /dev/null
+++ b/external/plyer/platforms/android/__init__.py
@@ -0,0 +1,12 @@
+from os import environ
+from jnius import autoclass
+
+ANDROID_VERSION = autoclass('android.os.Build$VERSION')
+SDK_INT = ANDROID_VERSION.SDK_INT
+
+if 'PYTHON_SERVICE_ARGUMENT' in environ:
+ PythonService = autoclass('org.renpy.android.PythonService')
+ activity = PythonService.mService
+else:
+ PythonActivity = autoclass('org.renpy.android.PythonActivity')
+ activity = PythonActivity.mActivity
diff --git a/external/plyer/platforms/android/accelerometer.py b/external/plyer/platforms/android/accelerometer.py
new file mode 100644
index 0000000..af07c52
--- /dev/null
+++ b/external/plyer/platforms/android/accelerometer.py
@@ -0,0 +1,74 @@
+'''
+Android accelerometer
+---------------------
+'''
+
+from plyer.facades import Accelerometer
+from jnius import PythonJavaClass, java_method, autoclass, cast
+from plyer.platforms.android import activity
+
+Context = autoclass('android.content.Context')
+Sensor = autoclass('android.hardware.Sensor')
+SensorManager = autoclass('android.hardware.SensorManager')
+
+
+class AccelerometerSensorListener(PythonJavaClass):
+ __javainterfaces__ = ['android/hardware/SensorEventListener']
+
+ def __init__(self):
+ super(AccelerometerSensorListener, self).__init__()
+ self.SensorManager = cast('android.hardware.SensorManager',
+ activity.getSystemService(Context.SENSOR_SERVICE))
+ self.sensor = self.SensorManager.getDefaultSensor(
+ Sensor.TYPE_ACCELEROMETER)
+
+ self.values = [None, None, None]
+
+ def enable(self):
+ self.SensorManager.registerListener(self, self.sensor,
+ SensorManager.SENSOR_DELAY_NORMAL)
+
+ def disable(self):
+ self.SensorManager.unregisterListener(self, self.sensor)
+
+ @java_method('(Landroid/hardware/SensorEvent;)V')
+ def onSensorChanged(self, event):
+ self.values = event.values[:3]
+
+ @java_method('(Landroid/hardware/Sensor;I)V')
+ def onAccuracyChanged(self, sensor, accuracy):
+ # Maybe, do something in future?
+ pass
+
+
+class AndroidAccelerometer(Accelerometer):
+ def __init__(self):
+ super(AndroidAccelerometer, self).__init__()
+ self.bState = False
+
+ def _enable(self):
+ if (not self.bState):
+ self.listener = AccelerometerSensorListener()
+ self.listener.enable()
+ self.bState = True
+
+ def _disable(self):
+ if (self.bState):
+ self.bState = False
+ self.listener.disable()
+ del self.listener
+
+ def _get_acceleration(self):
+ if (self.bState):
+ return tuple(self.listener.values)
+ else:
+ return (None, None, None)
+
+ def __del__(self):
+ if(self.bState):
+ self._disable()
+ super(self.__class__, self).__del__()
+
+
+def instance():
+ return AndroidAccelerometer()
diff --git a/external/plyer/platforms/android/audio.py b/external/plyer/platforms/android/audio.py
new file mode 100644
index 0000000..2115f19
--- /dev/null
+++ b/external/plyer/platforms/android/audio.py
@@ -0,0 +1,58 @@
+from jnius import autoclass
+
+from plyer.facades.audio import Audio
+
+# Recorder Classes
+MediaRecorder = autoclass('android.media.MediaRecorder')
+AudioSource = autoclass('android.media.MediaRecorder$AudioSource')
+OutputFormat = autoclass('android.media.MediaRecorder$OutputFormat')
+AudioEncoder = autoclass('android.media.MediaRecorder$AudioEncoder')
+
+# Player Classes
+MediaPlayer = autoclass('android.media.MediaPlayer')
+
+
+class AndroidAudio(Audio):
+ '''Audio for android.
+
+ For recording audio we use MediaRecorder Android class.
+ For playing audio we use MediaPlayer Android class.
+ '''
+
+ def __init__(self, file_path=None):
+ default_path = '/sdcard/testrecorder.3gp'
+ super(AndroidAudio, self).__init__(file_path or default_path)
+
+ self._recorder = None
+ self._player = None
+
+ def _start(self):
+ self._recorder = MediaRecorder()
+ self._recorder.setAudioSource(AudioSource.DEFAULT)
+ self._recorder.setOutputFormat(OutputFormat.DEFAULT)
+ self._recorder.setAudioEncoder(AudioEncoder.DEFAULT)
+ self._recorder.setOutputFile(self.file_path)
+
+ self._recorder.prepare()
+ self._recorder.start()
+
+ def _stop(self):
+ if self._recorder:
+ self._recorder.stop()
+ self._recorder.release()
+ self._recorder = None
+
+ if self._player:
+ self._player.stop()
+ self._player.release()
+ self._player = None
+
+ def _play(self):
+ self._player = MediaPlayer()
+ self._player.setDataSource(self.file_path)
+ self._player.prepare()
+ self._player.start()
+
+
+def instance():
+ return AndroidAudio()
diff --git a/external/plyer/platforms/android/battery.py b/external/plyer/platforms/android/battery.py
new file mode 100644
index 0000000..2ade1d2
--- /dev/null
+++ b/external/plyer/platforms/android/battery.py
@@ -0,0 +1,34 @@
+from jnius import autoclass, cast
+from plyer.platforms.android import activity
+from plyer.facades import Battery
+
+Intent = autoclass('android.content.Intent')
+BatteryManager = autoclass('android.os.BatteryManager')
+IntentFilter = autoclass('android.content.IntentFilter')
+
+
+class AndroidBattery(Battery):
+ def _get_state(self):
+ status = {"isCharging": None, "percentage": None}
+
+ ifilter = IntentFilter(Intent.ACTION_BATTERY_CHANGED)
+
+ batteryStatus = cast('android.content.Intent',
+ activity.registerReceiver(None, ifilter))
+
+ query = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1)
+ isCharging = (query == BatteryManager.BATTERY_STATUS_CHARGING or
+ query == BatteryManager.BATTERY_STATUS_FULL)
+
+ level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)
+ scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
+ percentage = level / float(scale)
+
+ status['isCharging'] = isCharging
+ status['percentage'] = percentage
+
+ return status
+
+
+def instance():
+ return AndroidBattery()
diff --git a/external/plyer/platforms/android/camera.py b/external/plyer/platforms/android/camera.py
new file mode 100644
index 0000000..344296d
--- /dev/null
+++ b/external/plyer/platforms/android/camera.py
@@ -0,0 +1,59 @@
+import android
+import android.activity
+from os import unlink
+from jnius import autoclass, cast
+from plyer.facades import Camera
+from plyer.platforms.android import activity
+
+Intent = autoclass('android.content.Intent')
+PythonActivity = autoclass('org.renpy.android.PythonActivity')
+MediaStore = autoclass('android.provider.MediaStore')
+Uri = autoclass('android.net.Uri')
+
+
+class AndroidCamera(Camera):
+
+ def _take_picture(self, on_complete, filename=None):
+ assert(on_complete is not None)
+ self.on_complete = on_complete
+ self.filename = filename
+ android.activity.unbind(on_activity_result=self._on_activity_result)
+ android.activity.bind(on_activity_result=self._on_activity_result)
+ intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
+ uri = Uri.parse('file://' + filename)
+ parcelable = cast('android.os.Parcelable', uri)
+ intent.putExtra(MediaStore.EXTRA_OUTPUT, parcelable)
+ activity.startActivityForResult(intent, 0x123)
+
+ def _take_video(self, on_complete, filename=None):
+ assert(on_complete is not None)
+ self.on_complete = on_complete
+ self.filename = filename
+ android.activity.unbind(on_activity_result=self._on_activity_result)
+ android.activity.bind(on_activity_result=self._on_activity_result)
+ intent = Intent(MediaStore.ACTION_VIDEO_CAPTURE)
+ uri = Uri.parse('file://' + filename)
+ parcelable = cast('android.os.Parcelable', uri)
+ intent.putExtra(MediaStore.EXTRA_OUTPUT, parcelable)
+
+ # 0 = low quality, suitable for MMS messages,
+ # 1 = high quality
+ intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1)
+ activity.startActivityForResult(intent, 0x123)
+
+ def _on_activity_result(self, requestCode, resultCode, intent):
+ if requestCode != 0x123:
+ return
+ android.activity.unbind(on_activity_result=self._on_activity_result)
+ if self.on_complete(self.filename):
+ self._unlink(self.filename)
+
+ def _unlink(self, fn):
+ try:
+ unlink(fn)
+ except:
+ pass
+
+
+def instance():
+ return AndroidCamera()
diff --git a/external/plyer/platforms/android/compass.py b/external/plyer/platforms/android/compass.py
new file mode 100644
index 0000000..7fb19d6
--- /dev/null
+++ b/external/plyer/platforms/android/compass.py
@@ -0,0 +1,74 @@
+'''
+Android Compass
+---------------------
+'''
+
+from plyer.facades import Compass
+from jnius import PythonJavaClass, java_method, autoclass, cast
+from plyer.platforms.android import activity
+
+Context = autoclass('android.content.Context')
+Sensor = autoclass('android.hardware.Sensor')
+SensorManager = autoclass('android.hardware.SensorManager')
+
+
+class MagneticFieldSensorListener(PythonJavaClass):
+ __javainterfaces__ = ['android/hardware/SensorEventListener']
+
+ def __init__(self):
+ super(MagneticFieldSensorListener, self).__init__()
+ self.SensorManager = cast('android.hardware.SensorManager',
+ activity.getSystemService(Context.SENSOR_SERVICE))
+ self.sensor = self.SensorManager.getDefaultSensor(
+ Sensor.TYPE_MAGNETIC_FIELD)
+
+ self.values = [None, None, None]
+
+ def enable(self):
+ self.SensorManager.registerListener(self, self.sensor,
+ SensorManager.SENSOR_DELAY_NORMAL)
+
+ def disable(self):
+ self.SensorManager.unregisterListener(self, self.sensor)
+
+ @java_method('(Landroid/hardware/SensorEvent;)V')
+ def onSensorChanged(self, event):
+ self.values = event.values[:3]
+
+ @java_method('(Landroid/hardware/Sensor;I)V')
+ def onAccuracyChanged(self, sensor, accuracy):
+ # Maybe, do something in future?
+ pass
+
+
+class AndroidCompass(Compass):
+ def __init__(self):
+ super(AndroidCompass, self).__init__()
+ self.bState = False
+
+ def _enable(self):
+ if (not self.bState):
+ self.listener = MagneticFieldSensorListener()
+ self.listener.enable()
+ self.bState = True
+
+ def _disable(self):
+ if (self.bState):
+ self.bState = False
+ self.listener.disable()
+ del self.listener
+
+ def _get_orientation(self):
+ if (self.bState):
+ return tuple(self.listener.values)
+ else:
+ return (None, None, None)
+
+ def __del__(self):
+ if(self.bState):
+ self._disable()
+ super(self.__class__, self).__del__()
+
+
+def instance():
+ return AndroidCompass()
diff --git a/external/plyer/platforms/android/email.py b/external/plyer/platforms/android/email.py
new file mode 100644
index 0000000..79923e4
--- /dev/null
+++ b/external/plyer/platforms/android/email.py
@@ -0,0 +1,40 @@
+from jnius import autoclass, cast
+from plyer.facades import Email
+from plyer.platforms.android import activity
+
+Intent = autoclass('android.content.Intent')
+AndroidString = autoclass('java.lang.String')
+
+
+class AndroidEmail(Email):
+ def _send(self, **kwargs):
+ intent = Intent(Intent.ACTION_SEND)
+ intent.setType('text/plain')
+
+ recipient = kwargs.get('recipient')
+ subject = kwargs.get('subject')
+ text = kwargs.get('text')
+ create_chooser = kwargs.get('create_chooser')
+
+ if recipient:
+ intent.putExtra(Intent.EXTRA_EMAIL, [recipient])
+ if subject:
+ android_subject = cast('java.lang.CharSequence',
+ AndroidString(subject))
+ intent.putExtra(Intent.EXTRA_SUBJECT, android_subject)
+ if text:
+ android_text = cast('java.lang.CharSequence',
+ AndroidString(text))
+ intent.putExtra(Intent.EXTRA_TEXT, android_text)
+
+ if create_chooser:
+ chooser_title = cast('java.lang.CharSequence',
+ AndroidString('Send message with:'))
+ activity.startActivity(Intent.createChooser(intent,
+ chooser_title))
+ else:
+ activity.startActivity(intent)
+
+
+def instance():
+ return AndroidEmail()
diff --git a/external/plyer/platforms/android/gps.py b/external/plyer/platforms/android/gps.py
new file mode 100644
index 0000000..fbe580f
--- /dev/null
+++ b/external/plyer/platforms/android/gps.py
@@ -0,0 +1,79 @@
+'''
+Android GPS
+-----------
+'''
+
+from plyer.facades import GPS
+from plyer.platforms.android import activity
+from jnius import autoclass, java_method, PythonJavaClass
+
+Looper = autoclass('android.os.Looper')
+LocationManager = autoclass('android.location.LocationManager')
+Context = autoclass('android.content.Context')
+
+
+class _LocationListener(PythonJavaClass):
+ __javainterfaces__ = ['android/location/LocationListener']
+
+ def __init__(self, root):
+ self.root = root
+ super(_LocationListener, self).__init__()
+
+ @java_method('(Landroid/location/Location;)V')
+ def onLocationChanged(self, location):
+ self.root.on_location(
+ lat=location.getLatitude(),
+ lon=location.getLongitude(),
+ speed=location.getSpeed(),
+ bearing=location.getBearing(),
+ altitude=location.getAltitude())
+
+ @java_method('(Ljava/lang/String;)V')
+ def onProviderEnabled(self, status):
+ if self.root.on_status:
+ self.root.on_status('provider-enabled', status)
+
+ @java_method('(Ljava/lang/String;)V')
+ def onProviderDisabled(self, status):
+ if self.root.on_status:
+ self.root.on_status('provider-disabled', status)
+
+ @java_method('(Ljava/lang/String;ILandroid/os/Bundle;)V')
+ def onStatusChanged(self, provider, status, extras):
+ if self.root.on_status:
+ s_status = 'unknown'
+ if status == 0x00:
+ s_status = 'out-of-service'
+ elif status == 0x01:
+ s_status = 'temporarily-unavailable'
+ elif status == 0x02:
+ s_status = 'available'
+ self.root.on_status('provider-status', '{}: {}'.format(
+ provider, s_status))
+
+
+class AndroidGPS(GPS):
+
+ def _configure(self):
+ if not hasattr(self, '_location_manager'):
+ self._location_manager = activity.getSystemService(
+ Context.LOCATION_SERVICE)
+ self._location_listener = _LocationListener(self)
+
+ def _start(self):
+ # XXX defaults should be configurable by the user, later
+ providers = self._location_manager.getProviders(False).toArray()
+ for provider in providers:
+ self._location_manager.requestLocationUpdates(
+ provider,
+ 1000, # minTime, in milliseconds
+ 1, # minDistance, in meters
+ self._location_listener,
+ Looper.getMainLooper())
+
+ def _stop(self):
+ self._location_manager.removeUpdates(self._location_listener)
+
+
+def instance():
+ return AndroidGPS()
diff --git a/external/plyer/platforms/android/gyroscope.py b/external/plyer/platforms/android/gyroscope.py
new file mode 100644
index 0000000..58747d7
--- /dev/null
+++ b/external/plyer/platforms/android/gyroscope.py
@@ -0,0 +1,74 @@
+'''
+Android Gyroscope
+---------------------
+'''
+
+from plyer.facades import Gyroscope
+from jnius import PythonJavaClass, java_method, autoclass, cast
+from plyer.platforms.android import activity
+
+Context = autoclass('android.content.Context')
+Sensor = autoclass('android.hardware.Sensor')
+SensorManager = autoclass('android.hardware.SensorManager')
+
+
+class GyroscopeSensorListener(PythonJavaClass):
+ __javainterfaces__ = ['android/hardware/SensorEventListener']
+
+ def __init__(self):
+ super(GyroscopeSensorListener, self).__init__()
+ self.SensorManager = cast('android.hardware.SensorManager',
+ activity.getSystemService(Context.SENSOR_SERVICE))
+ self.sensor = self.SensorManager.getDefaultSensor(
+ Sensor.TYPE_GYROSCOPE)
+
+ self.values = [None, None, None]
+
+ def enable(self):
+ self.SensorManager.registerListener(self, self.sensor,
+ SensorManager.SENSOR_DELAY_NORMAL)
+
+ def disable(self):
+ self.SensorManager.unregisterListener(self, self.sensor)
+
+ @java_method('(Landroid/hardware/SensorEvent;)V')
+ def onSensorChanged(self, event):
+ self.values = event.values[:3]
+
+ @java_method('(Landroid/hardware/Sensor;I)V')
+ def onAccuracyChanged(self, sensor, accuracy):
+ # Maybe, do something in future?
+ pass
+
+
+class AndroidGyroscope(Gyroscope):
+ def __init__(self):
+ super(AndroidGyroscope, self).__init__()
+ self.bState = False
+
+ def _enable(self):
+ if (not self.bState):
+ self.listener = GyroscopeSensorListener()
+ self.listener.enable()
+ self.bState = True
+
+ def _disable(self):
+ if (self.bState):
+ self.bState = False
+ self.listener.disable()
+ del self.listener
+
+ def _get_orientation(self):
+ if (self.bState):
+ return tuple(self.listener.values)
+ else:
+ return (None, None, None)
+
+ def __del__(self):
+ if(self.bState):
+ self._disable()
+ super(self.__class__, self).__del__()
+
+
+def instance():
+ return AndroidGyroscope()
diff --git a/external/plyer/platforms/android/irblaster.py b/external/plyer/platforms/android/irblaster.py
new file mode 100644
index 0000000..6c44717
--- /dev/null
+++ b/external/plyer/platforms/android/irblaster.py
@@ -0,0 +1,54 @@
+from jnius import autoclass
+
+from plyer.facades import IrBlaster
+from plyer.platforms.android import activity, SDK_INT, ANDROID_VERSION
+
+if SDK_INT >= 19:
+ Context = autoclass('android.content.Context')
+ ir_manager = activity.getSystemService(Context.CONSUMER_IR_SERVICE)
+else:
+ ir_manager = None
+
+
+class AndroidIrBlaster(IrBlaster):
+ def _exists(self):
+ if ir_manager and ir_manager.hasIrEmitter():
+ return True
+ return False
+
+ @property
+ def multiply_pulse(self):
+ '''Android 4.4.3+ uses microseconds instead of period counts
+ '''
+ return not (SDK_INT == 19 and
+ int(str(ANDROID_VERSION.RELEASE).rsplit('.', 1)[-1]) < 3)
+
+ def _get_frequencies(self):
+ if not ir_manager:
+ return None
+
+ if hasattr(self, '_frequencies'):
+ return self._frequencies
+
+ ir_frequencies = ir_manager.getCarrierFrequencies()
+ if not ir_frequencies:
+ return []
+
+ frequencies = []
+ for freqrange in ir_frequencies:
+ freq = (freqrange.getMinFrequency(), freqrange.getMaxFrequency())
+ frequencies.append(freq)
+
+ self._frequencies = frequencies
+ return frequencies
+
+ def _transmit(self, frequency, pattern, mode):
+ if self.multiply_pulse and mode == 'period':
+ pattern = self.periods_to_microseconds(frequency, pattern)
+ elif not self.multiply_pulse and mode == 'microseconds':
+ pattern = self.microseconds_to_periods(frequency, pattern)
+ ir_manager.transmit(frequency, pattern)
+
+
+def instance():
+ return AndroidIrBlaster()
diff --git a/external/plyer/platforms/android/notification.py b/external/plyer/platforms/android/notification.py
new file mode 100644
index 0000000..bfc3a25
--- /dev/null
+++ b/external/plyer/platforms/android/notification.py
@@ -0,0 +1,37 @@
+from jnius import autoclass
+from plyer.facades import Notification
+from plyer.platforms.android import activity, SDK_INT
+
+AndroidString = autoclass('java.lang.String')
+Context = autoclass('android.content.Context')
+NotificationBuilder = autoclass('android.app.Notification$Builder')
+Drawable = autoclass("{}.R$drawable".format(activity.getPackageName()))
+
+
+class AndroidNotification(Notification):
+ def _get_notification_service(self):
+ if not hasattr(self, '_ns'):
+ self._ns = activity.getSystemService(Context.NOTIFICATION_SERVICE)
+ return self._ns
+
+ def _notify(self, **kwargs):
+ icon = getattr(Drawable, kwargs.get('icon_android', 'icon'))
+ noti = NotificationBuilder(activity)
+ noti.setContentTitle(AndroidString(
+ kwargs.get('title').encode('utf-8')))
+ noti.setContentText(AndroidString(
+ kwargs.get('message').encode('utf-8')))
+ noti.setSmallIcon(icon)
+ noti.setAutoCancel(True)
+
+ if SDK_INT >= 16:
+ noti = noti.build()
+ else:
+ noti = noti.getNotification()
+
+ self._get_notification_service().notify(0, noti)
+
+
+def instance():
+ return AndroidNotification()
+
diff --git a/external/plyer/platforms/android/orientation.py b/external/plyer/platforms/android/orientation.py
new file mode 100644
index 0000000..e98e34d
--- /dev/null
+++ b/external/plyer/platforms/android/orientation.py
@@ -0,0 +1,43 @@
+from jnius import autoclass, cast
+from plyer.platforms.android import activity
+from plyer.facades import Orientation
+
+ActivityInfo = autoclass('android.content.pm.ActivityInfo')
+
+
+class AndroidOrientation(Orientation):
+
+ def _set_landscape(self, **kwargs):
+ reverse = kwargs.get('reverse')
+ if reverse:
+ activity.setRequestedOrientation(
+ ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE)
+ else:
+ activity.setRequestedOrientation(
+ ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
+
+ def _set_portrait(self, **kwargs):
+ reverse = kwargs.get('reverse')
+ if reverse:
+ activity.setRequestedOrientation(
+ ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT)
+ else:
+ activity.setRequestedOrientation(
+ ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
+
+ def _set_sensor(self, **kwargs):
+ mode = kwargs.get('mode')
+
+ if mode == 'any':
+ activity.setRequestedOrientation(
+ ActivityInfo.SCREEN_ORIENTATION_SENSOR)
+ elif mode == 'landscape':
+ activity.setRequestedOrientation(
+ ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE)
+ elif mode == 'portrait':
+ activity.setRequestedOrientation(
+ ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT)
+
+
+def instance():
+ return AndroidOrientation()
diff --git a/external/plyer/platforms/android/sms.py b/external/plyer/platforms/android/sms.py
new file mode 100644
index 0000000..8650968
--- /dev/null
+++ b/external/plyer/platforms/android/sms.py
@@ -0,0 +1,25 @@
+'''
+Android SMS
+-----------
+'''
+
+from jnius import autoclass
+from plyer.facades import Sms
+
+SmsManager = autoclass('android.telephony.SmsManager')
+
+
+class AndroidSms(Sms):
+
+ def _send(self, **kwargs):
+ sms = SmsManager.getDefault()
+
+ recipient = kwargs.get('recipient')
+ message = kwargs.get('message')
+
+ if sms:
+ sms.sendTextMessage(recipient, None, message, None, None)
+
+
+def instance():
+ return AndroidSms()
diff --git a/external/plyer/platforms/android/tts.py b/external/plyer/platforms/android/tts.py
new file mode 100644
index 0000000..eae2974
--- /dev/null
+++ b/external/plyer/platforms/android/tts.py
@@ -0,0 +1,24 @@
+from time import sleep
+from jnius import autoclass
+from plyer.facades import TTS
+from plyer.platforms.android import activity
+
+Locale = autoclass('java.util.Locale')
+TextToSpeech = autoclass('android.speech.tts.TextToSpeech')
+
+
+class AndroidTextToSpeech(TTS):
+ def _speak(self, **kwargs):
+ tts = TextToSpeech(activity, None)
+ tts.setLanguage(Locale.US) # TODO: locale specification as option
+ retries = 0 # First try rarely succeeds due to some timing issue
+ while retries < 100 and \
+ tts.speak(kwargs.get('message').encode('utf-8'),
+ TextToSpeech.QUEUE_FLUSH, None) == -1:
+ # -1 indicates error. Let's wait and then try again
+ sleep(0.1)
+ retries += 1
+
+
+def instance():
+ return AndroidTextToSpeech()
diff --git a/external/plyer/platforms/android/uniqueid.py b/external/plyer/platforms/android/uniqueid.py
new file mode 100644
index 0000000..b8561de
--- /dev/null
+++ b/external/plyer/platforms/android/uniqueid.py
@@ -0,0 +1,16 @@
+from jnius import autoclass
+from plyer.platforms.android import activity
+from plyer.facades import UniqueID
+
+Secure = autoclass('android.provider.Settings$Secure')
+
+
+class AndroidUniqueID(UniqueID):
+
+ def _get_uid(self):
+ return Secure.getString(activity.getContentResolver(),
+ Secure.ANDROID_ID)
+
+
+def instance():
+ return AndroidUniqueID()
diff --git a/external/plyer/platforms/android/vibrator.py b/external/plyer/platforms/android/vibrator.py
new file mode 100644
index 0000000..c28fe8e
--- /dev/null
+++ b/external/plyer/platforms/android/vibrator.py
@@ -0,0 +1,48 @@
+'''Implementation Vibrator for Android.'''
+
+from jnius import autoclass
+from plyer.facades import Vibrator
+from plyer.platforms.android import activity
+from plyer.platforms.android import SDK_INT
+
+Context = autoclass('android.content.Context')
+vibrator = activity.getSystemService(Context.VIBRATOR_SERVICE)
+
+
+class AndroidVibrator(Vibrator):
+ '''Android Vibrator class.
+
+ Supported features:
+ * vibrate for some period of time.
+ * vibrate from given pattern.
+ * cancel vibration.
+ * check whether Vibrator exists.
+ '''
+
+ def _vibrate(self, time=None, **kwargs):
+ if vibrator:
+ vibrator.vibrate(int(1000 * time))
+
+ def _pattern(self, pattern=None, repeat=None, **kwargs):
+ pattern = [int(1000 * time) for time in pattern]
+
+ if vibrator:
+ vibrator.vibrate(pattern, repeat)
+
+ def _exists(self, **kwargs):
+ if SDK_INT >= 11:
+ return vibrator.hasVibrator()
+ elif activity.getSystemService(Context.VIBRATOR_SERVICE) is None:
+ raise NotImplementedError()
+ return True
+
+ def _cancel(self, **kwargs):
+ vibrator.cancel()
+
+
+def instance():
+ '''Returns Vibrator with android features.
+
+ :return: instance of class AndroidVibrator
+ '''
+ return AndroidVibrator()
diff --git a/external/plyer/platforms/ios/__init__.py b/external/plyer/platforms/ios/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/external/plyer/platforms/ios/__init__.py
diff --git a/external/plyer/platforms/ios/accelerometer.py b/external/plyer/platforms/ios/accelerometer.py
new file mode 100644
index 0000000..bf1ef02
--- /dev/null
+++ b/external/plyer/platforms/ios/accelerometer.py
@@ -0,0 +1,34 @@
+'''
+iOS accelerometer
+-----------------
+
+Taken from: http://pyobjus.readthedocs.org/en/latest/pyobjus_ios.html \
+ #accessing-accelerometer
+'''
+
+from plyer.facades import Accelerometer
+from pyobjus import autoclass
+
+
+class IosAccelerometer(Accelerometer):
+
+ def __init__(self):
+ super(IosAccelerometer, self).__init__()
+ self.bridge = autoclass('bridge').alloc().init()
+ self.bridge.motionManager.setAccelerometerUpdateInterval_(0.1)
+
+ def _enable(self):
+ self.bridge.startAccelerometer()
+
+ def _disable(self):
+ self.bridge.stopAccelerometer()
+
+ def _get_acceleration(self):
+ return (
+ self.bridge.ac_x,
+ self.bridge.ac_y,
+ self.bridge.ac_z)
+
+
+def instance():
+ return IosAccelerometer()
diff --git a/external/plyer/platforms/ios/battery.py b/external/plyer/platforms/ios/battery.py
new file mode 100644
index 0000000..55aa2c6
--- /dev/null
+++ b/external/plyer/platforms/ios/battery.py
@@ -0,0 +1,36 @@
+from pyobjus import autoclass
+from pyobjus.dylib_manager import load_framework
+from plyer.facades import Battery
+
+load_framework('/System/Library/Frameworks/UIKit.framework')
+UIDevice = autoclass('UIDevice')
+
+
+class iOSBattery(Battery):
+ def __init__(self):
+ super(iOSBattery, self).__init__()
+ self.device = UIDevice.currentDevice()
+
+ def _get_state(self):
+ status = {"isCharging": None, "percentage": None}
+
+ if(not self.device.batteryMonitoringEnabled):
+ self.device.setBatteryMonitoringEnabled_(True)
+
+ if self.device.batteryState == 0:
+ isCharging = None
+ elif self.device.batteryState == 2:
+ isCharging = True
+ else:
+ isCharging = False
+
+ percentage = self.device.batteryLevel * 100.
+
+ status['isCharging'] = isCharging
+ status['percentage'] = percentage
+
+ return status
+
+
+def instance():
+ return iOSBattery()
diff --git a/external/plyer/platforms/ios/compass.py b/external/plyer/platforms/ios/compass.py
new file mode 100644
index 0000000..6e5c935
--- /dev/null
+++ b/external/plyer/platforms/ios/compass.py
@@ -0,0 +1,31 @@
+'''
+iOS Compass
+---------------------
+'''
+
+from plyer.facades import Compass
+from pyobjus import autoclass
+
+
+class IosCompass(Compass):
+
+ def __init__(self):
+ super(IosCompass, self).__init__()
+ self.bridge = autoclass('bridge').alloc().init()
+ self.bridge.motionManager.setMagnetometerUpdateInterval_(0.1)
+
+ def _enable(self):
+ self.bridge.startMagnetometer()
+
+ def _disable(self):
+ self.bridge.stopMagnetometer()
+
+ def _get_orientation(self):
+ return (
+ self.bridge.mg_x,
+ self.bridge.mg_y,
+ self.bridge.mg_z)
+
+
+def instance():
+ return IosCompass()
diff --git a/external/plyer/platforms/ios/email.py b/external/plyer/platforms/ios/email.py
new file mode 100644
index 0000000..7e55e4e
--- /dev/null
+++ b/external/plyer/platforms/ios/email.py
@@ -0,0 +1,41 @@
+try:
+ from urllib.parse import quote
+except ImportError:
+ from urllib import quote
+
+from plyer.facades import Email
+from pyobjus import autoclass, objc_str
+from pyobjus.dylib_manager import load_framework
+
+load_framework('/System/Library/Frameworks/UIKit.framework')
+
+NSURL = autoclass('NSURL')
+NSString = autoclass('NSString')
+UIApplication = autoclass('UIApplication')
+
+
+class iOSXEmail(Email):
+ def _send(self, **kwargs):
+ recipient = kwargs.get('recipient')
+ subject = kwargs.get('subject')
+ text = kwargs.get('text')
+
+ uri = "mailto:"
+ if recipient:
+ uri += str(recipient)
+ if subject:
+ uri += "?" if not "?" in uri else "&"
+ uri += "subject="
+ uri += quote(str(subject))
+ if text:
+ uri += "?" if not "?" in uri else "&"
+ uri += "body="
+ uri += quote(str(text))
+
+ nsurl = NSURL.alloc().initWithString_(objc_str(uri))
+
+ UIApplication.sharedApplication().openURL_(nsurl)
+
+
+def instance():
+ return iOSXEmail()
diff --git a/external/plyer/platforms/ios/gps.py b/external/plyer/platforms/ios/gps.py
new file mode 100644
index 0000000..4d6d665
--- /dev/null
+++ b/external/plyer/platforms/ios/gps.py
@@ -0,0 +1,45 @@
+'''
+iOS GPS
+-----------
+'''
+
+from pyobjus import autoclass, protocol
+from pyobjus.dylib_manager import load_framework
+from plyer.facades import GPS
+
+load_framework('/System/Library/Frameworks/CoreLocation.framework')
+CLLocationManager = autoclass('CLLocationManager')
+
+
+class IosGPS(GPS):
+ def _configure(self):
+ if not hasattr(self, '_location_manager'):
+ self._location_manager = CLLocationManager.alloc().init()
+
+ def _start(self):
+ self._location_manager.delegate = self
+
+ self._location_manager.requestWhenInUseAuthorization()
+ # NSLocationWhenInUseUsageDescription key must exist in Info.plist
+ # file. When the authorization prompt is displayed your app goes
+ # into pause mode and if your app doesn't support background mode
+ # it will crash.
+ self._location_manager.startUpdatingLocation()
+
+ def _stop(self):
+ self._location_manager.stopUpdatingLocation()
+
+ @protocol('CLLocationManagerDelegate')
+ def locationManager_didUpdateLocations_(self, manager, locations):
+ location = manager.location
+
+ self.on_location(
+ lat=location.coordinate.a,
+ lon=location.coordinate.b,
+ speed=location.speed,
+ bearing=location.course,
+ altitude=location.altitude)
+
+
+def instance():
+ return IosGPS()
diff --git a/external/plyer/platforms/ios/gyroscope.py b/external/plyer/platforms/ios/gyroscope.py
new file mode 100644
index 0000000..e8b93cf
--- /dev/null
+++ b/external/plyer/platforms/ios/gyroscope.py
@@ -0,0 +1,31 @@
+'''
+iOS Gyroscope
+---------------------
+'''
+
+from plyer.facades import Gyroscope
+from pyobjus import autoclass
+
+
+class IosGyroscope(Gyroscope):
+
+ def __init__(self):
+ super(IosGyroscope, self).__init__()
+ self.bridge = autoclass('bridge').alloc().init()
+ self.bridge.motionManager.setGyroscopeUpdateInterval_(0.1)
+
+ def _enable(self):
+ self.bridge.startGyroscope()
+
+ def _disable(self):
+ self.bridge.stopGyroscope()
+
+ def _get_orientation(self):
+ return (
+ self.bridge.gy_x,
+ self.bridge.gy_y,
+ self.bridge.gy_z)
+
+
+def instance():
+ return IosGyroscope()
diff --git a/external/plyer/platforms/ios/tts.py b/external/plyer/platforms/ios/tts.py
new file mode 100644
index 0000000..a711483
--- /dev/null
+++ b/external/plyer/platforms/ios/tts.py
@@ -0,0 +1,35 @@
+from pyobjus import autoclass, objc_str
+from pyobjus.dylib_manager import load_framework
+
+from plyer.facades import TTS
+
+load_framework('/System/Library/Frameworks/AVFoundation.framework')
+AVSpeechUtterance = autoclass('AVSpeechUtterance')
+AVSpeechSynthesizer = autoclass('AVSpeechSynthesizer')
+AVSpeechSynthesisVoice = autoclass('AVSpeechSynthesisVoice')
+
+
+class iOSTextToSpeech(TTS):
+ def __init__(self):
+ super(iOSTextToSpeech, self).__init__()
+ self.synth = AVSpeechSynthesizer.alloc().init()
+ self.voice = None
+
+ def _set_locale(self, locale="en-US"):
+ self.voice = AVSpeechSynthesisVoice.voiceWithLanguage_(objc_str(locale))
+
+ def _speak(self, **kwargs):
+ message = kwargs.get('message')
+
+ if(not self.voice):
+ self._set_locale()
+
+ utterance = \
+ AVSpeechUtterance.speechUtteranceWithString_(objc_str(message))
+
+ utterance.voice = self.voice
+ self.synth.speakUtterance_(utterance)
+
+
+def instance():
+ return iOSTextToSpeech()
diff --git a/external/plyer/platforms/ios/uniqueid.py b/external/plyer/platforms/ios/uniqueid.py
new file mode 100644
index 0000000..1587f4b
--- /dev/null
+++ b/external/plyer/platforms/ios/uniqueid.py
@@ -0,0 +1,17 @@
+from pyobjus import autoclass
+from pyobjus.dylib_manager import load_framework
+from plyer.facades import UniqueID
+
+load_framework('/System/Library/Frameworks/UIKit.framework')
+UIDevice = autoclass('UIDevice')
+
+
+class iOSUniqueID(UniqueID):
+
+ def _get_uid(self):
+ uuid = UIDevice.currentDevice().identifierForVendor.UUIDString()
+ return uuid.UTF8String()
+
+
+def instance():
+ return iOSUniqueID()
diff --git a/external/plyer/platforms/linux/__init__.py b/external/plyer/platforms/linux/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/external/plyer/platforms/linux/__init__.py
diff --git a/external/plyer/platforms/linux/accelerometer.py b/external/plyer/platforms/linux/accelerometer.py
new file mode 100644
index 0000000..7272c33
--- /dev/null
+++ b/external/plyer/platforms/linux/accelerometer.py
@@ -0,0 +1,36 @@
+'''
+Linux accelerometer
+---------------------
+'''
+
+from plyer.facades import Accelerometer
+import os
+import glob
+import re
+
+
+class LinuxAccelerometer(Accelerometer):
+
+ def _enable(self):
+ pass
+
+ def _disable(self):
+ pass
+
+ def _get_acceleration(self):
+ try:
+ pos = glob.glob("/sys/devices/platform/*/position")[0]
+ except IndexError:
+ raise Exception('Could not enable accelerometer!')
+
+ with open(pos, "r") as p:
+ t = p.read()
+ coords = re.findall(r"[-]?\d+\.?\d*", t)
+ # Apparently the acceleration on sysfs goes from -1000 to 1000.
+ # I divide it by 100 to make it equivalent to Android.
+ # The negative is because the coordinates are inverted on Linux
+ return [float(i) / -100 for i in coords]
+
+
+def instance():
+ return LinuxAccelerometer()
diff --git a/external/plyer/platforms/linux/battery.py b/external/plyer/platforms/linux/battery.py
new file mode 100644
index 0000000..0cdb763
--- /dev/null
+++ b/external/plyer/platforms/linux/battery.py
@@ -0,0 +1,46 @@
+from subprocess import Popen, PIPE
+from plyer.facades import Battery
+from plyer.utils import whereis_exe
+
+from os import environ
+
+
+class LinuxBattery(Battery):
+ def _get_state(self):
+ old_lang = environ.get('LANG')
+ environ['LANG'] = 'C'
+
+ status = {"isCharging": None, "percentage": None}
+
+ # We are supporting only one battery now
+ dev = "/org/freedesktop/UPower/device/battery_BAT0"
+ upower_process = Popen(["upower", "-d", dev],
+ stdout=PIPE)
+ output = upower_process.communicate()[0]
+
+ environ['LANG'] = old_lang
+
+ if not output:
+ return status
+
+ power_supply = percentage = None
+ for l in output.splitlines():
+ if 'power supply' in l:
+ power_supply = l.rpartition(':')[-1].strip()
+ if 'percentage' in l:
+ percentage = float(l.rpartition(':')[-1].strip()[:-1])
+
+ if(power_supply):
+ status['isCharging'] = power_supply != "yes"
+
+ status['percentage'] = percentage
+
+ return status
+
+
+def instance():
+ import sys
+ if whereis_exe('upower'):
+ return LinuxBattery()
+ sys.stderr.write("upower not found.")
+ return Battery()
diff --git a/external/plyer/platforms/linux/email.py b/external/plyer/platforms/linux/email.py
new file mode 100644
index 0000000..ceb5497
--- /dev/null
+++ b/external/plyer/platforms/linux/email.py
@@ -0,0 +1,36 @@
+import subprocess
+from urllib import quote
+try:
+ from urllib.parse import quote
+except ImportError:
+ from urllib import quote
+from plyer.utils import whereis_exe
+
+
+class LinuxEmail(Email):
+ def _send(self, **kwargs):
+ recipient = kwargs.get('recipient')
+ subject = kwargs.get('subject')
+ text = kwargs.get('text')
+
+ uri = "mailto:"
+ if recipient:
+ uri += str(recipient)
+ if subject:
+ uri += "?" if not "?" in uri else "&"
+ uri += "subject="
+ uri += quote(str(subject))
+ if text:
+ uri += "?" if not "?" in uri else "&"
+ uri += "body="
+ uri += quote(str(text))
+
+ subprocess.Popen(["xdg-open", uri])
+
+
+def instance():
+ import sys
+ if whereis_exe('xdg-open'):
+ return LinuxEmail()
+ sys.stderr.write("xdg-open not found.")
+ return Email()
diff --git a/external/plyer/platforms/linux/filechooser.py b/external/plyer/platforms/linux/filechooser.py
new file mode 100644
index 0000000..545487b
--- /dev/null
+++ b/external/plyer/platforms/linux/filechooser.py
@@ -0,0 +1,249 @@
+'''
+Linux file chooser
+------------------
+'''
+
+from plyer.facades import FileChooser
+from distutils.spawn import find_executable as which
+import os
+import subprocess as sp
+import time
+
+
+class SubprocessFileChooser(object):
+ '''A file chooser implementation that allows using
+ subprocess back-ends.
+ Normally you only need to override _gen_cmdline, executable,
+ separator and successretcode.
+ '''
+
+ executable = ""
+ '''The name of the executable of the back-end.
+ '''
+
+ separator = "|"
+ '''The separator used by the back-end. Override this for automatic
+ splitting, or override _split_output.
+ '''
+
+ successretcode = 0
+ '''The return code which is returned when the user doesn't close the
+ dialog without choosing anything, or when the app doesn't crash.
+ '''
+
+ path = None
+ multiple = False
+ filters = []
+ preview = False
+ title = None
+ icon = None
+ show_hidden = False
+
+ def __init__(self, **kwargs):
+ # Simulate Kivy's behavior
+ for i in kwargs:
+ setattr(self, i, kwargs[i])
+
+ _process = None
+
+ def _run_command(self, cmd):
+ self._process = sp.Popen(cmd, stdout=sp.PIPE)
+ while True:
+ ret = self._process.poll()
+ if ret is not None:
+ if ret == self.successretcode:
+ out = self._process.communicate()[0].strip()
+ self.selection = self._split_output(out)
+ return self.selection
+ else:
+ return None
+ time.sleep(0.1)
+
+ def _split_output(self, out):
+ '''This methods receives the output of the back-end and turns
+ it into a list of paths.
+ '''
+ return out.split(self.separator)
+
+ def _gen_cmdline(self):
+ '''Returns the command line of the back-end, based on the current
+ properties. You need to override this.
+ '''
+ raise NotImplementedError()
+
+ def run(self):
+ return self._run_command(self._gen_cmdline())
+
+
+class ZenityFileChooser(SubprocessFileChooser):
+ '''A FileChooser implementation using Zenity (on GNU/Linux).
+
+ Not implemented features:
+ * show_hidden
+ * preview
+ '''
+
+ executable = "zenity"
+ separator = "|"
+ successretcode = 0
+
+ def _gen_cmdline(self):
+ cmdline = [
+ which(self.executable),
+ "--file-selection",
+ "--confirm-overwrite"
+ ]
+ if self.multiple:
+ cmdline += ["--multiple"]
+ if self.mode == "save":
+ cmdline += ["--save"]
+ elif self.mode == "dir":
+ cmdline += ["--directory"]
+ if self.path:
+ cmdline += ["--filename", self.path]
+ if self.title:
+ cmdline += ["--name", self.title]
+ if self.icon:
+ cmdline += ["--window-icon", self.icon]
+ for f in self.filters:
+ if type(f) == str:
+ cmdline += ["--file-filter", f]
+ else:
+ cmdline += [
+ "--file-filter",
+ "{name} | {flt}".format(name=f[0], flt=" ".join(f[1:]))
+ ]
+ return cmdline
+
+
+class KDialogFileChooser(SubprocessFileChooser):
+ '''A FileChooser implementation using KDialog (on GNU/Linux).
+
+ Not implemented features:
+ * show_hidden
+ * preview
+ '''
+
+ executable = "kdialog"
+ separator = "\n"
+ successretcode = 0
+
+ def _gen_cmdline(self):
+ cmdline = [which(self.executable)]
+
+ filt = []
+
+ for f in self.filters:
+ if type(f) == str:
+ filt += [f]
+ else:
+ filt += list(f[1:])
+
+ if self.mode == "dir":
+ cmdline += [
+ "--getexistingdirectory",
+ (self.path if self.path else os.path.expanduser("~"))
+ ]
+ elif self.mode == "save":
+ cmdline += [
+ "--getopenfilename",
+ (self.path if self.path else os.path.expanduser("~")),
+ " ".join(filt)
+ ]
+ else:
+ cmdline += [
+ "--getopenfilename",
+ (self.path if self.path else os.path.expanduser("~")),
+ " ".join(filt)
+ ]
+ if self.multiple:
+ cmdline += ["--multiple", "--separate-output"]
+ if self.title:
+ cmdline += ["--title", self.title]
+ if self.icon:
+ cmdline += ["--icon", self.icon]
+ return cmdline
+
+
+class YADFileChooser(SubprocessFileChooser):
+ '''A NativeFileChooser implementation using YAD (on GNU/Linux).
+
+ Not implemented features:
+ * show_hidden
+ '''
+
+ executable = "yad"
+ separator = "|?|"
+ successretcode = 0
+
+ def _gen_cmdline(self):
+ cmdline = [
+ which(self.executable),
+ "--file-selection",
+ "--confirm-overwrite",
+ "--geometry",
+ "800x600+150+150"
+ ]
+ if self.multiple:
+ cmdline += ["--multiple", "--separator", self.separator]
+ if self.mode == "save":
+ cmdline += ["--save"]
+ elif self.mode == "dir":
+ cmdline += ["--directory"]
+ if self.preview:
+ cmdline += ["--add-preview"]
+ if self.path:
+ cmdline += ["--filename", self.path]
+ if self.title:
+ cmdline += ["--name", self.title]
+ if self.icon:
+ cmdline += ["--window-icon", self.icon]
+ for f in self.filters:
+ if type(f) == str:
+ cmdline += ["--file-filter", f]
+ else:
+ cmdline += [
+ "--file-filter",
+ "{name} | {flt}".format(name=f[0], flt=" ".join(f[1:]))
+ ]
+ return cmdline
+
+CHOOSERS = {
+ "gnome": ZenityFileChooser,
+ "kde": KDialogFileChooser,
+ "yad": YADFileChooser
+}
+
+
+class LinuxFileChooser(FileChooser):
+ '''FileChooser implementation for GNu/Linux. Accepts one additional
+ keyword argument, *desktop_override*, which, if set, overrides the
+ back-end that will be used. Set it to "gnome" for Zenity, to "kde"
+ for KDialog and to "yad" for YAD (Yet Another Dialog).
+ If set to None or not set, a default one will be picked based on
+ the running desktop environment and installed back-ends.
+ '''
+
+ desktop = None
+ if str(os.environ.get("XDG_CURRENT_DESKTOP")).lower() == "kde" \
+ and which("kdialog"):
+ desktop = "kde"
+ elif which("yad"):
+ desktop = "yad"
+ elif which("zenity"):
+ desktop = "gnome"
+
+ def _file_selection_dialog(self, desktop_override=desktop, **kwargs):
+ if not desktop_override:
+ desktop_override = desktop
+ # This means we couldn't find any back-end
+ if not desktop_override:
+ raise OSError("No back-end available. Please install one.")
+
+ chooser = CHOOSERS[desktop_override]
+ c = chooser(**kwargs)
+ return c.run()
+
+
+def instance():
+ return LinuxFileChooser()
diff --git a/external/plyer/platforms/linux/notification.py b/external/plyer/platforms/linux/notification.py
new file mode 100644
index 0000000..d78f130
--- /dev/null
+++ b/external/plyer/platforms/linux/notification.py
@@ -0,0 +1,52 @@
+import subprocess
+from plyer.facades import Notification
+from plyer.utils import whereis_exe
+
+
+class NotifySendNotification(Notification):
+ ''' Pops up a notification using notify-send
+ '''
+ def _notify(self, **kwargs):
+ subprocess.call(["notify-send",
+ kwargs.get('title'),
+ kwargs.get('message')])
+
+
+class NotifyDbus(Notification):
+ ''' notify using dbus interface
+ '''
+
+ def _notify(self, **kwargs):
+ summary = kwargs.get('title', "title")
+ body = kwargs.get('message', "body")
+ app_name = kwargs.get('app_name', '')
+ app_icon = kwargs.get('app_icon', '')
+ timeout = kwargs.get('timeout', 5000)
+ actions = kwargs.get('actions', [])
+ hints = kwargs.get('hints', [])
+ replaces_id = kwargs.get('replaces_id', 0)
+
+ _bus_name = 'org.freedesktop.Notifications'
+ _object_path = '/org/freedesktop/Notifications'
+ _interface_name = _bus_name
+
+ import dbus
+ session_bus = dbus.SessionBus()
+ obj = session_bus.get_object(_bus_name, _object_path)
+ interface = dbus.Interface(obj, _interface_name)
+ interface.Notify(app_name, replaces_id, app_icon,
+ summary, body, actions, hints, timeout)
+
+
+def instance():
+ import sys
+ try:
+ import dbus
+ return NotifyDbus()
+ except ImportError:
+ sys.stderr.write("python-dbus not installed. try:"
+ "`sudo pip install python-dbus`.")
+ if whereis_exe('notify-send'):
+ return NotifySendNotification()
+ sys.stderr.write("notify-send not found.")
+ return Notification()
diff --git a/external/plyer/platforms/linux/tts.py b/external/plyer/platforms/linux/tts.py
new file mode 100644
index 0000000..0a609e1
--- /dev/null
+++ b/external/plyer/platforms/linux/tts.py
@@ -0,0 +1,25 @@
+import subprocess
+from plyer.facades import TTS
+from plyer.utils import whereis_exe
+
+
+class EspeakTextToSpeech(TTS):
+ ''' Speaks using the espeak program
+ '''
+ def _speak(self, **kwargs):
+ subprocess.call(["espeak", kwargs.get('message')])
+
+
+class FliteTextToSpeech(TTS):
+ ''' Speaks using the flite program
+ '''
+ def _speak(self):
+ subprocess.call(["flite", "-t", kwargs.get('message'), "play"])
+
+
+def instance():
+ if whereis_exe('espeak'):
+ return EspeakTextToSpeech()
+ elif whereis_exe('flite'):
+ return FlitetextToSpeech()
+ return TTS()
diff --git a/external/plyer/platforms/linux/uniqueid.py b/external/plyer/platforms/linux/uniqueid.py
new file mode 100644
index 0000000..f7fff89
--- /dev/null
+++ b/external/plyer/platforms/linux/uniqueid.py
@@ -0,0 +1,30 @@
+from subprocess import Popen, PIPE
+from plyer.facades import UniqueID
+from plyer.utils import whereis_exe
+
+from os import environ
+
+
+class LinuxUniqueID(UniqueID):
+ def _get_uid(self):
+ old_lang = environ.get('LANG')
+ environ['LANG'] = 'C'
+ lshw_process = Popen(["lshw", "-quiet"], stdout=PIPE, stderr=PIPE)
+ grep_process = Popen(["grep", "-m1", "serial:"],
+ stdin=lshw_process.stdout, stdout=PIPE)
+ lshw_process.stdout.close()
+ output = grep_process.communicate()[0]
+ environ['LANG'] = old_lang
+
+ if output:
+ return output.split()[1]
+ else:
+ return None
+
+
+def instance():
+ import sys
+ if whereis_exe('lshw'):
+ return LinuxUniqueID()
+ sys.stderr.write("lshw not found.")
+ return UniqueID()
diff --git a/external/plyer/platforms/macosx/__init__.py b/external/plyer/platforms/macosx/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/external/plyer/platforms/macosx/__init__.py
diff --git a/external/plyer/platforms/macosx/accelerometer.py b/external/plyer/platforms/macosx/accelerometer.py
new file mode 100644
index 0000000..ec1fe77
--- /dev/null
+++ b/external/plyer/platforms/macosx/accelerometer.py
@@ -0,0 +1,25 @@
+'''
+MacOSX accelerometer
+---------------------
+'''
+
+from plyer.facades import Accelerometer
+from plyer.platforms.macosx.libs import osx_motion_sensor
+
+
+class OSXAccelerometer(Accelerometer):
+ def _enable(self):
+ try:
+ osx_motion_sensor.get_coord()
+ except:
+ raise Exception('Could not enable motion sensor on this macbook!')
+
+ def _disable(self):
+ pass
+
+ def _get_acceleration(self):
+ return osx_motion_sensor.get_coord()
+
+
+def instance():
+ return OSXAccelerometer()
diff --git a/external/plyer/platforms/macosx/battery.py b/external/plyer/platforms/macosx/battery.py
new file mode 100644
index 0000000..fe1c525
--- /dev/null
+++ b/external/plyer/platforms/macosx/battery.py
@@ -0,0 +1,47 @@
+from subprocess import Popen, PIPE
+from plyer.facades import Battery
+from plyer.utils import whereis_exe
+
+from os import environ
+
+
+class OSXBattery(Battery):
+ def _get_state(self):
+ old_lang = environ.get('LANG')
+ environ['LANG'] = 'C'
+
+ status = {"isCharging": None, "percentage": None}
+
+ ioreg_process = Popen(["ioreg", "-rc", "AppleSmartBattery"],
+ stdout=PIPE)
+ output = ioreg_process.communicate()[0]
+
+ environ['LANG'] = old_lang
+
+ if not output:
+ return status
+
+ IsCharging = MaxCapacity = CurrentCapacity = None
+ for l in output.splitlines():
+ if 'IsCharging' in l:
+ IsCharging = l.rpartition('=')[-1].strip()
+ if 'MaxCapacity' in l:
+ MaxCapacity = float(l.rpartition('=')[-1].strip())
+ if 'CurrentCapacity' in l:
+ CurrentCapacity = float(l.rpartition('=')[-1].strip())
+
+ if (IsCharging):
+ status['isCharging'] = IsCharging == "Yes"
+
+ if (CurrentCapacity and MaxCapacity):
+ status['percentage'] = 100. * CurrentCapacity / MaxCapacity
+
+ return status
+
+
+def instance():
+ import sys
+ if whereis_exe('ioreg'):
+ return OSXBattery()
+ sys.stderr.write("ioreg not found.")
+ return Battery()
diff --git a/external/plyer/platforms/macosx/email.py b/external/plyer/platforms/macosx/email.py
new file mode 100644
index 0000000..8e29fa9
--- /dev/null
+++ b/external/plyer/platforms/macosx/email.py
@@ -0,0 +1,38 @@
+import subprocess
+
+try:
+ from urllib.parse import quote
+except ImportError:
+ from urllib import quote
+
+from plyer.facades import Email
+from plyer.utils import whereis_exe
+
+
+class MacOSXEmail(Email):
+ def _send(self, **kwargs):
+ recipient = kwargs.get('recipient')
+ subject = kwargs.get('subject')
+ text = kwargs.get('text')
+
+ uri = "mailto:"
+ if recipient:
+ uri += str(recipient)
+ if subject:
+ uri += "?" if not "?" in uri else "&"
+ uri += "subject="
+ uri += quote(str(subject))
+ if text:
+ uri += "?" if not "?" in uri else "&"
+ uri += "body="
+ uri += quote(str(text))
+
+ subprocess.Popen(["open", uri])
+
+
+def instance():
+ import sys
+ if whereis_exe('open'):
+ return MacOSXEmail()
+ sys.stderr.write("open not found.")
+ return Email()
diff --git a/external/plyer/platforms/macosx/filechooser.py b/external/plyer/platforms/macosx/filechooser.py
new file mode 100644
index 0000000..5774cc4
--- /dev/null
+++ b/external/plyer/platforms/macosx/filechooser.py
@@ -0,0 +1,108 @@
+'''
+Mac OS X file chooser
+---------------------
+'''
+
+from plyer.facades import FileChooser
+from pyobjus import autoclass, objc_arr, objc_str
+from pyobjus.dylib_manager import load_framework, INCLUDE
+
+load_framework(INCLUDE.AppKit)
+NSURL = autoclass('NSURL')
+NSOpenPanel = autoclass('NSOpenPanel')
+NSSavePanel = autoclass('NSSavePanel')
+NSOKButton = 1
+
+
+class MacFileChooser(object):
+ '''A native implementation of file chooser dialogs using Apple's API
+ through pyobjus.
+
+ Not implemented features:
+ * filters (partial, wildcards are converted to extensions if possible.
+ Pass the Mac-specific "use_extensions" if you can provide
+ Mac OS X-compatible to avoid automatic conversion)
+ * multiple (only for save dialog. Available in open dialog)
+ * icon
+ * preview
+ '''
+
+ mode = "open"
+ path = None
+ multiple = False
+ filters = []
+ preview = False
+ title = None
+ icon = None
+ show_hidden = False
+ use_extensions = False
+
+ def __init__(self, **kwargs):
+ # Simulate Kivy's behavior
+ for i in kwargs:
+ setattr(self, i, kwargs[i])
+
+ def run(self):
+ panel = None
+ if self.mode in ("open", "dir"):
+ panel = NSOpenPanel.openPanel()
+ else:
+ panel = NSSavePanel.savePanel()
+
+ panel.setCanCreateDirectories_(True)
+
+ panel.setCanChooseDirectories_(self.mode == "dir")
+ panel.setCanChooseFiles_(self.mode != "dir")
+ panel.setShowsHiddenFiles_(self.show_hidden)
+
+ if self.title:
+ panel.setTitle_(objc_str(self.title))
+
+ if self.mode != "save" and self.multiple:
+ panel.setAllowsMultipleSelection_(True)
+
+ # Mac OS X does not support wildcards unlike the other platforms.
+ # This tries to convert wildcards to "extensions" when possible,
+ # ans sets the panel to also allow other file types, just to be safe.
+ if len(self.filters) > 0:
+ filthies = []
+ for f in self.filters:
+ if type(f) == str:
+ if not self.use_extensions:
+ if f.strip().endswith("*"):
+ continue
+ pystr = f.strip().split("*")[-1].split(".")[-1]
+ filthies.append(objc_str(pystr))
+ else:
+ for i in f[1:]:
+ if not self.use_extensions:
+ if f.strip().endswith("*"):
+ continue
+ pystr = f.strip().split("*")[-1].split(".")[-1]
+ filthies.append(objc_str(pystr))
+
+ ftypes_arr = objc_arr(filthies)
+ panel.setAllowedFileTypes_(ftypes_arr)
+ panel.setAllowsOtherFileTypes_(not self.use_extensions)
+
+ if self.path:
+ url = NSURL.fileURLWithPath_(self.path)
+ panel.setDirectoryURL_(url)
+
+ if panel.runModal_():
+ if self.mode == "save" or not self.multiple:
+ return [panel.filename().UTF8String()]
+ else:
+ return [i.UTF8String() for i in panel.filenames()]
+ return None
+
+
+class MacOSXFileChooser(FileChooser):
+ '''FileChooser implementation for Windows, using win3all.
+ '''
+ def _file_selection_dialog(self, **kwargs):
+ return MacFileChooser(**kwargs).run()
+
+
+def instance():
+ return MacOSXFileChooser()
diff --git a/external/plyer/platforms/macosx/libs/__init__.py b/external/plyer/platforms/macosx/libs/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/external/plyer/platforms/macosx/libs/__init__.py
diff --git a/external/plyer/platforms/macosx/libs/osx_motion_sensor.py b/external/plyer/platforms/macosx/libs/osx_motion_sensor.py
new file mode 100644
index 0000000..cd4fae4
--- /dev/null
+++ b/external/plyer/platforms/macosx/libs/osx_motion_sensor.py
@@ -0,0 +1,118 @@
+import ctypes
+from ctypes import (Structure, cdll, sizeof,
+ c_int, c_int8, c_int16, c_size_t)
+from ctypes.util import find_library
+import platform
+
+ERROR_DICT = {
+ "0": "IOKit Framework not found, is this OSX?",
+ "-1": "No SMCMotionSensor service",
+ "-2": "No sms device",
+ "-3": "Could not open motion sensor device",
+ "-4": "Did not receive any coordinates"
+}
+
+IOKit = cdll.LoadLibrary(find_library('IOKit'))
+
+
+class data_structure(Structure):
+ _fields_ = [
+ ('x', c_int16),
+ ('y', c_int16),
+ ('z', c_int16),
+ ('pad', c_int8 * 34),
+ ]
+
+
+void_p = ctypes.POINTER(ctypes.c_int)
+
+kern_return_t = ctypes.c_int
+KERN_SUCCESS = 0
+KERN_FUNC = 5 # SMC Motion Sensor on MacBook Pro
+
+mach_port_t = void_p
+MACH_PORT_NULL = 0
+
+io_object_t = ctypes.c_int
+io_object_t = ctypes.c_int
+io_iterator_t = void_p
+io_object_t = void_p
+io_connect_t = void_p
+IOItemCount = ctypes.c_uint
+
+CFMutableDictionaryRef = void_p
+
+
+def is_os_64bit():
+ return platform.machine().endswith('64')
+
+
+def read_sms():
+ result = kern_return_t()
+ masterPort = mach_port_t()
+
+ result = IOKit.IOMasterPort(MACH_PORT_NULL, ctypes.byref(masterPort))
+
+ IOKit.IOServiceMatching.restype = CFMutableDictionaryRef
+ matchingDictionary = IOKit.IOServiceMatching("SMCMotionSensor")
+
+ iterator = io_iterator_t()
+ result = IOKit.IOServiceGetMatchingServices(masterPort, matchingDictionary,
+ ctypes.byref(iterator))
+
+ if (result != KERN_SUCCESS):
+ raise ("No coordinates received!")
+ return -1, None
+
+ IOKit.IOIteratorNext.restype = io_object_t
+ smsDevice = IOKit.IOIteratorNext(iterator)
+
+ if not smsDevice:
+ return -2, None
+
+ dataPort = io_connect_t()
+ result = IOKit.IOServiceOpen(smsDevice, IOKit.mach_task_self(), 0,
+ ctypes.byref(dataPort))
+
+ if (result != KERN_SUCCESS):
+ return -3, None
+
+ inStructure = data_structure()
+ outStructure = data_structure()
+
+ if(is_os_64bit() or hasattr(IOKit, 'IOConnectCallStructMethod')):
+ structureInSize = IOItemCount(sizeof(data_structure))
+ structureOutSize = c_size_t(sizeof(data_structure))
+
+ result = IOKit.IOConnectCallStructMethod(dataPort, KERN_FUNC,
+ ctypes.byref(inStructure), structureInSize,
+ ctypes.byref(outStructure), ctypes.byref(structureOutSize))
+ else:
+ structureInSize = IOItemCount(sizeof(data_structure))
+ structureOutSize = IOItemCount(sizeof(data_structure))
+
+ result = IOConnectMethodStructureIStructureO(dataPort, KERN_FUNC,
+ structureInSize, ctypes.byref(structureOutSize),
+ ctypes.byref(inStructure), ctypes.byref(outStructure))
+
+ IOKit.IOServiceClose(dataPort)
+
+ if (result != KERN_SUCCESS):
+ return -4, None
+
+ return 1, outStructure
+
+
+def get_coord():
+ if not IOKit:
+ raise Exception(ERROR_DICT["0"])
+
+ ret, data = read_sms()
+
+ if (ret > 0):
+ if(data.x):
+ return (data.x, data.y, data.z)
+ else:
+ return (None, None, None)
+ else:
+ raise Exception(ERROR_DICT[str(ret)])
diff --git a/external/plyer/platforms/macosx/notification.py b/external/plyer/platforms/macosx/notification.py
new file mode 100644
index 0000000..a52ebe3
--- /dev/null
+++ b/external/plyer/platforms/macosx/notification.py
@@ -0,0 +1,27 @@
+from plyer.facades import Notification
+import Foundation
+import objc
+import AppKit
+
+
+class OSXNotification(Notification):
+ def _notify(self, **kwargs):
+ NSUserNotification = objc.lookUpClass('NSUserNotification')
+ NSUserNotificationCenter = objc.lookUpClass('NSUserNotificationCenter')
+ notification = NSUserNotification.alloc().init()
+ notification.setTitle_(kwargs.get('title').encode('utf-8'))
+ #notification.setSubtitle_(str(subtitle))
+ notification.setInformativeText_(kwargs.get('message').encode('utf-8'))
+ notification.setSoundName_("NSUserNotificationDefaultSoundName")
+ #notification.setHasActionButton_(False)
+ #notification.setOtherButtonTitle_("View")
+ #notification.setUserInfo_({"action":"open_url", "value":url})
+ NSUserNotificationCenter.defaultUserNotificationCenter() \
+ .setDelegate_(self)
+ NSUserNotificationCenter.defaultUserNotificationCenter() \
+ .scheduleNotification_(notification)
+
+
+def instance():
+ return OSXNotification()
+
diff --git a/external/plyer/platforms/macosx/tts.py b/external/plyer/platforms/macosx/tts.py
new file mode 100644
index 0000000..755e820
--- /dev/null
+++ b/external/plyer/platforms/macosx/tts.py
@@ -0,0 +1,25 @@
+import subprocess
+from plyer.facades import TTS
+from plyer.utils import whereis_exe
+
+
+class NativeSayTextToSpeech(TTS):
+ '''Speaks using the native OSX 'say' command
+ '''
+ def _speak(self, **kwargs):
+ subprocess.call(["say", kwargs.get('message')])
+
+
+class EspeakTextToSpeech(TTS):
+ '''Speaks using the espeak program
+ '''
+ def _speak(self, **kwargs):
+ subprocess.call(["espeak", kwargs.get('message')])
+
+
+def instance():
+ if whereis_exe('say'):
+ return NativeSayTextToSpeech()
+ elif whereis_exe('espeak'):
+ return EspeakTextToSpeech()
+ return TTS()
diff --git a/external/plyer/platforms/macosx/uniqueid.py b/external/plyer/platforms/macosx/uniqueid.py
new file mode 100644
index 0000000..51ba169
--- /dev/null
+++ b/external/plyer/platforms/macosx/uniqueid.py
@@ -0,0 +1,32 @@
+from subprocess import Popen, PIPE
+from plyer.facades import UniqueID
+from plyer.utils import whereis_exe
+
+from os import environ
+
+
+class OSXUniqueID(UniqueID):
+ def _get_uid(self):
+ old_lang = environ.get('LANG')
+ environ['LANG'] = 'C'
+
+ ioreg_process = Popen(["ioreg", "-l"], stdout=PIPE)
+ grep_process = Popen(["grep", "IOPlatformSerialNumber"],
+ stdin=ioreg_process.stdout, stdout=PIPE)
+ ioreg_process.stdout.close()
+ output = grep_process.communicate()[0]
+
+ environ['LANG'] = old_lang
+
+ if output:
+ return output.split()[3][1:-1]
+ else:
+ return None
+
+
+def instance():
+ import sys
+ if whereis_exe('ioreg'):
+ return OSXUniqueID()
+ sys.stderr.write("ioreg not found.")
+ return UniqueID()
diff --git a/external/plyer/platforms/win/__init__.py b/external/plyer/platforms/win/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/external/plyer/platforms/win/__init__.py
diff --git a/external/plyer/platforms/win/battery.py b/external/plyer/platforms/win/battery.py
new file mode 100644
index 0000000..04006f8
--- /dev/null
+++ b/external/plyer/platforms/win/battery.py
@@ -0,0 +1,21 @@
+from plyer.platforms.win.libs.batterystatus import battery_status
+from plyer.facades import Battery
+
+
+class WinBattery(Battery):
+ def _get_state(self):
+ status = {"isCharging": None, "percentage": None}
+
+ query = battery_status()
+
+ if (not query):
+ return status
+
+ status["isCharging"] = query["BatteryFlag"] == 8
+ status["percentage"] = query["BatteryLifePercent"]
+
+ return status
+
+
+def instance():
+ return WinBattery()
diff --git a/external/plyer/platforms/win/email.py b/external/plyer/platforms/win/email.py
new file mode 100644
index 0000000..e33f5cf
--- /dev/null
+++ b/external/plyer/platforms/win/email.py
@@ -0,0 +1,34 @@
+import os
+try:
+ from urllib.parse import quote
+except ImportError:
+ from urllib import quote
+from plyer.facades import Email
+
+
+class WindowsEmail(Email):
+ def _send(self, **kwargs):
+ recipient = kwargs.get('recipient')
+ subject = kwargs.get('subject')
+ text = kwargs.get('text')
+
+ uri = "mailto:"
+ if recipient:
+ uri += str(recipient)
+ if subject:
+ uri += "?" if not "?" in uri else "&"
+ uri += "subject="
+ uri += quote(str(subject))
+ if text:
+ uri += "?" if not "?" in uri else "&"
+ uri += "body="
+ uri += quote(str(text))
+
+ try:
+ os.startfile(uri)
+ except WindowsError:
+ print("Warning: unable to find a program able to send emails.")
+
+
+def instance():
+ return WindowsEmail()
diff --git a/external/plyer/platforms/win/filechooser.py b/external/plyer/platforms/win/filechooser.py
new file mode 100644
index 0000000..4d284dc
--- /dev/null
+++ b/external/plyer/platforms/win/filechooser.py
@@ -0,0 +1,112 @@
+'''
+Windows file chooser
+--------------------
+'''
+
+from plyer.facades import FileChooser
+from win32com.shell import shell, shellcon
+import os
+import win32gui
+import win32con
+import pywintypes
+
+
+class Win32FileChooser(object):
+ '''A native implementation of NativeFileChooser using the
+ Win32 API on Windows.
+
+ Not Implemented features (all dialogs):
+ * preview
+ * icon
+
+ Not implemented features (in directory selection only - it's limited
+ by Windows itself):
+ * preview
+ * window-icon
+ * multiple
+ * show_hidden
+ * filters
+ * path
+ '''
+
+ path = None
+ multiple = False
+ filters = []
+ preview = False
+ title = None
+ icon = None
+ show_hidden = False
+
+ def __init__(self, **kwargs):
+ # Simulate Kivy's behavior
+ for i in kwargs:
+ setattr(self, i, kwargs[i])
+
+ def run(self):
+ try:
+ if self.mode != "dir":
+ args = {}
+
+ if self.path:
+ args["InitialDir"] = os.path.dirname(self.path)
+ path = os.path.splitext(os.path.dirname(self.path))
+ args["File"] = path[0]
+ args["DefExt"] = path[1]
+ args["Title"] = self.title if self.title else "Pick a file..."
+ args["CustomFilter"] = 'Other file types\x00*.*\x00'
+ args["FilterIndex"] = 1
+
+ filters = ""
+ for f in self.filters:
+ if type(f) == str:
+ filters += (f + "\x00") * 2
+ else:
+ filters += f[0] + "\x00" + ";".join(f[1:]) + "\x00"
+ args["Filter"] = filters
+
+ flags = (win32con.OFN_EXTENSIONDIFFERENT |
+ win32con.OFN_OVERWRITEPROMPT)
+ if self.multiple:
+ flags |= win32con.OFN_ALLOWmultiple | win32con.OFN_EXPLORER
+ if self.show_hidden:
+ flags |= win32con.OFN_FORCESHOWHIDDEN
+ args["Flags"] = flags
+
+ if self.mode == "open":
+ self.fname, _, _ = win32gui.GetOpenFileNameW(**args)
+ elif self.mode == "save":
+ self.fname, _, _ = win32gui.GetSaveFileNameW(**args)
+
+ if self.fname:
+ if self.multiple:
+ seq = str(self.fname).split("\x00")
+ dir_n, base_n = seq[0], seq[1:]
+ self.selection = [os.path.join(dir_n, i)
+ for i in base_n]
+ else:
+ self.selection = str(self.fname).split("\x00")
+ else:
+ # From http://goo.gl/UDqCqo
+ pidl, display_name, image_list = shell.SHBrowseForFolder(
+ win32gui.GetDesktopWindow(),
+ None,
+ self.title if self.title else "Pick a folder...",
+ 0, None, None
+ )
+ self.selection = [str(shell.SHGetPathFromIDList(pidl))]
+
+ return self.selection
+ except (RuntimeError, pywintypes.error):
+ return None
+
+
+class WinFileChooser(FileChooser):
+ '''FileChooser implementation for Windows, using win3all.
+ '''
+
+ def _file_selection_dialog(self, **kwargs):
+ return Win32FileChooser(**kwargs).run()
+
+
+def instance():
+ return WinFileChooser()
diff --git a/external/plyer/platforms/win/libs/__init__.py b/external/plyer/platforms/win/libs/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/external/plyer/platforms/win/libs/__init__.py
diff --git a/external/plyer/platforms/win/libs/balloontip.py b/external/plyer/platforms/win/libs/balloontip.py
new file mode 100644
index 0000000..171b25f
--- /dev/null
+++ b/external/plyer/platforms/win/libs/balloontip.py
@@ -0,0 +1,145 @@
+# -- coding: utf-8 --
+
+__all__ = ('WindowsBalloonTip', 'balloon_tip')
+
+
+import time
+import ctypes
+from plyer.platforms.win.libs import win_api_defs
+from plyer.compat import PY2
+from threading import RLock
+
+
+WS_OVERLAPPED = 0x00000000
+WS_SYSMENU = 0x00080000
+WM_DESTROY = 2
+CW_USEDEFAULT = 8
+
+LR_LOADFROMFILE = 16
+LR_DEFAULTSIZE = 0x0040
+IDI_APPLICATION = 32512
+IMAGE_ICON = 1
+
+NOTIFYICON_VERSION_4 = 4
+NIM_ADD = 0
+NIM_MODIFY = 1
+NIM_DELETE = 2
+NIM_SETVERSION = 4
+NIF_MESSAGE = 1
+NIF_ICON = 2
+NIF_TIP = 4
+NIF_INFO = 0x10
+NIIF_USER = 4
+NIIF_LARGE_ICON = 0x20
+
+
+class WindowsBalloonTip(object):
+
+ _class_atom = 0
+ _wnd_class_ex = None
+ _hwnd = None
+ _hicon = None
+ _balloon_icon = None
+ _notify_data = None
+ _count = 0
+ _lock = RLock()
+
+ @staticmethod
+ def _get_unique_id():
+ WindowsBalloonTip._lock.acquire()
+ val = WindowsBalloonTip._count
+ WindowsBalloonTip._count += 1
+ WindowsBalloonTip._lock.release()
+ return val
+
+ def __init__(self, title, message, app_name, app_icon='', timeout=10):
+ ''' app_icon if given is a icon file.
+ '''
+
+ wnd_class_ex = win_api_defs.get_WNDCLASSEXW()
+ class_name = 'PlyerTaskbar' + str(WindowsBalloonTip._get_unique_id())
+ if PY2:
+ class_name = class_name.decode('utf8')
+ wnd_class_ex.lpszClassName = class_name
+ # keep ref to it as long as window is alive
+ wnd_class_ex.lpfnWndProc =\
+ win_api_defs.WindowProc(win_api_defs.DefWindowProcW)
+ wnd_class_ex.hInstance = win_api_defs.GetModuleHandleW(None)
+ if wnd_class_ex.hInstance is None:
+ raise Exception('Could not get windows module instance.')
+ class_atom = win_api_defs.RegisterClassExW(wnd_class_ex)
+ if class_atom == 0:
+ raise Exception('Could not register the PlyerTaskbar class.')
+ self._class_atom = class_atom
+ self._wnd_class_ex = wnd_class_ex
+
+ # create window
+ self._hwnd = win_api_defs.CreateWindowExW(0, class_atom,
+ '', WS_OVERLAPPED, 0, 0, CW_USEDEFAULT,
+ CW_USEDEFAULT, None, None, wnd_class_ex.hInstance, None)
+ if self._hwnd is None:
+ raise Exception('Could not get create window.')
+ win_api_defs.UpdateWindow(self._hwnd)
+
+ # load icon
+ if app_icon:
+ icon_flags = LR_LOADFROMFILE | LR_DEFAULTSIZE
+ hicon = win_api_defs.LoadImageW(None, app_icon, IMAGE_ICON, 0, 0,
+ icon_flags)
+ if hicon is None:
+ raise Exception('Could not load icon {}'.
+ format(icon_path_name))
+ self._balloon_icon = self._hicon = hicon
+ else:
+ self._hicon = win_api_defs.LoadIconW(None,
+ ctypes.cast(IDI_APPLICATION, win_api_defs.LPCWSTR))
+ self.notify(title, message, app_name)
+ if timeout:
+ time.sleep(timeout)
+
+ def __del__(self):
+ self.remove_notify()
+ if self._hicon is not None:
+ win_api_defs.DestroyIcon(self._hicon)
+ if self._wnd_class_ex is not None:
+ win_api_defs.UnregisterClassW(self._class_atom,
+ self._wnd_class_ex.hInstance)
+ if self._hwnd is not None:
+ win_api_defs.DestroyWindow(self._hwnd)
+
+ def notify(self, title, message, app_name):
+ ''' Displays a balloon in the systray. Can be called multiple times
+ with different parameter values.
+ '''
+ self.remove_notify()
+ # add icon and messages to window
+ hicon = self._hicon
+ flags = NIF_TIP | NIF_INFO
+ icon_flag = 0
+ if hicon is not None:
+ flags |= NIF_ICON
+ # if icon is default app's one, don't display it in message
+ if self._balloon_icon is not None:
+ icon_flag = NIIF_USER | NIIF_LARGE_ICON
+ notify_data = win_api_defs.get_NOTIFYICONDATAW(0, self._hwnd,
+ id(self), flags, 0, hicon, app_name, 0, 0, message,
+ NOTIFYICON_VERSION_4, title, icon_flag, win_api_defs.GUID(),
+ self._balloon_icon)
+
+ self._notify_data = notify_data
+ if not win_api_defs.Shell_NotifyIconW(NIM_ADD, notify_data):
+ raise Exception('Shell_NotifyIconW failed.')
+ if not win_api_defs.Shell_NotifyIconW(NIM_SETVERSION,
+ notify_data):
+ raise Exception('Shell_NotifyIconW failed.')
+
+ def remove_notify(self):
+ '''Removes the notify balloon, if displayed.
+ '''
+ if self._notify_data is not None:
+ win_api_defs.Shell_NotifyIconW(NIM_DELETE, self._notify_data)
+ self._notify_data = None
+
+
+def balloon_tip(**kwargs):
+ WindowsBalloonTip(**kwargs)
diff --git a/external/plyer/platforms/win/libs/batterystatus.py b/external/plyer/platforms/win/libs/batterystatus.py
new file mode 100644
index 0000000..ddb22cc
--- /dev/null
+++ b/external/plyer/platforms/win/libs/batterystatus.py
@@ -0,0 +1,13 @@
+__all__ = ('battery_status')
+
+
+import ctypes
+from plyer.platforms.win.libs import win_api_defs
+
+
+def battery_status():
+ status = win_api_defs.SYSTEM_POWER_STATUS()
+ if not win_api_defs.GetSystemPowerStatus(ctypes.pointer(status)):
+ raise Exception('Could not get system power status.')
+
+ return dict((field, getattr(status, field)) for field, _ in status._fields_)
diff --git a/external/plyer/platforms/win/libs/win_api_defs.py b/external/plyer/platforms/win/libs/win_api_defs.py
new file mode 100644
index 0000000..7aed430
--- /dev/null
+++ b/external/plyer/platforms/win/libs/win_api_defs.py
@@ -0,0 +1,200 @@
+''' Defines ctypes windows api.
+'''
+
+__all__ = ('GUID', 'get_DLLVERSIONINFO', 'MAKEDLLVERULL',
+ 'get_NOTIFYICONDATAW', 'CreateWindowExW', 'WindowProc',
+ 'DefWindowProcW', 'get_WNDCLASSEXW', 'GetModuleHandleW',
+ 'RegisterClassExW', 'UpdateWindow', 'LoadImageW',
+ 'Shell_NotifyIconW', 'DestroyIcon', 'UnregisterClassW',
+ 'DestroyWindow', 'LoadIconW')
+
+import ctypes
+from ctypes import Structure, windll, sizeof, POINTER, WINFUNCTYPE
+from ctypes.wintypes import (DWORD, HICON, HWND, UINT, WCHAR, WORD, BYTE,
+ LPCWSTR, INT, LPVOID, HINSTANCE, HMENU, LPARAM, WPARAM,
+ HBRUSH, HMODULE, ATOM, BOOL, HANDLE)
+LRESULT = LPARAM
+HRESULT = HANDLE
+HCURSOR = HICON
+
+
+class GUID(Structure):
+ _fields_ = [
+ ('Data1', DWORD),
+ ('Data2', WORD),
+ ('Data3', WORD),
+ ('Data4', BYTE * 8)
+ ]
+
+
+class DLLVERSIONINFO(Structure):
+ _fields_ = [
+ ('cbSize', DWORD),
+ ('dwMajorVersion', DWORD),
+ ('dwMinorVersion', DWORD),
+ ('dwBuildNumber', DWORD),
+ ('dwPlatformID', DWORD),
+ ]
+
+
+def get_DLLVERSIONINFO(*largs):
+ version_info = DLLVERSIONINFO(*largs)
+ version_info.cbSize = sizeof(DLLVERSIONINFO)
+ return version_info
+
+
+def MAKEDLLVERULL(major, minor, build, sp):
+ return (major << 48) | (minor << 32) | (build << 16) | sp
+
+
+NOTIFYICONDATAW_fields = [
+ ("cbSize", DWORD),
+ ("hWnd", HWND),
+ ("uID", UINT),
+ ("uFlags", UINT),
+ ("uCallbackMessage", UINT),
+ ("hIcon", HICON),
+ ("szTip", WCHAR * 128),
+ ("dwState", DWORD),
+ ("dwStateMask", DWORD),
+ ("szInfo", WCHAR * 256),
+ ("uVersion", UINT),
+ ("szInfoTitle", WCHAR * 64),
+ ("dwInfoFlags", DWORD),
+ ("guidItem", GUID),
+ ("hBalloonIcon", HICON),
+]
+
+
+class NOTIFYICONDATAW(Structure):
+ _fields_ = NOTIFYICONDATAW_fields[:]
+
+
+class NOTIFYICONDATAW_V3(Structure):
+ _fields_ = NOTIFYICONDATAW_fields[:-1]
+
+
+class NOTIFYICONDATAW_V2(Structure):
+ _fields_ = NOTIFYICONDATAW_fields[:-2]
+
+
+class NOTIFYICONDATAW_V1(Structure):
+ _fields_ = NOTIFYICONDATAW_fields[:6]
+
+
+NOTIFYICONDATA_V3_SIZE = sizeof(NOTIFYICONDATAW_V3)
+NOTIFYICONDATA_V2_SIZE = sizeof(NOTIFYICONDATAW_V2)
+NOTIFYICONDATA_V1_SIZE = sizeof(NOTIFYICONDATAW_V1)
+
+
+def get_NOTIFYICONDATAW(*largs):
+ notify_data = NOTIFYICONDATAW(*largs)
+
+ # get shell32 version to find correct NOTIFYICONDATAW size
+ DllGetVersion = windll.Shell32.DllGetVersion
+ DllGetVersion.argtypes = [POINTER(DLLVERSIONINFO)]
+ DllGetVersion.restype = HRESULT
+
+ version = get_DLLVERSIONINFO()
+ if DllGetVersion(version):
+ raise Exception('Cannot get Windows version numbers.')
+ v = MAKEDLLVERULL(version.dwMajorVersion, version.dwMinorVersion,
+ version.dwBuildNumber, version.dwPlatformID)
+
+ # from the version info find the NOTIFYICONDATA size
+ if v >= MAKEDLLVERULL(6, 0, 6, 0):
+ notify_data.cbSize = sizeof(NOTIFYICONDATAW)
+ elif v >= MAKEDLLVERULL(6, 0, 0, 0):
+ notify_data.cbSize = NOTIFYICONDATA_V3_SIZE
+ elif v >= MAKEDLLVERULL(5, 0, 0, 0):
+ notify_data.cbSize = NOTIFYICONDATA_V2_SIZE
+ else:
+ notify_data.cbSize = NOTIFYICONDATA_V1_SIZE
+ return notify_data
+
+
+CreateWindowExW = windll.User32.CreateWindowExW
+CreateWindowExW.argtypes = [DWORD, ATOM, LPCWSTR, DWORD, INT, INT, INT, INT,
+ HWND, HMENU, HINSTANCE, LPVOID]
+CreateWindowExW.restype = HWND
+
+GetModuleHandleW = windll.Kernel32.GetModuleHandleW
+GetModuleHandleW.argtypes = [LPCWSTR]
+GetModuleHandleW.restype = HMODULE
+
+WindowProc = WINFUNCTYPE(LRESULT, HWND, UINT, WPARAM, LPARAM)
+DefWindowProcW = windll.User32.DefWindowProcW
+DefWindowProcW.argtypes = [HWND, UINT, WPARAM, LPARAM]
+DefWindowProcW.restype = LRESULT
+
+
+class WNDCLASSEXW(Structure):
+ _fields_ = [
+ ('cbSize', UINT),
+ ('style', UINT),
+ ('lpfnWndProc', WindowProc),
+ ('cbClsExtra', INT),
+ ('cbWndExtra', INT),
+ ('hInstance', HINSTANCE),
+ ('hIcon', HICON),
+ ('hCursor', HCURSOR),
+ ('hbrBackground', HBRUSH),
+ ('lpszMenuName', LPCWSTR),
+ ('lpszClassName', LPCWSTR),
+ ('hIconSm', HICON),
+ ]
+
+
+def get_WNDCLASSEXW(*largs):
+ wnd_class = WNDCLASSEXW(*largs)
+ wnd_class.cbSize = sizeof(WNDCLASSEXW)
+ return wnd_class
+
+RegisterClassExW = windll.User32.RegisterClassExW
+RegisterClassExW.argtypes = [POINTER(WNDCLASSEXW)]
+RegisterClassExW.restype = ATOM
+
+UpdateWindow = windll.User32.UpdateWindow
+UpdateWindow.argtypes = [HWND]
+UpdateWindow.restype = BOOL
+
+LoadImageW = windll.User32.LoadImageW
+LoadImageW.argtypes = [HINSTANCE, LPCWSTR, UINT, INT, INT, UINT]
+LoadImageW.restype = HANDLE
+
+Shell_NotifyIconW = windll.Shell32.Shell_NotifyIconW
+Shell_NotifyIconW.argtypes = [DWORD, POINTER(NOTIFYICONDATAW)]
+Shell_NotifyIconW.restype = BOOL
+
+DestroyIcon = windll.User32.DestroyIcon
+DestroyIcon.argtypes = [HICON]
+DestroyIcon.restype = BOOL
+
+UnregisterClassW = windll.User32.UnregisterClassW
+UnregisterClassW.argtypes = [ATOM, HINSTANCE]
+UnregisterClassW.restype = BOOL
+
+DestroyWindow = windll.User32.DestroyWindow
+DestroyWindow.argtypes = [HWND]
+DestroyWindow.restype = BOOL
+
+LoadIconW = windll.User32.LoadIconW
+LoadIconW.argtypes = [HINSTANCE, LPCWSTR]
+LoadIconW.restype = HICON
+
+
+class SYSTEM_POWER_STATUS(ctypes.Structure):
+ _fields_ = [
+ ('ACLineStatus', BYTE),
+ ('BatteryFlag', BYTE),
+ ('BatteryLifePercent', BYTE),
+ ('Reserved1', BYTE),
+ ('BatteryLifeTime', DWORD),
+ ('BatteryFullLifeTime', DWORD),
+ ]
+
+SystemPowerStatusP = ctypes.POINTER(SYSTEM_POWER_STATUS)
+
+GetSystemPowerStatus = ctypes.windll.kernel32.GetSystemPowerStatus
+GetSystemPowerStatus.argtypes = [SystemPowerStatusP]
+GetSystemPowerStatus.restype = BOOL
diff --git a/external/plyer/platforms/win/notification.py b/external/plyer/platforms/win/notification.py
new file mode 100644
index 0000000..ea46e08
--- /dev/null
+++ b/external/plyer/platforms/win/notification.py
@@ -0,0 +1,13 @@
+from threading import Thread as thread
+
+from plyer.facades import Notification
+from plyer.platforms.win.libs.balloontip import balloon_tip
+
+
+class WindowsNotification(Notification):
+ def _notify(self, **kwargs):
+ thread(target=balloon_tip, kwargs=kwargs).start()
+
+
+def instance():
+ return WindowsNotification()
diff --git a/external/plyer/platforms/win/tts.py b/external/plyer/platforms/win/tts.py
new file mode 100644
index 0000000..e2539c3
--- /dev/null
+++ b/external/plyer/platforms/win/tts.py
@@ -0,0 +1,16 @@
+import subprocess
+from plyer.facades import TTS
+from plyer.utils import whereis_exe
+
+
+class EspeakTextToSpeech(TTS):
+ ''' Speaks using the espeak program
+ '''
+ def _speak(self, **kwargs):
+ subprocess.call(["espeak", kwargs.get('message')])
+
+
+def instance():
+ if whereis_exe('espeak.exe'):
+ return EspeakTextToSpeech()
+ return TTS()
diff --git a/external/plyer/platforms/win/uniqueid.py b/external/plyer/platforms/win/uniqueid.py
new file mode 100644
index 0000000..bfcf996
--- /dev/null
+++ b/external/plyer/platforms/win/uniqueid.py
@@ -0,0 +1,21 @@
+try:
+ import _winreg as regedit
+except:
+ try:
+ import winreg as regedit
+ except:
+ raise NotImplemented()
+
+from plyer.facades import UniqueID
+
+
+class WinUniqueID(UniqueID):
+ def _get_uid(self):
+ hKey = regedit.OpenKey(regedit.HKEY_LOCAL_MACHINE,
+ r"SOFTWARE\\Microsoft\\Cryptography")
+ value, _ = regedit.QueryValueEx(hKey, "MachineGuid")
+ return value
+
+
+def instance():
+ return WinUniqueID()
diff --git a/external/plyer/tools/pep8checker/pep8.py b/external/plyer/tools/pep8checker/pep8.py
new file mode 100644
index 0000000..63a78e2
--- /dev/null
+++ b/external/plyer/tools/pep8checker/pep8.py
@@ -0,0 +1,1956 @@
+#!/usr/bin/env python
+# pep8.py - Check Python source code formatting, according to PEP 8
+# Copyright (C) 2006 Johann C. Rocholl <johann@rocholl.net>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+r"""
+Check Python source code formatting, according to PEP 8:
+http://www.python.org/dev/peps/pep-0008/
+
+For usage and a list of options, try this:
+$ python pep8.py -h
+
+This program and its regression test suite live here:
+http://github.com/jcrocholl/pep8
+
+Groups of errors and warnings:
+E errors
+W warnings
+100 indentation
+200 whitespace
+300 blank lines
+400 imports
+500 line length
+600 deprecation
+700 statements
+900 syntax error
+
+You can add checks to this program by writing plugins. Each plugin is
+a simple function that is called for each line of source code, either
+physical or logical.
+
+Physical line:
+- Raw line of text from the input file.
+
+Logical line:
+- Multi-line statements converted to a single line.
+- Stripped left and right.
+- Contents of strings replaced with 'xxx' of same length.
+- Comments removed.
+
+The check function requests physical or logical lines by the name of
+the first argument:
+
+def maximum_line_length(physical_line)
+def extraneous_whitespace(logical_line)
+def blank_lines(logical_line, blank_lines, indent_level, line_number)
+
+The last example above demonstrates how check plugins can request
+additional information with extra arguments. All attributes of the
+Checker object are available. Some examples:
+
+lines: a list of the raw lines from the input file
+tokens: the tokens that contribute to this logical line
+line_number: line number in the input file
+blank_lines: blank lines before this one
+indent_char: first indentation character in this file (' ' or '\t')
+indent_level: indentation (with tabs expanded to multiples of 8)
+previous_indent_level: indentation on previous line
+previous_logical: previous logical line
+
+The docstring of each check function shall be the relevant part of
+text from PEP 8. It is printed if the user enables --show-pep8.
+Several docstrings contain examples directly from the PEP 8 document.
+
+Okay: spam(ham[1], {eggs: 2})
+E201: spam( ham[1], {eggs: 2})
+
+These examples are verified automatically when pep8.py is run with the
+--doctest option. You can add examples for your own check functions.
+The format is simple: "Okay" or error/warning code followed by colon
+and space, the rest of the line is example source code. If you put 'r'
+before the docstring, you can use \n for newline, \t for tab and \s
+for space.
+
+"""
+
+__version__ = '1.3.3'
+
+import os
+import sys
+import re
+import time
+import inspect
+import keyword
+import tokenize
+from optparse import OptionParser
+from fnmatch import fnmatch
+try:
+ from ConfigParser import RawConfigParser
+except ImportError:
+ from configparser import RawConfigParser
+ from io import TextIOWrapper
+
+DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git'
+DEFAULT_IGNORE = 'E24'
+if sys.platform == 'win32':
+ DEFAULT_CONFIG = os.path.expanduser(r'~\.pep8')
+else:
+ DEFAULT_CONFIG = os.path.join(os.getenv('XDG_CONFIG_HOME') or
+ os.path.expanduser('~/.config'), 'pep8')
+MAX_LINE_LENGTH = 80
+REPORT_FORMAT = {
+ 'default': '%(path)s:%(row)d:%(col)d: %(code)s %(text)s',
+ 'pylint': '%(path)s:%(row)d: [%(code)s] %(text)s',
+}
+
+
+SINGLETONS = frozenset(['False', 'None', 'True'])
+KEYWORDS = frozenset(keyword.kwlist + ['print']) - SINGLETONS
+BINARY_OPERATORS = frozenset([
+ '**=', '*=', '+=', '-=', '!=', '<>',
+ '%=', '^=', '&=', '|=', '==', '/=', '//=', '<=', '>=', '<<=', '>>=',
+ '%', '^', '&', '|', '=', '/', '//', '<', '>', '<<'])
+UNARY_OPERATORS = frozenset(['>>', '**', '*', '+', '-'])
+OPERATORS = BINARY_OPERATORS | UNARY_OPERATORS
+WHITESPACE = frozenset(' \t')
+SKIP_TOKENS = frozenset([tokenize.COMMENT, tokenize.NL, tokenize.NEWLINE,
+ tokenize.INDENT, tokenize.DEDENT])
+BENCHMARK_KEYS = ['directories', 'files', 'logical lines', 'physical lines']
+
+INDENT_REGEX = re.compile(r'([ \t]*)')
+RAISE_COMMA_REGEX = re.compile(r'raise\s+\w+\s*(,)')
+RERAISE_COMMA_REGEX = re.compile(r'raise\s+\w+\s*,\s*\w+\s*,\s*\w+')
+SELFTEST_REGEX = re.compile(r'(Okay|[EW]\d{3}):\s(.*)')
+ERRORCODE_REGEX = re.compile(r'[EW]\d{3}')
+DOCSTRING_REGEX = re.compile(r'u?r?["\']')
+EXTRANEOUS_WHITESPACE_REGEX = re.compile(r'[[({] | []}),;:]')
+WHITESPACE_AFTER_COMMA_REGEX = re.compile(r'[,;:]\s*(?: |\t)')
+COMPARE_SINGLETON_REGEX = re.compile(r'([=!]=)\s*(None|False|True)')
+COMPARE_TYPE_REGEX = re.compile(r'([=!]=|is|is\s+not)\s*type(?:s\.(\w+)Type'
+ r'|\(\s*(\(\s*\)|[^)]*[^ )])\s*\))')
+KEYWORD_REGEX = re.compile(r'(?:[^\s])(\s*)\b(?:%s)\b(\s*)' %
+ r'|'.join(KEYWORDS))
+OPERATOR_REGEX = re.compile(r'(?:[^\s])(\s*)(?:[-+*/|!<=>%&^]+)(\s*)')
+LAMBDA_REGEX = re.compile(r'\blambda\b')
+HUNK_REGEX = re.compile(r'^@@ -\d+,\d+ \+(\d+),(\d+) @@.*$')
+
+# Work around Python < 2.6 behaviour, which does not generate NL after
+# a comment which is on a line by itself.
+COMMENT_WITH_NL = tokenize.generate_tokens(['#\n'].pop).send(None)[1] == '#\n'
+
+
+##############################################################################
+# Plugins (check functions) for physical lines
+##############################################################################
+
+
+def tabs_or_spaces(physical_line, indent_char):
+ r"""
+ Never mix tabs and spaces.
+
+ The most popular way of indenting Python is with spaces only. The
+ second-most popular way is with tabs only. Code indented with a mixture
+ of tabs and spaces should be converted to using spaces exclusively. When
+ invoking the Python command line interpreter with the -t option, it issues
+ warnings about code that illegally mixes tabs and spaces. When using -tt
+ these warnings become errors. These options are highly recommended!
+
+ Okay: if a == 0:\n a = 1\n b = 1
+ E101: if a == 0:\n a = 1\n\tb = 1
+ """
+ indent = INDENT_REGEX.match(physical_line).group(1)
+ for offset, char in enumerate(indent):
+ if char != indent_char:
+ return offset, "E101 indentation contains mixed spaces and tabs"
+
+
+def tabs_obsolete(physical_line):
+ r"""
+ For new projects, spaces-only are strongly recommended over tabs. Most
+ editors have features that make this easy to do.
+
+ Okay: if True:\n return
+ W191: if True:\n\treturn
+ """
+ indent = INDENT_REGEX.match(physical_line).group(1)
+ if '\t' in indent:
+ return indent.index('\t'), "W191 indentation contains tabs"
+
+
+def trailing_whitespace(physical_line):
+ r"""
+ JCR: Trailing whitespace is superfluous.
+ FBM: Except when it occurs as part of a blank line (i.e. the line is
+ nothing but whitespace). According to Python docs[1] a line with only
+ whitespace is considered a blank line, and is to be ignored. However,
+ matching a blank line to its indentation level avoids mistakenly
+ terminating a multi-line statement (e.g. class declaration) when
+ pasting code into the standard Python interpreter.
+
+ [1] http://docs.python.org/reference/lexical_analysis.html#blank-lines
+
+ The warning returned varies on whether the line itself is blank, for easier
+ filtering for those who want to indent their blank lines.
+
+ Okay: spam(1)
+ W291: spam(1)\s
+ W293: class Foo(object):\n \n bang = 12
+ """
+ physical_line = physical_line.rstrip('\n') # chr(10), newline
+ physical_line = physical_line.rstrip('\r') # chr(13), carriage return
+ physical_line = physical_line.rstrip('\x0c') # chr(12), form feed, ^L
+ stripped = physical_line.rstrip(' \t\v')
+ if physical_line != stripped:
+ if stripped:
+ return len(stripped), "W291 trailing whitespace"
+ else:
+ return 0, "W293 blank line contains whitespace"
+
+
+#def trailing_blank_lines(physical_line, lines, line_number):
+# r"""
+# JCR: Trailing blank lines are superfluous.
+#
+# Okay: spam(1)
+# W391: spam(1)\n
+# """
+# if not physical_line.rstrip() and line_number == len(lines):
+# return 0, "W391 blank line at end of file"
+
+
+def missing_newline(physical_line):
+ """
+ JCR: The last line should have a newline.
+
+ Reports warning W292.
+ """
+ if physical_line.rstrip() == physical_line:
+ return len(physical_line), "W292 no newline at end of file"
+
+
+def maximum_line_length(physical_line, max_line_length):
+ """
+ Limit all lines to a maximum of 79 characters.
+
+ There are still many devices around that are limited to 80 character
+ lines; plus, limiting windows to 80 characters makes it possible to have
+ several windows side-by-side. The default wrapping on such devices looks
+ ugly. Therefore, please limit all lines to a maximum of 79 characters.
+ For flowing long blocks of text (docstrings or comments), limiting the
+ length to 72 characters is recommended.
+
+ Reports error E501.
+ """
+ line = physical_line.rstrip()
+ length = len(line)
+ if length > max_line_length:
+ if hasattr(line, 'decode'): # Python 2
+ # The line could contain multi-byte characters
+ try:
+ length = len(line.decode('utf-8'))
+ except UnicodeError:
+ pass
+ if length > max_line_length:
+ return (max_line_length, "E501 line too long "
+ "(%d > %d characters)" % (length, max_line_length))
+
+
+##############################################################################
+# Plugins (check functions) for logical lines
+##############################################################################
+
+
+def blank_lines(logical_line, blank_lines, indent_level, line_number,
+ previous_logical, previous_indent_level):
+ r"""
+ Separate top-level function and class definitions with two blank lines.
+
+ Method definitions inside a class are separated by a single blank line.
+
+ Extra blank lines may be used (sparingly) to separate groups of related
+ functions. Blank lines may be omitted between a bunch of related
+ one-liners (e.g. a set of dummy implementations).
+
+ Use blank lines in functions, sparingly, to indicate logical sections.
+
+ Okay: def a():\n pass\n\n\ndef b():\n pass
+ Okay: def a():\n pass\n\n\n# Foo\n# Bar\n\ndef b():\n pass
+
+ E301: class Foo:\n b = 0\n def bar():\n pass
+ E302: def a():\n pass\n\ndef b(n):\n pass
+ E303: def a():\n pass\n\n\n\ndef b(n):\n pass
+ E303: def a():\n\n\n\n pass
+ E304: @decorator\n\ndef a():\n pass
+ """
+ if line_number == 1:
+ return # Don't expect blank lines before the first line
+ if previous_logical.startswith('@'):
+ if blank_lines:
+ yield 0, "E304 blank lines found after function decorator"
+ elif blank_lines > 2 or (indent_level and blank_lines == 2):
+ yield 0, "E303 too many blank lines (%d)" % blank_lines
+ elif logical_line.startswith(('def ', 'class ', '@')):
+ if indent_level:
+ if not (blank_lines or previous_indent_level < indent_level or
+ DOCSTRING_REGEX.match(previous_logical)):
+ yield 0, "E301 expected 1 blank line, found 0"
+ elif blank_lines != 2:
+ yield 0, "E302 expected 2 blank lines, found %d" % blank_lines
+
+
+def extraneous_whitespace(logical_line):
+ """
+ Avoid extraneous whitespace in the following situations:
+
+ - Immediately inside parentheses, brackets or braces.
+
+ - Immediately before a comma, semicolon, or colon.
+
+ Okay: spam(ham[1], {eggs: 2})
+ E201: spam( ham[1], {eggs: 2})
+ E201: spam(ham[ 1], {eggs: 2})
+ E201: spam(ham[1], { eggs: 2})
+ E202: spam(ham[1], {eggs: 2} )
+ E202: spam(ham[1 ], {eggs: 2})
+ E202: spam(ham[1], {eggs: 2 })
+
+ E203: if x == 4: print x, y; x, y = y , x
+ E203: if x == 4: print x, y ; x, y = y, x
+ E203: if x == 4 : print x, y; x, y = y, x
+ """
+ line = logical_line
+ for match in EXTRANEOUS_WHITESPACE_REGEX.finditer(line):
+ text = match.group()
+ char = text.strip()
+ found = match.start()
+ if text == char + ' ':
+ # assert char in '([{'
+ yield found + 1, "E201 whitespace after '%s'" % char
+ elif line[found - 1] != ',':
+ code = ('E202' if char in '}])' else 'E203') # if char in ',;:'
+ yield found, "%s whitespace before '%s'" % (code, char)
+
+
+def whitespace_around_keywords(logical_line):
+ r"""
+ Avoid extraneous whitespace around keywords.
+
+ Okay: True and False
+ E271: True and False
+ E272: True and False
+ E273: True and\tFalse
+ E274: True\tand False
+ """
+ for match in KEYWORD_REGEX.finditer(logical_line):
+ before, after = match.groups()
+
+ if '\t' in before:
+ yield match.start(1), "E274 tab before keyword"
+ elif len(before) > 1:
+ yield match.start(1), "E272 multiple spaces before keyword"
+
+ if '\t' in after:
+ yield match.start(2), "E273 tab after keyword"
+ elif len(after) > 1:
+ yield match.start(2), "E271 multiple spaces after keyword"
+
+
+def missing_whitespace(logical_line):
+ """
+ JCR: Each comma, semicolon or colon should be followed by whitespace.
+
+ Okay: [a, b]
+ Okay: (3,)
+ Okay: a[1:4]
+ Okay: a[:4]
+ Okay: a[1:]
+ Okay: a[1:4:2]
+ E231: ['a','b']
+ E231: foo(bar,baz)
+ """
+ line = logical_line
+ for index in range(len(line) - 1):
+ char = line[index]
+ if char in ',;:' and line[index + 1] not in WHITESPACE:
+ before = line[:index]
+ if char == ':' and before.count('[') > before.count(']'):
+ continue # Slice syntax, no space required
+ if char == ',' and line[index + 1] == ')':
+ continue # Allow tuple with only one element: (3,)
+ yield index, "E231 missing whitespace after '%s'" % char
+
+
+def indentation(logical_line, previous_logical, indent_char,
+ indent_level, previous_indent_level):
+ r"""
+ Use 4 spaces per indentation level.
+
+ For really old code that you don't want to mess up, you can continue to
+ use 8-space tabs.
+
+ Okay: a = 1
+ Okay: if a == 0:\n a = 1
+ E111: a = 1
+
+ Okay: for item in items:\n pass
+ E112: for item in items:\npass
+
+ Okay: a = 1\nb = 2
+ E113: a = 1\n b = 2
+ """
+ if indent_char == ' ' and indent_level % 4:
+ yield 0, "E111 indentation is not a multiple of four"
+ indent_expect = previous_logical.endswith(':')
+ if indent_expect and indent_level <= previous_indent_level:
+ yield 0, "E112 expected an indented block"
+ if indent_level > previous_indent_level and not indent_expect:
+ yield 0, "E113 unexpected indentation"
+
+
+def continuation_line_indentation(logical_line, tokens, indent_level, verbose):
+ r"""
+ Continuation lines should align wrapped elements either vertically using
+ Python's implicit line joining inside parentheses, brackets and braces, or
+ using a hanging indent.
+
+ When using a hanging indent the following considerations should be applied:
+
+ - there should be no arguments on the first line, and
+
+ - further indentation should be used to clearly distinguish itself as a
+ continuation line.
+
+ Okay: a = (\n)
+ E123: a = (\n )
+
+ Okay: a = (\n 42)
+ E121: a = (\n 42)
+ E122: a = (\n42)
+ E123: a = (\n 42\n )
+ E124: a = (24,\n 42\n)
+ E125: if (a or\n b):\n pass
+ E126: a = (\n 42)
+ E127: a = (24,\n 42)
+ E128: a = (24,\n 42)
+ """
+ first_row = tokens[0][2][0]
+ nrows = 1 + tokens[-1][2][0] - first_row
+ if nrows == 1:
+ return
+
+ # indent_next tells us whether the next block is indented; assuming
+ # that it is indented by 4 spaces, then we should not allow 4-space
+ # indents on the final continuation line; in turn, some other
+ # indents are allowed to have an extra 4 spaces.
+ indent_next = logical_line.endswith(':')
+
+ row = depth = 0
+ # remember how many brackets were opened on each line
+ parens = [0] * nrows
+ # relative indents of physical lines
+ rel_indent = [0] * nrows
+ # visual indents
+ indent = [indent_level]
+ indent_chances = {}
+ last_indent = (0, 0)
+ if verbose >= 3:
+ print((">>> " + tokens[0][4].rstrip()))
+
+ for token_type, text, start, end, line in tokens:
+ newline = row < start[0] - first_row
+ if newline:
+ row = start[0] - first_row
+ newline = (not last_token_multiline and
+ token_type not in (tokenize.NL, tokenize.NEWLINE))
+
+ if newline:
+ # this is the beginning of a continuation line.
+ last_indent = start
+ if verbose >= 3:
+ print(("... " + line.rstrip()))
+
+ # record the initial indent.
+ rel_indent[row] = start[1] - indent_level
+
+ if depth:
+ # a bracket expression in a continuation line.
+ # find the line that it was opened on
+ for open_row in range(row - 1, -1, -1):
+ if parens[open_row]:
+ break
+ else:
+ # an unbracketed continuation line (ie, backslash)
+ open_row = 0
+ hang = rel_indent[row] - rel_indent[open_row]
+ visual_indent = indent_chances.get(start[1])
+
+ if token_type == tokenize.OP and text in ']})':
+ # this line starts with a closing bracket
+ if indent[depth]:
+ if start[1] != indent[depth]:
+ yield (start, 'E124 closing bracket does not match '
+ 'visual indentation')
+ elif hang:
+ yield (start, 'E123 closing bracket does not match '
+ 'indentation of opening bracket\'s line')
+ elif visual_indent is True:
+ # visual indent is verified
+ if not indent[depth]:
+ indent[depth] = start[1]
+ elif visual_indent in (text, str):
+ # ignore token lined up with matching one from a previous line
+ pass
+ elif indent[depth] and start[1] < indent[depth]:
+ # visual indent is broken
+ yield (start, 'E128 continuation line '
+ 'under-indented for visual indent')
+ elif hang == 4 or (indent_next and rel_indent[row] == 8):
+ # hanging indent is verified
+ pass
+ else:
+ # indent is broken
+ if hang <= 0:
+ error = 'E122', 'missing indentation or outdented'
+ elif indent[depth]:
+ error = 'E127', 'over-indented for visual indent'
+ elif hang % 4:
+ error = 'E121', 'indentation is not a multiple of four'
+ else:
+ error = 'E126', 'over-indented for hanging indent'
+ yield start, "%s continuation line %s" % error
+
+ # look for visual indenting
+ if parens[row] and token_type != tokenize.NL and not indent[depth]:
+ indent[depth] = start[1]
+ indent_chances[start[1]] = True
+ if verbose >= 4:
+ print(("bracket depth %s indent to %s" % (depth, start[1])))
+ # deal with implicit string concatenation
+ elif token_type == tokenize.STRING or text in ('u', 'ur', 'b', 'br'):
+ indent_chances[start[1]] = str
+
+ # keep track of bracket depth
+ if token_type == tokenize.OP:
+ if text in '([{':
+ depth += 1
+ indent.append(0)
+ parens[row] += 1
+ if verbose >= 4:
+ print(("bracket depth %s seen, col %s, visual min = %s" %
+ (depth, start[1], indent[depth])))
+ elif text in ')]}' and depth > 0:
+ # parent indents should not be more than this one
+ prev_indent = indent.pop() or last_indent[1]
+ for d in range(depth):
+ if indent[d] > prev_indent:
+ indent[d] = 0
+ for ind in list(indent_chances):
+ if ind >= prev_indent:
+ del indent_chances[ind]
+ depth -= 1
+ if depth:
+ indent_chances[indent[depth]] = True
+ for idx in range(row, -1, -1):
+ if parens[idx]:
+ parens[idx] -= 1
+ break
+ assert len(indent) == depth + 1
+ if start[1] not in indent_chances:
+ # allow to line up tokens
+ indent_chances[start[1]] = text
+
+ last_token_multiline = (start[0] != end[0])
+
+ if indent_next and rel_indent[-1] == 4:
+ yield (last_indent, "E125 continuation line does not distinguish "
+ "itself from next logical line")
+
+
+def whitespace_before_parameters(logical_line, tokens):
+ """
+ Avoid extraneous whitespace in the following situations:
+
+ - Immediately before the open parenthesis that starts the argument
+ list of a function call.
+
+ - Immediately before the open parenthesis that starts an indexing or
+ slicing.
+
+ Okay: spam(1)
+ E211: spam (1)
+
+ Okay: dict['key'] = list[index]
+ E211: dict ['key'] = list[index]
+ E211: dict['key'] = list [index]
+ """
+ prev_type = tokens[0][0]
+ prev_text = tokens[0][1]
+ prev_end = tokens[0][3]
+ for index in range(1, len(tokens)):
+ token_type, text, start, end, line = tokens[index]
+ if (token_type == tokenize.OP and
+ text in '([' and
+ start != prev_end and
+ (prev_type == tokenize.NAME or prev_text in '}])') and
+ # Syntax "class A (B):" is allowed, but avoid it
+ (index < 2 or tokens[index - 2][1] != 'class') and
+ # Allow "return (a.foo for a in range(5))"
+ not keyword.iskeyword(prev_text)):
+ yield prev_end, "E211 whitespace before '%s'" % text
+ prev_type = token_type
+ prev_text = text
+ prev_end = end
+
+
+def whitespace_around_operator(logical_line):
+ r"""
+ Avoid extraneous whitespace in the following situations:
+
+ - More than one space around an assignment (or other) operator to
+ align it with another.
+
+ Okay: a = 12 + 3
+ E221: a = 4 + 5
+ E222: a = 4 + 5
+ E223: a = 4\t+ 5
+ E224: a = 4 +\t5
+ """
+ for match in OPERATOR_REGEX.finditer(logical_line):
+ before, after = match.groups()
+
+ if '\t' in before:
+ yield match.start(1), "E223 tab before operator"
+ elif len(before) > 1:
+ yield match.start(1), "E221 multiple spaces before operator"
+
+ if '\t' in after:
+ yield match.start(2), "E224 tab after operator"
+ elif len(after) > 1:
+ yield match.start(2), "E222 multiple spaces after operator"
+
+
+def missing_whitespace_around_operator(logical_line, tokens):
+ r"""
+ - Always surround these binary operators with a single space on
+ either side: assignment (=), augmented assignment (+=, -= etc.),
+ comparisons (==, <, >, !=, <>, <=, >=, in, not in, is, is not),
+ Booleans (and, or, not).
+
+ - Use spaces around arithmetic operators.
+
+ Okay: i = i + 1
+ Okay: submitted += 1
+ Okay: x = x * 2 - 1
+ Okay: hypot2 = x * x + y * y
+ Okay: c = (a + b) * (a - b)
+ Okay: foo(bar, key='word', *args, **kwargs)
+ Okay: baz(**kwargs)
+ Okay: negative = -1
+ Okay: spam(-1)
+ Okay: alpha[:-i]
+ Okay: if not -5 < x < +5:\n pass
+ Okay: lambda *args, **kw: (args, kw)
+
+ E225: i=i+1
+ E225: submitted +=1
+ E225: x = x*2 - 1
+ E225: hypot2 = x*x + y*y
+ E225: c = (a+b) * (a-b)
+ E225: c = alpha -4
+ E225: z = x **y
+ """
+ parens = 0
+ need_space = False
+ prev_type = tokenize.OP
+ prev_text = prev_end = None
+ for token_type, text, start, end, line in tokens:
+ if token_type in (tokenize.NL, tokenize.NEWLINE, tokenize.ERRORTOKEN):
+ # ERRORTOKEN is triggered by backticks in Python 3000
+ continue
+ if text in ('(', 'lambda'):
+ parens += 1
+ elif text == ')':
+ parens -= 1
+ if need_space:
+ if start != prev_end:
+ need_space = False
+ elif text == '>' and prev_text in ('<', '-'):
+ # Tolerate the "<>" operator, even if running Python 3
+ # Deal with Python 3's annotated return value "->"
+ pass
+ else:
+ yield prev_end, "E225 missing whitespace around operator"
+ need_space = False
+ elif token_type == tokenize.OP and prev_end is not None:
+ if text == '=' and parens:
+ # Allow keyword args or defaults: foo(bar=None).
+ pass
+ elif text in BINARY_OPERATORS:
+ need_space = True
+ elif text in UNARY_OPERATORS:
+ # Allow unary operators: -123, -x, +1.
+ # Allow argument unpacking: foo(*args, **kwargs).
+ if prev_type == tokenize.OP:
+ if prev_text in '}])':
+ need_space = True
+ elif prev_type == tokenize.NAME:
+ if prev_text not in KEYWORDS:
+ need_space = True
+ elif prev_type not in SKIP_TOKENS:
+ need_space = True
+ if need_space and start == prev_end:
+ yield prev_end, "E225 missing whitespace around operator"
+ need_space = False
+ prev_type = token_type
+ prev_text = text
+ prev_end = end
+
+
+def whitespace_around_comma(logical_line):
+ r"""
+ Avoid extraneous whitespace in the following situations:
+
+ - More than one space around an assignment (or other) operator to
+ align it with another.
+
+ Note: these checks are disabled by default
+
+ Okay: a = (1, 2)
+ E241: a = (1, 2)
+ E242: a = (1,\t2)
+ """
+ line = logical_line
+ for m in WHITESPACE_AFTER_COMMA_REGEX.finditer(line):
+ found = m.start() + 1
+ if '\t' in m.group():
+ yield found, "E242 tab after '%s'" % m.group()[0]
+ else:
+ yield found, "E241 multiple spaces after '%s'" % m.group()[0]
+
+
+def whitespace_around_named_parameter_equals(logical_line, tokens):
+ """
+ Don't use spaces around the '=' sign when used to indicate a
+ keyword argument or a default parameter value.
+
+ Okay: def complex(real, imag=0.0):
+ Okay: return magic(r=real, i=imag)
+ Okay: boolean(a == b)
+ Okay: boolean(a != b)
+ Okay: boolean(a <= b)
+ Okay: boolean(a >= b)
+
+ E251: def complex(real, imag = 0.0):
+ E251: return magic(r = real, i = imag)
+ """
+ parens = 0
+ no_space = False
+ prev_end = None
+ for token_type, text, start, end, line in tokens:
+ if no_space:
+ no_space = False
+ if start != prev_end:
+ yield (prev_end,
+ "E251 no spaces around keyword / parameter equals")
+ elif token_type == tokenize.OP:
+ if text == '(':
+ parens += 1
+ elif text == ')':
+ parens -= 1
+ elif parens and text == '=':
+ no_space = True
+ if start != prev_end:
+ yield (prev_end,
+ "E251 no spaces around keyword / parameter equals")
+ prev_end = end
+
+
+def whitespace_before_inline_comment(logical_line, tokens):
+ """
+ Separate inline comments by at least two spaces.
+
+ An inline comment is a comment on the same line as a statement. Inline
+ comments should be separated by at least two spaces from the statement.
+ They should start with a # and a single space.
+
+ Okay: x = x + 1 # Increment x
+ Okay: x = x + 1 # Increment x
+ E261: x = x + 1 # Increment x
+ E262: x = x + 1 #Increment x
+ E262: x = x + 1 # Increment x
+ """
+ prev_end = (0, 0)
+ for token_type, text, start, end, line in tokens:
+ if token_type == tokenize.COMMENT:
+ if not line[:start[1]].strip():
+ continue
+ if prev_end[0] == start[0] and start[1] < prev_end[1] + 2:
+ yield (prev_end,
+ "E261 at least two spaces before inline comment")
+ if text.startswith('# ') or not text.startswith('# '):
+ yield start, "E262 inline comment should start with '# '"
+ elif token_type != tokenize.NL:
+ prev_end = end
+
+
+def imports_on_separate_lines(logical_line):
+ r"""
+ Imports should usually be on separate lines.
+
+ Okay: import os\nimport sys
+ E401: import sys, os
+
+ Okay: from subprocess import Popen, PIPE
+ Okay: from myclas import MyClass
+ Okay: from foo.bar.yourclass import YourClass
+ Okay: import myclass
+ Okay: import foo.bar.yourclass
+ """
+ line = logical_line
+ if line.startswith('import '):
+ found = line.find(',')
+ if -1 < found:
+ yield found, "E401 multiple imports on one line"
+
+
+def compound_statements(logical_line):
+ r"""
+ Compound statements (multiple statements on the same line) are
+ generally discouraged.
+
+ While sometimes it's okay to put an if/for/while with a small body
+ on the same line, never do this for multi-clause statements. Also
+ avoid folding such long lines!
+
+ Okay: if foo == 'blah':\n do_blah_thing()
+ Okay: do_one()
+ Okay: do_two()
+ Okay: do_three()
+
+ E701: if foo == 'blah': do_blah_thing()
+ E701: for x in lst: total += x
+ E701: while t < 10: t = delay()
+ E701: if foo == 'blah': do_blah_thing()
+ E701: else: do_non_blah_thing()
+ E701: try: something()
+ E701: finally: cleanup()
+ E701: if foo == 'blah': one(); two(); three()
+
+ E702: do_one(); do_two(); do_three()
+ """
+ line = logical_line
+ found = line.find(':')
+ if -1 < found < len(line) - 1:
+ before = line[:found]
+ if (before.count('{') <= before.count('}') and # {'a': 1} (dict)
+ before.count('[') <= before.count(']') and # [1:2] (slice)
+ before.count('(') <= before.count(')') and # (Python 3 annotation)
+ not LAMBDA_REGEX.search(before)): # lambda x: x
+ yield found, "E701 multiple statements on one line (colon)"
+ found = line.find(';')
+ if -1 < found:
+ yield found, "E702 multiple statements on one line (semicolon)"
+
+
+def explicit_line_join(logical_line, tokens):
+ r"""
+ Avoid explicit line join between brackets.
+
+ The preferred way of wrapping long lines is by using Python's implied line
+ continuation inside parentheses, brackets and braces. Long lines can be
+ broken over multiple lines by wrapping expressions in parentheses. These
+ should be used in preference to using a backslash for line continuation.
+
+ E502: aaa = [123, \\n 123]
+ E502: aaa = ("bbb " \\n "ccc")
+
+ Okay: aaa = [123,\n 123]
+ Okay: aaa = ("bbb "\n "ccc")
+ Okay: aaa = "bbb " \\n "ccc"
+ """
+ prev_start = prev_end = parens = 0
+ for token_type, text, start, end, line in tokens:
+ if start[0] != prev_start and parens and backslash:
+ yield backslash, "E502 the backslash is redundant between brackets"
+ if end[0] != prev_end:
+ if line.rstrip('\r\n').endswith('\\'):
+ backslash = (end[0], len(line.splitlines()[-1]) - 1)
+ else:
+ backslash = None
+ prev_start = prev_end = end[0]
+ else:
+ prev_start = start[0]
+ if token_type == tokenize.OP:
+ if text in '([{':
+ parens += 1
+ elif text in ')]}':
+ parens -= 1
+
+
+def comparison_to_singleton(logical_line):
+ """
+ Comparisons to singletons like None should always be done
+ with "is" or "is not", never the equality operators.
+
+ Okay: if arg is not None:
+ E711: if arg != None:
+ E712: if arg == True:
+
+ Also, beware of writing if x when you really mean if x is not None --
+ e.g. when testing whether a variable or argument that defaults to None was
+ set to some other value. The other value might have a type (such as a
+ container) that could be false in a boolean context!
+ """
+ match = COMPARE_SINGLETON_REGEX.search(logical_line)
+ if match:
+ same = (match.group(1) == '==')
+ singleton = match.group(2)
+ msg = "'if cond is %s:'" % (('' if same else 'not ') + singleton)
+ if singleton in ('None',):
+ code = 'E711'
+ else:
+ code = 'E712'
+ nonzero = ((singleton == 'True' and same) or
+ (singleton == 'False' and not same))
+ msg += " or 'if %scond:'" % ('' if nonzero else 'not ')
+ yield match.start(1), ("%s comparison to %s should be %s" %
+ (code, singleton, msg))
+
+
+def comparison_type(logical_line):
+ """
+ Object type comparisons should always use isinstance() instead of
+ comparing types directly.
+
+ Okay: if isinstance(obj, int):
+ E721: if type(obj) is type(1):
+
+ When checking if an object is a string, keep in mind that it might be a
+ unicode string too! In Python 2.3, str and unicode have a common base
+ class, basestring, so you can do:
+
+ Okay: if isinstance(obj, basestring):
+ Okay: if type(a1) is type(b1):
+ """
+ match = COMPARE_TYPE_REGEX.search(logical_line)
+ if match:
+ inst = match.group(3)
+ if inst and isidentifier(inst) and inst not in SINGLETONS:
+ return # Allow comparison for types which are not obvious
+ yield match.start(1), "E721 do not compare types, use 'isinstance()'"
+
+
+def python_3000_has_key(logical_line):
+ r"""
+ The {}.has_key() method will be removed in the future version of
+ Python. Use the 'in' operation instead.
+
+ Okay: if "alph" in d:\n print d["alph"]
+ W601: assert d.has_key('alph')
+ """
+ pos = logical_line.find('.has_key(')
+ if pos > -1:
+ yield pos, "W601 .has_key() is deprecated, use 'in'"
+
+
+def python_3000_raise_comma(logical_line):
+ """
+ When raising an exception, use "raise ValueError('message')"
+ instead of the older form "raise ValueError, 'message'".
+
+ The paren-using form is preferred because when the exception arguments
+ are long or include string formatting, you don't need to use line
+ continuation characters thanks to the containing parentheses. The older
+ form will be removed in Python 3000.
+
+ Okay: raise DummyError("Message")
+ W602: raise DummyError, "Message"
+ """
+ match = RAISE_COMMA_REGEX.match(logical_line)
+ if match and not RERAISE_COMMA_REGEX.match(logical_line):
+ yield match.start(1), "W602 deprecated form of raising exception"
+
+
+def python_3000_not_equal(logical_line):
+ """
+ != can also be written <>, but this is an obsolete usage kept for
+ backwards compatibility only. New code should always use !=.
+ The older syntax is removed in Python 3000.
+
+ Okay: if a != 'no':
+ W603: if a <> 'no':
+ """
+ pos = logical_line.find('<>')
+ if pos > -1:
+ yield pos, "W603 '<>' is deprecated, use '!='"
+
+
+def python_3000_backticks(logical_line):
+ """
+ Backticks are removed in Python 3000.
+ Use repr() instead.
+
+ Okay: val = repr(1 + 2)
+ W604: val = `1 + 2`
+ """
+ pos = logical_line.find('`')
+ if pos > -1:
+ yield pos, "W604 backticks are deprecated, use 'repr()'"
+
+
+##############################################################################
+# Helper functions
+##############################################################################
+
+
+if '' == ''.encode():
+ # Python 2: implicit encoding.
+ def readlines(filename):
+ f = open(filename)
+ try:
+ return f.readlines()
+ finally:
+ f.close()
+
+ isidentifier = re.compile(r'[a-zA-Z_]\w*').match
+ stdin_get_value = sys.stdin.read
+else:
+ # Python 3
+ def readlines(filename):
+ f = open(filename, 'rb')
+ try:
+ coding, lines = tokenize.detect_encoding(f.readline)
+ f = TextIOWrapper(f, coding, line_buffering=True)
+ return [l.decode(coding) for l in lines] + f.readlines()
+ except (LookupError, SyntaxError, UnicodeError):
+ f.close()
+ # Fall back if files are improperly declared
+ f = open(filename, encoding='latin-1')
+ return f.readlines()
+ finally:
+ f.close()
+
+ isidentifier = str.isidentifier
+ stdin_get_value = TextIOWrapper(sys.stdin.buffer, errors='ignore').read
+readlines.__doc__ = " Read the source code."
+
+
+def expand_indent(line):
+ r"""
+ Return the amount of indentation.
+ Tabs are expanded to the next multiple of 8.
+
+ >>> expand_indent(' ')
+ 4
+ >>> expand_indent('\t')
+ 8
+ >>> expand_indent(' \t')
+ 8
+ >>> expand_indent(' \t')
+ 8
+ >>> expand_indent(' \t')
+ 16
+ """
+ if '\t' not in line:
+ return len(line) - len(line.lstrip())
+ result = 0
+ for char in line:
+ if char == '\t':
+ result = result // 8 * 8 + 8
+ elif char == ' ':
+ result += 1
+ else:
+ break
+ return result
+
+
+def mute_string(text):
+ """
+ Replace contents with 'xxx' to prevent syntax matching.
+
+ >>> mute_string('"abc"')
+ '"xxx"'
+ >>> mute_string("'''abc'''")
+ "'''xxx'''"
+ >>> mute_string("r'abc'")
+ "r'xxx'"
+ """
+ # String modifiers (e.g. u or r)
+ start = text.index(text[-1]) + 1
+ end = len(text) - 1
+ # Triple quotes
+ if text[-3:] in ('"""', "'''"):
+ start += 2
+ end -= 2
+ return text[:start] + 'x' * (end - start) + text[end:]
+
+
+def parse_udiff(diff, patterns=None, parent='.'):
+ rv = {}
+ path = nrows = None
+ for line in diff.splitlines():
+ if nrows:
+ if line[:1] != '-':
+ nrows -= 1
+ continue
+ if line[:3] == '@@ ':
+ row, nrows = [int(g) for g in HUNK_REGEX.match(line).groups()]
+ rv[path].update(list(range(row, row + nrows)))
+ elif line[:3] == '+++':
+ path = line[4:].split('\t', 1)[0]
+ if path[:2] == 'b/':
+ path = path[2:]
+ rv[path] = set()
+ return dict([(os.path.join(parent, path), rows)
+ for (path, rows) in list(rv.items())
+ if rows and filename_match(path, patterns)])
+
+
+def filename_match(filename, patterns, default=True):
+ """
+ Check if patterns contains a pattern that matches filename.
+ If patterns is unspecified, this always returns True.
+ """
+ if not patterns:
+ return default
+ return any(fnmatch(filename, pattern) for pattern in patterns)
+
+
+##############################################################################
+# Framework to run all checks
+##############################################################################
+
+
+def find_checks(argument_name):
+ """
+ Find all globally visible functions where the first argument name
+ starts with argument_name.
+ """
+ for name, function in list(globals().items()):
+ if not inspect.isfunction(function):
+ continue
+ args = inspect.getargspec(function)[0]
+ if args and args[0].startswith(argument_name):
+ codes = ERRORCODE_REGEX.findall(function.__doc__ or '')
+ yield name, codes, function, args
+
+
+class Checker(object):
+ """
+ Load a Python source file, tokenize it, check coding style.
+ """
+
+ def __init__(self, filename, lines=None,
+ options=None, report=None, **kwargs):
+ if options is None:
+ options = StyleGuide(kwargs).options
+ else:
+ assert not kwargs
+ self._io_error = None
+ self._physical_checks = options.physical_checks
+ self._logical_checks = options.logical_checks
+ self.max_line_length = options.max_line_length
+ self.verbose = options.verbose
+ self.filename = filename
+ if filename is None:
+ self.filename = 'stdin'
+ self.lines = lines or []
+ elif lines is None:
+ try:
+ self.lines = readlines(filename)
+ except IOError:
+ exc_type, exc = sys.exc_info()[:2]
+ self._io_error = '%s: %s' % (exc_type.__name__, exc)
+ self.lines = []
+ else:
+ self.lines = lines
+ self.report = report or options.report
+ self.report_error = self.report.error
+
+ def readline(self):
+ """
+ Get the next line from the input buffer.
+ """
+ self.line_number += 1
+ if self.line_number > len(self.lines):
+ return ''
+ return self.lines[self.line_number - 1]
+
+ def readline_check_physical(self):
+ """
+ Check and return the next physical line. This method can be
+ used to feed tokenize.generate_tokens.
+ """
+ line = self.readline()
+ if line:
+ self.check_physical(line)
+ return line
+
+ def run_check(self, check, argument_names):
+ """
+ Run a check plugin.
+ """
+ arguments = []
+ for name in argument_names:
+ arguments.append(getattr(self, name))
+ return check(*arguments)
+
+ def check_physical(self, line):
+ """
+ Run all physical checks on a raw input line.
+ """
+ self.physical_line = line
+ if self.indent_char is None and line[:1] in WHITESPACE:
+ self.indent_char = line[0]
+ for name, check, argument_names in self._physical_checks:
+ result = self.run_check(check, argument_names)
+ if result is not None:
+ offset, text = result
+ self.report_error(self.line_number, offset, text, check)
+
+ def build_tokens_line(self):
+ """
+ Build a logical line from tokens.
+ """
+ self.mapping = []
+ logical = []
+ length = 0
+ previous = None
+ for token in self.tokens:
+ token_type, text = token[0:2]
+ if token_type in SKIP_TOKENS:
+ continue
+ if token_type == tokenize.STRING:
+ text = mute_string(text)
+ if previous:
+ end_row, end = previous[3]
+ start_row, start = token[2]
+ if end_row != start_row: # different row
+ prev_text = self.lines[end_row - 1][end - 1]
+ if prev_text == ',' or (prev_text not in '{[('
+ and text not in '}])'):
+ logical.append(' ')
+ length += 1
+ elif end != start: # different column
+ fill = self.lines[end_row - 1][end:start]
+ logical.append(fill)
+ length += len(fill)
+ self.mapping.append((length, token))
+ logical.append(text)
+ length += len(text)
+ previous = token
+ self.logical_line = ''.join(logical)
+ assert self.logical_line.strip() == self.logical_line
+
+ def check_logical(self):
+ """
+ Build a line from tokens and run all logical checks on it.
+ """
+ self.build_tokens_line()
+ self.report.increment_logical_line()
+ first_line = self.lines[self.mapping[0][1][2][0] - 1]
+ indent = first_line[:self.mapping[0][1][2][1]]
+ self.previous_indent_level = self.indent_level
+ self.indent_level = expand_indent(indent)
+ if self.verbose >= 2:
+ print((self.logical_line[:80].rstrip()))
+ for name, check, argument_names in self._logical_checks:
+ if self.verbose >= 4:
+ print((' ' + name))
+ for result in self.run_check(check, argument_names):
+ offset, text = result
+ if isinstance(offset, tuple):
+ orig_number, orig_offset = offset
+ else:
+ for token_offset, token in self.mapping:
+ if offset >= token_offset:
+ orig_number = token[2][0]
+ orig_offset = (token[2][1] + offset - token_offset)
+ self.report_error(orig_number, orig_offset, text, check)
+ self.previous_logical = self.logical_line
+
+ def generate_tokens(self):
+ if self._io_error:
+ self.report_error(1, 0, 'E902 %s' % self._io_error, readlines)
+ tokengen = tokenize.generate_tokens(self.readline_check_physical)
+ try:
+ for token in tokengen:
+ yield token
+ except (SyntaxError, tokenize.TokenError):
+ exc_type, exc = sys.exc_info()[:2]
+ offset = exc.args[1]
+ if len(offset) > 2:
+ offset = offset[1:3]
+ self.report_error(offset[0], offset[1],
+ 'E901 %s: %s' % (exc_type.__name__, exc.args[0]),
+ self.generate_tokens)
+ generate_tokens.__doc__ = " Check if the syntax is valid."
+
+ def check_all(self, expected=None, line_offset=0):
+ """
+ Run all checks on the input file.
+ """
+ self.report.init_file(self.filename, self.lines, expected, line_offset)
+ self.line_number = 0
+ self.indent_char = None
+ self.indent_level = 0
+ self.previous_logical = ''
+ self.tokens = []
+ self.blank_lines = blank_lines_before_comment = 0
+ parens = 0
+ for token in self.generate_tokens():
+ self.tokens.append(token)
+ token_type, text = token[0:2]
+ if self.verbose >= 3:
+ if token[2][0] == token[3][0]:
+ pos = '[%s:%s]' % (token[2][1] or '', token[3][1])
+ else:
+ pos = 'l.%s' % token[3][0]
+ print(('l.%s\t%s\t%s\t%r' %
+ (token[2][0], pos, tokenize.tok_name[token[0]], text)))
+ if token_type == tokenize.COMMENT or token_type == tokenize.STRING:
+ for sre in re.finditer(r"[:.;,] ?[A-Za-z]", text):
+ pos = sre.span()[0]
+ part = text[:pos]
+ line = token[2][0] + part.count('\n')
+ offset = 0 if part.count('\n') > 0 else token[2][1]
+ col = offset + pos - part.rfind('\n') + 1
+ if sre.group(0)[0] == '.':
+ self.report_error(line, col,
+ 'E289 Too many spaces after period. Use only one.',
+ check=None)
+ elif sre.group(0)[0] == ',':
+ self.report_error(line, col,
+ 'E288 Too many spaces after comma. Use only one.',
+ check=None)
+ else:
+ self.report_error(line, col,
+ 'E287 Too many spaces after punctuation. '
+ 'Use only one.',
+ check=None)
+ if token_type == tokenize.OP:
+ if text in '([{':
+ parens += 1
+ elif text in '}])':
+ parens -= 1
+ elif not parens:
+ if token_type == tokenize.NEWLINE:
+ if self.blank_lines < blank_lines_before_comment:
+ self.blank_lines = blank_lines_before_comment
+ self.check_logical()
+ self.tokens = []
+ self.blank_lines = blank_lines_before_comment = 0
+ elif token_type == tokenize.NL:
+ if len(self.tokens) == 1:
+ # The physical line contains only this token.
+ self.blank_lines += 1
+ self.tokens = []
+ elif token_type == tokenize.COMMENT and len(self.tokens) == 1:
+ if blank_lines_before_comment < self.blank_lines:
+ blank_lines_before_comment = self.blank_lines
+ self.blank_lines = 0
+ if COMMENT_WITH_NL:
+ # The comment also ends a physical line
+ self.tokens = []
+ if self.blank_lines > 1:
+ self.report_error(token[2][0],0,
+ 'E389 File ends in multiple blank lines',
+ check=None)
+
+ return self.report.get_file_results()
+
+
+class BaseReport(object):
+ """Collect the results of the checks."""
+ print_filename = False
+
+ def __init__(self, options):
+ self._benchmark_keys = options.benchmark_keys
+ self._ignore_code = options.ignore_code
+ # Results
+ self.elapsed = 0
+ self.total_errors = 0
+ self.counters = dict.fromkeys(self._benchmark_keys, 0)
+ self.messages = {}
+
+ def start(self):
+ """Start the timer."""
+ self._start_time = time.time()
+
+ def stop(self):
+ """Stop the timer."""
+ self.elapsed = time.time() - self._start_time
+
+ def init_file(self, filename, lines, expected, line_offset):
+ """Signal a new file."""
+ self.filename = filename
+ self.lines = lines
+ self.expected = expected or ()
+ self.line_offset = line_offset
+ self.file_errors = 0
+ self.counters['files'] += 1
+ self.counters['physical lines'] += len(lines)
+
+ def increment_logical_line(self):
+ """Signal a new logical line."""
+ self.counters['logical lines'] += 1
+
+ def error(self, line_number, offset, text, check):
+ """Report an error, according to options."""
+ code = text[:4]
+ if self._ignore_code(code):
+ return
+ if code in self.counters:
+ self.counters[code] += 1
+ else:
+ self.counters[code] = 1
+ self.messages[code] = text[5:]
+ # Don't care about expected errors or warnings
+ if code in self.expected:
+ return
+ if self.print_filename and not self.file_errors:
+ print((self.filename))
+ self.file_errors += 1
+ self.total_errors += 1
+ return code
+
+ def get_file_results(self):
+ """Return the count of errors and warnings for this file."""
+ return self.file_errors
+
+ def get_count(self, prefix=''):
+ """Return the total count of errors and warnings."""
+ return sum([self.counters[key]
+ for key in self.messages if key.startswith(prefix)])
+
+ def get_statistics(self, prefix=''):
+ """
+ Get statistics for message codes that start with the prefix.
+
+ prefix='' matches all errors and warnings
+ prefix='E' matches all errors
+ prefix='W' matches all warnings
+ prefix='E4' matches all errors that have to do with imports
+ """
+ return ['%-7s %s %s' % (self.counters[key], key, self.messages[key])
+ for key in sorted(self.messages) if key.startswith(prefix)]
+
+ def print_statistics(self, prefix=''):
+ """Print overall statistics (number of errors and warnings)."""
+ for line in self.get_statistics(prefix):
+ print(line)
+
+ def print_benchmark(self):
+ """Print benchmark numbers."""
+ print(('%-7.2f %s' % (self.elapsed, 'seconds elapsed')))
+ if self.elapsed:
+ for key in self._benchmark_keys:
+ print(('%-7d %s per second (%d total)' %
+ (self.counters[key] / self.elapsed, key,
+ self.counters[key])))
+
+
+class FileReport(BaseReport):
+ print_filename = True
+
+
+class StandardReport(BaseReport):
+ """Collect and print the results of the checks."""
+
+ def __init__(self, options):
+ super(StandardReport, self).__init__(options)
+ self._fmt = REPORT_FORMAT.get(options.format.lower(),
+ options.format)
+ self._repeat = options.repeat
+ self._show_source = options.show_source
+ self._show_pep8 = options.show_pep8
+
+ def error(self, line_number, offset, text, check):
+ """
+ Report an error, according to options.
+ """
+ code = super(StandardReport, self).error(line_number, offset,
+ text, check)
+ if code and (self.counters[code] == 1 or self._repeat):
+ print((self._fmt % {
+ 'path': self.filename,
+ 'row': self.line_offset + line_number, 'col': offset + 1,
+ 'code': code, 'text': text[5:],
+ }))
+ if self._show_source:
+ if line_number > len(self.lines):
+ line = ''
+ else:
+ line = self.lines[line_number - 1]
+ print((line.rstrip()))
+ print((' ' * offset + '^'))
+ if self._show_pep8 and check is not None:
+ print((check.__doc__.lstrip('\n').rstrip()))
+ return code
+
+
+class DiffReport(StandardReport):
+ """Collect and print the results for the changed lines only."""
+
+ def __init__(self, options):
+ super(DiffReport, self).__init__(options)
+ self._selected = options.selected_lines
+
+ def error(self, line_number, offset, text, check):
+ if line_number not in self._selected[self.filename]:
+ return
+ return super(DiffReport, self).error(line_number, offset, text, check)
+
+
+class TestReport(StandardReport):
+ """Collect the results for the tests."""
+
+ def __init__(self, options):
+ options.benchmark_keys += ['test cases', 'failed tests']
+ super(TestReport, self).__init__(options)
+ self._verbose = options.verbose
+
+ def get_file_results(self):
+ # Check if the expected errors were found
+ label = '%s:%s:1' % (self.filename, self.line_offset)
+ codes = sorted(self.expected)
+ for code in codes:
+ if not self.counters.get(code):
+ self.file_errors += 1
+ self.total_errors += 1
+ print(('%s: error %s not found' % (label, code)))
+ if self._verbose and not self.file_errors:
+ print(('%s: passed (%s)' %
+ (label, ' '.join(codes) or 'Okay')))
+ self.counters['test cases'] += 1
+ if self.file_errors:
+ self.counters['failed tests'] += 1
+ # Reset counters
+ for key in set(self.counters) - set(self._benchmark_keys):
+ del self.counters[key]
+ self.messages = {}
+ return self.file_errors
+
+ def print_results(self):
+ results = ("%(physical lines)d lines tested: %(files)d files, "
+ "%(test cases)d test cases%%s." % self.counters)
+ if self.total_errors:
+ print((results % ", %s failures" % self.total_errors))
+ else:
+ print((results % ""))
+ print(("Test failed." if self.total_errors else "Test passed."))
+
+
+class StyleGuide(object):
+ """Initialize a PEP-8 instance with few options."""
+
+ def __init__(self, *args, **kwargs):
+ # build options from the command line
+ parse_argv = kwargs.pop('parse_argv', False)
+ config_file = kwargs.pop('config_file', None)
+ options, self.paths = process_options(parse_argv=parse_argv,
+ config_file=config_file)
+ if args or kwargs:
+ # build options from dict
+ options_dict = dict(*args, **kwargs)
+ options.__dict__.update(options_dict)
+ if 'paths' in options_dict:
+ self.paths = options_dict['paths']
+
+ self.runner = self.input_file
+ self.options = options
+
+ if not options.reporter:
+ options.reporter = BaseReport if options.quiet else StandardReport
+
+ for index, value in enumerate(options.exclude):
+ options.exclude[index] = value.rstrip('/')
+ # Ignore all checks which are not explicitly selected
+ options.select = tuple(options.select or ())
+ options.ignore = tuple(options.ignore or options.select and ('',))
+ options.benchmark_keys = BENCHMARK_KEYS[:]
+ options.ignore_code = self.ignore_code
+ options.physical_checks = self.get_checks('physical_line')
+ options.logical_checks = self.get_checks('logical_line')
+ self.init_report()
+
+ def init_report(self, reporter=None):
+ """Initialize the report instance."""
+ self.options.report = (reporter or self.options.reporter)(self.options)
+ return self.options.report
+
+ def check_files(self, paths=None):
+ """Run all checks on the paths."""
+ if paths is None:
+ paths = self.paths
+ report = self.options.report
+ runner = self.runner
+ report.start()
+ for path in paths:
+ if os.path.isdir(path):
+ self.input_dir(path)
+ elif not self.excluded(path):
+ runner(path)
+ report.stop()
+ return report
+
+ def input_file(self, filename, lines=None, expected=None, line_offset=0):
+ """Run all checks on a Python source file."""
+ if self.options.verbose:
+ print(('checking %s' % filename))
+ fchecker = Checker(filename, lines=lines, options=self.options)
+ return fchecker.check_all(expected=expected, line_offset=line_offset)
+
+ def input_dir(self, dirname):
+ """Check all files in this directory and all subdirectories."""
+ dirname = dirname.rstrip('/')
+ if self.excluded(dirname):
+ return 0
+ counters = self.options.report.counters
+ verbose = self.options.verbose
+ filepatterns = self.options.filename
+ runner = self.runner
+ for root, dirs, files in os.walk(dirname):
+ if verbose:
+ print(('directory ' + root))
+ counters['directories'] += 1
+ for subdir in sorted(dirs):
+ if self.excluded(subdir):
+ dirs.remove(subdir)
+ for filename in sorted(files):
+ # contain a pattern that matches?
+ if ((filename_match(filename, filepatterns) and
+ not self.excluded(filename))):
+ runner(os.path.join(root, filename))
+
+ def excluded(self, filename):
+ """
+ Check if options.exclude contains a pattern that matches filename.
+ """
+ basename = os.path.basename(filename)
+ return filename_match(basename, self.options.exclude, default=False)
+
+ def ignore_code(self, code):
+ """
+ Check if the error code should be ignored.
+
+ If 'options.select' contains a prefix of the error code,
+ return False. Else, if 'options.ignore' contains a prefix of
+ the error code, return True.
+ """
+ return (code.startswith(self.options.ignore) and
+ not code.startswith(self.options.select))
+
+ def get_checks(self, argument_name):
+ """
+ Find all globally visible functions where the first argument name
+ starts with argument_name and which contain selected tests.
+ """
+ checks = []
+ for name, codes, function, args in find_checks(argument_name):
+ if any(not (code and self.ignore_code(code)) for code in codes):
+ checks.append((name, function, args))
+ return sorted(checks)
+
+
+def init_tests(pep8style):
+ """
+ Initialize testing framework.
+
+ A test file can provide many tests. Each test starts with a
+ declaration. This declaration is a single line starting with '#:'.
+ It declares codes of expected failures, separated by spaces or 'Okay'
+ if no failure is expected.
+ If the file does not contain such declaration, it should pass all
+ tests. If the declaration is empty, following lines are not checked,
+ until next declaration.
+
+ Examples:
+
+ * Only E224 and W701 are expected: #: E224 W701
+ * Following example is conform: #: Okay
+ * Don't check these lines: #:
+ """
+ report = pep8style.init_report(TestReport)
+ runner = pep8style.input_file
+
+ def run_tests(filename):
+ """Run all the tests from a file."""
+ lines = readlines(filename) + ['#:\n']
+ line_offset = 0
+ codes = ['Okay']
+ testcase = []
+ count_files = report.counters['files']
+ for index, line in enumerate(lines):
+ if not line.startswith('#:'):
+ if codes:
+ # Collect the lines of the test case
+ testcase.append(line)
+ continue
+ if codes and index:
+ codes = [c for c in codes if c != 'Okay']
+ # Run the checker
+ runner(filename, testcase, expected=codes,
+ line_offset=line_offset)
+ # output the real line numbers
+ line_offset = index + 1
+ # configure the expected errors
+ codes = line.split()[1:]
+ # empty the test case buffer
+ del testcase[:]
+ report.counters['files'] = count_files + 1
+ return report.counters['failed tests']
+
+ pep8style.runner = run_tests
+
+
+def selftest(options):
+ """
+ Test all check functions with test cases in docstrings.
+ """
+ count_failed = count_all = 0
+ report = BaseReport(options)
+ counters = report.counters
+ checks = options.physical_checks + options.logical_checks
+ for name, check, argument_names in checks:
+ for line in check.__doc__.splitlines():
+ line = line.lstrip()
+ match = SELFTEST_REGEX.match(line)
+ if match is None:
+ continue
+ code, source = match.groups()
+ checker = Checker(None, options=options, report=report)
+ for part in source.split(r'\n'):
+ part = part.replace(r'\t', '\t')
+ part = part.replace(r'\s', ' ')
+ checker.lines.append(part + '\n')
+ checker.check_all()
+ error = None
+ if code == 'Okay':
+ if len(counters) > len(options.benchmark_keys):
+ codes = [key for key in counters
+ if key not in options.benchmark_keys]
+ error = "incorrectly found %s" % ', '.join(codes)
+ elif not counters.get(code):
+ error = "failed to find %s" % code
+ # Keep showing errors for multiple tests
+ for key in set(counters) - set(options.benchmark_keys):
+ del counters[key]
+ report.messages = {}
+ count_all += 1
+ if not error:
+ if options.verbose:
+ print(("%s: %s" % (code, source)))
+ else:
+ count_failed += 1
+ print(("%s: %s:" % (__file__, error)))
+ for line in checker.lines:
+ print((line.rstrip()))
+ return count_failed, count_all
+
+
+def read_config(options, args, arglist, parser):
+ """Read both user configuration and local configuration."""
+ config = RawConfigParser()
+
+ user_conf = options.config
+ if user_conf and os.path.isfile(user_conf):
+ if options.verbose:
+ print(('user configuration: %s' % user_conf))
+ config.read(user_conf)
+
+ parent = tail = args and os.path.abspath(os.path.commonprefix(args))
+ while tail:
+ local_conf = os.path.join(parent, '.pep8')
+ if os.path.isfile(local_conf):
+ if options.verbose:
+ print(('local configuration: %s' % local_conf))
+ config.read(local_conf)
+ break
+ parent, tail = os.path.split(parent)
+
+ if config.has_section('pep8'):
+ option_list = dict([(o.dest, o.type or o.action)
+ for o in parser.option_list])
+
+ # First, read the default values
+ new_options, _ = parser.parse_args([])
+
+ # Second, parse the configuration
+ for opt in config.options('pep8'):
+ if options.verbose > 1:
+ print((' %s = %s' % (opt, config.get('pep8', opt))))
+ if opt.replace('_', '-') not in parser.config_options:
+ print(('Unknown option: \'%s\'\n not in [%s]' %
+ (opt, ' '.join(parser.config_options))))
+ sys.exit(1)
+ normalized_opt = opt.replace('-', '_')
+ opt_type = option_list[normalized_opt]
+ if opt_type in ('int', 'count'):
+ value = config.getint('pep8', opt)
+ elif opt_type == 'string':
+ value = config.get('pep8', opt)
+ else:
+ assert opt_type in ('store_true', 'store_false')
+ value = config.getboolean('pep8', opt)
+ setattr(new_options, normalized_opt, value)
+
+ # Third, overwrite with the command-line options
+ options, _ = parser.parse_args(arglist, values=new_options)
+
+ return options
+
+
+def process_options(arglist=None, parse_argv=False, config_file=None):
+ """Process options passed either via arglist or via command line args."""
+ if not arglist and not parse_argv:
+ # Don't read the command line if the module is used as a library.
+ arglist = []
+ if config_file is True:
+ config_file = DEFAULT_CONFIG
+ parser = OptionParser(version=__version__,
+ usage="%prog [options] input ...")
+ parser.config_options = [
+ 'exclude', 'filename', 'select', 'ignore', 'max-line-length', 'count',
+ 'format', 'quiet', 'show-pep8', 'show-source', 'statistics', 'verbose']
+ parser.add_option('-v', '--verbose', default=0, action='count',
+ help="print status messages, or debug with -vv")
+ parser.add_option('-q', '--quiet', default=0, action='count',
+ help="report only file names, or nothing with -qq")
+ parser.add_option('-r', '--repeat', default=True, action='store_true',
+ help="(obsolete) show all occurrences of the same error")
+ parser.add_option('--first', action='store_false', dest='repeat',
+ help="show first occurrence of each error")
+ parser.add_option('--exclude', metavar='patterns', default=DEFAULT_EXCLUDE,
+ help="exclude files or directories which match these "
+ "comma separated patterns (default: %default)")
+ parser.add_option('--filename', metavar='patterns', default='*.py',
+ help="when parsing directories, only check filenames "
+ "matching these comma separated patterns "
+ "(default: %default)")
+ parser.add_option('--select', metavar='errors', default='',
+ help="select errors and warnings (e.g. E,W6)")
+ parser.add_option('--ignore', metavar='errors', default='',
+ help="skip errors and warnings (e.g. E4,W)")
+ parser.add_option('--show-source', action='store_true',
+ help="show source code for each error")
+ parser.add_option('--show-pep8', action='store_true',
+ help="show text of PEP 8 for each error "
+ "(implies --first)")
+ parser.add_option('--statistics', action='store_true',
+ help="count errors and warnings")
+ parser.add_option('--count', action='store_true',
+ help="print total number of errors and warnings "
+ "to standard error and set exit code to 1 if "
+ "total is not null")
+ parser.add_option('--max-line-length', type='int', metavar='n',
+ default=MAX_LINE_LENGTH,
+ help="set maximum allowed line length "
+ "(default: %default)")
+ parser.add_option('--format', metavar='format', default='default',
+ help="set the error format [default|pylint|<custom>]")
+ parser.add_option('--diff', action='store_true',
+ help="report only lines changed according to the "
+ "unified diff received on STDIN")
+ group = parser.add_option_group("Testing Options")
+ group.add_option('--testsuite', metavar='dir',
+ help="run regression tests from dir")
+ group.add_option('--doctest', action='store_true',
+ help="run doctest on myself")
+ group.add_option('--benchmark', action='store_true',
+ help="measure processing speed")
+ group = parser.add_option_group("Configuration", description=(
+ "The project options are read from the [pep8] section of the .pep8 "
+ "file located in any parent folder of the path(s) being processed. "
+ "Allowed options are: %s." % ', '.join(parser.config_options)))
+ group.add_option('--config', metavar='path', default=config_file,
+ help="config file location (default: %default)")
+
+ options, args = parser.parse_args(arglist)
+ options.reporter = None
+
+ if options.testsuite:
+ args.append(options.testsuite)
+ elif not options.doctest:
+ if parse_argv and not args:
+ if os.path.exists('.pep8') or options.diff:
+ args = ['.']
+ else:
+ parser.error('input not specified')
+ options = read_config(options, args, arglist, parser)
+ options.reporter = parse_argv and options.quiet == 1 and FileReport
+
+ if options.filename:
+ options.filename = options.filename.split(',')
+ options.exclude = options.exclude.split(',')
+ if options.select:
+ options.select = options.select.split(',')
+ if options.ignore:
+ options.ignore = options.ignore.split(',')
+ elif not (options.select or
+ options.testsuite or options.doctest) and DEFAULT_IGNORE:
+ # The default choice: ignore controversial checks
+ # (for doctest and testsuite, all checks are required)
+ options.ignore = DEFAULT_IGNORE.split(',')
+
+ if options.diff:
+ options.reporter = DiffReport
+ stdin = stdin_get_value()
+ options.selected_lines = parse_udiff(stdin, options.filename, args[0])
+ args = sorted(options.selected_lines)
+
+ return options, args
+
+
+def _main():
+ """Parse options and run checks on Python source."""
+ pep8style = StyleGuide(parse_argv=True, config_file=True)
+ options = pep8style.options
+ if options.doctest:
+ import doctest
+ fail_d, done_d = doctest.testmod(report=False, verbose=options.verbose)
+ fail_s, done_s = selftest(options)
+ count_failed = fail_s + fail_d
+ if not options.quiet:
+ count_passed = done_d + done_s - count_failed
+ print(("%d passed and %d failed." % (count_passed, count_failed)))
+ print(("Test failed." if count_failed else "Test passed."))
+ if count_failed:
+ sys.exit(1)
+ if options.testsuite:
+ init_tests(pep8style)
+ report = pep8style.check_files()
+ if options.statistics:
+ report.print_statistics()
+ if options.benchmark:
+ report.print_benchmark()
+ if options.testsuite and not options.quiet:
+ report.print_results()
+ if report.total_errors:
+ if options.count:
+ sys.stderr.write(str(report.total_errors) + '\n')
+ sys.exit(1)
+
+
+if __name__ == '__main__':
+ _main()
diff --git a/external/plyer/tools/pep8checker/pep8base.html b/external/plyer/tools/pep8checker/pep8base.html
new file mode 100644
index 0000000..e69ca6f
--- /dev/null
+++ b/external/plyer/tools/pep8checker/pep8base.html
@@ -0,0 +1,70 @@
+<html>
+ <head>
+ <title>Kivy Styleguide Check</title>
+ <style type="text/css">
+ body, html
+ {
+ background: black url('data:image/png;base64,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') repeat; color: #2D3841;
+ font-weight: 100;
+ margin: 0;
+ }
+
+ table
+ {
+ width: 100%%;
+ color: #BDC8D1;
+ }
+
+ table th,
+ table td
+ {
+ padding: 8px;
+ }
+
+ table tr:hover td
+ {
+ background-color: #393939;
+ }
+
+ table th
+ {
+ background-color: #191919;
+ text-align: left !important;
+ }
+
+ #header
+ {
+ clear: both;
+ padding: 0px 0px 10px 20px;
+ height: 150px;
+ }
+
+ #wrapper
+ {
+ margin: auto;
+ width: 800px;
+ color: #BDC8D1;
+ }
+
+ #wrapper h1
+ {
+ background-color: #191919;
+ color: white;
+ letter-spacing: 2px;
+ margin: 0px;
+ padding: 8px 15px;
+ font-family: 'Lucida Grande',Calibri,Verdana,sans-serif;
+ font-weight: 100;
+ }
+
+ .page-content {
+ padding: 20px;
+ background-color:#232323;
+ }
+ </style>
+ </head>
+ <body>
+ <div id="wrapper">
+ <h1>Kivy Styleguide (PEP8) Check</h1>
+ <div class="page-content">
+
diff --git a/external/plyer/tools/pep8checker/pep8kivy.py b/external/plyer/tools/pep8checker/pep8kivy.py
new file mode 100644
index 0000000..245928e
--- /dev/null
+++ b/external/plyer/tools/pep8checker/pep8kivy.py
@@ -0,0 +1,109 @@
+import sys
+from os import walk
+from os.path import isdir, join, abspath, dirname
+import pep8
+import time
+
+htmlmode = False
+
+pep8_ignores = (
+ 'E125', # continuation line does not
+ # distinguish itself from next logical line
+ 'E126', # continuation line over-indented for hanging indent
+ 'E127', # continuation line over-indented for visual indent
+ 'E128') # continuation line under-indented for visual indent
+
+class KivyStyleChecker(pep8.Checker):
+
+ def __init__(self, filename):
+ pep8.Checker.__init__(self, filename, ignore=pep8_ignores)
+
+ def report_error(self, line_number, offset, text, check):
+ if htmlmode is False:
+ return pep8.Checker.report_error(self,
+ line_number, offset, text, check)
+
+ # html generation
+ print('<tr><td>{0}</td><td>{1}</td></tr>'.format(line_number, text))
+
+
+if __name__ == '__main__':
+
+ def usage():
+ print('Usage: python pep8kivy.py [-html] <file_or_folder_to_check>*')
+ print('Folders will be checked recursively.')
+ sys.exit(1)
+
+ if len(sys.argv) < 2:
+ usage()
+ if sys.argv[1] == '-html':
+ if len(sys.argv) < 3:
+ usage()
+ else:
+ htmlmode = True
+ targets = sys.argv[-1].split()
+ elif sys.argv == 2:
+ targets = sys.argv[-1]
+ else:
+ targets = sys.argv[-1].split()
+
+ def check(fn):
+ try:
+ checker = KivyStyleChecker(fn)
+ except IOError:
+ # File couldn't be opened, so was deleted apparently.
+ # Don't check deleted files.
+ return 0
+ return checker.check_all()
+
+ errors = 0
+ exclude_dirs = ['/lib', '/coverage', '/pep8', '/doc']
+ exclude_files = ['kivy/gesture.py', 'osx/build.py', 'win32/build.py',
+ 'kivy/tools/stub-gl-debug.py',
+ 'kivy/modules/webdebugger.py',
+ 'kivy/modules/_webdebugger.py']
+ for target in targets:
+ if isdir(target):
+ if htmlmode:
+ path = join(dirname(abspath(__file__)), 'pep8base.html')
+ print(open(path, 'r').read())
+ print('''<p>Generated: %s</p><table>''' % (time.strftime('%c')))
+
+ for dirpath, dirnames, filenames in walk(target):
+ cont = False
+ for pat in exclude_dirs:
+ if pat in dirpath:
+ cont = True
+ break
+ if cont:
+ continue
+ for filename in filenames:
+ if not filename.endswith('.py'):
+ continue
+ cont = False
+ complete_filename = join(dirpath, filename)
+ for pat in exclude_files:
+ if complete_filename.endswith(pat):
+ cont = True
+ if cont:
+ continue
+
+ if htmlmode:
+ print('<tr><th colspan="2">%s</td></tr>' \
+ % complete_filename)
+ errors += check(complete_filename)
+
+ if htmlmode:
+ print('</div></div></table></body></html>')
+
+ else:
+ # Got a single file to check
+ for pat in exclude_dirs + exclude_files:
+ if pat in target:
+ break
+ else:
+ if target.endswith('.py'):
+ errors += check(target)
+
+ # If errors is 0 we return with 0. That's just fine.
+ sys.exit(errors)
diff --git a/external/plyer/tools/pep8checker/pre-commit.githook b/external/plyer/tools/pep8checker/pre-commit.githook
new file mode 100644
index 0000000..23d119c
--- /dev/null
+++ b/external/plyer/tools/pep8checker/pre-commit.githook
@@ -0,0 +1,78 @@
+#!/usr/bin/env python
+
+'''
+ Kivy Git Pre-Commit Hook to Enforce Styleguide
+ ==============================================
+
+ This script is not supposed to be run directly.
+ Instead, copy it to your kivy/.git/hooks/ directory, call it 'pre-commit'
+ and make it executable.
+
+ If you attempt to commit, git will run this script, which in turn will run
+ the styleguide checker over your code and abort the commit if there are any
+ errors. If that happens, please fix & retry.
+
+ To install::
+
+ cp kivy/tools/pep8checker/pre-commit.githook .git/hooks/pre-commit
+ chmod +x .git/hooks/pre-commit
+'''
+
+import sys, os
+from os.path import dirname, abspath, sep, join
+from subprocess import call, Popen, PIPE
+
+curdir = dirname(abspath(__file__))
+kivydir = sep.join(curdir.split(sep)[:-2])
+srcdir = join(kivydir, 'kivy')
+script = join(srcdir, 'tools', 'pep8checker', 'pep8kivy.py')
+try:
+ with open(script): pass
+except IOError:
+ # if this not the kivy project, find the script file in the kivy project
+ os.environ['KIVY_NO_CONSOLELOG'] = '1'
+ import kivy
+ script = join(dirname(kivy.__file__), 'tools', 'pep8checker', 'pep8kivy.py')
+ srcdir = ''
+
+# Only check the files that were staged
+#proc = Popen(['git', 'diff', '--cached', '--name-only', 'HEAD'], stdout=PIPE)
+#targets = [join(kivydir, target) for target in proc.stdout]
+
+# Correction: only check the files that were staged, but do not include
+# deleted files.
+proc = Popen(['git', 'diff', '--cached', '--name-status', 'HEAD'], stdout=PIPE)
+proc.wait()
+
+# This gives output like the following:
+#
+# A examples/widgets/lists/list_simple_in_kv.py
+# A examples/widgets/lists/list_simple_in_kv_2.py
+# D kivy/uix/observerview.py
+#
+# So check for D entries and remove them from targets.
+#
+targets = []
+for target in proc.stdout:
+ parts = [p.strip() for p in target.split()]
+ if parts[0] != 'D':
+ targets.append(join(kivydir, target.decode(encoding='UTF-8')))
+
+# Untested possibility: After making the changes above for removing deleted
+# files from targets, saw also where the git diff call could be:
+#
+# git diff --cached --name-only --diff-filter=ACM
+# (leaving off D)
+#
+# and we could then remove the special handling in python for targets above.
+
+call(['git', 'stash', 'save', '--keep-index', '--quiet'])
+retval = call([sys.executable, script, srcdir] + targets)
+call(['git', 'stash', 'pop', '--quiet'])
+
+if retval:
+ # There are styleguide violations
+ print("Error:", retval, "styleguide violation(s) encountered!")
+ print("Your commit has been aborted. Please fix the violations and retry.")
+ sys.exit(retval)
+
diff --git a/external/plyer/utils.py b/external/plyer/utils.py
new file mode 100644
index 0000000..347c8d5
--- /dev/null
+++ b/external/plyer/utils.py
@@ -0,0 +1,135 @@
+'''
+Utils
+=====
+
+'''
+
+__all__ = ('platform', )
+
+from os import environ
+from os import path
+from sys import platform as _sys_platform
+
+
+class Platform(object):
+ # refactored to class to allow module function to be replaced
+ # with module variable
+
+ def __init__(self):
+ self._platform_ios = None
+ self._platform_android = None
+
+ def __eq__(self, other):
+ return other == self._get_platform()
+
+ def __ne__(self, other):
+ return other != self._get_platform()
+
+ def __str__(self):
+ return self._get_platform()
+
+ def __repr__(self):
+ return 'platform name: \'{platform}\' from: \n{instance}'.format(
+ platform=self._get_platform(),
+ instance=super(Platform, self).__repr__()
+ )
+
+ def __hash__(self):
+ return self._get_platform().__hash__()
+
+ def _get_platform(self):
+
+ if self._platform_android is None:
+ # ANDROID_ARGUMENT and ANDROID_PRIVATE are 2 environment variables
+ # from python-for-android project
+ self._platform_android = 'ANDROID_ARGUMENT' in environ
+
+ if self._platform_ios is None:
+ self._platform_ios = (environ.get('KIVY_BUILD', '') == 'ios')
+
+ # On android, _sys_platform return 'linux2', so prefer to check the
+ # import of Android module than trying to rely on _sys_platform.
+ if self._platform_android is True:
+ return 'android'
+ elif self._platform_ios is True:
+ return 'ios'
+ elif _sys_platform in ('win32', 'cygwin'):
+ return 'win'
+ elif _sys_platform == 'darwin':
+ return 'macosx'
+ elif _sys_platform[:5] == 'linux':
+ return 'linux'
+ return 'unknown'
+
+
+platform = Platform()
+
+
+class Proxy(object):
+ # taken from http://code.activestate.com/recipes/496741-object-proxying/
+
+ __slots__ = ['_obj', '_name', '_facade']
+
+ def __init__(self, name, facade):
+ object.__init__(self)
+ object.__setattr__(self, '_obj', None)
+ object.__setattr__(self, '_name', name)
+ object.__setattr__(self, '_facade', facade)
+
+ def _ensure_obj(self):
+ obj = object.__getattribute__(self, '_obj')
+ if obj:
+ return obj
+ # do the import
+ try:
+ name = object.__getattribute__(self, '_name')
+ module = 'plyer.platforms.{}.{}'.format(
+ platform, name)
+ mod = __import__(module, fromlist='.')
+ obj = mod.instance()
+ except:
+ import traceback
+ traceback.print_exc()
+ facade = object.__getattribute__(self, '_facade')
+ obj = facade()
+
+ object.__setattr__(self, '_obj', obj)
+ return obj
+
+ def __getattribute__(self, name):
+ if name == '__doc__':
+ return
+ object.__getattribute__(self, '_ensure_obj')()
+ return getattr(object.__getattribute__(self, '_obj'), name)
+
+ def __delattr__(self, name):
+ object.__getattribute__(self, '_ensure_obj')()
+ delattr(object.__getattribute__(self, '_obj'), name)
+
+ def __setattr__(self, name, value):
+ object.__getattribute__(self, '_ensure_obj')()
+ setattr(object.__getattribute__(self, '_obj'), name, value)
+
+ def __bool__(self):
+ object.__getattribute__(self, '_ensure_obj')()
+ return bool(object.__getattribute__(self, '_obj'))
+
+ def __str__(self):
+ object.__getattribute__(self, '_ensure_obj')()
+ return str(object.__getattribute__(self, '_obj'))
+
+ def __repr__(self):
+ object.__getattribute__(self, '_ensure_obj')()
+ return repr(object.__getattribute__(self, '_obj'))
+
+
+def whereis_exe(program):
+ ''' Tries to find the program on the system path.
+ Returns the path if it is found or None if it's not found.
+ '''
+ path_split = ';' if platform == 'win' else ':'
+ for p in environ.get('PATH', '').split(path_split):
+ if path.exists(path.join(p, program)) and \
+ not path.isdir(path.join(p, program)):
+ return path.join(p, program)
+ return None
diff --git a/external/plyer/utils.pyc b/external/plyer/utils.pyc
new file mode 100644
index 0000000..ff2d89a
--- /dev/null
+++ b/external/plyer/utils.pyc
Binary files differ