pax_global_header00006660000000000000000000000064143337204400014512gustar00rootroot0000000000000052 comment=28b2363fc596b269f899366e01e932baefe533bb plyer-2.1.0/000077500000000000000000000000001433372044000126455ustar00rootroot00000000000000plyer-2.1.0/.gitattributes000066400000000000000000000000261433372044000155360ustar00rootroot00000000000000*.sh text=auto eol=lf plyer-2.1.0/.github/000077500000000000000000000000001433372044000142055ustar00rootroot00000000000000plyer-2.1.0/.github/workflows/000077500000000000000000000000001433372044000162425ustar00rootroot00000000000000plyer-2.1.0/.github/workflows/ci_osx.yml000066400000000000000000000006531433372044000202550ustar00rootroot00000000000000name: Continuous Integration with OSX on: [push, pull_request] jobs: tests: runs-on: macos-latest steps: - uses: actions/checkout@v2 - name: Set up Python 3.x uses: actions/setup-python@v1 with: python-version: 3.x - name: Install dependencies run: | source ci/ci_osx.sh dependencies - name: Tests run: | source ci/ci_osx.sh tests plyer-2.1.0/.github/workflows/ci_ubuntu.yml000066400000000000000000000020421433372044000207600ustar00rootroot00000000000000name: Continuous Integration with Ubuntu on: [push, pull_request] jobs: style: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Python 3.x uses: actions/setup-python@v1 with: python-version: 3.x - name: Install Dependencies run: | source ci/ci_ubuntu.sh style_dependencies - name: Style run: | source ci/ci_ubuntu.sh style tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Python 3.x uses: actions/setup-python@v1 with: python-version: 3.x - name: Install dependencies run: | source ci/ci_ubuntu.sh dependencies - name: Tests run: | source ci/ci_ubuntu.sh tests - name: Upload Coverage if: github.event_name == 'push' && github.ref == 'refs/head/master' env: COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} run: | source ci/ci_ubuntu.sh upload_coverage plyer-2.1.0/.github/workflows/ci_windows.yml000066400000000000000000000006651433372044000211410ustar00rootroot00000000000000name: Continuous Integration with Windows on: [push, pull_request] jobs: tests: runs-on: windows-latest steps: - uses: actions/checkout@v2 - name: Set up Python 3.x uses: actions/setup-python@v1 with: python-version: 3.x - name: Install dependencies run: | . .\ci\ci_windows.ps1 Dependencies - name: Tests run: | . .\ci\ci_windows.ps1 Tests plyer-2.1.0/.github/workflows/plyer_deploy.yml000066400000000000000000000016571433372044000215050ustar00rootroot00000000000000name: Deploy to PyPI on: create jobs: deploy: if: startsWith(github.ref, 'refs/tags/') runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v1 with: python-version: '3.x' - name: Install dependencies run: | source ci/ci_ubuntu.sh deployment_dependencies - name: Build Package run: | source ci/ci_ubuntu.sh build - name: Create artifacts uses: actions/upload-artifact@v1 with: name: plyer_artifact path: dist - name: Upload to GitHub Releases uses: softprops/action-gh-release@v0.1.14 with: files: dist/* draft: true - name: Publish to PyPI env: TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} run: | source ci/ci_ubuntu.sh deploy plyer-2.1.0/.gitignore000066400000000000000000000003331433372044000146340ustar00rootroot00000000000000*.pyc *.pyo .*.swp .*.swo build docs/build/ dist *.egg-info venv *.komodoproject .komodotools # PyDev .project .pydevproject .settings/ bin .buildozer .idea # VS Code .vscode # Mac *.DS_Store # Coverage .coverage plyer-2.1.0/CHANGELOG.md000066400000000000000000001467521433372044000144750ustar00rootroot00000000000000# Change Log ## [2.1.0](https://github.com/kivy/plyer/tree/2.1.0) (2022-11-12) [Full Changelog](https://github.com/kivy/plyer/compare/2.0.0...2.1.0) **Implemented enhancements:** - Use xdg-desktop-portal for Notification inside Flatpak [\#680](https://github.com/kivy/plyer/pull/680) ([JakobDev](https://github.com/JakobDev)) **Closed issues:** - Exception is thrown on try to play recorded audio clip [\#713](https://github.com/kivy/plyer/issues/713) - iOS: filechooser - multiple files selection is not working [\#707](https://github.com/kivy/plyer/issues/707) - Speech to text is not working with api 30 or more [\#693](https://github.com/kivy/plyer/issues/693) - Traceback \(most recent call last\): File "jnius/jnius\_proxy.pxi", line 50, in jnius.jnius.PythonJavaClass.invoke File "jnius/jnius\_proxy.pxi", line 74, in jnius.jnius.PythonJavaClass.\_invoke NotImplementedError: The method \('onLocationChanged', \('V', \('Ljava/util/List;',\)\)\) is not implemented [\#687](https://github.com/kivy/plyer/issues/687) - Notify on Windows but without archiving notifications [\#684](https://github.com/kivy/plyer/issues/684) - Notification icon as base64 instead of path [\#679](https://github.com/kivy/plyer/issues/679) - plyer's last release was ~2 years ago, the number of open PRs is absurd, and the last meaningful commit was months ago [\#674](https://github.com/kivy/plyer/issues/674) - iOS Filechooser that picks all document types \(UIDocumentPickerViewController\) [\#673](https://github.com/kivy/plyer/issues/673) - Python dbus error on Linux when sending notification [\#658](https://github.com/kivy/plyer/issues/658) - Is that project dead? [\#650](https://github.com/kivy/plyer/issues/650) - Notification for android not working \[ Drawable.icon \] [\#647](https://github.com/kivy/plyer/issues/647) - audio don't work on android [\#644](https://github.com/kivy/plyer/issues/644) - Unittests are failing on new clone [\#637](https://github.com/kivy/plyer/issues/637) - Proposing a PR to fix a few small typos [\#622](https://github.com/kivy/plyer/issues/622) - Remove python2 mentions from setup.py [\#608](https://github.com/kivy/plyer/issues/608) - vibrator on android 10 java.lang.SecurityException [\#606](https://github.com/kivy/plyer/issues/606) - How to change prompt input to upper or lower case in JS? [\#603](https://github.com/kivy/plyer/issues/603) - Does this repo is maintained? [\#592](https://github.com/kivy/plyer/issues/592) - Plyer notification not working on android [\#591](https://github.com/kivy/plyer/issues/591) - Feature Request: Add Intent to Android Gallery\(for Pictures\) [\#588](https://github.com/kivy/plyer/issues/588) - plyer.filechooser.save\_file doesn't work on macOS X Catalina [\#578](https://github.com/kivy/plyer/issues/578) - macOS notification NSUserNotificationCenter is deprecated + missing Info.plist [\#449](https://github.com/kivy/plyer/issues/449) **Merged pull requests:** - action-gh-release now uses `github.token` [\#724](https://github.com/kivy/plyer/pull/724) ([misl6](https://github.com/misl6)) - Bump version to 2.1.0 for release [\#723](https://github.com/kivy/plyer/pull/723) ([misl6](https://github.com/misl6)) - Bump action-gh-release to a newer version [\#721](https://github.com/kivy/plyer/pull/721) ([misl6](https://github.com/misl6)) - Update supported Python versions [\#720](https://github.com/kivy/plyer/pull/720) ([misl6](https://github.com/misl6)) - Fixes some E275. + other minor PEP8 fixes [\#711](https://github.com/kivy/plyer/pull/711) ([misl6](https://github.com/misl6)) - Document linux support for orientation [\#709](https://github.com/kivy/plyer/pull/709) ([rshah713](https://github.com/rshah713)) - Document supported platforms for humidity [\#704](https://github.com/kivy/plyer/pull/704) ([rshah713](https://github.com/rshah713)) - Keyword should only hold name of license [\#701](https://github.com/kivy/plyer/pull/701) ([rshah713](https://github.com/rshah713)) - Document supported platforms in native filechooser [\#700](https://github.com/kivy/plyer/pull/700) ([rshah713](https://github.com/rshah713)) - Fix Keystore comment to point at correct class [\#697](https://github.com/kivy/plyer/pull/697) ([rshah713](https://github.com/rshah713)) - Add missing platform for barometer [\#695](https://github.com/kivy/plyer/pull/695) ([rshah713](https://github.com/rshah713)) - Add missing platforms in audio [\#694](https://github.com/kivy/plyer/pull/694) ([rshah713](https://github.com/rshah713)) - Fixes a failing test for notification [\#692](https://github.com/kivy/plyer/pull/692) ([misl6](https://github.com/misl6)) - Fixes style check [\#691](https://github.com/kivy/plyer/pull/691) ([misl6](https://github.com/misl6)) - Clear documentation for Processors [\#689](https://github.com/kivy/plyer/pull/689) ([rshah713](https://github.com/rshah713)) - Create clear documentation for Keystore [\#688](https://github.com/kivy/plyer/pull/688) ([rshah713](https://github.com/rshah713)) - Added tick in ios native file chooser row [\#685](https://github.com/kivy/plyer/pull/685) ([Neizvestnyj](https://github.com/Neizvestnyj)) - :zap: Fix pep8 violations [\#678](https://github.com/kivy/plyer/pull/678) ([Zen-CODE](https://github.com/Zen-CODE)) - :hammer: Fix pep 8 failure for CICD [\#677](https://github.com/kivy/plyer/pull/677) ([Zen-CODE](https://github.com/Zen-CODE)) - fix some errors in readme file [\#676](https://github.com/kivy/plyer/pull/676) ([AdamMusa](https://github.com/AdamMusa)) - android 11+ compatibility Documents folder [\#672](https://github.com/kivy/plyer/pull/672) ([moonpyx](https://github.com/moonpyx)) - Use sys.getandroidapilevel for more robust Android detection [\#670](https://github.com/kivy/plyer/pull/670) ([rdb](https://github.com/rdb)) - More robust way to get application icon on Android for notification [\#669](https://github.com/kivy/plyer/pull/669) ([rdb](https://github.com/rdb)) - Added the ability to track the closure of the file manager without selecting content [\#667](https://github.com/kivy/plyer/pull/667) ([Neizvestnyj](https://github.com/Neizvestnyj)) - Fix bug, when user canceled filechooser, `on_selection` does not dispatched [\#666](https://github.com/kivy/plyer/pull/666) ([Neizvestnyj](https://github.com/Neizvestnyj)) - Bigger buffer, allows large selection [\#655](https://github.com/kivy/plyer/pull/655) ([akshayaurora](https://github.com/akshayaurora)) - fix: fix filechooser save dialog for the KDE [\#652](https://github.com/kivy/plyer/pull/652) ([psyrykh](https://github.com/psyrykh)) - Change R$drawable to R$mipmap in notification.py for android platform [\#648](https://github.com/kivy/plyer/pull/648) ([masterjoseph914](https://github.com/masterjoseph914)) - linux/storagepath: fixup a host of issues [\#646](https://github.com/kivy/plyer/pull/646) ([rski](https://github.com/rski)) - Change `PythonActivity` java class [\#642](https://github.com/kivy/plyer/pull/642) ([Neizvestnyj](https://github.com/Neizvestnyj)) - Enabled transient notifications on Linux [\#639](https://github.com/kivy/plyer/pull/639) ([olumidesan](https://github.com/olumidesan)) - updated-device-name-implementation-for-backward-compatibility [\#634](https://github.com/kivy/plyer/pull/634) ([ljnath](https://github.com/ljnath)) - Added contributors in readme.md file [\#633](https://github.com/kivy/plyer/pull/633) ([ljnath](https://github.com/ljnath)) - Fixed pep8 errors [\#632](https://github.com/kivy/plyer/pull/632) ([ljnath](https://github.com/ljnath)) - Removed python2.6|7 reference and added reference for python 3.6|7|8 [\#631](https://github.com/kivy/plyer/pull/631) ([ljnath](https://github.com/ljnath)) - Support to get android device name or hostname for linux and windows [\#630](https://github.com/kivy/plyer/pull/630) ([ljnath](https://github.com/ljnath)) - \#611 add filters for file chooser on android [\#624](https://github.com/kivy/plyer/pull/624) ([akshayaurora](https://github.com/akshayaurora)) - docs: fix a few simple typos [\#623](https://github.com/kivy/plyer/pull/623) ([akshayaurora](https://github.com/akshayaurora)) - Add check for Trinity Desktop Environment [\#620](https://github.com/kivy/plyer/pull/620) ([akshayaurora](https://github.com/akshayaurora)) - FileChooser: MacOS: Use objectAtIndex\_ to get multiple items [\#618](https://github.com/kivy/plyer/pull/618) ([akshayaurora](https://github.com/akshayaurora)) - add installation section in README.md [\#563](https://github.com/kivy/plyer/pull/563) ([tshirtman](https://github.com/tshirtman)) ## [2.0.0](https://github.com/kivy/plyer/tree/2.0.0) (2020-11-09) [Full Changelog](https://github.com/kivy/plyer/compare/1.4.3...2.0.0) **Closed issues:** - Macox notification - AttributeError: 'NoneType' object has no attribute 'setDelegate\_' [\#586](https://github.com/kivy/plyer/issues/586) - Can't display notifications with Plyer [\#582](https://github.com/kivy/plyer/issues/582) - Unable to set app orientation [\#579](https://github.com/kivy/plyer/issues/579) - Does plyer allow you to open another app? [\#577](https://github.com/kivy/plyer/issues/577) - Calling notification.notify\(\) raises "No Usable Implementation Found!" Error on Android [\#575](https://github.com/kivy/plyer/issues/575) - tts is not working [\#572](https://github.com/kivy/plyer/issues/572) - bluetooth for Android [\#571](https://github.com/kivy/plyer/issues/571) - raise NotImplementedError\(\) NotImplementedError when I use tts [\#567](https://github.com/kivy/plyer/issues/567) - Filter variable may be uninitialized in MacOSX filechooser [\#566](https://github.com/kivy/plyer/issues/566) - Plyer camera cannot save image to the IOS phone [\#561](https://github.com/kivy/plyer/issues/561) - How to turn on gps ?? [\#556](https://github.com/kivy/plyer/issues/556) - How to disable mock location \(fake gps\) in kivy [\#555](https://github.com/kivy/plyer/issues/555) - Release notes for v 1.4.3? [\#550](https://github.com/kivy/plyer/issues/550) - battery.status isCharging always shows false in WINDOWS [\#541](https://github.com/kivy/plyer/issues/541) - Filechooser on mac: using path, crashes python [\#524](https://github.com/kivy/plyer/issues/524) - Android Filechooser not working [\#512](https://github.com/kivy/plyer/issues/512) **Merged pull requests:** - Some APIs are only available for open panels. [\#590](https://github.com/kivy/plyer/pull/590) ([matham](https://github.com/matham)) - Fix uninitialized variable in MacOSX filechooser. [\#568](https://github.com/kivy/plyer/pull/568) ([Mulugruntz](https://github.com/Mulugruntz)) - Fixing crash on MacOSX filechooser [\#565](https://github.com/kivy/plyer/pull/565) ([Mulugruntz](https://github.com/Mulugruntz)) - Uses Python 3 syntax [\#554](https://github.com/kivy/plyer/pull/554) ([AndreMiras](https://github.com/AndreMiras)) - Feature/drop python2 [\#553](https://github.com/kivy/plyer/pull/553) ([AndreMiras](https://github.com/AndreMiras)) - Fixes linter errors [\#552](https://github.com/kivy/plyer/pull/552) ([AndreMiras](https://github.com/AndreMiras)) - Remove unused linters [\#548](https://github.com/kivy/plyer/pull/548) ([ghost](https://github.com/ghost)) - Fix linter warnings [\#547](https://github.com/kivy/plyer/pull/547) ([ghost](https://github.com/ghost)) - Modification of Status isCharging in windows [\#546](https://github.com/kivy/plyer/pull/546) ([irm19](https://github.com/irm19)) ## [1.4.3](https://github.com/kivy/plyer/tree/1.4.3) (2020-03-27) [Full Changelog](https://github.com/kivy/plyer/compare/1.4.2...1.4.3) **Closed issues:** - IOS - GPS : 'IosGPS' object has no attribute '\_location\_manager' [\#538](https://github.com/kivy/plyer/issues/538) - Android FileChooser crashes when back button pressed [\#534](https://github.com/kivy/plyer/issues/534) - Notification not working on android [\#533](https://github.com/kivy/plyer/issues/533) - FileChooser on Android: "on selection" fires multiple times. [\#530](https://github.com/kivy/plyer/issues/530) - KIVY cannot access the android camera.. [\#521](https://github.com/kivy/plyer/issues/521) - No notification icons on Linux \(Gnome\) [\#514](https://github.com/kivy/plyer/issues/514) - Vibrator is not working on Android [\#509](https://github.com/kivy/plyer/issues/509) - notification.notify crashes Android Pie devices [\#504](https://github.com/kivy/plyer/issues/504) - Vibrate revision in api 26 [\#501](https://github.com/kivy/plyer/issues/501) **Merged pull requests:** - Fix linter warnings in examples/gps/main.py [\#545](https://github.com/kivy/plyer/pull/545) ([ghost](https://github.com/ghost)) - Switch to GitHub actions [\#544](https://github.com/kivy/plyer/pull/544) ([ghost](https://github.com/ghost)) - Fix crash in Android Notification \(SDK\_INT \>= 26\) [\#543](https://github.com/kivy/plyer/pull/543) ([ghost](https://github.com/ghost)) - Add native iOS FileBrowser [\#542](https://github.com/kivy/plyer/pull/542) ([Zen-CODE](https://github.com/Zen-CODE)) - Prevent crash when cancelling filechooser [\#536](https://github.com/kivy/plyer/pull/536) ([Zen-CODE](https://github.com/Zen-CODE)) - Make win filechooser use modern windows browser and fix small issues. [\#535](https://github.com/kivy/plyer/pull/535) ([matham](https://github.com/matham)) - Prevent re-binding of callback on each call [\#532](https://github.com/kivy/plyer/pull/532) ([Zen-CODE](https://github.com/Zen-CODE)) - Add permission request to plyer GPS example [\#529](https://github.com/kivy/plyer/pull/529) ([Zen-CODE](https://github.com/Zen-CODE)) - Handle absence of LinuxFileChooser backend [\#526](https://github.com/kivy/plyer/pull/526) ([Cheaterman](https://github.com/Cheaterman)) - Fix vibrator, which was not working on Android devices. [\#523](https://github.com/kivy/plyer/pull/523) ([ghost](https://github.com/ghost)) - Added logic to support the `on_status` method of the gps facade for i… [\#519](https://github.com/kivy/plyer/pull/519) ([Dirk-Sandberg](https://github.com/Dirk-Sandberg)) - Fix default audio file\_path assignment error, file\_path change for Py3 [\#518](https://github.com/kivy/plyer/pull/518) ([Nephyx](https://github.com/Nephyx)) - Add Windows applications storage path [\#517](https://github.com/kivy/plyer/pull/517) ([magnusvmt](https://github.com/magnusvmt)) - fix typo in supported API list [\#516](https://github.com/kivy/plyer/pull/516) ([holdbar](https://github.com/holdbar)) - fix the issue that tts.speak\(\) crashes on android [\#511](https://github.com/kivy/plyer/pull/511) ([Chao-Jen](https://github.com/Chao-Jen)) - Addresses plyer issue \#240. [\#502](https://github.com/kivy/plyer/pull/502) ([Dirk-Sandberg](https://github.com/Dirk-Sandberg)) - Update README.md to add opencollective [\#499](https://github.com/kivy/plyer/pull/499) ([tito](https://github.com/tito)) - Bump to 1.4.0 for release [\#496](https://github.com/kivy/plyer/pull/496) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) ## [1.4.2](https://github.com/kivy/plyer/tree/1.4.2) (2019-09-05) [Full Changelog](https://github.com/kivy/plyer/compare/1.4.1...1.4.2) ## [1.4.1](https://github.com/kivy/plyer/tree/1.4.1) (2019-09-05) [Full Changelog](https://github.com/kivy/plyer/compare/1.4.0...1.4.1) **Closed issues:** - I'll be Working on Linux audio [\#497](https://github.com/kivy/plyer/issues/497) - Notification and service [\#494](https://github.com/kivy/plyer/issues/494) - Windows notification - NotImplementedError: No usable implementation found! [\#485](https://github.com/kivy/plyer/issues/485) - macOS notification NSUserNotificationCenter is deprecated + missing Info.plist [\#449](https://github.com/kivy/plyer/issues/449) ## [1.4.0](https://github.com/kivy/plyer/tree/1.4.0) (2018-12-31) [Full Changelog](https://github.com/kivy/plyer/compare/1.3.2...1.4.0) **Implemented enhancements:** - Windows microphone support [\#179](https://github.com/kivy/plyer/issues/179) **Closed issues:** - jnius.jnius.JavaException: Class not found 'android/content/INTENT' [\#479](https://github.com/kivy/plyer/issues/479) - macOS storagepath uses non-standard path for get\_home\_dir\(\) [\#450](https://github.com/kivy/plyer/issues/450) - Example Applications break down on a real device [\#338](https://github.com/kivy/plyer/issues/338) - Feature request: Accelerometer on Linux \(computers\) [\#9](https://github.com/kivy/plyer/issues/9) - Linux wifi implementation via rockymeza/wifi is broken [\#487](https://github.com/kivy/plyer/issues/487) - Hardcoded 'wlan0' does not work on all devices [\#477](https://github.com/kivy/plyer/issues/477) - GNU/Linux wifi disconnect\(\) not working on Ubuntu 15.04+ [\#452](https://github.com/kivy/plyer/issues/452) - Plyer Email [\#420](https://github.com/kivy/plyer/issues/420) - notification not working on android [\#402](https://github.com/kivy/plyer/issues/402) - plyer.accelerometer not working with Kivy Launcher [\#401](https://github.com/kivy/plyer/issues/401) - New PyPI release after 1.3.0 [\#400](https://github.com/kivy/plyer/issues/400) - plyer.notify.notification doesn't show ticker in Android [\#378](https://github.com/kivy/plyer/issues/378) - plyer.uniqueid.id causes crash on Android with sdl2 [\#245](https://github.com/kivy/plyer/issues/245) - audio: JVM exception occurred: setAudioSource failed. [\#210](https://github.com/kivy/plyer/issues/210) - Something wrong with encoding in AndroidNotification [\#175](https://github.com/kivy/plyer/issues/175) **Merged pull requests:** - Implement WiFi for Linux with nmcli [\#495](https://github.com/kivy/plyer/pull/495) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Enhance Android notifications with toast and big icons [\#493](https://github.com/kivy/plyer/pull/493) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Fix android notifications missing channel on Oreo and later [\#492](https://github.com/kivy/plyer/pull/492) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Add Android Native filechooser and external SD card path to StoragePath [\#491](https://github.com/kivy/plyer/pull/491) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Fix OSX builds on Travis [\#490](https://github.com/kivy/plyer/pull/490) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Add audio recording and playing for Windows [\#489](https://github.com/kivy/plyer/pull/489) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Add support for interfaces in Linux WiFi [\#488](https://github.com/kivy/plyer/pull/488) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - macOS tests - audio, battery, bluetooth, storagepath [\#482](https://github.com/kivy/plyer/pull/482) ([Nephyx](https://github.com/Nephyx)) - Change 'Speech' to 'STT' [\#484](https://github.com/kivy/plyer/pull/484) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - New extended CPU details implementation [\#483](https://github.com/kivy/plyer/pull/483) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Fix uniqueid for linux platform [\#481](https://github.com/kivy/plyer/pull/481) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Fix CI jobs reporting wrong coverage \(non-imported modules ignored\) [\#480](https://github.com/kivy/plyer/pull/480) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - thegrymek: Android speech recognition [\#471](https://github.com/kivy/plyer/pull/471) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) ## [1.3.2](https://github.com/kivy/plyer/tree/1.3.2) (2018-11-16) [Full Changelog](https://github.com/kivy/plyer/compare/1.3.1...1.3.2) **Implemented enhancements:** - \[Feature Request\] Termux support [\#360](https://github.com/kivy/plyer/issues/360) - storage path support [\#152](https://github.com/kivy/plyer/issues/152) - unicode broken in notifications on windows 8. [\#17](https://github.com/kivy/plyer/issues/17) - Feature Request: Add adjustable tooltip text to Windows notification [\#14](https://github.com/kivy/plyer/issues/14) **Closed issues:** - plyer notifications raising NotImplementedError on android [\#467](https://github.com/kivy/plyer/issues/467) - TypeError when running examples in Python 3 [\#392](https://github.com/kivy/plyer/issues/392) - when i use buildozer and python3crystax to build apk it not work ? [\#380](https://github.com/kivy/plyer/issues/380) - Windows filechooser crash [\#375](https://github.com/kivy/plyer/issues/375) - Using the camera crashes the app [\#369](https://github.com/kivy/plyer/issues/369) - after calling an plyer audio function from an accelerometer function, App crashes. [\#361](https://github.com/kivy/plyer/issues/361) - uniqueid.id fails under android and windows7 32bit python2.7 [\#277](https://github.com/kivy/plyer/issues/277) - Strange string returned by filechooser on Windows [\#177](https://github.com/kivy/plyer/issues/177) - Email Support for OSX [\#32](https://github.com/kivy/plyer/issues/32) - GPS Support for iOS [\#22](https://github.com/kivy/plyer/issues/22) - Camera Support for iOS [\#21](https://github.com/kivy/plyer/issues/21) - Example app for Camera facade [\#16](https://github.com/kivy/plyer/issues/16) **Merged pull requests:** - Move TODO item to a separate issue [\#478](https://github.com/kivy/plyer/pull/478) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Add @deprecated decorator [\#476](https://github.com/kivy/plyer/pull/476) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Fixed macOS using non-standard path for get\_home\_dir\(\) [\#475](https://github.com/kivy/plyer/pull/475) ([Nephyx](https://github.com/Nephyx)) - Removed unnecessary grep dependency [\#474](https://github.com/kivy/plyer/pull/474) ([Nephyx](https://github.com/Nephyx)) - Add enable & disable WiFi for Linux and MacOS [\#473](https://github.com/kivy/plyer/pull/473) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Cleaning the plyer root directory and CI scripts [\#468](https://github.com/kivy/plyer/pull/468) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Add more distros via Docker images [\#466](https://github.com/kivy/plyer/pull/466) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Add Linux Screenshot with X11's X Window Dump [\#463](https://github.com/kivy/plyer/pull/463) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Revert uppercase on autoclass values [\#462](https://github.com/kivy/plyer/pull/462) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Fixing Java class name [\#461](https://github.com/kivy/plyer/pull/461) ([clevermindgames](https://github.com/clevermindgames)) - Increase timeout for notification test [\#460](https://github.com/kivy/plyer/pull/460) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Add Windows Screenshot with ctypes+pywin32 [\#459](https://github.com/kivy/plyer/pull/459) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Add screenshot test for OSX [\#458](https://github.com/kivy/plyer/pull/458) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - OSX Screenshot \(Rebased PR \#324\) [\#457](https://github.com/kivy/plyer/pull/457) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Rebased PR \#239 + fixes [\#455](https://github.com/kivy/plyer/pull/455) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Bump to 1.3.2.dev0 [\#446](https://github.com/kivy/plyer/pull/446) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Bump to 1.3.1 [\#445](https://github.com/kivy/plyer/pull/445) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Add extra options for installation via setuptools [\#438](https://github.com/kivy/plyer/pull/438) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) ## [1.3.1](https://github.com/kivy/plyer/tree/1.3.1) (2018-10-14) [Full Changelog](https://github.com/kivy/plyer/compare/1.3.0...1.3.1) **Implemented enhancements:** - Audio recording for macOS [\#428](https://github.com/kivy/plyer/pull/428) ([Nephyx](https://github.com/Nephyx)) **Closed issues:** - plyer.wifi.is\_enabled\(\) always return false running in python3 [\#436](https://github.com/kivy/plyer/issues/436) - kivy-ios fails to build plyer [\#417](https://github.com/kivy/plyer/issues/417) - Python 3 TabError [\#398](https://github.com/kivy/plyer/issues/398) - is there a way of using the front camera instead of back [\#391](https://github.com/kivy/plyer/issues/391) - storagepath.py \_get\_application\_dir: NotImplementedError [\#389](https://github.com/kivy/plyer/issues/389) - plyer app crashes on android [\#387](https://github.com/kivy/plyer/issues/387) - Can't pip install plyer through git [\#385](https://github.com/kivy/plyer/issues/385) - speech to text [\#382](https://github.com/kivy/plyer/issues/382) - text2speech doesn't work on platform Linux [\#372](https://github.com/kivy/plyer/issues/372) - Changing file\_path in audio.py example is not working [\#356](https://github.com/kivy/plyer/issues/356) - How to change file\_path of audio in plyer? [\#355](https://github.com/kivy/plyer/issues/355) - Accelerometer in plyer [\#354](https://github.com/kivy/plyer/issues/354) - iOS Gyroscope crashes [\#352](https://github.com/kivy/plyer/issues/352) - Need keystore for storing user credentials [\#350](https://github.com/kivy/plyer/issues/350) - plyer examples app always crashes in android phones, It says "Unfortunately "Name of app" has stopped working." [\#349](https://github.com/kivy/plyer/issues/349) - Notification not working on Windows [\#333](https://github.com/kivy/plyer/issues/333) - Accelerometer not working on linux [\#327](https://github.com/kivy/plyer/issues/327) - 1.3.0 broke notifications on Windows [\#318](https://github.com/kivy/plyer/issues/318) - No encoding and °C causes SyntaxError [\#312](https://github.com/kivy/plyer/issues/312) - GPS double output on output return value [\#302](https://github.com/kivy/plyer/issues/302) - Cannot import wifi module in windows [\#272](https://github.com/kivy/plyer/issues/272) **Merged pull requests:** - Add note about Windows icon format [\#444](https://github.com/kivy/plyer/pull/444) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Mark new audio recording feature in README.rst [\#443](https://github.com/kivy/plyer/pull/443) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Fix not decoding bytes in Linux orientation.py [\#442](https://github.com/kivy/plyer/pull/442) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Fix b' embedded in the path string for Windows' choose\_dir\(\) [\#441](https://github.com/kivy/plyer/pull/441) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Add missing parameter for Windows' WlanCloseHandle\(\) [\#440](https://github.com/kivy/plyer/pull/440) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Revert facades/wifi.py change from PR \#301 [\#439](https://github.com/kivy/plyer/pull/439) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Update wifi.py [\#437](https://github.com/kivy/plyer/pull/437) ([Vibhu-Agarwal](https://github.com/Vibhu-Agarwal)) - Remove notification webhook from travis [\#434](https://github.com/kivy/plyer/pull/434) ([dessant](https://github.com/dessant)) - Fix Travis build for pull requests [\#432](https://github.com/kivy/plyer/pull/432) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Changed OSX storage path from ctypes to pyobjus [\#431](https://github.com/kivy/plyer/pull/431) ([Nephyx](https://github.com/Nephyx)) - Fix Pylint errors W0150, W0511, W0601, W0603, W0610 [\#430](https://github.com/kivy/plyer/pull/430) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Fix Pylint errors W0611, W0612, W0622, W0702, W0703 [\#427](https://github.com/kivy/plyer/pull/427) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Disable all Pylint errors to fix red jobs [\#426](https://github.com/kivy/plyer/pull/426) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Downgrade Travis due to missing docker service on Ubuntu Bionic [\#425](https://github.com/kivy/plyer/pull/425) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Switch from Ubuntu Trusty to Ubuntu Bionic LTS [\#424](https://github.com/kivy/plyer/pull/424) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Fix style issues in utils, compat and setup.py [\#423](https://github.com/kivy/plyer/pull/423) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Clickable notifications, fixes \#154 [\#422](https://github.com/kivy/plyer/pull/422) ([AndreMiras](https://github.com/AndreMiras)) - Fix style in test\_facade.py [\#418](https://github.com/kivy/plyer/pull/418) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Fix pep8 issues in files [\#416](https://github.com/kivy/plyer/pull/416) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Add script for CI deployment to PyPI [\#413](https://github.com/kivy/plyer/pull/413) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Fix travis to use real branch instead of detached HEAD [\#412](https://github.com/kivy/plyer/pull/412) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Add linux battery from sysclass [\#411](https://github.com/kivy/plyer/pull/411) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Switch to unicode in macosx battery.py [\#410](https://github.com/kivy/plyer/pull/410) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Fix test\_facade failing for Py3 by switching to Mocks [\#409](https://github.com/kivy/plyer/pull/409) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Add Dockerfiles for testing, fix tests and style [\#408](https://github.com/kivy/plyer/pull/408) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Implemented storagepath in linux [\#407](https://github.com/kivy/plyer/pull/407) ([Sires0](https://github.com/Sires0)) - Improve messages for missing dependencies [\#406](https://github.com/kivy/plyer/pull/406) ([dolang](https://github.com/dolang)) - Updates README.rst, removes dup in supported API [\#403](https://github.com/kivy/plyer/pull/403) ([AndreMiras](https://github.com/AndreMiras)) - why should decode 'l' again and again [\#396](https://github.com/kivy/plyer/pull/396) ([xhimanshuz](https://github.com/xhimanshuz)) - Correcting issue https://github.com/kivy/plyer/issues/392 for linux p… [\#395](https://github.com/kivy/plyer/pull/395) ([ghost](https://github.com/ghost)) - Number Of Processors for Linux Platform [\#394](https://github.com/kivy/plyer/pull/394) ([salil-gtm](https://github.com/salil-gtm)) - enhancement: bluetooth status [\#388](https://github.com/kivy/plyer/pull/388) ([kapilnayar](https://github.com/kapilnayar)) - Show that bluetooth is not supported [\#379](https://github.com/kivy/plyer/pull/379) ([zerox1212](https://github.com/zerox1212)) - Arrange APIs table in alphabetical order [\#377](https://github.com/kivy/plyer/pull/377) ([sumitmadhwani](https://github.com/sumitmadhwani)) - Add list of supported platforms to facade [\#376](https://github.com/kivy/plyer/pull/376) ([sumitmadhwani](https://github.com/sumitmadhwani)) - Add iOS api for storage path [\#374](https://github.com/kivy/plyer/pull/374) ([sumitmadhwani](https://github.com/sumitmadhwani)) - update readme [\#373](https://github.com/kivy/plyer/pull/373) ([sandeepsajan0](https://github.com/sandeepsajan0)) - Update buildozer.spec [\#370](https://github.com/kivy/plyer/pull/370) ([sandeepsajan0](https://github.com/sandeepsajan0)) - Warn instead stder.write for linux notif errors [\#368](https://github.com/kivy/plyer/pull/368) ([sametmax](https://github.com/sametmax)) - Add coveralls.io report [\#367](https://github.com/kivy/plyer/pull/367) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Fix pep8 in plyer [\#366](https://github.com/kivy/plyer/pull/366) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - iOS Barometer API [\#363](https://github.com/kivy/plyer/pull/363) ([sumitmadhwani](https://github.com/sumitmadhwani)) - Fix iOS Gyroscope crash issue [\#353](https://github.com/kivy/plyer/pull/353) ([sumitmadhwani](https://github.com/sumitmadhwani)) - Keystore implementation. [\#351](https://github.com/kivy/plyer/pull/351) ([brentpicasso](https://github.com/brentpicasso)) - iOS Spatial orientation [\#348](https://github.com/kivy/plyer/pull/348) ([sumitmadhwani](https://github.com/sumitmadhwani)) - iOS Gravity sensor [\#347](https://github.com/kivy/plyer/pull/347) ([sumitmadhwani](https://github.com/sumitmadhwani)) - Linux Brightness API [\#346](https://github.com/kivy/plyer/pull/346) ([sumitmadhwani](https://github.com/sumitmadhwani)) - Brightness API [\#344](https://github.com/kivy/plyer/pull/344) ([sumitmadhwani](https://github.com/sumitmadhwani)) - Fix bug in WindowsBalloonTip [\#343](https://github.com/kivy/plyer/pull/343) ([Chronial](https://github.com/Chronial)) - Storage path API [\#342](https://github.com/kivy/plyer/pull/342) ([sumitmadhwani](https://github.com/sumitmadhwani)) - Compass uncalibrated [\#341](https://github.com/kivy/plyer/pull/341) ([sumitmadhwani](https://github.com/sumitmadhwani)) - Gyroscope uncalibrated sensor [\#337](https://github.com/kivy/plyer/pull/337) ([sumitmadhwani](https://github.com/sumitmadhwani)) - Spatial Orientation for android [\#336](https://github.com/kivy/plyer/pull/336) ([sumitmadhwani](https://github.com/sumitmadhwani)) - Update win\_api\_defs.py [\#335](https://github.com/kivy/plyer/pull/335) ([sumitmadhwani](https://github.com/sumitmadhwani)) - Add some tests + Appveyor [\#329](https://github.com/kivy/plyer/pull/329) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Fix notification ticker error + pep8 [\#328](https://github.com/kivy/plyer/pull/328) ([KeyWeeUsr](https://github.com/KeyWeeUsr)) - Pep8 fix in temperature.py [\#322](https://github.com/kivy/plyer/pull/322) ([sumitmadhwani](https://github.com/sumitmadhwani)) - Example for Unique ID facade [\#321](https://github.com/kivy/plyer/pull/321) ([sumitmadhwani](https://github.com/sumitmadhwani)) - Android Humidity [\#301](https://github.com/kivy/plyer/pull/301) ([bhaveshAn](https://github.com/bhaveshAn)) - fix handling of notifications' timeout on Linux [\#297](https://github.com/kivy/plyer/pull/297) ([benoit-pierre](https://github.com/benoit-pierre)) - Facade Wifi [\#290](https://github.com/kivy/plyer/pull/290) ([bhaveshAn](https://github.com/bhaveshAn)) - orientation feature for linux [\#273](https://github.com/kivy/plyer/pull/273) ([susmit](https://github.com/susmit)) - Fix wifi module. [\#244](https://github.com/kivy/plyer/pull/244) ([account-login](https://github.com/account-login)) ## [1.3.0](https://github.com/kivy/plyer/tree/1.3.0) (2017-03-23) [Full Changelog](https://github.com/kivy/plyer/compare/v1.2.4...1.3.0) **Implemented enhancements:** - Feature Request: Alarms [\#8](https://github.com/kivy/plyer/issues/8) **Closed issues:** - Notification.notify crashes android app [\#296](https://github.com/kivy/plyer/issues/296) - GPS android crash on launch [\#288](https://github.com/kivy/plyer/issues/288) - Send SMS feature not working [\#261](https://github.com/kivy/plyer/issues/261) - gps.configure\(\) results in exception [\#257](https://github.com/kivy/plyer/issues/257) - v1.2.4 archive not available via github [\#234](https://github.com/kivy/plyer/issues/234) - SyntaxError in wifi.py for Linux [\#230](https://github.com/kivy/plyer/issues/230) - New PyPi release please, to fix static jfieldID not valid for class java.lang.Class\ [\#229](https://github.com/kivy/plyer/issues/229) - Drag-and-drop: originate in Kivy, drop in some external app [\#228](https://github.com/kivy/plyer/issues/228) - GPS Issue after in iOS after last changes in plyer [\#224](https://github.com/kivy/plyer/issues/224) - battery.status isCharging always shows false [\#221](https://github.com/kivy/plyer/issues/221) - GPS example only updating location once [\#217](https://github.com/kivy/plyer/issues/217) - uniqueid.id raises exception on Windows [\#212](https://github.com/kivy/plyer/issues/212) - Redundant libs folder [\#209](https://github.com/kivy/plyer/issues/209) - accelerometer on Android with Kivy Launcher 1.9.0 not working [\#206](https://github.com/kivy/plyer/issues/206) - Camera on android doesn't return to app [\#200](https://github.com/kivy/plyer/issues/200) - android compass suggestion [\#195](https://github.com/kivy/plyer/issues/195) - more example code to the docs [\#166](https://github.com/kivy/plyer/issues/166) - Mail API on linux raises error NameError: name 'Email' is not defined [\#131](https://github.com/kivy/plyer/issues/131) - native gui widgets [\#124](https://github.com/kivy/plyer/issues/124) - android: using gps app cannot resume from pause [\#112](https://github.com/kivy/plyer/issues/112) - Please upgrade pypi ! [\#94](https://github.com/kivy/plyer/issues/94) - UniqueID using OpenID [\#83](https://github.com/kivy/plyer/issues/83) - Display the notification in the right places [\#78](https://github.com/kivy/plyer/issues/78) - Python3 All The Plyer! [\#12](https://github.com/kivy/plyer/issues/12) **Merged pull requests:** - Modify readme [\#308](https://github.com/kivy/plyer/pull/308) ([malverick](https://github.com/malverick)) - Add version tags in light and temperature facade [\#307](https://github.com/kivy/plyer/pull/307) ([malverick](https://github.com/malverick)) - Android ambient temperature sensor [\#293](https://github.com/kivy/plyer/pull/293) ([malverick](https://github.com/malverick)) - Android light sensor [\#292](https://github.com/kivy/plyer/pull/292) ([malverick](https://github.com/malverick)) - Plyer android proximity sensor [\#287](https://github.com/kivy/plyer/pull/287) ([malverick](https://github.com/malverick)) - Plyer android pressure sensor [\#286](https://github.com/kivy/plyer/pull/286) ([malverick](https://github.com/malverick)) - Update readme and plyer/\_\_init\_\_.py [\#285](https://github.com/kivy/plyer/pull/285) ([malverick](https://github.com/malverick)) - Plyer android gravity sensor [\#283](https://github.com/kivy/plyer/pull/283) ([malverick](https://github.com/malverick)) - Add on\_pause function [\#274](https://github.com/kivy/plyer/pull/274) ([malverick](https://github.com/malverick)) - uniqueid\_facade [\#270](https://github.com/kivy/plyer/pull/270) ([bhaveshAn](https://github.com/bhaveshAn)) - add bin and .buildozer directory to .gitignore [\#259](https://github.com/kivy/plyer/pull/259) ([malverick](https://github.com/malverick)) - pep8 fixes [\#250](https://github.com/kivy/plyer/pull/250) ([malverick](https://github.com/malverick)) - update code [\#249](https://github.com/kivy/plyer/pull/249) ([kiok46](https://github.com/kiok46)) - Adding small examples in facade files [\#237](https://github.com/kivy/plyer/pull/237) ([kiok46](https://github.com/kiok46)) - Fix TypeError if `LANG` is not set in on osx [\#232](https://github.com/kivy/plyer/pull/232) ([ForeverWintr](https://github.com/ForeverWintr)) - fix \#230 [\#231](https://github.com/kivy/plyer/pull/231) ([kiok46](https://github.com/kiok46)) - fix gps issue for ios [\#225](https://github.com/kivy/plyer/pull/225) ([kiok46](https://github.com/kiok46)) - Fixed issue \#221 [\#223](https://github.com/kivy/plyer/pull/223) ([Warlord77](https://github.com/Warlord77)) - Add flash example [\#219](https://github.com/kivy/plyer/pull/219) ([kiok46](https://github.com/kiok46)) - Make gps request parameters configurable [\#218](https://github.com/kivy/plyer/pull/218) ([kiok46](https://github.com/kiok46)) - Wifi Facade. OSX, Windows, Linux [\#213](https://github.com/kivy/plyer/pull/213) ([kiok46](https://github.com/kiok46)) - add sms for ios [\#203](https://github.com/kivy/plyer/pull/203) ([kiok46](https://github.com/kiok46)) - check android for namespace, otherwise use renpy [\#199](https://github.com/kivy/plyer/pull/199) ([kived](https://github.com/kived)) - fix p4a revamp [\#198](https://github.com/kivy/plyer/pull/198) ([kived](https://github.com/kived)) - Rewrite notification on Mac using PyOBJus [\#192](https://github.com/kivy/plyer/pull/192) ([andong777](https://github.com/andong777)) - Call for ios [\#191](https://github.com/kivy/plyer/pull/191) ([kiok46](https://github.com/kiok46)) - Note on requirements for iOS [\#187](https://github.com/kivy/plyer/pull/187) ([doratoa](https://github.com/doratoa)) - Adding battery example, notification ticker and gps example update [\#183](https://github.com/kivy/plyer/pull/183) ([kiok46](https://github.com/kiok46)) - Call and dial for android [\#181](https://github.com/kivy/plyer/pull/181) ([kiok46](https://github.com/kiok46)) - Dial or Call for android [\#180](https://github.com/kivy/plyer/pull/180) ([kiok46](https://github.com/kiok46)) - Added accuracy argument to on\_location call. [\#174](https://github.com/kivy/plyer/pull/174) ([lipi](https://github.com/lipi)) - Introduce camera access for ios and a example. [\#167](https://github.com/kivy/plyer/pull/167) ([akshayaurora](https://github.com/akshayaurora)) - macosx: fix incorrect method name in filechooser [\#165](https://github.com/kivy/plyer/pull/165) ([kived](https://github.com/kived)) - linux email import fix [\#151](https://github.com/kivy/plyer/pull/151) ([thegrymek](https://github.com/thegrymek)) - Merge android columns [\#148](https://github.com/kivy/plyer/pull/148) ([dessant](https://github.com/dessant)) - Camera example [\#41](https://github.com/kivy/plyer/pull/41) ([trivedigaurav](https://github.com/trivedigaurav)) ## [v1.2.4](https://github.com/kivy/plyer/tree/v1.2.4) (2015-06-01) [Full Changelog](https://github.com/kivy/plyer/compare/1.2.3...v1.2.4) **Implemented enhancements:** - Update platform check code [\#109](https://github.com/kivy/plyer/issues/109) **Closed issues:** - webhook test [\#142](https://github.com/kivy/plyer/issues/142) - Sync style check updates from the Kivy repo [\#141](https://github.com/kivy/plyer/issues/141) - GPS on android doesn't work \(a strange error\) [\#136](https://github.com/kivy/plyer/issues/136) - Create toast notification facade for Android and iOS [\#126](https://github.com/kivy/plyer/issues/126) - uniqueid.id empty on linux. [\#114](https://github.com/kivy/plyer/issues/114) - Gyroscope Support for iOS [\#111](https://github.com/kivy/plyer/issues/111) - AndroidUniqueID doesn't use Android ID [\#107](https://github.com/kivy/plyer/issues/107) - OverflowError: Python int too large to convert to C long \[android lollipop\] [\#103](https://github.com/kivy/plyer/issues/103) - Feature request: ability to open browser to a particular page [\#98](https://github.com/kivy/plyer/issues/98) - AndroidGPS list GPS Provider but use hardcoded "gps" [\#54](https://github.com/kivy/plyer/issues/54) - Email Support for Android \< 4.0 [\#42](https://github.com/kivy/plyer/issues/42) **Merged pull requests:** - style fixes [\#147](https://github.com/kivy/plyer/pull/147) ([dessant](https://github.com/dessant)) - add pydev files to gitignore [\#146](https://github.com/kivy/plyer/pull/146) ([dessant](https://github.com/dessant)) - Plyer style guide update [\#145](https://github.com/kivy/plyer/pull/145) ([thegrymek](https://github.com/thegrymek)) - Plyer audio for android with facade and example [\#144](https://github.com/kivy/plyer/pull/144) ([thegrymek](https://github.com/thegrymek)) - fix versionchanged tag [\#143](https://github.com/kivy/plyer/pull/143) ([dessant](https://github.com/dessant)) - update info about support email for android\<4.0 [\#140](https://github.com/kivy/plyer/pull/140) ([thegrymek](https://github.com/thegrymek)) - added plyer.facade to setuptools package [\#139](https://github.com/kivy/plyer/pull/139) ([thegrymek](https://github.com/thegrymek)) - splitted facades [\#138](https://github.com/kivy/plyer/pull/138) ([thegrymek](https://github.com/thegrymek)) - Inclement orientation [\#135](https://github.com/kivy/plyer/pull/135) ([thegrymek](https://github.com/thegrymek)) - remove unused variables [\#134](https://github.com/kivy/plyer/pull/134) ([thegrymek](https://github.com/thegrymek)) - fix \#107 - Use Android\_ID instead of IMEI [\#133](https://github.com/kivy/plyer/pull/133) ([aron-bordin](https://github.com/aron-bordin)) - vibrator for android v \< 4.0 [\#129](https://github.com/kivy/plyer/pull/129) ([thegrymek](https://github.com/thegrymek)) - PEP8 and typo fixes in MacOS X file chooser. [\#123](https://github.com/kivy/plyer/pull/123) ([robertjerovsek](https://github.com/robertjerovsek)) - pep8 - removed unused imports and variables [\#122](https://github.com/kivy/plyer/pull/122) ([thegrymek](https://github.com/thegrymek)) - Pep8 fix [\#121](https://github.com/kivy/plyer/pull/121) ([laltin](https://github.com/laltin)) - add video recoding to Camera facade and camera.py [\#120](https://github.com/kivy/plyer/pull/120) ([pspchucky](https://github.com/pspchucky)) - Use environ to change LANG to 'C' while calling shell processes [\#119](https://github.com/kivy/plyer/pull/119) ([trivedigaurav](https://github.com/trivedigaurav)) - add IrBlaster facade and Android implementation [\#118](https://github.com/kivy/plyer/pull/118) ([kived](https://github.com/kived)) - Android gps.py: fixed location provider cycling [\#117](https://github.com/kivy/plyer/pull/117) ([JimmyStavros](https://github.com/JimmyStavros)) - iOS GPS support [\#116](https://github.com/kivy/plyer/pull/116) ([laltin](https://github.com/laltin)) - use environ to change LANG to 'C' while calling lshw [\#115](https://github.com/kivy/plyer/pull/115) ([tshirtman](https://github.com/tshirtman)) - responds to issue 109 https://github.com/kivy/plyer/issues/109 [\#110](https://github.com/kivy/plyer/pull/110) ([AlbericC](https://github.com/AlbericC)) - Add file chooser facade and support for Linux and Windows [\#106](https://github.com/kivy/plyer/pull/106) ([Depaulicious](https://github.com/Depaulicious)) ## [1.2.3](https://github.com/kivy/plyer/tree/1.2.3) (2015-01-27) [Full Changelog](https://github.com/kivy/plyer/compare/1.2.2...1.2.3) ## [1.2.2](https://github.com/kivy/plyer/tree/1.2.2) (2015-01-27) [Full Changelog](https://github.com/kivy/plyer/compare/1.2.1...1.2.2) **Closed issues:** - NotImplementedError: No usable implementation found! whith usable implementations on the system. [\#108](https://github.com/kivy/plyer/issues/108) - Gyro example [\#101](https://github.com/kivy/plyer/issues/101) - Notification is not working in android [\#93](https://github.com/kivy/plyer/issues/93) - plyer.notification.notfy\(\) raises NotImplementedError under Python 3.3 in Linux but not Python 2.7 [\#58](https://github.com/kivy/plyer/issues/58) **Merged pull requests:** - Linux platform check made compatible with python 3.3+ \(Fixes \#58\) [\#102](https://github.com/kivy/plyer/pull/102) ([helenst](https://github.com/helenst)) ## [1.2.1](https://github.com/kivy/plyer/tree/1.2.1) (2014-08-19) [Full Changelog](https://github.com/kivy/plyer/compare/1.2.0...1.2.1) **Implemented enhancements:** - Feature Request: codec-independent sound player [\#2](https://github.com/kivy/plyer/issues/2) **Closed issues:** - Battery status connected is actually isCharging [\#84](https://github.com/kivy/plyer/issues/84) - Email Support for Windows [\#36](https://github.com/kivy/plyer/issues/36) - Accelerometer Support for OSX [\#29](https://github.com/kivy/plyer/issues/29) - Email Support for Linux [\#28](https://github.com/kivy/plyer/issues/28) - Email Support for iOS [\#25](https://github.com/kivy/plyer/issues/25) - TextToSpeech Support for iOS [\#24](https://github.com/kivy/plyer/issues/24) **Merged pull requests:** - fix print statement [\#92](https://github.com/kivy/plyer/pull/92) ([dessant](https://github.com/dessant)) - iOS UUID facade [\#90](https://github.com/kivy/plyer/pull/90) ([trivedigaurav](https://github.com/trivedigaurav)) - Removing build\_ext from plyer [\#89](https://github.com/kivy/plyer/pull/89) ([trivedigaurav](https://github.com/trivedigaurav)) - iOS Email Facade [\#88](https://github.com/kivy/plyer/pull/88) ([trivedigaurav](https://github.com/trivedigaurav)) - iOS Battery [\#86](https://github.com/kivy/plyer/pull/86) ([trivedigaurav](https://github.com/trivedigaurav)) - Change connected to isCharging [\#85](https://github.com/kivy/plyer/pull/85) ([trivedigaurav](https://github.com/trivedigaurav)) - Return None until sensor data is available [\#82](https://github.com/kivy/plyer/pull/82) ([trivedigaurav](https://github.com/trivedigaurav)) - Update compass.py [\#80](https://github.com/kivy/plyer/pull/80) ([ChrisCole42](https://github.com/ChrisCole42)) - Use whereis\_exe to check for binaries [\#79](https://github.com/kivy/plyer/pull/79) ([trivedigaurav](https://github.com/trivedigaurav)) - Update compass.py [\#77](https://github.com/kivy/plyer/pull/77) ([ChrisCole42](https://github.com/ChrisCole42)) - Maintenance [\#75](https://github.com/kivy/plyer/pull/75) ([trivedigaurav](https://github.com/trivedigaurav)) - facade docstring revision [\#74](https://github.com/kivy/plyer/pull/74) ([dessant](https://github.com/dessant)) - Query Battery info/status [\#73](https://github.com/kivy/plyer/pull/73) ([trivedigaurav](https://github.com/trivedigaurav)) - Revert "Activity was imported twice" [\#71](https://github.com/kivy/plyer/pull/71) ([trivedigaurav](https://github.com/trivedigaurav)) - Fix tabbing [\#70](https://github.com/kivy/plyer/pull/70) ([trivedigaurav](https://github.com/trivedigaurav)) - Gyroscope facade proxy declarations [\#69](https://github.com/kivy/plyer/pull/69) ([trivedigaurav](https://github.com/trivedigaurav)) - Linux accelerometer facade [\#68](https://github.com/kivy/plyer/pull/68) ([trivedigaurav](https://github.com/trivedigaurav)) - Update README.rst [\#67](https://github.com/kivy/plyer/pull/67) ([trivedigaurav](https://github.com/trivedigaurav)) ## [1.2.0](https://github.com/kivy/plyer/tree/1.2.0) (2014-06-24) **Implemented enhancements:** - Feature Request: Add adjustable timeout option to Windows notification [\#13](https://github.com/kivy/plyer/issues/13) - Changes notify to use ctypes instead of win32gui so we could use unicode. [\#18](https://github.com/kivy/plyer/pull/18) ([matham](https://github.com/matham)) - User-specified icon support for Windows notifications [\#11](https://github.com/kivy/plyer/pull/11) ([brousch](https://github.com/brousch)) - Added Vibrator facade and android implementation [\#6](https://github.com/kivy/plyer/pull/6) ([inclement](https://github.com/inclement)) **Closed issues:** - GPS example crashes [\#40](https://github.com/kivy/plyer/issues/40) - TextToSpeech Example App [\#20](https://github.com/kivy/plyer/issues/20) - Accelerometer Example App [\#19](https://github.com/kivy/plyer/issues/19) **Merged pull requests:** - Plyer Unique ID facade [\#66](https://github.com/kivy/plyer/pull/66) ([trivedigaurav](https://github.com/trivedigaurav)) - Switched to pyjnius [\#63](https://github.com/kivy/plyer/pull/63) ([trivedigaurav](https://github.com/trivedigaurav)) - Update README [\#62](https://github.com/kivy/plyer/pull/62) ([trivedigaurav](https://github.com/trivedigaurav)) - Gyroscope Facades [\#60](https://github.com/kivy/plyer/pull/60) ([trivedigaurav](https://github.com/trivedigaurav)) - Ios compass [\#59](https://github.com/kivy/plyer/pull/59) ([trivedigaurav](https://github.com/trivedigaurav)) - Plyer compass facade [\#57](https://github.com/kivy/plyer/pull/57) ([trivedigaurav](https://github.com/trivedigaurav)) - Update README [\#56](https://github.com/kivy/plyer/pull/56) ([trivedigaurav](https://github.com/trivedigaurav)) - Using sudden motion sensor as accelerometer on OSX [\#55](https://github.com/kivy/plyer/pull/55) ([trivedigaurav](https://github.com/trivedigaurav)) - Added sms facade, example and android implementation [\#52](https://github.com/kivy/plyer/pull/52) ([mihaineacsu](https://github.com/mihaineacsu)) - add Mac OS X email support [\#49](https://github.com/kivy/plyer/pull/49) ([Depaulicious](https://github.com/Depaulicious)) - add Windows email support [\#48](https://github.com/kivy/plyer/pull/48) ([Depaulicious](https://github.com/Depaulicious)) - added Linux email support [\#47](https://github.com/kivy/plyer/pull/47) ([Depaulicious](https://github.com/Depaulicious)) - Add compat module, remove decoding of strings in notification [\#46](https://github.com/kivy/plyer/pull/46) ([matham](https://github.com/matham)) - Created an accelerometer example. Uses garden graph to plot the values [\#39](https://github.com/kivy/plyer/pull/39) ([trivedigaurav](https://github.com/trivedigaurav)) - Shows an error popup if there is no TTS [\#38](https://github.com/kivy/plyer/pull/38) ([trivedigaurav](https://github.com/trivedigaurav)) - Text to Speech Example [\#37](https://github.com/kivy/plyer/pull/37) ([trivedigaurav](https://github.com/trivedigaurav)) - readme typo corrected [\#15](https://github.com/kivy/plyer/pull/15) ([ghost](https://github.com/ghost)) - Introduce dbus notification [\#10](https://github.com/kivy/plyer/pull/10) ([akshayaurora](https://github.com/akshayaurora)) - Added an email facade and basic android implementation [\#5](https://github.com/kivy/plyer/pull/5) ([inclement](https://github.com/inclement)) - Tts [\#1](https://github.com/kivy/plyer/pull/1) ([brousch](https://github.com/brousch)) \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*plyer-2.1.0/LICENSE000066400000000000000000000020711433372044000136520ustar00rootroot00000000000000Copyright (c) 2010-2017 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. plyer-2.1.0/MANIFEST.in000066400000000000000000000000631433372044000144020ustar00rootroot00000000000000include *README.rst *LICENSE include *CHANGELOG.md plyer-2.1.0/Makefile000066400000000000000000000015441433372044000143110ustar00rootroot00000000000000PYTHON = python CHECKSCRIPT = plyer/tools/pep8checker/pep8kivy.py PLYER_DIR = plyer/ build: $(PYTHON) setup.py build force: $(PYTHON) setup.py build -f debug: $(PYTHON) setup.py build -f -g pdf: $(MAKE) -C docs latex && make -C docs/build/latex all-pdf html: env USE_EMBEDSIGNATURE=1 $(MAKE) force $(MAKE) -C docs html style: $(PYTHON) $(CHECKSCRIPT) . stylereport: $(PYTHON) $(CHECKSCRIPT) -html $(PLYER_DIR) hook: # Install pre-commit git hook to check your changes for styleguide # consistency. cp plyer/tools/pep8checker/pre-commit.githook .git/hooks/pre-commit chmod +x .git/hooks/pre-commit install: python setup.py install clean: -rm -rf docs/build -rm -rf build -find plyer -iname '*.so' -exec rm {} \; -find plyer -iname '*.pyc' -exec rm {} \; -find plyer -iname '*.pyo' -exec rm {} \; distclean: clean -git clean -dxf -e debian plyer-2.1.0/README.md000066400000000000000000000162571433372044000141370ustar00rootroot00000000000000# Plyer Plyer is a platform-independent api to use features commonly found on various platforms, notably mobile ones, in Python. [![coverage](https://coveralls.io/repos/kivy/plyer/badge.svg?branch=master)](https://coveralls.io/r/kivy/plyer?branch=master) [![Backers on Open Collective](https://opencollective.com/kivy/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/kivy/sponsors/badge.svg)](#sponsors) ![Continuous Integration with Ubuntu](https://github.com/kivy/plyer/workflows/Continuous%20Integration%20with%20Ubuntu/badge.svg) ![Continuous Integration with OSX](https://github.com/kivy/plyer/workflows/Continuous%20Integration%20with%20OSX/badge.svg) ![Continuous Integration with Windows](https://github.com/kivy/plyer/workflows/Continuous%20Integration%20with%20Windows/badge.svg) ![Deploy to PyPI](https://github.com/kivy/plyer/workflows/Deploy%20to%20PyPI/badge.svg) ## How plyer works? 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 Android(python-for-android), pyjnius is used - On iOS(kivy-ios), pyobjus is used - On windows/mac/linux, commonly found libraries and programs will be used ## Supported APIs | Platform | Android | iOS | Windows | OS X | Linux | | ------------------------------ | ------- | --- | ------- | ---- | ----- | | Accelerometer | ✔ | ✔ | | ✔ | ✔ | | Audio recording | ✔ | | ✔ | ✔ | | | Barometer | ✔ | ✔ | | | | | Battery | ✔ | ✔ | ✔ | ✔ | ✔ | | Bluetooth | ✔ | | | ✔ | | | Brightness | ✔ | ✔ | | | ✔ | | Call | ✔ | ✔ | | | | | Camera (taking picture) | ✔ | ✔ | | | | | Compass | ✔ | ✔ | | | | | CPU count | | | ✔ | ✔ | ✔ | | Devicename | ✔ | | ✔ | ✔ | ✔ | | Email (open mail client) | ✔ | ✔ | ✔ | ✔ | ✔ | | Flash | ✔ | ✔ | | | | | GPS | ✔ | ✔ | | | | | Gravity | ✔ | ✔ | | | | | Gyroscope | ✔ | ✔ | | | | | Humidity | ✔ | | | | | | IR Blaster | ✔ | | | | | | Keystore | ✔ | ✔ | ✔ | ✔ | ✔ | | Light | ✔ | | | | | | Native file chooser | ✔ | ✔ | ✔ | ✔ | ✔ | | Notifications | ✔ | | ✔ | ✔ | ✔ | | Orientation | ✔ | | | | ✔ | | Proximity | ✔ | | | | | | Screenshot | | | ✔ | ✔ | ✔ | | SMS (send messages) | ✔ | ✔ | | | | | Spatial Orientation | ✔ | ✔ | | | | | Speech to text | ✔ | | | | | | Storage Path | ✔ | ✔ | ✔ | ✔ | ✔ | | Temperature | ✔ | | | | | | Text to speech | ✔ | ✔ | ✔ | ✔ | ✔ | | Unique ID | ✔ | ✔ | ✔ | ✔ | ✔ | | Vibrator | ✔ | ✔ | | | | | Wifi | | | ✔ | ✔ | ✔ | ## Installation To use on desktop: `pip install plyer` To use in python-for-android/kivy-ios: add `plyer` to your requirements if needed. ## Support If you need assistance, you can ask for help on our mailing list: * User Group : https://groups.google.com/group/kivy-users * Email : kivy-users@googlegroups.com Discord channel: * Server : https://chat.kivy.org * Channel : #dev ## Contributing We love pull requests and discussing novel ideas. Check out our [contribution guide](http://kivy.org/docs/contribute.html) and feel free to improve Plyer. The following mailing list and IRC channel are used exclusively for discussions about developing the Kivy framework and its sister projects: * Dev Group : https://groups.google.com/group/kivy-dev * Email : kivy-dev@googlegroups.com IRC channel: * Server : irc.freenode.net * Port : 6667, 6697 (SSL only) * Channel : #kivy-dev ## License Plyer is released under the terms of the MIT License. Please refer to the LICENSE file. ## Contributors This project exists thanks to all the people who contribute. [[Contribute](http://kivy.org/docs/contribute.html)]. ## Backers Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/kivy#backer)] ## Sponsors Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/kivy#sponsor)] plyer-2.1.0/ci/000077500000000000000000000000001433372044000132405ustar00rootroot00000000000000plyer-2.1.0/ci/ci_osx.sh000066400000000000000000000006321433372044000150610ustar00rootroot00000000000000#!/bin/sh dependencies() { python -m pip install --upgrade pip pip install --upgrade -r devrequirements.txt pip install https://github.com/kivy/pyobjus/zipball/master } tests() { pip install --editable . coverage run \ --source ./plyer \ -m unittest discover \ --start-directory ./plyer/tests \ --top-level-directory . \ --failfast coverage report -m } plyer-2.1.0/ci/ci_ubuntu.sh000066400000000000000000000022771433372044000156010ustar00rootroot00000000000000#!/bin/sh set -ex dependencies() { # install default packages sudo apt-get update && \ sudo apt-get -y --force-yes install \ build-essential \ openjdk-8-jdk \ lshw \ wget \ git \ && apt-get -y autoremove \ && apt-get -y clean # generate user folder locations (Pictures, Downloads, ...) xdg-user-dirs-update # install PIP python -V python -m pip install --upgrade pip # install dev packages python -m pip install \ --upgrade \ --requirement devrequirements.txt python -m pip install pyjnius python -m pip install . } deployment_dependencies() { python -m pip install --upgrade pip pip install setuptools wheel twine } style_dependencies() { python -m pip install --upgrade pip pip install flake8 } style() { python -m flake8 . --show-source } tests() { # tests and coverage for plyer package python -m coverage run \ --source plyer \ -m unittest discover \ --start-directory plyer/tests \ --top-level-directory . \ --failfast coverage report -m } upload_coverage() { python -m coveralls } build() { python setup.py sdist python setup.py bdist_wheel --universal } deploy() { # deploy to PyPI python -m twine upload dist/* } plyer-2.1.0/ci/ci_windows.ps1000066400000000000000000000007701433372044000160360ustar00rootroot00000000000000function Dependencies { python -m pip install --requirement devrequirements.txt python -m pip install . echo Plyer version is python -c "import plyer;print(plyer.__version__)" } function Tests { $current_directory = (pwd).Path python -m coverage run ` --source "$current_directory\plyer" ` -m unittest discover ` --start-directory "$current_directory\plyer\tests" ` --top-level-directory "$current_directory" ` --failfast python -m coverage report -m } plyer-2.1.0/ci/docker/000077500000000000000000000000001433372044000145075ustar00rootroot00000000000000plyer-2.1.0/ci/docker/Dockerfile.archlinux.py3000066400000000000000000000016121433372044000212070ustar00rootroot00000000000000FROM base/archlinux ENV APP_DIR=/app RUN mkdir $APP_DIR WORKDIR $APP_DIR # install default packages RUN pacman -Fy && \ pacman -Sy && \ yes |pacman -Sy \ gcc \ extra/python \ jdk-openjdk \ lshw \ wget \ apparmor \ xdg-user-dirs \ && yes |pacman -Rns $(pacman -Qtdq) ||true \ && yes |pacman -Sc # generate user folder locations (Pictures, Downloads, ...) RUN xdg-user-dirs-update # install PIP RUN wget https://bootstrap.pypa.io/get-pip.py -O get-pip3.py RUN python -V && \ python get-pip3.py && \ rm get-pip3.py && \ python -m pip install --upgrade pip # install dev packages COPY devrequirements.txt . RUN python -m pip install \ --upgrade \ --requirement devrequirements.txt RUN python -m pip install pyjnius COPY . $APP_DIR COPY ./ci/entrypoint.sh $APP_DIR ENV PYTHON=/usr/bin/python ENTRYPOINT ["/app/entrypoint.sh", "env"] plyer-2.1.0/ci/docker/Dockerfile.bionic.py3000066400000000000000000000016421433372044000204600ustar00rootroot00000000000000FROM ubuntu:bionic-20180821 ENV APP_DIR=/app RUN mkdir $APP_DIR WORKDIR $APP_DIR # install default packages RUN apt-get update && \ apt-get -y --force-yes install \ build-essential \ python3-setuptools \ python3.6-dev \ openjdk-8-jdk \ lshw \ wget \ git \ && apt-get -y autoremove \ && apt-get -y clean # generate user folder locations (Pictures, Downloads, ...) RUN xdg-user-dirs-update # install PIP RUN wget https://bootstrap.pypa.io/get-pip.py -O get-pip3.py RUN python3.6 -V && \ python3.6 get-pip3.py && \ rm get-pip3.py && \ python3.6 -m pip install --upgrade pip # install dev packages COPY devrequirements.txt . RUN python3.6 -m pip install \ --upgrade \ --requirement devrequirements.txt RUN python3.6 -m pip install pyjnius COPY . $APP_DIR COPY ./ci/entrypoint.sh $APP_DIR RUN python3.6 -m pip install . ENTRYPOINT ["/app/entrypoint.sh", "py3"] plyer-2.1.0/ci/docker/Dockerfile.bionic.style000066400000000000000000000001011433372044000210720ustar00rootroot00000000000000FROM plyer:py3 ENTRYPOINT ["/app/entrypoint.sh", "py3", "style"] plyer-2.1.0/ci/docker/Dockerfile.fedora28.py3000066400000000000000000000015251433372044000206270ustar00rootroot00000000000000FROM fedora:28 ENV APP_DIR=/app RUN mkdir $APP_DIR WORKDIR $APP_DIR # install default packages # redhat-rpm-config: https://stackoverflow.com/a/34641068/5994041 RUN yum -y install \ gcc \ python3-devel \ java-1.8.0-openjdk \ java-1.8.0-openjdk-devel \ lshw \ wget \ xdg-user-dirs \ redhat-rpm-config \ && yum -y autoremove \ && yum clean all # generate user folder locations (Pictures, Downloads, ...) RUN xdg-user-dirs-update # install PIP RUN python3.6 -V && \ python3.6 -m pip install --upgrade pip # install dev packages COPY devrequirements.txt . RUN python3.6 -m pip install \ --upgrade \ --requirement devrequirements.txt RUN python3.6 -m pip install pyjnius COPY . $APP_DIR COPY ./ci/entrypoint.sh $APP_DIR RUN python3.6 -m pip install . ENTRYPOINT ["/app/entrypoint.sh", "py3"] plyer-2.1.0/ci/docker_build.sh000077500000000000000000000007161433372044000162310ustar00rootroot00000000000000#!/bin/sh # build docker images from Travis matrix if [ "$TRAVIS_OS_NAME" = "linux" ] && [ "$DOCK" = "1" ] then docker build \ --tag plyer:py3 \ --file ci/docker/Dockerfile.$IMAGE.py3 \ "$(pwd)" # style image that inherits layers from Python 3 image if [ "$RUN" = "style" ] then docker build \ --tag plyer:style \ --file ci/docker/Dockerfile.$IMAGE.style \ "$(pwd)" fi fi plyer-2.1.0/ci/docker_run.sh000077500000000000000000000023001433372044000157250ustar00rootroot00000000000000#!/bin/sh if [ "$TRAVIS_OS_NAME" = "linux" ] && [ "$DOCK" = "1" ] then if [ "$RUN" = "unit" ] then # running coverage report (COVERALLS=1) # and even deploy to PyPI (PLYER_DEPLOY=1) if asked for if [ "$COVERALLS" = "1" ] then docker run \ --interactive \ --tty \ --env COVERALLS_REPO_TOKEN=$COVERALLS_REPO_TOKEN \ --env COVERALLS_SERVICE_NAME=travis-ci \ --env TRAVIS_JOB_ID=$TRAVIS_JOB_ID \ --env TRAVIS_PULL_REQUEST=$TRAVIS_PULL_REQUEST \ --env PLYER_DEPLOY=${PLYER_DEPLOY:-"0"} \ --env TWINE_REPOSITORY=$TWINE_REPOSITORY \ --env TWINE_REPOSITORY_URL=$TWINE_REPOSITORY_URL \ --env TWINE_USERNAME=$TWINE_USERNAME \ --env TWINE_PASSWORD=$TWINE_PASSWORD \ plyer:py3 # ordinary tests run else docker run \ --interactive \ --tty \ plyer:py3 fi elif [ "$RUN" = "style" ] then docker run \ --interactive \ --tty \ plyer:style fi fi plyer-2.1.0/ci/entrypoint.sh000077500000000000000000000024101433372044000160070ustar00rootroot00000000000000#!/bin/sh set -vex if [ "$1" = "py3" ] || [ "$1" = "pep8" ] then PYTHON=$(which python3.6) elif [ "$1" = "env" ] then PYTHON=$PYTHON else exit 1 fi $PYTHON -V # pep8 check if [ "$2" = "style" ] then $PYTHON -m pycodestyle "$(pwd)" \ --exclude=pep8.py,utils.py \ --ignore=E402,W503 touch "$(pwd)/__init__.py" $PYTHON -m pylint \ --jobs=0 \ --disable=C0103,C0111,C0123,C0200,C0325,C0411,C0412,C1801,E0203,E0401 \ --disable=E0602,E0611,E0711,E1003,E1101,E1102,R0201,R0205,R0801,R0903 \ --disable=R0912,R0914,R1702,R1705,R1710,R1711,R1714,W0101,W0109 \ --disable=W0201,W0212,W0221,W0223,W0401 \ --disable=W0613,W0614 \ "$(pwd)" exit 0 fi # tests and coverage for plyer package $PYTHON -m coverage run \ --source $APP_DIR/plyer \ -m unittest discover \ --start-directory $APP_DIR/plyer/tests \ --top-level-directory $APP_DIR \ --failfast coverage report -m # submit coverage report from tests to coveralls.io # requires: REPO_TOKEN, SERVICE_NAME, JOB_ID, PULL_REQUEST coveralls || true # deploy to PyPI if set in CI with PLYER_DEPLOY variable if [ "$PLYER_DEPLOY" = "1" ]; then $PYTHON setup.py sdist bdist_wheel $PYTHON -m twine upload dist/* fi plyer-2.1.0/ci/github_checkout_branch.sh000077500000000000000000000003031433372044000202570ustar00rootroot00000000000000#!/bin/sh # use real branch name instead of detached HEAD # unless the job is created for a GitHub Pull Request if [ "$TRAVIS_PULL_REQUEST_BRANCH" = "" ] then git checkout $TRAVIS_BRANCH fi plyer-2.1.0/ci/osx_fix_cellar.sh000077500000000000000000000006001433372044000165740ustar00rootroot00000000000000#!/bin/sh # uninstall old GNUpg, install new one and add Brew # 'Cellar' folder to the path (contains binaries) if [ "$TRAVIS_OS_NAME" = "osx" ] then brew uninstall gnupg brew install gnupg2 sudo ln -sv /usr/local/Cellar/gnupg /usr/local/Cellar/gpg || true sudo ln -sv /usr/local/Cellar/gnupg /usr/local/Cellar/gpg2 || true export PATH=$PATH:/usr/local/Cellar fi plyer-2.1.0/ci/osx_get_pipvirtualenv.sh000077500000000000000000000015661433372044000202470ustar00rootroot00000000000000#!/bin/sh # manual download of get-pip.py on OSX because TLS1.2+ is required # source: pyfound.blogspot.com/2017/01/time-to-upgrade-your-python-tls-v12.html # install pip, virtualenv and plyer deps to virtualenv if [ "$TRAVIS_OS_NAME" = "osx" ] then curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py if [ "$PY" = "3" ] then sudo python3 get-pip.py else sudo python get-pip.py fi if [ "$PY" = "3" ] then pip3 install --user virtualenv python3 -m virtualenv env else pip install --user virtualenv python -m virtualenv env fi # activate virtualenv source env/bin/activate # install requirements from PyPI pip install --upgrade --requirement devrequirements.txt # install PyOBJus from source (master branch) pip install https://github.com/kivy/pyobjus/zipball/master fi plyer-2.1.0/ci/osx_get_py3.sh000077500000000000000000000005121433372044000160400ustar00rootroot00000000000000#!/bin/sh # get Py3 because it's not present in any OSX image on Travis if [ "$TRAVIS_OS_NAME" = "osx" ] then pyftp=https://www.python.org/ftp/python/3.5.4/ py3pkg=python-3.5.4rc1-macosx10.6.pkg if [ "$PY" = "3" ] then curl -O -L $pyftp$py3pkg sudo installer -package $py3pkg -target / fi fi plyer-2.1.0/ci/osx_run_tests.sh000077500000000000000000000004741433372044000165230ustar00rootroot00000000000000#!/bin/sh if [ "$TRAVIS_OS_NAME" = "osx" ] then source env/bin/activate pip install --editable . coverage run \ --source ./plyer \ -m unittest discover \ --start-directory ./plyer/tests \ --top-level-directory . \ --failfast coverage report -m fi plyer-2.1.0/ci/win_install.bat000066400000000000000000000002501433372044000162500ustar00rootroot00000000000000@echo off %PYTHON% -m pip install --requirement devrequirements.txt %PYTHON% -m pip install . echo Plyer version is %PYTHON% -c "import plyer;print(plyer.__version__)" plyer-2.1.0/ci/win_set_python.bat000066400000000000000000000002111433372044000167730ustar00rootroot00000000000000@echo off set PYTHON=C:\Python36\python.exe :: cd to Plyer folder and set PYTHONPATH cd C:\projects\app set PYTHONPATH=%PYTHONPATH%;%cd% plyer-2.1.0/ci/win_style.bat000066400000000000000000000011541433372044000157460ustar00rootroot00000000000000@echo off if "%STYLE%"=="1" ( %PYTHON% -m pycodestyle "%cd%" ^ --exclude=pep8.py,utils.py ^ --ignore=E402,W503 ^ && echo off > "%cd%\__init__.py" && echo on ^ && %PYTHON% -m pylint ^ --disable=C0103,C0111,C0123,C0200,C0325,C0411,C0412,C1801,E0203 ^ --disable=E0401,E0602,E0611,E0711,E1003,E1101,E1102,R0201,R0205 ^ --disable=R0205,R0801,R0903,R0912,R0914,R1702,R1705,R1710,R1711 ^ --disable=R1714,W0101,W0109,W0150,W0201,W0212,W0221,W0223,W0401 ^ --disable=W0511,W0601,W0603,W0610,W0611,W0612,W0613,W0614,W0622 ^ --disable=W0702,W0703 "%cd%" ) plyer-2.1.0/ci/win_tests.bat000066400000000000000000000004311433372044000157450ustar00rootroot00000000000000@echo off if not "%STYLE%"=="1" ( %PYTHON% -m coverage run ^ --source %cd%\plyer ^ -m unittest discover ^ --start-directory %cd%\plyer\tests ^ --top-level-directory %cd% ^ --failfast ^ && %PYTHON% -m coverage report -m ) plyer-2.1.0/devrequirements.txt000066400000000000000000000000371433372044000166300ustar00rootroot00000000000000mock coverage coveralls cython plyer-2.1.0/docs/000077500000000000000000000000001433372044000135755ustar00rootroot00000000000000plyer-2.1.0/docs/Makefile000066400000000000000000000151571433372044000152460ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Plyer.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Plyer.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/Plyer" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Plyer" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." plyer-2.1.0/docs/source/000077500000000000000000000000001433372044000150755ustar00rootroot00000000000000plyer-2.1.0/docs/source/conf.py000066400000000000000000000200151433372044000163720ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Plyer documentation build configuration file, created by # sphinx-quickstart on Wed Jul 3 15:27:01 2013. # # This file is execfile()d with the current directory set to its containing dir # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import os import sys import plyer # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath('../..')) # -- General configuration ---------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'Plyer' copyright = u'2013, Mathieu Virbel, Akshay Aurora, Gabriel Petier, Ben Rousch' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = plyer.__version__ # The full version, including alpha/beta/rc tags. release = plyer.__version__ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all documents # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # -- Options for HTML output -------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'Plyerdoc' # -- Options for LaTeX output ------------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]) latex_documents = [( 'index', 'Plyer.tex', u'Plyer Documentation', u'Mathieu Virbel, Akshay Aurora, Gabriel Petier, Ben Rousch', 'manual' ), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output ------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'plyer', u'Plyer Documentation', [u'Mathieu Virbel, Akshay Aurora, Gabriel Petier, Ben Rousch'], 1) ] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ----------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [( 'index', 'Plyer', u'Plyer Documentation', u'Mathieu Virbel, Akshay Aurora, Gabriel Petier, Ben Rousch', 'Plyer', 'One line description of project.', 'Miscellaneous' ), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # texinfo_no_detailmenu = False plyer-2.1.0/docs/source/index.rst000066400000000000000000000007711433372044000167430ustar00rootroot00000000000000.. Plyer documentation master file, created by sphinx-quickstart on Wed Jul 3 15:18:02 2013. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to Plyer ================ Plyer is a Python library for accessing features of your hardware / platforms. .. automodule:: plyer :members: .. automodule:: plyer.facades :members: Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` plyer-2.1.0/examples/000077500000000000000000000000001433372044000144635ustar00rootroot00000000000000plyer-2.1.0/examples/README000066400000000000000000000011101433372044000153340ustar00rootroot00000000000000Each facade has its own set of examples here. For each facade, there is a simple example which shows basic usage of the facade and its options. This simple example typically requires Kivy to run and buildozer to compile for Android. Requirements for run these examples on android devices: kivy,plyer,android These requirements should be rewrite in the buildozer.spec of example before run the example. There may also be more advanced examples for a facade. Requirements for the advanced examples may require a different framework (QT, Tkinter, etc) or additional Python modules. plyer-2.1.0/examples/accelerometer/000077500000000000000000000000001433372044000172755ustar00rootroot00000000000000plyer-2.1.0/examples/accelerometer/basic/000077500000000000000000000000001433372044000203565ustar00rootroot00000000000000plyer-2.1.0/examples/accelerometer/basic/README000066400000000000000000000006561433372044000212450ustar00rootroot00000000000000Basic acccelerometer example. Python-for-android compilation: ./distribute.sh -m 'kivy plyer' cd dist/default ./build.py --package org.test.accelsimpleexample --name "Kivy Basic Accelerometer" \ --dir /path/to/plyer/examples/accelerometer/basic --version 1.0 \ debug installd Buildozer: cd /path/to/plyer/examples/accelerometer/basic # edit the buildozer.spec if required, then buildozer android debug deploy run plyer-2.1.0/examples/accelerometer/basic/accelerometertest.kv000066400000000000000000000011161433372044000244310ustar00rootroot00000000000000#:kivy 1.8.0 : BoxLayout: orientation: 'vertical' Label: id: x_label text: 'X: ' Label: id: y_label text: 'Y: ' Label: id: z_label text: 'Z: ' Label: id: accel_status text: '' BoxLayout: size_hint_y: None height: '48dp' padding: '4dp' ToggleButton: id: toggle_button text: 'Start accelerometer' on_press: root.do_toggle()plyer-2.1.0/examples/accelerometer/basic/buildozer.spec000066400000000000000000000117301433372044000232330ustar00rootroot00000000000000[app] # (str) Title of your application title = Kivy Basic Accelerometer # (str) Package name package.name = accelbasicexample # (str) Package domain (needed for android/ios packaging) package.domain = org.test # (str) Source code where the main.py live source.dir = . # (list) Source files to include (let empty to include all the files) source.include_exts = py,png,jpg,kv,atlas # (list) Source files to exclude (let empty to not exclude anything) #source.exclude_exts = spec # (list) List of directory to exclude (let empty to not exclude anything) #source.exclude_dirs = tests, bin # (list) List of exclusions using pattern matching #source.exclude_patterns = license,images/*/*.jpg # (str) Application versioning (method 1) # version.regex = __version__ = '(.*)' # version.filename = %(source.dir)s/main.py # (str) Application versioning (method 2) version = 1.0 # (list) Application requirements #for android device-> requirements=plyer,kivy,android requirements = plyer,kivy # (str) Presplash of the application #presplash.filename = %(source.dir)s/data/presplash.png # (str) Icon of the application #icon.filename = %(source.dir)s/data/icon.png # (str) Supported orientation (one of landscape, portrait or all) orientation = portrait # (bool) Indicate if the application should be fullscreen or not fullscreen = 0 # # Android specific # # (list) Permissions # android.permissions = android.hardware.sensor.accelerometer # (int) Android API to use #android.api = 14 # (int) Minimum API required (8 = Android 2.2 devices) #android.minapi = 8 # (int) Android SDK version to use #android.sdk = 21 # (str) Android NDK version to use #android.ndk = 9 # (bool) Use --private data storage (True) or --dir public storage (False) #android.private_storage = True # (str) Android NDK directory (if empty, it will be automatically downloaded.) #android.ndk_path = # (str) Android SDK directory (if empty, it will be automatically downloaded.) #android.sdk_path = # (str) Android entry point, default is ok for Kivy-based app #android.entrypoint = org.renpy.android.PythonActivity # (list) List of Java .jar files to add to the libs so that pyjnius can access # their classes. Don't add jars that you do not need, since extra jars can slow # down the build process. Allows wildcards matching, for example: # OUYA-ODK/libs/*.jar #android.add_jars = foo.jar,bar.jar,path/to/more/*.jar # (list) List of Java files to add to the android project (can be java or a # directory containing the files) #android.add_src = # (str) python-for-android branch to use, if not master, useful to try # not yet merged features. #android.branch = master # (str) OUYA Console category. Should be one of GAME or APP # If you leave this blank, OUYA support will not be enabled #android.ouya.category = GAME # (str) Filename of OUYA Console icon. It must be a 732x412 png image. #android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png # (str) XML file to include as an intent filters in tag #android.manifest.intent_filters = # (list) Android additionnal libraries to copy into libs/armeabi #android.add_libs_armeabi = libs/android/*.so # (bool) Indicate whether the screen should stay on # Don't forget to add the WAKE_LOCK permission if you set this to True #android.wakelock = False # (list) Android application meta-data to set (key=value format) #android.meta_data = # (list) Android library project to add (will be added in the # project.properties automatically.) #android.library_references = # # iOS specific # # (str) Name of the certificate to use for signing the debug version # Get a list of available identities: buildozer ios list_identities #ios.codesign.debug = "iPhone Developer: ()" # (str) Name of the certificate to use for signing the release version #ios.codesign.release = %(ios.codesign.debug)s [buildozer] # (int) Log level (0 = error only, 1 = info, 2 = debug (with command output)) log_level = 2 # ----------------------------------------------------------------------------- # List as sections # # You can define all the "list" as [section:key]. # Each line will be considered as a option to the list. # Let's take [app] / source.exclude_patterns. # Instead of doing: # # [app] # source.exclude_patterns = license,data/audio/*.wav,data/images/original/* # # This can be translated into: # # [app:source.exclude_patterns] # license # data/audio/*.wav # data/images/original/* # # ----------------------------------------------------------------------------- # Profiles # # You can extend section / key with a profile # For example, you want to deploy a demo version of your application without # HD content. You could first change the title to add "(demo)" in the name # and extend the excluded directories to remove the HD content. # # [app@demo] # title = My Application (demo) # # [app:source.exclude_patterns@demo] # images/hd/* # # Then, invoke the command line with the "demo" profile: # # buildozer --profile demo android debug plyer-2.1.0/examples/accelerometer/basic/main.py000066400000000000000000000027671433372044000216700ustar00rootroot00000000000000''' Basic accelerometer example. ''' from kivy.app import App from kivy.uix.boxlayout import BoxLayout from kivy.clock import Clock from plyer import accelerometer class AccelerometerTest(BoxLayout): def __init__(self): super().__init__() self.sensorEnabled = False def do_toggle(self): try: if not self.sensorEnabled: accelerometer.enable() Clock.schedule_interval(self.get_acceleration, 1 / 20.) self.sensorEnabled = True self.ids.toggle_button.text = "Stop Accelerometer" else: accelerometer.disable() Clock.unschedule(self.get_acceleration) self.sensorEnabled = False self.ids.toggle_button.text = "Start Accelerometer" except NotImplementedError: import traceback traceback.print_exc() status = "Accelerometer is not implemented for your platform" self.ids.accel_status.text = status def get_acceleration(self, dt): val = accelerometer.acceleration[:3] if not val == (None, None, None): self.ids.x_label.text = "X: " + str(val[0]) self.ids.y_label.text = "Y: " + str(val[1]) self.ids.z_label.text = "Z: " + str(val[2]) class AccelerometerTestApp(App): def build(self): return AccelerometerTest() def on_pause(self): return True if __name__ == '__main__': AccelerometerTestApp().run() plyer-2.1.0/examples/accelerometer/using_graph/000077500000000000000000000000001433372044000216035ustar00rootroot00000000000000plyer-2.1.0/examples/accelerometer/using_graph/README000066400000000000000000000013301433372044000224600ustar00rootroot00000000000000Python-for-android compilation: ./distribute.sh -m 'kivy plyer' cd dist/default ./build.py --package org.test.accelexample --name "Kivy Accelerometer" \ --dir /path/to/plyer/examples/accelerometer/using_graph --version 1.0 \ debug installd Buildozer: cd /path/to/plyer/examples/accelerometer/using_graph # edit the buildozer.spec if required, then buildozer android debug deploy run This example uses Garden Graph widget by matham. Available at https://github.com/kivy-garden/garden.graph. The required files are present it ./libs/garden/garden.graph. If you need to update these files, use: cd /path/to/plyer/examples/accelerometer/using_graph /path/to/kivy_instalation/kivy/tools/garden install --app graph plyer-2.1.0/examples/accelerometer/using_graph/Screenshot_Android_4.3.png000066400000000000000000001047631433372044000264650ustar00rootroot00000000000000PNG  IHDR8gVsBITOIDATxAo۸Pf@͢f. z y5q,_:*)-$,rz݀>umn@j"Mu]{7 eYj_LE |C 6d$ hBqֻ ;i]kɝ4K;78im /Lt W>b]Mn]='|I_R @F' [k!l%[pIh[p_4Zk0 [p|Y-`έ@B @4!@B @4Zk{.@ֺr4T@B4!T@B4 ! hB! m= Vksm**@B @4!@B ڞl\ 6P @FhB! hB! h\.`[uh h2@B @4!@B @4Zk{.@ֺr4T@B4!T@B4 ! hB! m= Vksm*@B @4!@B ڞl\ 6P @P @FhB! hB! h\.`[uh h2@B @4!@B @4Zk{.@ֺr4T@B4 ! hB! hBZsm= ئ*H @4!@B @4!@B ڞl\ @4!@B @4!@B @4Zk{.@ֺr4T@B4 ! hB! hBZsm= ئ*H @4!@B @4!@B ڞ@>K|r)e׊S Y 0ZK6|n -8fOK)/\UE@LLe1ГBs@Q0+4T= \)\zOF @w>q=<޵fHuu 0uRwHk9|Rc ʺ>>$L>z}KB쭀@iRQ 輊3S\JYFo{)cjy3~/[ @$-^Jѻ% !=O hПC5s6]Jtn8lpR^BO @/Ny`Rjh>c}@*@%N-,==^ ,.oqdʁ)=|L9݆R~~NRJ9|K8^ifp)Dgq'8CeYj}3ZMe)p孲MΪr-PnO\Г{kn`nɇTUs/2pNK)jo-8~iRR~f;Qyz?7}Ǥ gSuY}Fgy:"F vGiMΙ%hNnJ 5^}I:y6rn<Ξ>pQz7xK)N:|! {䚹n/~918dIL)4^JY yDL@U2ˉoU n"&鷜x2:L'R~Sg-W6 o>G>`*{5'9&iUDfJ8Gy }cƝԤtu Q9|M5>_çr95_r1g-kr-e)e)c-bhOZ8i֞4HS2M-Sݺ/vQS˲ N}WRR~l @2w͌{Yf\J+I8'Y Bްߥ牌Ȓ,2-$n{r_N~?z2D$W]W[BBQssYva7_y~ ;';ccLetŗ'0J\74xDT@3!,S}>U_LF5'N.;-83Qjuah|.xNn9{t3j" ǗAuvyHjH^CιϺ\%LUpX2 mu}-lKzƝ q ;G2ws3.dm?.#q;dh&2g3}-qGt7D4344=tg;;>ah=Vы/ Yl1 |4_gֱ'YcgXLibAi3m<<$1:rtε# m}0=ya1+K:78?)3 :u7Uz6M/Џ+t B<QuHk} Q0Y_v3 cAƽ|&‘Џ@: z CL7zga u48PS?a&V?hcցGDtyPgG'd }:} J>8CzW#Gc <˲ 0SKipB,,wLc}maFoczN_eRʵ B*p/\ҷvó<ɖA6ngv?1k:K)$Ǫ$i8X뀹y):`&\9;Vjv}-g]眝sݦ-EiC*jisPF6h& $DhKFԀXbEE+*4R[ hK۴{svcݳ{w|)gyΜgfoIj R݀+S*nC>ZiB7Y7>Et GVv-bvP(*#gSUUQX$fyTdٍXnl;)F?]B t*r"#`-@2B+RF)).U4h ; RRZao4E_kXyFAC/di87Ͷy=lvHIq0jg [vged@RLAVE^}Úgsm>NsYY1XbDKVuuճ9k,g7𝶊M3řl}"hVt +U? @?\&+gq2V"hTtNJ'n d-6%j&BȪ8@ǵa5讞kKYάwTH+*zӑX(VV֎ ~u|g*\SюX?ѻR=EN'OWmoybڶ`ڽ!BO }5MD+|COuBͼ+ԟ?3֓:Fd+pt7ɭopQhӁ>EQ6,(PnRmfr]@uCҟo?a,KсtxK:`w) Na[>YJe2Sߝ3HM3+,O˓cн7ZM_E~mBDeD6GeU6 <СOr!чΆz@8v﮾-~[bBg2+?9`  lpFJ=F+zwʁIj *5n!Uw#Vc|X! y\7yA|ylO%p8/#;uZ=n,9)mdљZzdv))mt^J'n Wa1ە؟&P-hDVtg[82=^v{i6"=lVhzlT(QVf=Z؋:,r>ꔼ.I7(Rz29"zi`H)I8_$A8niEґېDU-Kn:=߂w2*V}Ý@`D^jq[N[62_Nf;-IM#sXߋӫ_ 9l  vЅztqgހU@Ӏ.?x%=2pn6R[vm69h1yoRv:3߶[,}Rovm@N"n" {PoXm{o |FF|GSowһzb=2sR:V) }GRR#ig|4:A9@b<"ѳW흪E^L 躠/yZPZ0jQIFO]$uMc:̝_wvA 0h,#Q2L٧- ƳlQ 98J42eʭYt`;5;'jJQ "Q?SjMѰO؀S*('Tz{Y4VU5LlTf@ЈmKP6w] hނQ™LM)=i+`x!EQ@p Z ]4ww3ЄTs@id4L"k֬X@b-@Fg3B+{9˨ 6V@8ᑾׁ@O `Yk$V_˾in`)}g1ahՏZ4j !5tR;vCXTl]:Hn>|']'l7ku- imNtʔFmVBj%-bks#a}j44b]G m@\*^ +݁"yFm̂mhz!w҈L{s2luX(53AZF¢nu ޝ0ݧ"okUX@C aV?x֓SY|/?ۿ e :lVmͮG?jՇ `>;'Q\1CK/Ua2`2?K0z9QJUN8ݕUt n_Au6B>Gh#@CBSr+J]j?d(Y1kQvd5@}eZeU@+4u y 4A0~pBUE;,L]H/ceJz  SUƯGʥf{RX0JU 4-hZQ$"A"dW:%77VO^9N;4nq6{4t=UwynOv #C(vrGW';lI3Svw5hΠίD{=SJKCݨ%^:~&]~b*:vvGu^隟xJ+z0 pxȑ .`mm-t=䎂J^W;tY8ԉ-͓=+R+y Ro`r h4گڛn[[=K/_rm> 7bnHO.;0RںI))Zf`6tp8+nj;d0qUUvmu*PrRlMMfeJyvEUl]_Av#Tޙ $ i)#`TUUcկ~c=[[Xe2TU5L.i[ +WGϕQ Xb 2VBlB4>꫟xi\ɓ'766N:w<駟_w@o(NGCgVo>n~>VaPnD2\ or['˿!>s?s^{e:t/?|@1}Xm?g|w1\r؇"p݈_?⋗2˴Gy> >e6+?mKԱwagrg%La:sg>9|8&ڔ@}SOJۙ\0Ev|!@kvЀ虃SN]tQ_ڔ+ER+.,ݩsBpuPL@8v|WAv0=z~,Y~ƀ e=36wDVZ!}mX oVCW\W~wwKnY'_[ĈF[ӻ~IY l#HUU)+|gQp8 ggǎ{_w~%/yg}E]uU}k=:S}k爒V@Pl@t3T[)};g;q[W6=:Jo|,>ey~|o}I?[Tً= V=HxB 4}FJGShׂSpd ._x`<O&,ud2>//^xsY/@',s1Hֻ -}`$W=z5\~g}瞻c=ǎǏO="zZւϯZPab@æ;ivngmd^ 6Ab49CL*2{09r__{'IUU_MXY柧ؚѢợ[?ŶVN.Ld~$.iFLa;݆[]upʰ 9u0>3?3'Nꡇ+zGWol!2j맕L|)?XP?`Z_3]K= ڋ]sc)˲wt]zWYUU;vСC)Ke@ ~z9ΝLv }dϤso+=Ov񪪪T5 Έ uLOвZ#vmnFۅXA-@x|L&[n~?6&1K[ şcr Z@?oY9Volw_ki!qD/n%wMnԊ[痬+ }ºqe>BȿЀQ3F;ɤՆdddJ=moC@/1='u+js>e)=҃& RH鼔V tȑᄏ m'oB8US>g'yq3gvu*Yȁ>iM~8X2˵5oBj0Lԡpnqpxo+뮻hnæ3?յρd#҃WLKÉݴ@_zCmnnVUR:uT|kȑ#_|ѣGo+r_h@T"e6fKڪ4y;@)8'L ע(+S:ҭ> 􀽕~-[0HUU u`0p8_`PoOMUo _UE{i؏_eQ4q1i{Y U42)DJ3@o,o҆i"}YX 4[y'9R?G6w<Omz3wZaUUve*Mz*Sp}72 4@~WҺ7}@hVB tirl0)d"z0 (W|W>|(mkkk)SNM&CM&pxԩ}Vp8ꪫ~L&\ʥ6R:v:ʧ hJUUkY[[{{߻9_,3_4P(zL&eYO~ǎ;O8괛P I#T)R IUUF8~ꧦO=ԯگ=z9yN/s_|Yg]ve7x__NCK.9qD۲,?~w~^ko_m?otHvVUUEz뭛wo!"]Ϩ(7ͷr:u;w~ꫯ@Y< 1O N d%:`=\bSzaCoH1E 7t\{\,˲Kڭ6<Tf@Ř,jd2ˊ(b044<TgcǎO_~СpX-2灊,FCd3yy{^>|$Z J)#<3\(,d93g??\Q-$/Jo=ԧʢ?WWz޴0Rz)@@Ũ/xO|NǻD替[emmmYo-[Oce5:7}Cz;VUk^ @}i Њ;>|>| _UU,~G~~:h4|b|ZS`0k~~iᣏ>7pg}_ d.76f_wooOWկ~^kkkm 31)`pu{_u`]Oh-XQJUJ޽Hi[,`0(bmm/|;o}<Weyoo9@-Hd(~뮙d2?ַ.ZM@m7 kkkx`sssf<}c?#?r,@hEpvMo?G}tfd2s7x瞻f[j@Umdh ^[[{?vɓ'wgc<?'gu֊#@m@cfNQ_?|#@m@e[k^C54Kqp+7xt=}w7۷}p8l=h 4ЊHx<>~ʯ\s5Sh 24&_}{K_Ҷ 4s#86N̥aԸٷXA hGUUAO?;V(~(,@,Ȇd2i&k1Us+Ѧ-@(SA0b~BmM1HiRu4{"`2}]SUm=61/b#-w˰CJE6T] z}`zڐӊ:^V֍=24B^s@p9d`lД4m':KT@ե_> Qdj .fdP *Î `OhRSn(kwoQ]$,bl@@h*)Q}@n>yn 8umnj!T<-UJ*a)9RRғ172OHEJ^ ĺqCX܄裲9Mʰe$h^!@:o烲f: vc0Sѕ ,) R*S*S:vcz6i8I4;UڇliReM\cEV!q:rfQ<,5_ڜӤ<[WO/>bȆ>`N֡/<ۂSpo:PMMbT"y0Ы`T+gO*;tw3`)aC==q(g b>(9׷AJ6jS7:z9L&:/vU CZ=jX !:#:Q LyNFg!ohN3=o$O`mvH\ P>@tWOc{觞 z|⣫87n-g.*Rڰb®ƶpڰox?'gY{m9Ȇ;a8.SJbh`.ba:Yn @:^^gxՒ7v :%RJ) !Vƙ:}3n$~2ȩkl#!~$%߱lACSջr*v V7iF+ceK|`Q֎=W X$nʔ*=U*b!NDyDewƂu6NvҞ葪"ƺUFJeJGb!ǽ=t!,,\vP," ( k&3z"w)q.lN2~QyȘ:z?-f_și?GEf9o=+C.g޻l-bOQK̎aʭ æUVEcL\#*aT۩iX)]'GŎϠ6ʪIbe9fmRJ7t[O6CfZeռDb쒁LR*? @c6̿}eܰzJNd.B)QuM@@w9F UʟR[y^)F@ڰuV$FS:B4ٟdU(lm&̑:KhzJElAj +Jțe }2@4vtgL gV R0: %PB;CX 84K_Ȅ¬P}@" jd8+*$fy[%k@{ 'g OUU݄\錆'dlX͊T 2a?lXbq  I`'Gnv薱{d hjje)Nikl]`!C&  %t(*1JgADt3SĘ1` F5#f Б\sv;T7nU~*Rjwa)}6,Cq!A;02ǫnmfQJ!`ţmS*)mjwSjQ\xCJ;J äDﳤmJ@(O79QigL߁Sc'Z x+W`]K 8^ÚvXd.KՎgjۧ7m<a h ޳: ͙" 'דk5Yh% f1q h416SJn [V.D} Mgh)_.z]@#4s[S--O<αHe 'lڙBKLb5i|U=ޱS;8c\.4bk! !&*U=viuե0+ 7v.).r h"U~iƋ[ژ :mvd!g9*eր==qg;L[S4h+%==5S8~g7K8 9*6 59svC2508O`.NϮ8xlJv/6"=؆S\M _pnٍ6=dXM'+g`v`칑5g, mr>-p 4Sk%׀yL|RX6md@0Xl) xAlS#R7qK͑S_ۖ0r$0;KpЃMJe$QFs7A w)6r6˳O&pЅ1ںiM7{ j@gT@3MJE<8>,Rvo"Z0U1p8`Z肏v!hGNo0ާ @JɼY-H)*wbR߹+t7C[8*RJp@ p , rf){'z9R9OɠרﵩdM蚳8`b67Epަg)%Q}쵚:>28);˜Mq @%x !\vHi[f[W<^x;+}2|QËn8w_Cl3c%Pq1Ohf888M ݌@ >Iq'45meWo6"-0>9N>XϘCIsJW[ҹ_HGA8!m=F vG2'>gpbYmJNS @#X0k;/>.x9C>m=Z__ @D`=a` )- XmLlױ tN`k}~J.wx BvFyXRڮP&' `\ @5.kM׀V "@8+gd .b5Jzcpy"t7+|eK X}J}pCJ) 9CJ tO+vxq!\m=~J)Jw*uٔp}'LRx6 4#2 |JƓTEy@@mCW1`%P۶3; h@7@J hBl^Ὅ7gW>&э7  V`q @4N6rikn|^y%w9ݮlxojj :mjwRRC4Nj|6h8=ǯ_̧/ͳүELc5ָz}MȒ 5Ul] S<=====Rjwzۏ? mjw9n۷~l믯p(Ӽ(ᅬ믿{@#>Tsg}~ŧO>}N@/]k?_l6_MWJ_?矧<J㏿v駟4Oǯlfyzz4uR z]FJ鸬5-OI+g hBy|u?IIZڦwM) =(8DXDD" [Ttu]UQ *r*R*r'=MJ/BI{L&L-'3>OwO,40`@<ϱbhJv횾NjDDQQQJfѴ6-Jѣ>#8pছn 㒒/Ml2gΜOx'x7߬߶mۢE92Z2x<ޱc/[zu]]]MMͲeマ~bhj Dke/$ 4:LH&3fK.d֭MRo=4_| 26Jnj ~znݺd2L&"F766~(**g?Tld*׾H4x<~766vcDݻ=S@v^RxALbːvh;i-o߾}{n*zמzM6UTTuYw^744L6,++kll7oٳlRQQ~ C6l6mڴx$%UV_n]O<ď]v rqe<J[ܶkmo߾+"x`ӟttusCCC*ڻw?6/hv[E=zؼysP_ѣO?vOQ5Aق#chT*u-dy_>#ap/f,s]wr-ty˖-aw't|%n;vTTTd,vGKnذ}͕ HRw}KKKz뭠ƍ;t?~ӧOϲ4JȠ!<.**jZ\j͞=>[ ,'8p@s%S٬SNi9ݻǏ<Ⱦ}SO=65yd+ȓZ, .Dsim۶En)i%;uV`ҤIa?9M/?Z*ѣ)y@544;`466Xfݺu4֮]\ikf'LFɣGnD:H$’555  ;)V7oޜhT{4ݻwx3ܹ|؂hHgϞ,!u޽{sl!ݺukfmBV@aؘZ9*:M.++6}a| C M42b2Ym+8PZZ5jmhP(y N[fl߾=IEEE>CV@A˚5۶m #^zMh4Rk>X+_}d2|mhF -j ^z);,B@@m 3~W@Oھ}{p}NГ&M8tv%ZQQQee5\hѢw{pTjhh X,k׮}sO>dYYY,:uŋ{^zߎb;w߿UU㋋cXBTĀr4>5hР8b%ߴ6=sٹsg2L$L4(S,lM.k O?=u? M8s2|ꩧjjj;Dق2ػwo*7FEwmn|.e!~Gfl9J-_)rEuY}ѣGQQQmm֭[.] /<7onll̥kD"',/_~,k<+_8|7Ō s9F!( g)E3{'1ǚ\zTXX8F.\ _88@8.*//ORKRx!S-\J=f r2\uuu?uZ~h8N{m۶3xtԩk׮ͧ|躺3Q#Jݻx<7r!}8ʤ"F  #4! @Ph B @A(4| ,JS] @fyt2̳LV@p$@Ph B @A@'"l27>N&ee_ @Y<ϧRT>@Ph ":aAq2<((c2! x>dJ. 3+(+8 (|DB @d@Ph B @A,S] @fT*h28+ 6'Ƀ22/Ph B @A(4! x>dJoH 8Lqp~@Ph B @A(4! d. T*O|DB pt:+d-pd:+ #4! @Ph STx@|0<[[ #4pԩӐ!CvZVVJz뭷zkA(^lNQQQpJ!u,;v :H$8㌳>{x<\KܜL&|%K̙3g2<̫:8^x]wuxt/AQ^!Cd|7 'p '\tEַ.]zh>0eʔp-'ܩSݻw!8Q,Ϗ9=z[v}uuuEEEA~kƍ+VT޽'NŞ|ɺRYY9bĈǢI&͞=0 kĉx|ٵ{8Ky4K"{gٸqcծ={8pԨQUUUfj[_gy\^|c-4iRnl'O>D"'r 笳κ x20~Ǎsܹscj˖-[lyWǎ[]]GqE7>L4)8ؿIII,4hPϞ=nzXuْH6f̘=cGǖN;-sR|9x%L۷W_=p@Ʀ|#>"۷3<)SA_۷oϞ=?!eРA+ɲ8?O<9x<ީSa EMdo!Xy5wQ88DvС -oCE]ԱcŻᄏi.,c xO>|xNjjjVXk\mgΜ<O=TpRC 93Fѵk,_^z7pY 6,|ŋSԼy< ֜4iҚ5kZ(xȑG6lX.]:vL&~/YdٲeoZ+nLFnݺ5qx<^ZZ:f̘ &ݻK.555k֬^hQccc!۷:۷oի.\t?>g}6L~wy#Gܹs]w믧D>s{ ny-_s9sf>O>d*J$SL4iҀ***jkkwڵ|{n[tʔ)&Lׯ_yy޽{wر|ݼys۾}qM81zV^=w܅ &ɦ_|qEEE36~Wd:t0~SO=5 d?۷^8r.];XhQ.3~'-w֭[p<`uֵ;SGi  饗^z '-۾}.] 8[t>q,!Wdgm1x{}g9s3mΤItҽ{׿Θ fX,֡CO<_lٷՇ r嗧dXwȑ,Y|irW\ѭ[h`ƦNvoyǎ{gO:ڰaCsr-< M֮])g|cp 80zswڇ?e˖=YzիW,>bwq7p?ٿ/q㮺c=pwu+++իW8{֭Kk;>ж%x곟lxr촩] ^x% m-8c˛os99Qݺu Qz2"cx<~gz뭃 #pN:_ѣsLϞ=۵kwM7M2%f* <ӾDW棸x„ q}}믿۷`N:tI-:[neȐ!Mg#%K4}[nIPXwҥM/}KݻwoEPkРA_׆ުi:s=_bQxF\#G.vؑq_WKx≷vѣsW^]t V|x'>7xc>%K$\rG?{M7iǀN8_I'Neee0ZL=ܛo9-n'bݵkD"q~S `*8w]] acƌ  pWw}wih~|YW{ɸrvҤI* ,x7kkk+**7f̘x<ޡCnVX}0={'>$6mZx;dǎkOi6ga=pΝn =eʔgoSOqٲeoFMMMQQQ^ڿX,+ V׆dCCÊ+V\{?~Aݗ_~9ɓ?τnڴi۷o/..ׯߩZVVn[ou-I=q+"h˖-ZQ>}N=ԊnŎt2tiӦVTT `ĉW_}u=R={,X`ƍuuu} N}_җ㎥Kf_ݻwk6Xf͚Eܹ4#?򑏬_ ^Y~3K^~i2^m۶7q`}n[o5{ *^{ݻwG M; R͛7F:|_L봩]>dpɖ.]rʚ򲲲dwkQt.8r '5XyϜ9sÆ w =W_X]]=ҥW\as̹kkk+;u}nرx뮻[4ۣG /0͜9駟~6{衇>_|űf0v繭Ĕ)S,Zh߾}۷bcƌСCw->X{ݺuk ۱cGnee5\]jս޻e˖u֙v15C=4k֬7 H$***ʌ{766Aj9mV;uteC=4{9y䑴OuQ&M6? n׌s۹s+2XrJE: |p]s5rK]]]*'|rcc{.}֬Y'O/#+'5k֬S^wuoAvN?{t/_WO>dcccXG$e ;zk&R]]m۶,7d2_OSӇz(KMW_}u"زe=ܓc2plСCooqСi2dŨhC -[hd{y7=z?}<~᧞z*xn^`/ڶm*++3fL޽{OQ۷o_9xp\v?poyw[&dǎ/rL^pa 6y2M.ԩSpc=iVSS3cƌ)}ŝ[>򑏄?裳fڿtڵgϞ۷ow}s}c᝶tis,۽{!ݻwoN ѥEEEǏ;СCtaʔ)q*zc/>쒒썿foRG}4azӈ&LP\\KO?;s={4ϥ^^^~g?W}}}uKJJ«s'x"g}687w"~-k׮<~ O?txX;3@gQR||p>׳xfϞqҢ+c۟y晌_ K۷/Zp׳\={zig?-^2#J~G?C{UWE]l|Pz ׬Yq,׭[nݺK.->^ۿsر#H:r)wa7 .ܻwopcpΜ9vZp}nt&]{%0t.)))..N8} 7QAaŊ۷ogϞ g;-xxc0dz>|۶mͽp停i%۵kik:uX(.ӦF xdӒ,ksdG=4pLKR۶m/#L< /ٳgv񢢢K/k൦w]v C,)^(Zk׮ e/cǎd2Tx68qbx`w}7{ YpX,wchY^^ȳtǃ'zcǎSNeڀb:I&=M$gy`FqBX,VVVֶ]ue\r&zOڝ}-im(Tft%8 T*/.\9rdIu3斣|eJKKoZ7}[LM.BhI&=iWk?6,..[e Ѫ=7tL&***jfK;-!FwZ)DMg/kUf~z4@L &J޽{ޫWѣG@ݻ7\ELXyneǣoz,w>lذe˖E_ܻwo2Q ϷuWo?nU 9ڿ_.\GMׇ]xj!pXf/M6ɎlOmΩ~>Oٳg|>EwСCmtk[<9m{12JcD5Mc}F۷oљljWuTmmmu.ۭDD}^vȳC##ѧBMV^Gn;v#Һwm9=z->gu=|ݻs_8qb෿~Iwǧvڃ>݇ޫ gcڵdLsչg555ݻw[|;vGoF;m׮]YfoݺuCvo_,X8 sQ~ V7hQ߾}7l4%%%'N  Ym޼yAQFE\~}ׯU b{nUUV}mUV^֞`s.]'k֬9V@ً>b6@dֹs82hZ3'|rxOl11bD#G ۰tk׮}뭷ZJ^zɓ'G]bEx\UU>/oF8GnGhݪr/w5jTEh{^>;}ps xM DHmFK. 7߿ ' -\08O>;w3x8LZSSL _z?}^mcƌŋ8p`yߒ%K0`РA]h{2zŋÎڷoCh.]tΝq [x ^74.:M 4@ h\UVVk.o߾sNc*nZw I_CC3<6~ed,Y\\|嗇M=-QRR2mڴYj<6mZڊ+oߞ4姜rJpꫯXq׮]K. 槤N QSSꫯSwWfy[ڬԼ+׍V߷o߳>ԍW]uU񢢢6|Nꫯ.+++DGM566>\ve͝cqqe]֪;hz<{555TdON? ^2#:3f̘1mڴAk.HR/DQQiv뭷עE-[ִ5kք ԇ>xDZ"<{pCp ;ww_?|nMM\>uYӦM+..Ke7.(Jrl-SO b˖-kQJx;w &a_{&aD"QQQqg;ߩlՌm߾=o;J쨩?O[n 2dȍ7صkݘH$;mĈHv ˟g/,eZfMx|疕5hY <`ǻuя~.]vMvܹ޽{92ܹolllܹsO?X>}n^zw)))֭[>}p}}~%%%x|̘1ӧO_pӡCWUU 666?w޽{trEMN[7rJ4pl ׺viѣGbT*%[n1cFsV,X76lXPW^ӦMKk*jժUwo`tYYĉ'No*jhhO~2o޼Oq_W****++mCNd͚5?O[[nC ?o޼VPWWhѢq˓'N] /tST7.\]PwڴiN&cǎ;vl.uWX1}n!۵k7jԨQF~^9 :D;:W^=cƌ/| 9N0a„ MK666?>r?VX1cƌo1[pի"ooҾtz8؂8lذ!Dtdžwd]]O-Gs E8iҤ5kw߽~ ̃YbMߝ5k֌36ltd2|\Ԓ%Ko,X L4mquuukK,^{`mslll\pa>5lٲo~/nn~Uo5:v[uuusKR`i՛׿yUtp8̛7oC6lX~w޵kҢ={;֭[jkV__bsnO?~|>}***RTMM͖-[2nJ6mtw6lÇܹsEEŞ={jjjVX1o޼e˖['d_Ə?a„N8SN _}Օ+W-a8qbcccvbX7o^]]]EEEQQQ~oRE-^xUUUC ҥKyyy,۳gϖ-[VZꫯFºUUUUUUڵkYYY2ܳg϶mx9s_>cM6} 7nܨQ*++;T*o߾wyg˖-7oޮ]0oiM>}cǎ9rdeeeN;lڴiҥY:jll V7qC0a‰'صk]v\r޼yS"602!ٛͅ1cFttH${7/_2ڵn:uꩧڷoߊD".]4#͛KA;wܴiSxLj|-K{;)܅#dpc֦O6KEck_7ܪ Zt{,]}d~_ 6D-6Y i @YڃsZK @fy>lF @fV@PV@p$@Ph=@'Pj]v@򍏓AG(4! xdJ. 3+(+(ü:a d2g _ @Ph B @A,S] @fT*h2;+ 60N&yQe B @A(4! x>dJ. ü:a d2g  @A#@Ph B @A(4! x[lDVmh߾}۶m۳gOUqV|NkIENDB`plyer-2.1.0/examples/accelerometer/using_graph/accelerometerdemo.kv000066400000000000000000000016661433372044000256350ustar00rootroot00000000000000#:kivy 1.8.0 : BoxLayout: orientation: 'vertical' padding: 20 Graph: size_hint_y: 0.8 id: graph_plot xlabel:'Time' ylabel:'Value' y_grid_label: True x_grid_label: True padding: 5 xmin:0 xmax:100 ymin:-15 ymax:20 Button: id: toggle_button text: 'Start Accelerometer' size_hint_y: 0.2 on_press: root.do_toggle() : size_hint: .7, .4 title: "Error" BoxLayout: orientation: 'vertical' padding: 10 spacing: 20 Label: size_hint_y: 0.4 text: "This feature has not yet been implemented on this platform." Button: text: 'Dismiss' size_hint_y: 0.4 on_press: root.dismiss() plyer-2.1.0/examples/accelerometer/using_graph/buildozer.spec000066400000000000000000000116401433372044000244600ustar00rootroot00000000000000[app] # (str) Title of your application title = Plyer Accelerometer Example # (str) Package name package.name = accelexample # (str) Package domain (needed for android/ios packaging) package.domain = org.test # (str) Source code where the main.py live source.dir = . # (list) Source files to include (let empty to include all the files) source.include_exts = py,png,jpg,kv,atlas # (list) Source files to exclude (let empty to not exclude anything) #source.exclude_exts = spec # (list) List of directory to exclude (let empty to not exclude anything) #source.exclude_dirs = tests, bin # (list) List of exclusions using pattern matching #source.exclude_patterns = license,images/*/*.jpg # (str) Application versioning (method 1) # version.regex = __version__ = '(.*)' # version.filename = %(source.dir)s/main.py # (str) Application versioning (method 2) version = 1.0 # (list) Application requirements requirements = plyer,kivy # (str) Presplash of the application #presplash.filename = %(source.dir)s/data/presplash.png # (str) Icon of the application #icon.filename = %(source.dir)s/data/icon.png # (str) Supported orientation (one of landscape, portrait or all) orientation = portrait # (bool) Indicate if the application should be fullscreen or not fullscreen = 0 # # Android specific # # (list) Permissions # android.permissions = android.hardware.sensor.accelerometer # (int) Android API to use #android.api = 14 # (int) Minimum API required (8 = Android 2.2 devices) #android.minapi = 8 # (int) Android SDK version to use #android.sdk = 21 # (str) Android NDK version to use #android.ndk = 9 # (bool) Use --private data storage (True) or --dir public storage (False) #android.private_storage = True # (str) Android NDK directory (if empty, it will be automatically downloaded.) #android.ndk_path = # (str) Android SDK directory (if empty, it will be automatically downloaded.) #android.sdk_path = # (str) Android entry point, default is ok for Kivy-based app #android.entrypoint = org.renpy.android.PythonActivity # (list) List of Java .jar files to add to the libs so that pyjnius can access # their classes. Don't add jars that you do not need, since extra jars can slow # down the build process. Allows wildcards matching, for example: # OUYA-ODK/libs/*.jar #android.add_jars = foo.jar,bar.jar,path/to/more/*.jar # (list) List of Java files to add to the android project (can be java or a # directory containing the files) #android.add_src = # (str) python-for-android branch to use, if not master, useful to try # not yet merged features. #android.branch = master # (str) OUYA Console category. Should be one of GAME or APP # If you leave this blank, OUYA support will not be enabled #android.ouya.category = GAME # (str) Filename of OUYA Console icon. It must be a 732x412 png image. #android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png # (str) XML file to include as an intent filters in tag #android.manifest.intent_filters = # (list) Android additionnal libraries to copy into libs/armeabi #android.add_libs_armeabi = libs/android/*.so # (bool) Indicate whether the screen should stay on # Don't forget to add the WAKE_LOCK permission if you set this to True #android.wakelock = False # (list) Android application meta-data to set (key=value format) #android.meta_data = # (list) Android library project to add (will be added in the # project.properties automatically.) #android.library_references = # # iOS specific # # (str) Name of the certificate to use for signing the debug version # Get a list of available identities: buildozer ios list_identities #ios.codesign.debug = "iPhone Developer: ()" # (str) Name of the certificate to use for signing the release version #ios.codesign.release = %(ios.codesign.debug)s [buildozer] # (int) Log level (0 = error only, 1 = info, 2 = debug (with command output)) log_level = 2 # ----------------------------------------------------------------------------- # List as sections # # You can define all the "list" as [section:key]. # Each line will be considered as a option to the list. # Let's take [app] / source.exclude_patterns. # Instead of doing: # # [app] # source.exclude_patterns = license,data/audio/*.wav,data/images/original/* # # This can be translated into: # # [app:source.exclude_patterns] # license # data/audio/*.wav # data/images/original/* # # ----------------------------------------------------------------------------- # Profiles # # You can extend section / key with a profile # For example, you want to deploy a demo version of your application without # HD content. You could first change the title to add "(demo)" in the name # and extend the excluded directories to remove the HD content. # # [app@demo] # title = My Application (demo) # # [app:source.exclude_patterns@demo] # images/hd/* # # Then, invoke the command line with the "demo" profile: # # buildozer --profile demo android debug plyer-2.1.0/examples/accelerometer/using_graph/libs/000077500000000000000000000000001433372044000225345ustar00rootroot00000000000000plyer-2.1.0/examples/accelerometer/using_graph/libs/garden/000077500000000000000000000000001433372044000237745ustar00rootroot00000000000000plyer-2.1.0/examples/accelerometer/using_graph/libs/garden/garden.graph/000077500000000000000000000000001433372044000263345ustar00rootroot00000000000000plyer-2.1.0/examples/accelerometer/using_graph/libs/garden/garden.graph/README.md000066400000000000000000000033711433372044000276170ustar00rootroot00000000000000Graph ====== The `Graph` widget is a widget for displaying plots. It supports drawing multiple plot with different colors on the Graph. It also supports a title, ticks, labeled ticks, grids and a log or linear representation on both the x and y axis, independently. To display a plot. First create a graph which will function as a "canvas" for the plots. Then create plot objects e.g. MeshLinePlot and add them to the graph. To create a graph with x-axis between 0-100, y-axis between -1 to 1, x and y labels of and X and Y, respectively, x major and minor ticks every 25, 5 units, respectively, y major ticks every 1 units, full x and y grids and with a red line plot containing a sin wave on this range:: from kivy.garden.graph import Graph, MeshLinePlot graph = Graph(xlabel='X', ylabel='Y', x_ticks_minor=5, x_ticks_major=25, y_ticks_major=1, y_grid_label=True, x_grid_label=True, padding=5, x_grid=True, y_grid=True, xmin=-0, xmax=100, ymin=-1, ymax=1) plot = MeshLinePlot(color=[1, 0, 0, 1]) plot.points = [(x, sin(x / 10.)) for x in xrange(0, 101)] graph.add_plot(plot) The `MeshLinePlot` plot is a particular plot which draws a set of points using a mesh object. The points are given as a list of tuples, with each tuple being a (x, y) coordinate in the graph's units. You can create different types of plots other than `MeshLinePlot` by inheriting from the `Plot` class and implementing the required functions. The `Graph` object provides a "canvas" to which a Plot's instructions are added. The plot object is responsible for updating these instructions to show within the bounding box of the graph the proper plot. The Graph notifies the Plot when it needs to be redrawn due to changes. See the `MeshLinePlot` class for how it is done. plyer-2.1.0/examples/accelerometer/using_graph/libs/garden/garden.graph/__init__.py000066400000000000000000001050131433372044000304450ustar00rootroot00000000000000''' Graph ====== The :class:`Graph` widget is a widget for displaying plots. It supports drawing multiple plot with different colors on the Graph. It also supports a title, ticks, labeled ticks, grids and a log or linear representation on both the x and y axis, independently. To display a plot. First create a graph which will function as a "canvas" for the plots. Then create plot objects e.g. MeshLinePlot and add them to the graph. To create a graph with x-axis between 0-100, y-axis between -1 to 1, x and y labels of and X and Y, respectively, x major and minor ticks every 25, 5 units, respectively, y major ticks every 1 units, full x and y grids and with a red line plot containing a sin wave on this range:: from kivy.garden.graph import Graph, MeshLinePlot graph = Graph(xlabel='X', ylabel='Y', x_ticks_minor=5, x_ticks_major=25, y_ticks_major=1, y_grid_label=True, x_grid_label=True, padding=5, x_grid=True, y_grid=True, xmin=-0, xmax=100, ymin=-1, ymax=1) plot = MeshLinePlot(color=[1, 0, 0, 1]) plot.points = [(x, sin(x / 10.)) for x in range(0, 101)] graph.add_plot(plot) The MeshLinePlot plot is a particular plot which draws a set of points using a mesh object. The points are given as a list of tuples, with each tuple being a (x, y) coordinate in the graph's units. You can create different types of plots other than MeshLinePlot by inheriting from the Plot class and implementing the required functions. The Graph object provides a "canvas" to which a Plot's instructions are added. The plot object is responsible for updating these instructions to show within the bounding box of the graph the proper plot. The Graph notifies the Plot when it needs to be redrawn due to changes. See the MeshLinePlot class for how it is done. .. note:: The graph uses a stencil view to clip the plots to the graph display area. As with the stencil graphics instructions, you cannot stack more than 8 stencil-aware widgets. ''' __all__ = ('Graph', 'Plot', 'MeshLinePlot', 'MeshStemPlot') from math import radians from kivy.uix.widget import Widget from kivy.uix.label import Label from kivy.uix.stencilview import StencilView from kivy.properties import NumericProperty, BooleanProperty,\ BoundedNumericProperty, StringProperty, ListProperty, ObjectProperty,\ DictProperty, AliasProperty from kivy.clock import Clock from kivy.graphics import Mesh, Color from kivy.graphics.transformation import Matrix from kivy.event import EventDispatcher from kivy.lang import Builder from kivy import metrics from math import log10, floor, ceil from decimal import Decimal Builder.load_string(''' #:kivy 1.1.0 : canvas.before: PushMatrix MatrixInstruction: matrix: self.transform canvas.after: PopMatrix ''') class RotateLabel(Label): transform = ObjectProperty(Matrix()) class Graph(Widget): '''Graph class, see module documentation for more information. ''' # triggers a full reload of graphics _trigger = ObjectProperty(None) # triggers only a repositioning of objects due to size/pos updates _trigger_size = ObjectProperty(None) # holds widget with the x-axis label _xlabel = ObjectProperty(None) # holds widget with the y-axis label _ylabel = ObjectProperty(None) # holds all the x-axis tick mark labels _x_grid_label = ListProperty([]) # holds all the y-axis tick mark labels _y_grid_label = ListProperty([]) # holds the stencil view that clipse the plots to graph area _plot_area = ObjectProperty(None) # the mesh drawing all the ticks/grids _mesh = ObjectProperty(None) # the mesh which draws the surrounding rectangle _mesh_rect = ObjectProperty(None) # a list of locations of major and minor ticks. The values are not # but is in the axis min - max range _ticks_majorx = ListProperty([]) _ticks_minorx = ListProperty([]) _ticks_majory = ListProperty([]) _ticks_minory = ListProperty([]) def __init__(self, **kwargs): super().__init__(**kwargs) self._mesh = Mesh(mode='lines') self._mesh_rect = Mesh(mode='line_strip') val = 0.25 self.canvas.add(Color(1 * val, 1 * val, 1 * val)) self.canvas.add(self._mesh) self.canvas.add(Color(1, 1, 1)) self.canvas.add(self._mesh_rect) mesh = self._mesh_rect mesh.vertices = [0] * (5 * 4) mesh.indices = [k for k in range(5)] self._plot_area = StencilView() self.add_widget(self._plot_area) self._trigger = Clock.create_trigger(self._redraw_all) self._trigger_size = Clock.create_trigger(self._redraw_size) self.bind(center=self._trigger_size, padding=self._trigger_size, font_size=self._trigger_size, plots=self._trigger_size, x_grid=self._trigger_size, y_grid=self._trigger_size, draw_border=self._trigger_size) self.bind(xmin=self._trigger, xmax=self._trigger, xlog=self._trigger, x_ticks_major=self._trigger, x_ticks_minor=self._trigger, xlabel=self._trigger, x_grid_label=self._trigger, ymin=self._trigger, ymax=self._trigger, ylog=self._trigger, y_ticks_major=self._trigger, y_ticks_minor=self._trigger, ylabel=self._trigger, y_grid_label=self._trigger) self._trigger() def _get_ticks(self, major, minor, log, s_min, s_max): if major and s_max > s_min: if log: s_min = log10(s_min) s_max = log10(s_max) # count the decades in min - max. This is in actual decades, # not logs. n_decades = floor(s_max - s_min) # for the fractional part of the last decade, we need to # convert the log value, x, to 10**x but need to handle # differently if the last incomplete decade has a decade # boundary in it if floor(s_min + n_decades) != floor(s_max): n_decades += 1 - (10 ** (s_min + n_decades + 1) - 10 ** s_max) / 10 ** floor(s_max + 1) else: n_decades += ((10 ** s_max - 10 ** (s_min + n_decades)) / 10 ** floor(s_max + 1)) # this might be larger than what is needed, but we delete # excess later n_ticks_major = n_decades / float(major) n_ticks = int(floor(n_ticks_major * (minor if minor >= 1. else 1.0))) + 2 # in decade multiples, e.g. 0.1 of the decade, the distance # between ticks decade_dist = major / float(minor if minor else 1.0) points_minor = [0] * n_ticks points_major = [0] * n_ticks k = 0 # position in points major k2 = 0 # position in points minor # because each decade is missing 0.1 of the decade, if a tick # falls in < min_pos skip it min_pos = 0.1 - 0.00001 * decade_dist s_min_low = floor(s_min) # first real tick location. value is in fractions of decades # from the start we have to use decimals here, otherwise # floating point inaccuracies results in bad values start_dec = ceil((10 ** Decimal(s_min - s_min_low - 1)) / Decimal(decade_dist)) * decade_dist count_min = (0 if not minor else floor(start_dec / decade_dist) % minor) start_dec += s_min_low count = 0 # number of ticks we currently have passed start while True: # this is the current position in decade that we are. # e.g. -0.9 means that we're at 0.1 of the 10**ceil(-0.9) # decade pos_dec = start_dec + decade_dist * count pos_dec_low = floor(pos_dec) diff = pos_dec - pos_dec_low zero = abs(diff) < 0.001 * decade_dist if zero: # the same value as pos_dec but in log scale pos_log = pos_dec_low else: pos_log = log10((pos_dec - pos_dec_low ) * 10 ** ceil(pos_dec)) if pos_log > s_max: break count += 1 if zero or diff >= min_pos: if minor and not count_min % minor: points_major[k] = pos_log k += 1 else: points_minor[k2] = pos_log k2 += 1 count_min += 1 # n_ticks = len(points) else: # distance between each tick tick_dist = major / float(minor if minor else 1.0) n_ticks = int(floor((s_max - s_min) / tick_dist) + 1) points_major = [0] * int( floor((s_max - s_min) / float(major)) + 1 ) points_minor = [0] * (n_ticks - len(points_major) + 1) k = 0 # position in points major k2 = 0 # position in points minor for m in range(0, n_ticks): if minor and m % minor: points_minor[k2] = m * tick_dist + s_min k2 += 1 else: points_major[k] = m * tick_dist + s_min k += 1 del points_major[k:] del points_minor[k2:] else: points_major = [] points_minor = [] return points_major, points_minor def _update_labels(self): xlabel = self._xlabel ylabel = self._ylabel x = self.x y = self.y width = self.width height = self.height padding = self.padding x_next = padding + x y_next = padding + y xextent = x + width yextent = y + height ymin = self.ymin ymax = self.ymax xmin = self.xmin precision = self.precision x_overlap = False y_overlap = False # set up x and y axis labels if xlabel: xlabel.text = self.xlabel xlabel.texture_update() xlabel.size = xlabel.texture_size xlabel.pos = (x + width / 2. - xlabel.width / 2., padding + y) y_next += padding + xlabel.height if ylabel: ylabel.text = self.ylabel ylabel.texture_update() ylabel.size = ylabel.texture_size ylabel.x = padding + x - (ylabel.width / 2. - ylabel.height / 2.) x_next += padding + ylabel.height xpoints = self._ticks_majorx xlabels = self._x_grid_label xlabel_grid = self.x_grid_label ylabel_grid = self.y_grid_label ypoints = self._ticks_majory ylabels = self._y_grid_label # now x and y tick mark labels if len(ylabels) and ylabel_grid: # horizontal size of the largest tick label, to have enough room ylabels[0].text = precision % ypoints[0] ylabels[0].texture_update() y1 = ylabels[0].texture_size y_start = y_next + ( padding + y1[1] if len(xlabels) and xlabel_grid else 0 ) + (padding + y1[1] if not y_next else 0) yextent = y + height - padding - y1[1] / 2. if self.ylog: ymax = log10(ymax) ymin = log10(ymin) ratio = (yextent - y_start) / float(ymax - ymin) y_start -= y1[1] / 2. func = (lambda x: 10 ** x) if self.ylog else lambda x: x y1 = y1[0] for k in range(len(ylabels)): ylabels[k].text = precision % func(ypoints[k]) ylabels[k].texture_update() ylabels[k].size = ylabels[k].texture_size y1 = max(y1, ylabels[k].texture_size[0]) ylabels[k].pos = (x_next, y_start + (ypoints[k] - ymin) * ratio) if len(ylabels) > 1 and ylabels[0].top > ylabels[1].y: y_overlap = True else: x_next += y1 + padding if len(xlabels) and xlabel_grid: func = log10 if self.xlog else lambda x: x # find the distance from the end that'll fit the last tick label xlabels[0].text = precision % func(xpoints[-1]) xlabels[0].texture_update() xextent = x + width - xlabels[0].texture_size[0] / 2. - padding # find the distance from the start that'll fit the first tick label if not x_next: xlabels[0].text = precision % func(xpoints[0]) xlabels[0].texture_update() x_next = padding + xlabels[0].texture_size[0] / 2. xmin = func(xmin) ratio = (xextent - x_next) / float(func(self.xmax) - xmin) func = (lambda x: 10 ** x) if self.xlog else lambda x: x right = -1 for k in range(len(xlabels)): xlabels[k].text = precision % func(xpoints[k]) # update the size so we can center the labels on ticks xlabels[k].texture_update() xlabels[k].size = xlabels[k].texture_size xlabels[k].pos = (x_next + (xpoints[k] - xmin) * ratio - xlabels[k].texture_size[0] / 2., y_next) if xlabels[k].x < right: x_overlap = True break right = xlabels[k].right if not x_overlap: y_next += padding + xlabels[0].texture_size[1] # now re-center the x and y axis labels if xlabel: xlabel.x = x_next + (xextent - x_next) / 2. - xlabel.width / 2. if ylabel: ylabel.y = y_next + (yextent - y_next) / 2. - ylabel.height / 2. t = Matrix().translate(ylabel.center[0], ylabel.center[1], 0) t = t.multiply(Matrix().rotate(-radians(270), 0, 0, 1)) ylabel.transform = t.multiply(Matrix().translate(-ylabel.center[0], -ylabel.center[1], 0)) if x_overlap: for k in range(len(xlabels)): xlabels[k].text = '' if y_overlap: for k in range(len(ylabels)): ylabels[k].text = '' return x_next, y_next, xextent, yextent def _update_ticks(self, size): # re-compute the positions of the bounding rectangle mesh = self._mesh_rect vert = mesh.vertices if self.draw_border: vert[0] = size[0] vert[1] = size[1] vert[4] = size[2] vert[5] = size[1] vert[8] = size[2] vert[9] = size[3] vert[12] = size[0] vert[13] = size[3] vert[16] = size[0] vert[17] = size[1] else: vert[0:18] = [0 for k in range(18)] mesh.vertices = vert # re-compute the positions of the x/y axis ticks mesh = self._mesh vert = mesh.vertices start = 0 xpoints = self._ticks_majorx ypoints = self._ticks_majory ylog = self.ylog xlog = self.xlog xmin = self.xmin xmax = self.xmax if xlog: xmin = log10(xmin) xmax = log10(xmax) ymin = self.ymin ymax = self.ymax if ylog: xmin = log10(ymin) ymax = log10(ymax) if len(xpoints): top = size[3] if self.x_grid else metrics.dp(12) + size[1] ratio = (size[2] - size[0]) / float(xmax - xmin) for k in range(start, len(xpoints) + start): vert[k * 8] = size[0] + (xpoints[k - start] - xmin) * ratio vert[k * 8 + 1] = size[1] vert[k * 8 + 4] = vert[k * 8] vert[k * 8 + 5] = top start += len(xpoints) if len(ypoints): top = size[2] if self.y_grid else metrics.dp(12) + size[0] ratio = (size[3] - size[1]) / float(ymax - ymin) for k in range(start, len(ypoints) + start): vert[k * 8 + 1] = size[1] + (ypoints[k - start] - ymin) * ratio vert[k * 8 + 5] = vert[k * 8 + 1] vert[k * 8] = size[0] vert[k * 8 + 4] = top mesh.vertices = vert def _update_plots(self, size): ylog = self.ylog xlog = self.xlog xmin = self.xmin xmax = self.xmax ymin = self.ymin ymax = self.ymax for plot in self.plots: plot._update(xlog, xmin, xmax, ylog, ymin, ymax, size) def _redraw_all(self, *args): # add/remove all the required labels font_size = self.font_size if self.xlabel: if not self._xlabel: xlabel = Label(font_size=font_size) self.add_widget(xlabel) self._xlabel = xlabel else: xlabel = self._xlabel if xlabel: self.remove_widget(xlabel) self._xlabel = None grids = self._x_grid_label xpoints_major, xpoints_minor = self._get_ticks(self.x_ticks_major, self.x_ticks_minor, self.xlog, self.xmin, self.xmax) self._ticks_majorx = xpoints_major self._ticks_minorx = xpoints_minor if not self.x_grid_label: n_labels = 0 else: n_labels = len(xpoints_major) for k in range(n_labels, len(grids)): self.remove_widget(grids[k]) del grids[n_labels:] grid_len = len(grids) grids.extend([None] * (n_labels - len(grids))) for k in range(grid_len, n_labels): grids[k] = Label(font_size=font_size) self.add_widget(grids[k]) if self.ylabel: if not self._ylabel: ylabel = RotateLabel(font_size=font_size) self.add_widget(ylabel) self._ylabel = ylabel else: ylabel = self._ylabel if ylabel: self.remove_widget(ylabel) self._ylabel = None grids = self._y_grid_label ypoints_major, ypoints_minor = self._get_ticks(self.y_ticks_major, self.y_ticks_minor, self.ylog, self.ymin, self.ymax) self._ticks_majory = ypoints_major self._ticks_minory = ypoints_minor if not self.y_grid_label: n_labels = 0 else: n_labels = len(ypoints_major) for k in range(n_labels, len(grids)): self.remove_widget(grids[k]) del grids[n_labels:] grid_len = len(grids) grids.extend([None] * (n_labels - len(grids))) for k in range(grid_len, n_labels): grids[k] = Label(font_size=font_size) self.add_widget(grids[k]) mesh = self._mesh n_points = (len(xpoints_major) + len(xpoints_minor) + len(ypoints_major) + len(ypoints_minor)) mesh.vertices = [0] * (n_points * 8) mesh.indices = [k for k in range(n_points * 2)] self._redraw_size() def _redraw_size(self, *args): # size a 4-tuple describing the bounding box in which we can draw # graphs, it's (x0, y0, x1, y1), which correspond with the bottom left # and top right corner locations, respectively size = self._update_labels() self._plot_area.pos = (size[0], size[1]) self._plot_area.size = (size[2] - size[0], size[3] - size[1]) self._update_ticks(size) self._update_plots(size) def add_plot(self, plot): '''Add a new plot to this graph. :Parameters: `plot`: Plot to add to this graph. >>> graph = Graph() >>> plot = MeshLinePlot(mode='line_strip', color=[1, 0, 0, 1]) >>> plot.points = [(x / 10., sin(x / 50.)) for x in range(-0, 101)] >>> graph.add_plot(plot) ''' area = self._plot_area for group in plot._get_drawings(): area.canvas.add(group) self.plots = self.plots + [plot] def remove_plot(self, plot): '''Remove a plot from this graph. :Parameters: `plot`: Plot to remove from this graph. >>> graph = Graph() >>> plot = MeshLinePlot(mode='line_strip', color=[1, 0, 0, 1]) >>> plot.points = [(x / 10., sin(x / 50.)) for x in range(-0, 101)] >>> graph.add_plot(plot) >>> graph.remove_plot(plot) ''' self._plot_area.canvas.remove_group(plot._get_group()) self.plots.remove(plot) xmin = NumericProperty(0.) '''The x-axis minimum value. If :data:`xlog` is True, xmin must be larger than zero. :data:`xmin` is a :class:`~kivy.properties.NumericProperty`, defaults to 0. ''' xmax = NumericProperty(100.) '''The x-axis maximum value, larger than xmin. :data:`xmax` is a :class:`~kivy.properties.NumericProperty`, defaults to 0. ''' xlog = BooleanProperty(False) '''Determines whether the x-axis should be displayed logarithmically (True) or linearly (False). :data:`xlog` is a :class:`~kivy.properties.BooleanProperty`, defaults to False. ''' x_ticks_major = BoundedNumericProperty(0, min=0) '''Distance between major tick marks on the x-axis. Determines the distance between the major tick marks. Major tick marks start from min and re-occur at every ticks_major until :data:`xmax`. If :data:`xmax` doesn't overlap with a integer multiple of ticks_major, no tick will occur at :data:`xmax`. Zero indicates no tick marks. If :data:`xlog` is true, then this indicates the distance between ticks in multiples of current decade. E.g. if :data:`xmin` is 0.1 and ticks_major is 0.1, it means there will be a tick at every 10th of the decade, i.e. 0.1 ... 0.9, 1, 2... If it is 0.3, the ticks will occur at 0.1, 0.3, 0.6, 0.9, 2, 5, 8, 10. You'll notice that it went from 8 to 10 instead of to 20, that's so that we can say 0.5 and have ticks at every half decade, e.g. 0.1, 0.5, 1, 5, 10, 50... Similarly, if ticks_major is 1.5, there will be ticks at 0.1, 5, 100, 5,000... Also notice, that there's always a major tick at the start. Finally, if e.g. :data:`xmin` is 0.6 and this 0.5 there will be ticks at 0.6, 1, 5... :data:`x_ticks_major` is a :class:`~kivy.properties.BoundedNumericProperty`, defaults to 0. ''' x_ticks_minor = BoundedNumericProperty(0, min=0) '''The number of sub-intervals that divide x_ticks_major. Determines the number of sub-intervals into which ticks_major is divided, if non-zero. The actual number of minor ticks between the major ticks is ticks_minor - 1. Only used if ticks_major is non-zero. If there's no major tick at xmax then the number of minor ticks after the last major tick will be however many ticks fit until xmax. If self.xlog is true, then this indicates the number of intervals the distance between major ticks is divided. The result is the number of multiples of decades between ticks. I.e. if ticks_minor is 10, then if ticks_major is 1, there will be ticks at 0.1, 0.2...0.9, 1, 2, 3... If ticks_major is 0.3, ticks will occur at 0.1, 0.12, 0.15, 0.18... Finally, as is common, if ticks major is 1, and ticks minor is 5, there will be ticks at 0.1, 0.2, 0.4... 0.8, 1, 2... :data:`x_ticks_minor` is a :class:`~kivy.properties.BoundedNumericProperty`, defaults to 0. ''' x_grid = BooleanProperty(False) '''Determines whether the x-axis has tick marks or a full grid. If :data:`x_ticks_major` is non-zero, then if x_grid is False tick marks will be displayed at every major tick. If x_grid is True, instead of ticks, a vertical line will be displayed at every major tick. :data:`x_grid` is a :class:`~kivy.properties.BooleanProperty`, defaults to False. ''' x_grid_label = BooleanProperty(False) '''Whether labels should be displayed beneath each major tick. If true, each major tick will have a label containing the axis value. :data:`x_grid_label` is a :class:`~kivy.properties.BooleanProperty`, defaults to False. ''' xlabel = StringProperty('') '''The label for the x-axis. If not empty it is displayed in the center of the axis. :data:`xlabel` is a :class:`~kivy.properties.StringProperty`, defaults to ''. ''' ymin = NumericProperty(0.) '''The y-axis minimum value. If :data:`ylog` is True, ymin must be larger than zero. :data:`ymin` is a :class:`~kivy.properties.NumericProperty`, defaults to 0. ''' ymax = NumericProperty(100.) '''The y-axis maximum value, larger than ymin. :data:`ymax` is a :class:`~kivy.properties.NumericProperty`, defaults to 0. ''' ylog = BooleanProperty(False) '''Determines whether the y-axis should be displayed logarithmically (True) or linearly (False). :data:`ylog` is a :class:`~kivy.properties.BooleanProperty`, defaults to False. ''' y_ticks_major = BoundedNumericProperty(0, min=0) '''Distance between major tick marks. See :data:`x_ticks_major`. :data:`y_ticks_major` is a :class:`~kivy.properties.BoundedNumericProperty`, defaults to 0. ''' y_ticks_minor = BoundedNumericProperty(0, min=0) '''The number of sub-intervals that divide ticks_major. See :data:`x_ticks_minor`. :data:`y_ticks_minor` is a :class:`~kivy.properties.BoundedNumericProperty`, defaults to 0. ''' y_grid = BooleanProperty(False) '''Determines whether the y-axis has tick marks or a full grid. See :data:`x_grid`. :data:`y_grid` is a :class:`~kivy.properties.BooleanProperty`, defaults to False. ''' y_grid_label = BooleanProperty(False) '''Whether labels should be displayed beneath each major tick. If true, each major tick will have a label containing the axis value. :data:`y_grid_label` is a :class:`~kivy.properties.BooleanProperty`, defaults to False. ''' ylabel = StringProperty('') '''The label for the y-axis. If not empty it is displayed in the center of the axis. :data:`ylabel` is a :class:`~kivy.properties.StringProperty`, defaults to ''. ''' padding = NumericProperty('5dp') '''Padding distances between the labels, titles and graph, as well between the widget and the objects near the boundaries. :data:`padding` is a :class:`~kivy.properties.NumericProperty`, defaults to 5dp. ''' font_size = NumericProperty('15sp') '''Font size of the labels. :data:`font_size` is a :class:`~kivy.properties.NumericProperty`, defaults to 15sp. ''' precision = StringProperty('%g') '''Determines the numerical precision of the tick mark labels. This value governs how the numbers are converted into string representation. Accepted values are those listed in Python's manual in the "String Formatting Operations" section. :data:`precision` is a :class:`~kivy.properties.StringProperty`, defaults to '%g'. ''' draw_border = BooleanProperty(True) '''Whether a border is drawn around the canvas of the graph where the plots are displayed. :data:`draw_border` is a :class:`~kivy.properties.BooleanProperty`, defaults to True. ''' plots = ListProperty([]) '''Holds a list of all the plots in the graph. To add and remove plots from the graph use :data:`add_plot` and :data:`add_plot`. Do not add directly edit this list. :data:`plots` is a :class:`~kivy.properties.ListProperty`, defaults to []. ''' class Plot(EventDispatcher): '''Plot class, see module documentation for more information. ''' # this function is called by graph whenever any of the parameters # change. The plot should be recalculated then. # log, min, max indicate the axis settings. # size a 4-tuple describing the bounding box in which we can draw # graphs, it's (x0, y0, x1, y1), which correspond with the bottom left # and top right corner locations, respectively. def _update(self, xlog, xmin, xmax, ylog, ymin, ymax, size): pass # returns a string which is unique and is the group name given to all the # instructions returned by _get_drawings. Graph uses this to remove # these instructions when needed. def _get_group(self): return '' # returns a list of canvas instructions that will be added to the graph's # canvas. These instructions must belong to a group as described # in _get_group. def _get_drawings(self): return [] class MeshLinePlot(Plot): '''MeshLinePlot class which displays a set of points similar to a mesh. ''' # mesh which forms the plot _mesh = ObjectProperty(None) # color of the plot _color = ObjectProperty(None) _trigger = ObjectProperty(None) # most recent values of the params used to draw the plot _params = DictProperty({'xlog': False, 'xmin': 0, 'xmax': 100, 'ylog': False, 'ymin': 0, 'ymax': 100, 'size': (0, 0, 0, 0)}) def __init__(self, **kwargs): self._color = Color(1, 1, 1, group='LinePlot%d' % id(self)) self._mesh = Mesh(mode='line_strip', group='LinePlot%d' % id(self)) super().__init__(**kwargs) self._trigger = Clock.create_trigger(self._redraw) self.bind(_params=self._trigger, points=self._trigger) def _update(self, xlog, xmin, xmax, ylog, ymin, ymax, size): self._params = {'xlog': xlog, 'xmin': xmin, 'xmax': xmax, 'ylog': ylog, 'ymin': ymin, 'ymax': ymax, 'size': size} def _redraw(self, *args): points = self.points mesh = self._mesh vert = mesh.vertices ind = mesh.indices params = self._params funcx = log10 if params['xlog'] else lambda x: x funcy = log10 if params['ylog'] else lambda x: x xmin = funcx(params['xmin']) ymin = funcy(params['ymin']) diff = len(points) - len(vert) / 4 size = params['size'] ratiox = (size[2] - size[0]) / float(funcx(params['xmax']) - xmin) ratioy = (size[3] - size[1]) / float(funcy(params['ymax']) - ymin) if diff < 0: del vert[4 * len(points):] del ind[len(points):] elif diff > 0: ind.extend(range(len(ind), len(ind) + diff)) vert.extend([0] * (diff * 4)) for k in range(len(points)): vert[k * 4] = (funcx(points[k][0]) - xmin) * ratiox + size[0] vert[k * 4 + 1] = (funcy(points[k][1]) - ymin) * ratioy + size[1] mesh.vertices = vert def _get_group(self): return 'LinePlot%d' % id(self) def _get_drawings(self): return [self._color, self._mesh] def _set_mode(self, value): self._mesh.mode = value mode = AliasProperty(lambda self: self._mesh.mode, _set_mode) '''VBO Mode used for drawing the points. Can be one of: 'points', 'line_strip', 'line_loop', 'lines', 'triangle_strip', 'triangle_fan'. See :class:`~kivy.graphics.Mesh` for more details. Defaults to 'line_strip'. ''' def _set_color(self, value): self._color.rgba = value color = AliasProperty(lambda self: self._color.rgba, _set_color) '''Plot color, in the format [r, g, b, a] with values between 0-1. Defaults to [1, 1, 1, 1]. ''' points = ListProperty([]) '''List of x, y points to be displayed in the plot. The elements of points are 2-tuples, (x, y). The points are displayed based on the mode setting. :data:`points` is a :class:`~kivy.properties.ListProperty`, defaults to []. ''' class MeshStemPlot(MeshLinePlot): '''MeshStemPlot uses the MeshLinePlot class to draw a stem plot. The data provided is graphed from origin to the data point. ''' def _redraw(self, *args): points = self.points mesh = self._mesh self._mesh.mode = 'lines' vert = mesh.vertices ind = mesh.indices params = self._params funcx = log10 if params['xlog'] else lambda x: x funcy = log10 if params['ylog'] else lambda x: x xmin = funcx(params['xmin']) ymin = funcy(params['ymin']) diff = len(points) * 2 - len(vert) / 4 size = params['size'] ratiox = (size[2] - size[0]) / float(funcx(params['xmax']) - xmin) ratioy = (size[3] - size[1]) / float(funcy(params['ymax']) - ymin) if diff < 0: del vert[4 * len(points):] del ind[len(points):] elif diff > 0: ind.extend(range(len(ind), len(ind) + diff)) vert.extend([0] * (diff * 4)) for k in range(len(points)): vert[k * 8] = (funcx(points[k][0]) - xmin) * ratiox + size[0] vert[k * 8 + 1] = (0 - ymin) * ratioy + size[1] vert[k * 8 + 4] = (funcx(points[k][0]) - xmin) * ratiox + size[0] vert[k * 8 + 5] = (funcy(points[k][1]) - ymin) * ratioy + size[1] mesh.vertices = vert if __name__ == '__main__': from math import sin, cos from kivy.app import App class TestApp(App): def build(self): graph = Graph(xlabel='Cheese', ylabel='Apples', x_ticks_minor=5, x_ticks_major=25, y_ticks_major=1, y_grid_label=True, x_grid_label=True, padding=5, xlog=False, ylog=False, x_grid=True, y_grid=True, xmin=-50, xmax=50, ymin=-1, ymax=1) plot = MeshLinePlot(color=[1, 0, 0, 1]) plot.points = [(x / 10., sin(x / 50.)) for x in range(-500, 501)] graph.add_plot(plot) plot = MeshLinePlot(color=[0, 1, 0, 1]) plot.points = [(x / 10., cos(x / 50.)) for x in range(-600, 501)] graph.add_plot(plot) plot = MeshLinePlot(color=[0, 0, 1, 1]) graph.add_plot(plot) plot.points = [(x, x / 50.) for x in range(-50, 51)] return graph TestApp().run() plyer-2.1.0/examples/accelerometer/using_graph/libs/garden/garden.graph/graph.png000066400000000000000000001425641433372044000301570ustar00rootroot00000000000000PNG  IHDR.}\sRGBgAMA a pHYsod IDATx^]u$ws$_>;ߍMpl8Mل jtL1PCH(BA4 P zc]pk9s>}{_ff̏}j{W_}^zw?) 1` .ફw?SOz⭷xǛowx82@1~BoekxǫW^鎟wӢR\^ʍAbxUbnפxWص:BOHT  hHLa4;x+yANF/Bī%т̖&D~䄴%una!mvQ|wu3c&xjA}!HСC馛&N'.\|w^}bqw\qf-9ŌO1/33f WΜyĬY "-e'c6is&Mu['L%ǏǸ+n/cp%,.qx\wE^x!c!?ѣŘuyWw|u3G1bƴs:lؔ!C0& 1Y 8ᬳ .0`g^v(Ɵqg 1aIg1`Ҁq3Ϝq N?1O; 2SOSx\rɗ'ctE'(ƅ'c?qA\cI1G= Fy$yGqᇏbD߾֫r衃{t!g|0T]*STAi/`c1$/CzdqP-P6;^ 1O}BEaiAIcTvDkT@̎ΐNVK y: )+ Ro]'u>8iXyb* %*X^ǭ'ӇE߾A${፭?∑GɾF=>b4?>LXQq'|i]ӟ^ g5|@AL<b!Ӈ1lsΙ9|8|5|8#F1⊑#8+[qy]yyWѣ3Fk]=hkf}?]m[to{cOzRN+&aN<#Mb1.<{LX+[1ɴ[iWB60]Cr ]!!e^*PŠ< 6U Cܪ1p;ݫ; kxHaHs'5:G y )1bW&|tD,dx1.dT1 IȆGBf*6 .ғNSO lqU2p66 l%daEBf6 Y`c"!b}+X,d@1x;׾bS1ADp(Ym "@f׶mzfFkoҍ^q֫7z&o4(..8絛1/8 |tFňhc…1ncI_,A+C#Dpٳ!Vx㊛n={ 7]+WB&X+$1g['NKb a . B00a_\vXap &IƴŰ4 k_ < 뒟NFIdUg 54 }+ kKe0Hd`<,0.0i%z/AȾvU6d i`<$xR"AZPD6c%F .\5RC0(o}aHY[*vubcq1Íj@D"Q3-_ Yu m ٱǂkc'p,10m,ME2fcѥ22Ye-!ccx UNcr&WP1N0,QTl̈\Be`bdzeˆ'Ӟzڮk=N{i/_7릾O}Ŕ~Vxϣ#vT8 g^^߿}'k_/w¶E&*}a 0C/:^릛V|[nY3gΚn;շܲ`l^p8t28,^cB/IqqtxULV10sɶ.q=,,5*QD/%a\0X¢S Ɲz*%qO B_ǃia*~ q%a2EQ ViK*_Ca0#V+1:ժBƣS0$xRemhc14wrB) )+ Ro]'9qpLqq%鈖qb6ֻ73-Xp1-| KX(dhc11XHl Κ,:ژ$dhcL8dhf\X22񯭋d`cb/<̎uv{/cz96lx 7nعi:oذ[eK&=ⴌ< bK>;>/mXȌČOkQkƿ>벟ϺYACq3xƿapg3>9c3^Ϸ)غ;ԧ^~ZT1lYQtBx OF820v ,20ЯՑ~u3oL`{-R-X,፫nl7-:H@?cƼFBaI*6gDvabu{El8Q$ˡYD5*I~" aÆAM<aHHu'; O>:)\|бP"% L0f`- 2$h`Y + D"+⢉K)!dʈN*0[ x|)R}a mcJD!9^!HR!Mhyi*F!N[!%!6S Ğg#%bР_z 1~d/xĎGdx|r=_\c8(4Ic4Ic4v/N ^tk.x}ԳΖiҤ P#?剉c;z5Q@;$ ec{x]WZ(\/5n؂`Zs%.ݼlX ~iɒ=ohjQ,cuH.cѩb0T1~>.q;|=_ C k{Xt=lRŰ+a [̺@\W0ip^}Sc K؅QT2ʷW"+)_CD@4HȾvU6d /1$xR"cʪ*!-˸`H 9Z&:J+0xfJ]!7@XbJQJCMyЮUK?89vCc>:vc<5vcdĊOBƾ!EɄYD*_c'kE$ z|ر:b\Ab,xd駹WtxGOώxO)?>zx#IQp)9pȁG:rtWXFŮfժgu8Q{315s氏#Ͽw [qskq]dk[V29nqYtu,o46}W= B]Kz؇^}1W$C<,RYQ51n $] /0Q +aX$a̺@N8$ bǟ> B65{c'ī-ao-a =+%;/rU uGҬ"!+WvtU L.BcHyHɗ 1]S/i B01ZkTH!!ZC\!MO!60f]!R1 Q0m,2Xz%$XvDB&1B66&}mS.;4ۤmc)WE22uPp-9r2pW֭wt!nE1nc魷1gβ9s@vnr'z@_`/#x-[}޷]z!!6uHvN{hisӞ~sOn!&r*?S|=89;aYې[4^P1-D@C[yX>K\;Xl=mY/ "a66{6Z5߿v1ëb%109am Qb*qe~"{Xt=l|00a)5?}qkWT4oQWox|2U0D ۀb")_Catpa Ӯʆl`<:%L -:|61i"20 iz ې!T noV@J֖0!DÐ EC<鵮Ƣż}XXBB;d =eu2veS:>l؊yƞy0.<,p\8ǧ4/2䲡C>u&1s!&wޔQ-oDGO3C|=s<>w\1҂o}iѷ[_\/?}}g}ώy3.y0?Goswc?.| 3֗6_0[gLZbsn*-bw-[e v=ثmk>x`]mrkN^>MKnX/u cW]%,^U,ws 9auZn &Jda.&KX늗(awm@Ŏ=xߊ %, DK0/J ejVO2"SUV76֩VB0NC -!N!5x$4--!dC ^åB20 i gÐRCl!b=/Mh IŸ1_o=^gDA F1~m l,Xd/Z?D!C'B>BDBfvd}Tl9O;M1iz <Lԍ wpá{@.:aGq9_R'"8e6~7}m[ {~_/V|k?un;G?3<}7GGXgg+>??:m=k{o=l񧱊l,U@кᇕLŢ_pGVm_fLn_|k a j c&w ٸx Anu%12V1~U,=o< #/ƈ]t$L9XbXPDwyD1e`~1-axsXKΰKҮ1$ 1@{[l, Yd`A #X`@Y0/ɖHXZ6ƍG2ܐ+#0I,4:|H0- d C^åB 0QP4I[!YÐ[*vobG(5hch`\ji=k09>}}yo⛟_}so}nG+.>IS|}DG[~}|s~l3l+b ^\x_I0xB.}Ivزe[/gYs5b>d0xdKXEl,T66$lE $dhc(d\˞{P^xW`{N:qϹxpKXt˜u?&;fyCL=IG⬟~8)V.X8s~y?_`%?|sXs~ko!m>t-;tQ\ܷ?8C\{GX?KNJ#cőutA[}xq܁wΔ~b߅x ;ں먃+d3.8gNUQBa= #|m@ *2Wr1ؿ\)*֎6.}F9{v=oJ^18S(̜]$h{Fax%_C ΋a7ar `:HyVTZqN~?_ +GGh=+GZV ;plGY6VqUE#VnȾjU6daXh Iw|)R1%6Ʒgܰ}CvpfABH(kaHY[*ubϋC(.k\ÚK`qlcpHH 2FXEѯaO*?(dadLZWw.d,Z6!:1yG&wQALbL=brS\,/v%YX1#z~pY&̺\jĕN)\> Ÿ~Xtը/y,I?dkN{)}מlgK>^A⑤owBk,ﳵ?ϺSʓAp]_LP1| c/xfIzOWWܾzߠ\n ;pݠem/ݶbno(6LN H$U ֿ빇Gm0.P?&Hv *1 ng7I>lKP=%aiX*57a %O1^Tÿql!sh?S}E.ŷE76ƅ Z瞁?V/\zW\qt!'nr□'7q##x|qOd^%ɓoֲrb֖0~=,sH 3 p+IK.uHD2AH~A"J0 7gHc)Z at/!eC\dŅP\7%@LHʕjU!d40[ u<)acJ0x i$DM`tb19 )%bKNy,i(q!p*& b\0Ά)$_pR !shdz;']"Ib- p!cc _! IB 7uׁ_'poܲmz6ލb'Ďڱa3GKܿa3M[ܼ c'Ėmm;xdi1_ʎmO>g?=w3X<7oy_⥅CWx+^ x (^ fϛ,9[̾?bk-[/Kbp(G@ŦO7u*X%($dba0JFXDDa}5I[],|"n ¿Iq1%Et5Bhx#^!c6vlaj0X(ElH+,. +.+-:ժlC001$R楢Xǔ=7~u 4 Bb-G)H}mHY͔zC:!R40BR1ưfp"oat51vm ?J!Ëd!`cL2iҎu놞t!|?3|_/_* x~o?s{>{?|>G|9/bcz _7~pd?˟>{w|'eI~ܳ`0de[6W}c l c}ѿD =LaĿ(n<9U_0g<ݪ/xDaba?7wX`V[{.R!,e{U/nB#;c Y2ӮJl`= E!YR)wEkc8@!CU #WS WxEC_USc=lip&N2_!cNƆFGN[$&(JxOtKƝ>x5dXΎ`OrOd6ɓo2eԩM=` 1kb\qEF$VH-C= i6&+͞|7ݴ*5+d'[" b ޸/j+'auuDbX]rI]yxX|= IY|sXTPP\Ű_1;@¢a0J04Hł˩J4{TF˖dTe"VJBZaqI+).DeuG2"!+WZtzU% !Ɛt/ 7%6%nոs7u pfABHSkfE!*Ð)ubϋǗ!p$Ú&Cs\|^aiU W{Xؠlca_!Kɢ_C'C!cIƜ, ]W1'kQPrm솋/mn.0lln% YH1. !Ij6&XwTW]F7/暥]l @VtӊoƋdnnԲ.X; ^[ꘄEDm_cKrk]WDcwwU}&a1 < .eR&zX?(0a\0v &JX|gX$aøJq+q+nK?,OfIjU00,/0aCI$O$ŋa;a<@$~$a% Clx ?Efo1kcͲ!|IeK2O2"!VFj!,RHb%$?KE-b?:h)bi.t4źgVt9 )%1Ɗ"| J*bưx00ĩ*H2\aǛp1{N`cT/u YWaQb-w~zdNB6&:Yt xy 혭cB/1'`cW1~mm-CcSlLT10d&d]&(a*d_$cɢXL΢@9kfuyt=&2-X] kFIG$a=bDCI~1 =0?!;(s{j2%,~< IŰ$lG'&azX JŃN6!Vb 䇰bVi*_CaU/IU$dJ qyakX u)R!]c[Ɗ1dB^CrHkfE߈!Y!T n%c6aRxai_!q1gl u/\d sn!k9YE_adu2hlg3X(d[Bmlft,Mhc*l]r~a, $= ÑP|3 O3`cwD4/EX0CIQDѾaQpcKbԨ&FaLa_] cJ HvH*0a0C *M70T. GlG$Z+eŃoBX4F" +_Iq%ŎULʕT0Z-$BrCJTX>T9 )1 bW&v4^888\ŸU  ncs\^agU΅X{q!Ccw@m,2 1&d_[\ KB21%1'ë+$.dEl ?o1ncL _Rn!D,HIbu'<:0Z3g{1;."!am 8`/1DA3]^dg"9l:8"fcэB_c3; 1YFhcdx P6Ѻ?dN?ecE.'hcaT 쯻Uecx!a<(hcxphTg$ؓ;vz܈LD`Vᇟ=~!gv̈x*|))xI1#v숿8!ض[1ز(޼dnŃ׷#GcZ܃qwCl.mk`l]:U0\bŊM[Ѿ_/hd_϶Gb{ܰa'FkxSUn]|nm۶a-7h[g=j`Qds6E"Y'OU ^4KW(((((((((4K/l,A@#  4ܣfآ+$# 0xX]fbAA&*m,V1}+H  L SH  ̓bbAAQ1R1  CAA ¨ٟل ?/~  ?*#Tl7lT  FTGtA*vWUٳg7L*FAD@JP1xèqC*FAD*V;f͊T   AtجY!ZZ GLXR60 hLFidT,9{X쵇EؓNY~}H ʴj@,k9 u V; j KmV 54I-[HIq=<0~L +tt?԰ZȱȪe :I*BqJB-2T,Pq]34]g6*V:Tl̙TL#*=B"g&.b~gJ$eT /^+B7*VvPLhP"{M0Tg~T(h)Hk!R%Re$d BP FR 5wZa*Ȅ!QYfT3 Hň$h&eepp0"BMU1x H4S)8P R75e@[ºT,0R1(YU݅@2%c>*X~*Tpb U?jdREpaWi9*Up9P4VUL}lb 5UbG[0q##Vo+ ^y1lj SAXU1UdZ%fx*6s&QrLG'!k. 5_ŴT{ZNB{*e+@WS:# #tk^8pzyq(P`|2=aӲĘ*N*"*6}: PIň,uu鈙AXU1]5DL AK*q5* P"X Z fHQ(Hň,4!43EŔ,bcV<P5 UWpWjxp6VyӦ=CbD2z{tjQ1Aƾ؀9WpT㰤bz 5\T["U1 z{Z[F*D U0VL *}hf*DzX mUP{HňlLLrg.IN*Qfv8G*5iRŀ@ [83uxU7m~@ _IňdMt(fK Q2VdZ*T-P1Ce/THΦ**oU #>6+pRQJB5pH6 *fnt/U259xp6VSY3GFLxJr*W 5)PH8*N2#"1{7炊:\kRU 0pʔ,T*V&rPc\ P1qSGb3gHR1*Ftc5\ۤb͢d-+݅`2Z3m(fU[fjxTsY3T#Excԩ%itWźOEB*]&)UjzR1t!>>־uZEE1bcSaL'FEe t13ծb(MqbLFcP LS Ӵi*mLP1 R1BZY8HŚBBRN$|P`JŬ g'GU m,Vīb;wm-s*1S*)aJR<&E8SbZY˴҉<; &"bx=mS@ X7sA$XSTe؟:YG[Z6&]#8Щ‘)XQ(Q V UW:LS @?}:Xn8V1P1*F ЩI36RPUI(tOII'uH!TC*v7޸%R1r͞T,= U<ʟɔ*u5*`bĪbb&'U1=SK.ٙS1ՂEt0 6U,)E0aPUPT1PW&NtU`@T XQ+T(yjLBfs8%O4S*!^r 2R1p:Y"m5 "Vw{nr5 bӱHU b']oG™RD*C+T+yRD"E0%XÁ;-< #S PBy2Ұ)$KIjB5bP2ՆΫ9T1 iRޭؕS2xWfDmc6StUxh)ӱxT,%S|TL)BQ*VNMppTă]{rItX*5IjB5bޖ;?hP@VTjXBUbxW@W(XC18蚎2b G}ZbDUZ&haBU+lmS11R]g/pbuCk,Ĵf*"ߕXLMW'P@Em2=ʧIeXaZZV %]ˣS 5XzBTRb/Ybon3e;w5 qei*_5b >T(0%S\ɼb?REu*XeS%S*Q TBͦ *&I{8Tă**S>Cb"},b! BNbᬨQP^׬="X<3;{oOA5 P:/hӏB%#Ҩ9ÀZX|X[oS͘ kl6}Tɘ)T#ΜB͠3hHzx*Tk5k^dcbO rTIjB5Q dZe K"m~U1R5b2%2B5Ч&ʃM)§*) P2UT1.P{@\q Xk(-XM0SƝeȴ6Uŕ)^PUPPF*,XshJ߉3-?f:ԄU+TWPQ;„Xy7Q[GU1J+b"ygZ~:cjJŌ%*4㎙xkba ]`_P~؂+b "yX>aR:`PI6uQ1IN+~*'[*_T콷ZԺ*#5p|!8+)^)BM*@!&Dxbe$]U10F* B%s& T 4Lma6 WW^PBD* eZ*ܣ5IT NƺRoyCPbt%VbigJZ `WyDTl2T)LUȴt$ J*P&A BYsP; UEI*V™{ bacPͪ5BM9s0p$dFŞ 9dt "Rw]u+o7Sb'W&yR1\bH8o~wV?mTE<@v?o`ItS_9sVAz+ċbM GZdJ$a5Gr2ѨPUŰ,T%QTi7Xv5O՟'^IpU1v`R4Jg PO2UPzj孷K`QD*{wϛb\yT@oC߹yU*Tr*fĒəRDE<@^|շ!bgϿ'2RgF 5ѭ,qaPI8T>@dH-x'"LŞy%lܹw͝Tב]3>K*Vs™ubcP& Ue<HI*X$aw͛غ qX =nX/T]&"L-၊qu:p,U{**ҳ=>H|T;Y,ZnѢW{Tc5P™];* paZP:K zgvlX/^;+v՜zLb  J*Ɓ* $SKP18͓ +G !S`*s``K`L/6,]a Rcd!'bѹ,qa㴔B*Tr* R!+S*2(Vi$ő-۴l|xIiLMGRPRb"T".T es 5CS_d ^Ul<^۽T5߲2-dBbV*T*m|@k%fO2UPW}ebۺjՖ+!6X /9CBRZ U Ed3 doyTݻů+X,_T7նի!F/ՙp@烊˔JP1vbfOzזBXbuV쥗HL&[B%'SU1.6PjDE<"^hwo 7V'2-0}P1 %tzy*TOBV1xg~h$?S*¨ض5kxD*_X=@@P#X*TR1 *TIY) Ћy(V|+ b_?p[_&-AʹL 4T,Hl%uP9TȺBguZg"X ZBXbn_+[B[B搊BU/] dj*/zqE3 d*cSW^ݺؽF*;7nda|}WIjK8FkD dJ/K*U ^EwP"Xo ^baCbmMRSi"R Ub݄Y XxBՋx0{Wt Ȗ-}S1W GX塽P}#LMRwgȮj(P3e5bP&%B΅ĕ8A5m6Őģ\Ÿwu>K*V,צFɤbP˖q䲄)0Pb 8J KģLV<ùw*&>E*V mvMGoU+/*6h&'ߠU 4xBR1Y BW15͜B& wJ*K8 iBjKT,&Blf*G)qJP-bk}gI]*3!ds|TCF$ںryGmb)RNGhδs:zb@8KauXE ᪘0һF9>x*ę$9'RŜ6* dgIb4 6ǧ[<#DbbPIŊkbDY/͙6tGqa$Ė+IͪTT8-RaP}ƿL1#I d9DQ,A*V;*֙iSq .U  tU1*T tGqZbMmTxK'vR1qZbEP1鐊 PkvGqPFy$1YؽbbɫvR1qZtzBCfbM,*.ţ8*VbT -YJT~U hzNT 3@B5rP#Xn(W1ncmè,qfgT~R1q]UuPtSF:T(%GAHCg|R1q]bqT;pf7}$X #e$ӆ7?D(H{7T&XGeH(3.aP=EpBT@Tk(U CV1z@ `z\ŨPݶ߱%~fbTv2M>(RC{HżuVx*ӢC& 4Q< UX[Ÿm% hy 0zsY BLq2٥ zD(HT rtRL[O*/*XqgAAb{ЯXGDCP11R g̴5bAW1: & WGU,12i8`ģXTAOPsN  T82{ bP't&E kᴜT he<:QWyJ*Vid R1 %KXՀpL*Ʊpxm.Rv 5*e . b X %GXTxx'~R1OPP1;\Q4C##L)S#3TcI&tCC T,p\9{?l_8*Vm,g(TI*v@rԿP@TA:8eEjHزezHB8srb~Ybt$J# T, B-"ibm\la cbuF lƂT 0'&HŒppʊ˴xTl!:W/1R`tC*e2TX4bT"(ֆK9cʖ{,X؄B˙bdZZ藊&2 x6GV0WE# bXSTxB'F^K XDտPIŲl"g`("S%Kr0F*VT/<4s6b[RU'K%7iHk%g|01A0HB$4,jyEU j*P4 xTl?~Lp3+gJ*w*fp'sb8;qi jAHDT10֥bK*{-u7 MU1 }eĥ1iAH1RZ` @* MT("8:4N9gꐨmb9TꢂY?!1RZNgiykw*<71o0-iPD5K 4{Jdk4FŰtVV 5jXL G"AՂ@!  Q^A))Uab93&2-( O8 sip*iIkXT [cA.TUq:>} LjAHPυ#$SR1_P}T1mlJ= F*ӗDnHHC.:-^}U ud.TR&2-.݈*6G**& bBF9 06aVHrsTэbbNdil*V=eKmL6i2q>& xtbjb&=LIbeb@ jjjU*/'u"3-.ݠ&X`2\u~^K}/;F*UcpReh5T,p_{ţRp4U!NI*Ŕ*W/M5.TR|~nU *mvYK*SR1/PW1H^,*j*EzD!j)*b׬! pfLIżBTŊ&>*X>^$Q홖 R1;Z.Wj$LBhX>^$Q홖 RpᴇTdZ _ӭb^Lya@N!TAڻxHH*8>!˃T |-TOU HïҨ[ \ż/TЛi) 4p^K=_=(g@T1Ɛ"כi) NcfdZ_#GSJ1 %;*X!~ bK4Zŀ,hWfJ*{_aRM} 㳊yT^fz x яHjA8sabPU8/I%RB%+Gw"bbpbiV1LǗ GeQ*9ʹx1hXP- 7}]Fh.-!+J KN++"bpaX8qKT1,Hdm(곊U,Բ!"DULxV*PYOpأBLgKHŊH3-+"XH:[J]R1xbypl@p Z iIaZM<bJ؇~{Ã[N>| _o~ zDbi;CW1 L>u,}SkSZ )uRr4PTl2%۹s^;i$p|3L=~_OPGB]8Zzs)3`O]atI I{bK^'ׁB,ؐC6VZžoֱϜs9֭N#.T9#Bդbx35v`\IaZY<bJX77O}*{v8é*FhzX[a5U-9ԬPIurhɴxXs*p!9<zi!t#NM*V@*-XY< *Ǐ?dZ 'NB[AŰyVZؤ)y{bB xaV+駟VAGv85*'N8իԥd+XESTEVcV"oG1~7lׇ1aj = |P@eER`U "[VZuQGDsOM mgq>䪘[34WFvKsX N*S+@ŸV|/c=N4iһ;p}5Ͻ޽{̘1xر->VAGKI{b_U8~Y%3QR4PUCT1۶zu5I̝;ҥK.\UPW&Q2duPP QW+j>ᤫR**6o*wݏ>۷ܳg N}{)m.TL./$k';LDI*ҌBUI l0xwyw5jT*=3_ŨciBN7b&GU$tU2U=*'^M#pz!3jU !x*<ǎM&L$NKRǪW1**'^M#HeHq^eZ' #SLM(0#/KT,E UE<@(x5J *ap۫L iģbL3 (Լ,IŪNƕ3UTUW1?jA*\ R1ҙUJjI @y)U"AV1?jAJ@)iT  S/R/C|!jPTi8ޢ"TLO@G8W![bVq?pz$0SR1Ci^X~5F8IW.T*QEIR[#-SR18~87P 0T *XuIr*TlJbUCTzC;.|B{-u.\dsI {#N8IWTE_b{M^K>C5gJ*f TLpRxTW1C۷6bꐊYGsއb7bRANVU?ԩS|ͷ~*="oRЀOTfx\XŀZp6(*ڟD UE<$b'?;W 66W{-"F: b)9-bN2UbViS1N >PlzV^H졿U<(Q<ʹXZbԺPUUlԑGjV1􈈸@{i%gS1Lu3RPb%BubbI8 P*A* G8gb9p hj3 RSXZbԺPUăT,pfs-RWxR1]bӀ Xb*Y HŌcs"SsWBWW1ncbX62gT13U dYbL \Y  UEv)]QINcJe"LVNP1bbe!s <0H,K% ӆ;@nWRBR6/s5V1_2mKFiJŀ:p)C g/F UESx9ZʟJ*f7V=+~݊ x*6F*EVHź*{PBEBDճWpTU)LUăTUjEiXpLI, Ӫ$O UE<$ B߮U8_ s* dojQbqiSF"b]9O)XD߈B PI, ӪbمZidnըXil/Tj(6^ptU(ßiMT,pV=%~jT*A*V G]*hC8=)e̔c: =C*pxT, R1H0{3 0v`d*elgp>R1#sx&i3I(N QL;>Fb@]v8(ϲQBUR䎇fh=Ս^ވC҆'}#;SR1j.Kx裎"KDa"WS4LN] T>V3U8R*A*Vܥ\'VOD86E`@@%GHc5ST1S545vTăT pj4Lb0{"B8ßi}T,A!&S."Y*A* X7aI^`kP͔T>*ijv*A*V! "Ώ >!LD*f@AQ| W6(N5V1H ơLIBLP զj"bUJj {0%}#7SR1+T3 xRbM2Jjک=$0ƥFJ*KT(T B]Np2eKŠ!i +iB%sBjMŊj"bpSdʨw<HLkb )vR1SԢPUăT"ÉXZF5au?4pz$PEdJ* T48#S p"bvs0 UHo9F 3H HШXElM )#p+wn8b@;6aMZQ~XE2CA^`t:脳oɔT 6 Ub QR(Oj0U tOz*+g|R1_*A*Vzu@daWcDR1W_vTLiK8N# م# 'ӘT ΤTHLb )|R1_*A*V n6ϡz*T`bPI N2URTH~ sLuT,%i"b1[6N8xbp\TmG7)`:)XuS1 )QVTm^*③bw6zZ#c=S͎Q8FLI\aPuT,/&*A*VS;Z=᫊a_NcTB%sLuT,xUN`r '6^V41SHU8m T, H70iT{ T!2t\R1$xd1R4Lp#NmP17LM/D5UkAB\Qur㡁ѹV)3x՞35R3%s hpb ݙG{HҀs? 1r4|8U1]fXbnPIŌrG׏X+Ծp20*ԂHjZ™R kJE}m]T,&pn{mWtO[4PKeJ*;"qLU#Uņ6F*.![8ǖJ1yҲ|J*[gb6d_,HUsz6T,PT0T ~=:*i5jZT1 _hZ]Q5¢"b-bB^<^=l]1!sLTxذ^mYT, ;$Le v}y d,;bPIllG =0ncI2Y9e wI.=*"9GsgT™R #RR1 T ^B-BmU,!PIlNGHG5Cm^T,fp:[m{-26GTbљV 2Ud{e׮={r#FJΰWoeXf)[bљVY"E2R1ncH2C R1hkײKbM*"Vŀ0w8%tˬ'EEڶz5xTT){-=T3{\ŀ@FPPUߟ! }E5EhKxiCu7d}􀤽ֲ`hcYHA)S$Jh"*v~A*VNV 'T:Zl6pT$fVTR1PMTi8b UET1 ͎5{(iJb^Q%Sc#M8iDfpNP*ƦF<&dNT T+djubdׁq$:MWZZ^hֵPLb@ { ]™Rj1bbxMtfD]b@lFFL5TA*X!X78#L!A6R CJgH T7)T*XM!nSUc!Fu :;S̮Pbj"MQp26ITjP*A*Vp7u,^[ ]?J*pI%lTLr G pLvD'!2 ]J*25.R17HbC*J He=]zIŐr6Pb@(;\B-rUpkB)T O *Ikk9yHLM6T xW⥎,SLbFbOv8*xRULPD(xh3QʅTCMT,a-1L>R17Y*A*fp~t$e k8-RϔTC<)TR17Y*A*fR1`gi/R1j7bR4S-"s;P.*A*f,Bf } 6b6Pb@ #[P"U,)!xPupf&緱ld73%Bn3B/ 3BЋ]FL*& -B*'>.* RR1SW} .2B9-DT,-4 ;*H{C]"*]9TL:p׷n_ n6: n8mђiT,PIŜB)HŪb_HDP%H$?Sm!s}*A*f}aqle 2>pb"=h)fB_PE}Gq#@PPpf|7dJ*-9oK5Q11R p9?uN jT[* }^SR1dU}<3cC?Y R1 *TƩX8C38]*H{"*A*Vzp&7ai ]yB%s"bcUlҥb K H$`S3 Z LT1fv#]W1ncbȚr~LWvӐIdeڼBmrVfLZT,)~x$kqW<^[Q 2%L4T1~x*6F*.ޡ5Sp7abAgj7aR1_7UăTrLf$ԅ6h0)X0XOT#S `[RIPTXS4JH4z 'L "4Mdt3ԭځ$IŚݪ"\0T TLpmkCqʸkw}/R1Rp rb_-x~Y]פbFhw߸aPb@K@8*Ą  UE< &R1u億kI^oNR0 5R1˙VV1xH*A*fhsryyfHŲ?SsJ*FF* ;ijUR1+ *'NK A*JkT?b;W#QZ.j*_g"bVhU7;d$t{:VK+C\_\]2L^ŌA*uxYAϤ`ꐊL*QyO"bVhiOMuzpzRHR1*Pb3 *yGO"big:A*ϙ-TR1jeU WEbUăTuxsOA'X|}5B*Lgn>gjPIb]jY+PUăTjgiZ˴UăT0]Z&;ļT,tPvQJŚ:ƛBUR1$MGÕ3n~RT,'SŘj$eePI|ěVR1$UɅW~&pV$eE*hLwt5)@/TaJLįjR1K(13SӅJ*EiR欨UăT0I%b`@J~pT@>;TM ɔTSk 3IVrOS R1d R65?[Ec{WcP5C*f 5jq="b<2Y0HŪ6d0x]w'Ld&Ov T*TR1UăTyFʁ#47-/ӺA*AH:п(Y )T:]Eg)y{g Z;\B"KB-VF3-bλ8} 3@ ^Y9 gZ+Hb/Z+TR?GX3WTw7*G C(,6i>Ŕp2UT,M5"* exLsUa'1DE69!3SWQn0 QwvQ(!)y02bPXcWTw5 {"bG޽!HJSt:‹=A*(? )TRd,y*X8R1 莵{]l*xEuv8Wu_{-TB-Bm˅J* %|GQHQuR1TE OX{+89'x:Y8ʟUXRT\j3i9"'#aTŘ92g%+eZkHJ,O T,T*; F$JXQ*~ %~4U`*ѯ)eRTF3*TR0?p: *9P? @N*w8gZZ3_bYX 5CŠc,O " G%jbH8?Iq{B-D T,@ 0iay<-!G¾CqM*6/TR,l?p:B%<$QţXR )߽¡PB3uXbRLT c#,UxTLh\_tD6檘qotx|-nsXD>G0Y<%Y8h&;߽TZF3uXb!}H RXx*,:< k.nȆT U_yi̴[*Q#Թ&GaP=pR`/<Ԃѝi4TŨPA*Vï&B% at:k+A< UۘbP1mG18sG Z&B5E + ]G*$ 5A< 4GW90t}5pPT( UwTR1C@h8u-UQ RђVxbgard$MT1*%iEThtRT1JPuUUxCT1.ӧ&*V}#b(ʎG0[ oiQVR*Xu PH§BX# T{z[Ű;[?0jeAX Pe Xu`,KUB-uf̔T>(tkSaW??0a~O$G X|<};Yd;AT S**X}/{qxBT1*aS~zÆ o| O~ǚT 0wH2`4D6X PB H4NʔTnh-N(^6X8{ѡb1#~Q9ŚJf*Yyi B-)X WxUlaqYvH,SO {B5 p%J=J*VO(_-Seˆ6V] Q=џ;@c؁N3: GVL(?H ,T@4eJQy[HIPņv1*ʅ߷g*XD/ei<)H+TQjQ,>ѭ}E} e# b%AAD 9H  TlRP11R1  {H*ዊAAT={r## W1ncM*FAaT!*6o_Ulܸq{{5f̘{ﭷ:㧺("\//\o}M7_%>Q6"TM B "_;ncm6桊]}wutW^~|"E^Cw ~}{{nf|>lD<--D0TlѢEӦM;/^-[v뭷c"!擟dh=wyGErژ*iӦ#FKGcǎ}gHV7xGȀ B´r*6&*SO׿~/7OO?Ogyc"!jömn|V6"TM "Z4 !D*/~qw} G]xSO=Ã?Xkzp '|_۴hPiТAtcZ9 ؖ+X{ { ̚5_L)tg4,",,s@B,*&@ -D.&#v{:7 >޽{1cرc[|,R5DX!ǧ?W_}uƌ]e#B2UBӠE( F2~a|)"h;woOu!GE( UŦMv3裏N:$|.|{a?ٳ'<٧Op޽{gP tG`ooIJh PhVPUowU?GQ? N7 4O,*fB !hAAR1  gAA8#EŞ~D>bAAF!# pAA3TAA rT mT  bAAW1R1  R˗AAhT gϞ~AA5 'm۶_Oӧ_|1>b \PņtR1 ᤓN؎;fϞEҞ 6W>}1ADY*/9',Yfȑ_W>}߆4;?裁~_ٳ'\7s9 _‚ {챌AC##Od<w}w͚54N=ԗ_~K/Կ٤?O[Wƍ |J A*6&|@AA H  !Db{"# 0JAAAT  Y*mT  *jK0H  L@*FAT;H  b̙sK)((((((((4ƈt-5cƀm^`lY";kMlYؘKfĆ%KăcbQs6cǮ\ cժⱭT^m3Yc:3R7&GkDpt:B<:k8 QbeY*)EۣSĢliռsNK YHR q! TxP~6xFŐ*|XHY>U+'/.y¬%J 1,WWGNJDb2î<׾6m)g-,&<#Fҋcxn̴֫Wv =1CtE(1vJx=gO8/d8 =ۯ߹qG:HGU$}t88Z{ln\pqC:cGH G!h!Fu' >R p68`1RjX,Ύ銸VAJ@%Gg c`}bἐj\pn~פbQ;Tz h0G@ڐ|1C3ZB=$(T( N<͜\xH WZxN}!Q:25kIb/ RXOp .0:<7q ^UCvR ~u'3N\PpsmQCzqb`e_R 4#U0[Zw$-pQP6R)FKĈ4ҦKaBt1Txj 5$=Q ȭ@i4G F'ZX@+aK eC\u!DBH/m,a[S͊% CZC̈T!.(-haEaat/?ĺ-| `YJ"A\Ė+:t~S3&O?cF_kM~nN+1'ߓRA)|*~-)BB$qciF$mw8NpvH?'U& i*| i4!%,m!k'[Á Šk+D,.X- QX$E\<%_BV}B{ἐjk}a;ynvG=S~hR]쀂E Z&ɽ/ˢU!XT1EhD璅!Z!V`![hf*Ү M!_u[2!.u8qqĮ.Pf!VZGRBX!i7}:^'O>3>l]jڐ!U W%Mū;W&nHY!.CIb|*|KK iyurG!&'8; i*6Үi"3CV7*&z5Q|qIU H:T8bbɃLRĵD9:8G^<5vyן xvG[S~Ҁ0c/9V%b!Tj1%{N1e1^3U)nK6'|08:bW is@+R5 !dUL8pˊ^ `{]wu]v~Sg^~?Zؽ;+ LRڕvd!N~(ASBA G|4xVԓC̰"Obڃ[!銨]b3Y=PfX -.P޽{Nշ3붬IENDB`plyer-2.1.0/examples/accelerometer/using_graph/main.py000066400000000000000000000052401433372044000231020ustar00rootroot00000000000000''' This example uses Kivy Garden Graph addon to draw graphs plotting the accelerometer values in X,Y and Z axes. The package is installed in the directory: ./libs/garden/garden.graph To read more about kivy garden, visit: http://kivy-garden.github.io/. ''' import kivy from kivy.app import App from kivy.uix.boxlayout import BoxLayout from kivy.uix.popup import Popup from kivy.clock import Clock from plyer import accelerometer from kivy.garden.graph import MeshLinePlot kivy.require('1.8.0') class AccelerometerDemo(BoxLayout): def __init__(self): super().__init__() self.sensorEnabled = False self.graph = self.ids.graph_plot # For all X, Y and Z axes self.plot = [] self.plot.append(MeshLinePlot(color=[1, 0, 0, 1])) # X - Red self.plot.append(MeshLinePlot(color=[0, 1, 0, 1])) # Y - Green self.plot.append(MeshLinePlot(color=[0, 0, 1, 1])) # Z - Blue self.reset_plots() for plot in self.plot: self.graph.add_plot(plot) def reset_plots(self): for plot in self.plot: plot.points = [(0, 0)] self.counter = 1 def do_toggle(self): try: if not self.sensorEnabled: accelerometer.enable() Clock.schedule_interval(self.get_acceleration, 1 / 20.) self.sensorEnabled = True self.ids.toggle_button.text = "Stop Accelerometer" else: accelerometer.disable() self.reset_plots() Clock.unschedule(self.get_acceleration) self.sensorEnabled = False self.ids.toggle_button.text = "Start Accelerometer" except NotImplementedError: popup = ErrorPopup() popup.open() def get_acceleration(self, dt): if (self.counter == 100): # We re-write our points list if number of values exceed 100. # ie. Move each timestamp to the left. for plot in self.plot: del plot.points[0] plot.points[:] = [(i[0] - 1, i[1]) for i in plot.points[:]] self.counter = 99 val = accelerometer.acceleration[:3] if not val == (None, None, None): self.plot[0].points.append((self.counter, val[0])) self.plot[1].points.append((self.counter, val[1])) self.plot[2].points.append((self.counter, val[2])) self.counter += 1 class AccelerometerDemoApp(App): def build(self): return AccelerometerDemo() def on_pause(self): return True class ErrorPopup(Popup): pass if __name__ == '__main__': AccelerometerDemoApp().run() plyer-2.1.0/examples/audio/000077500000000000000000000000001433372044000155645ustar00rootroot00000000000000plyer-2.1.0/examples/audio/buildozer.spec000066400000000000000000000130351433372044000204410ustar00rootroot00000000000000[app] # (str) Title of your application title = Plyer Audio Example # (str) Package name package.name = audioexample # (str) Package domain (needed for android/ios packaging) package.domain = org.test # (str) Source code where the main.py live source.dir = . # (list) Source files to include (let empty to include all the files) source.include_exts = py,png,jpg,kv,atlas # (list) Source files to exclude (let empty to not exclude anything) #source.exclude_exts = spec # (list) List of directory to exclude (let empty to not exclude anything) #source.exclude_dirs = tests, bin # (list) List of exclusions using pattern matching #source.exclude_patterns = license,images/*/*.jpg # (str) Application versioning (method 1) #version.regex = __version__ = ['"](.*)['"] #version.filename = %(source.dir)s/main.py # (str) Application versioning (method 2) version = 0.1 # (list) Application requirements # comma seperated e.g. requirements = sqlite3,kivy requirements = kivy,plyer # (str) Custom source folders for requirements # Sets custom source for any requirements with recipes # requirements.source.kivy = ../../kivy # (list) Garden requirements #garden_requirements = # (str) Presplash of the application #presplash.filename = %(source.dir)s/data/presplash.png # (str) Icon of the application #icon.filename = %(source.dir)s/data/icon.png # (str) Supported orientation (one of landscape, portrait or all) orientation = portrait # (bool) Indicate if the application should be fullscreen or not fullscreen = 0 # # Android specific # # (list) Permissions android.permissions = RECORD_AUDIO,WAKE_LOCK # (int) Android API to use #android.api = 14 # (int) Minimum API required (8 = Android 2.2 devices) #android.minapi = 8 # (int) Android SDK version to use #android.sdk = 21 # (str) Android NDK version to use #android.ndk = 9c # (bool) Use --private data storage (True) or --dir public storage (False) #android.private_storage = True # (str) Android NDK directory (if empty, it will be automatically downloaded.) #android.ndk_path = # (str) Android SDK directory (if empty, it will be automatically downloaded.) #android.sdk_path = # (str) python-for-android git clone directory (if empty, it will be automatically cloned from github) #android.p4a_dir = # (list) python-for-android whitelist #android.p4a_whitelist = # (str) Android entry point, default is ok for Kivy-based app #android.entrypoint = org.renpy.android.PythonActivity # (list) List of Java .jar files to add to the libs so that pyjnius can access # their classes. Don't add jars that you do not need, since extra jars can slow # down the build process. Allows wildcards matching, for example: # OUYA-ODK/libs/*.jar #android.add_jars = foo.jar,bar.jar,path/to/more/*.jar # (list) List of Java files to add to the android project (can be java or a # directory containing the files) #android.add_src = # (str) python-for-android branch to use, if not master, useful to try # not yet merged features. #android.branch = master # (str) OUYA Console category. Should be one of GAME or APP # If you leave this blank, OUYA support will not be enabled #android.ouya.category = GAME # (str) Filename of OUYA Console icon. It must be a 732x412 png image. #android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png # (str) XML file to include as an intent filters in tag #android.manifest.intent_filters = # (list) Android additionnal libraries to copy into libs/armeabi #android.add_libs_armeabi = libs/android/*.so #android.add_libs_armeabi_v7a = libs/android-v7/*.so #android.add_libs_x86 = libs/android-x86/*.so #android.add_libs_mips = libs/android-mips/*.so # (bool) Indicate whether the screen should stay on # Don't forget to add the WAKE_LOCK permission if you set this to True #android.wakelock = False # (list) Android application meta-data to set (key=value format) #android.meta_data = # (list) Android library project to add (will be added in the # project.properties automatically.) #android.library_references = # # iOS specific # # (str) Name of the certificate to use for signing the debug version # Get a list of available identities: buildozer ios list_identities #ios.codesign.debug = "iPhone Developer: ()" # (str) Name of the certificate to use for signing the release version #ios.codesign.release = %(ios.codesign.debug)s [buildozer] # (int) Log level (0 = error only, 1 = info, 2 = debug (with command output)) log_level = 1 # (int) Display warning if buildozer is run as root (0 = False, 1 = True) warn_on_root = 1 # ----------------------------------------------------------------------------- # List as sections # # You can define all the "list" as [section:key]. # Each line will be considered as a option to the list. # Let's take [app] / source.exclude_patterns. # Instead of doing: # #[app] #source.exclude_patterns = license,data/audio/*.wav,data/images/original/* # # This can be translated into: # #[app:source.exclude_patterns] #license #data/audio/*.wav #data/images/original/* # # ----------------------------------------------------------------------------- # Profiles # # You can extend section / key with a profile # For example, you want to deploy a demo version of your application without # HD content. You could first change the title to add "(demo)" in the name # and extend the excluded directories to remove the HD content. # #[app@demo] #title = My Application (demo) # #[app:source.exclude_patterns@demo] #images/hd/* # # Then, invoke the command line with the "demo" profile: # #buildozer --profile demo android debug plyer-2.1.0/examples/audio/main.py000066400000000000000000000045151433372044000170670ustar00rootroot00000000000000from kivy.app import App from kivy.lang import Builder from kivy.properties import NumericProperty from kivy.properties import ObjectProperty from kivy.uix.boxlayout import BoxLayout Builder.load_string(''' #:import audio_player plyer.audio : audio: audio_player orientation: 'vertical' padding: '50dp' spacing: '20dp' Label: id: state_label size_hint_y: None height: sp(40) text: 'AudioPlayer State: ' + str(root.audio.state) Label: id: location_label size_hint_y: None height: sp(40) text: 'Recording Location: ' + str(root.audio.file_path) Button: id: record_button text: 'Start Recording' on_release: root.start_recording() Button: id: play_button text: 'Play' on_release: root.play_recording() ''') class AudioInterface(BoxLayout): '''Root Widget.''' audio = ObjectProperty() time = NumericProperty(0) has_record = False def start_recording(self): state = self.audio.state if state == 'ready': self.audio.start() if state == 'recording': self.audio.stop() self.has_record = True self.update_labels() def play_recording(self): state = self.audio.state if state == 'playing': self.audio.stop() else: self.audio.play() self.update_labels() def update_labels(self): record_button = self.ids['record_button'] play_button = self.ids['play_button'] state_label = self.ids['state_label'] state = self.audio.state state_label.text = 'AudioPlayer State: ' + state play_button.disabled = not self.has_record if state == 'ready': record_button.text = 'Start Recording' if state == 'recording': record_button.text = 'Press to Stop Recording' play_button.disabled = True if state == 'playing': play_button.text = 'Stop' record_button.disabled = True else: play_button.text = 'Press to play' record_button.disabled = False class AudioApp(App): def build(self): return AudioInterface() def on_pause(self): return True if __name__ == "__main__": AudioApp().run() plyer-2.1.0/examples/barometer/000077500000000000000000000000001433372044000164435ustar00rootroot00000000000000plyer-2.1.0/examples/barometer/buildozer.spec000066400000000000000000000152351433372044000213240ustar00rootroot00000000000000[app] # (str) Title of your application title = Plyer Barometer Example # (str) Package name package.name = plyer.barometer # (str) Package domain (needed for android/ios packaging) package.domain = org.test # (str) Source code where the main.py live source.dir = . # (list) Source files to include (let empty to include all the files) source.include_exts = py,png,jpg,kv,atlas # (list) List of inclusions using pattern matching #source.include_patterns = assets/*,images/*.png # (list) Source files to exclude (let empty to not exclude anything) #source.exclude_exts = spec # (list) List of directory to exclude (let empty to not exclude anything) #source.exclude_dirs = tests, bin # (list) List of exclusions using pattern matching #source.exclude_patterns = license,images/*/*.jpg # (str) Application versioning (method 1) version = 0.1 # (str) Application versioning (method 2) # version.regex = __version__ = ['"](.*)['"] # version.filename = %(source.dir)s/main.py # (list) Application requirements # comma seperated e.g. requirements = sqlite3,kivy requirements = kivy, plyer # (str) Custom source folders for requirements # Sets custom source for any requirements with recipes #requirements.source.plyer = # (list) Garden requirements #garden_requirements = # (str) Presplash of the application #presplash.filename = %(source.dir)s/data/presplash.png # (str) Icon of the application #icon.filename = %(source.dir)s/data/icon.png # (str) Supported orientation (one of landscape, portrait or all) orientation = portrait # (list) List of service to declare #services = NAME:ENTRYPOINT_TO_PY,NAME2:ENTRYPOINT2_TO_PY # # OSX Specific # # # author = © Copyright Info # # Android specific # # (bool) Indicate if the application should be fullscreen or not fullscreen = 0 # (list) Permissions #android.permissions = INTERNET # (int) Android API to use #android.api = 19 # (int) Minimum API required #android.minapi = 9 # (int) Android SDK version to use #android.sdk = 20 # (str) Android NDK version to use #android.ndk = 9c # (bool) Use --private data storage (True) or --dir public storage (False) #android.private_storage = True # (str) Android NDK directory (if empty, it will be automatically downloaded.) #android.ndk_path = # (str) Android SDK directory (if empty, it will be automatically downloaded.) #android.sdk_path = # (str) ANT directory (if empty, it will be automatically downloaded.) #android.ant_path = # (str) python-for-android git clone directory (if empty, it will be automatically cloned from github) #android.p4a_dir = # (str) The directory in which python-for-android should look for your own build recipes (if any) #p4a.local_recipes = # (list) python-for-android whitelist #android.p4a_whitelist = # (bool) If True, then skip trying to update the Android sdk # This can be useful to avoid excess Internet downloads or save time # when an update is due and you just want to test/build your package # android.skip_update = False # (str) Bootstrap to use for android builds (android_new only) # android.bootstrap = sdl2 # (str) Android entry point, default is ok for Kivy-based app #android.entrypoint = org.renpy.android.PythonActivity # (list) List of Java .jar files to add to the libs so that pyjnius can access # their classes. Don't add jars that you do not need, since extra jars can slow # down the build process. Allows wildcards matching, for example: # OUYA-ODK/libs/*.jar #android.add_jars = foo.jar,bar.jar,path/to/more/*.jar # (list) List of Java files to add to the android project (can be java or a # directory containing the files) #android.add_src = # (str) python-for-android branch to use, if not master, useful to try # not yet merged features. #android.branch = master # (str) OUYA Console category. Should be one of GAME or APP # If you leave this blank, OUYA support will not be enabled #android.ouya.category = GAME # (str) Filename of OUYA Console icon. It must be a 732x412 png image. #android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png # (str) XML file to include as an intent filters in tag #android.manifest.intent_filters = # (list) Android additionnal libraries to copy into libs/armeabi #android.add_libs_armeabi = libs/android/*.so #android.add_libs_armeabi_v7a = libs/android-v7/*.so #android.add_libs_x86 = libs/android-x86/*.so #android.add_libs_mips = libs/android-mips/*.so # (bool) Indicate whether the screen should stay on # Don't forget to add the WAKE_LOCK permission if you set this to True #android.wakelock = False # (list) Android application meta-data to set (key=value format) #android.meta_data = # (list) Android library project to add (will be added in the # project.properties automatically.) #android.library_references = # (str) Android logcat filters to use #android.logcat_filters = *:S python:D # (bool) Copy library instead of making a libpymodules.so #android.copy_libs = 1 # # iOS specific # # (str) Path to a custom kivy-ios folder #ios.kivy_ios_dir = ../kivy-ios # (str) Name of the certificate to use for signing the debug version # Get a list of available identities: buildozer ios list_identities #ios.codesign.debug = "iPhone Developer: ()" # (str) Name of the certificate to use for signing the release version #ios.codesign.release = %(ios.codesign.debug)s [buildozer] # (int) Log level (0 = error only, 1 = info, 2 = debug (with command output)) log_level = 2 # (int) Display warning if buildozer is run as root (0 = False, 1 = True) warn_on_root = 1 # (str) Path to build artifact storage, absolute or relative to spec file # build_dir = ./.buildozer # (str) Path to build output (i.e. .apk, .ipa) storage # bin_dir = ./bin # ----------------------------------------------------------------------------- # List as sections # # You can define all the "list" as [section:key]. # Each line will be considered as a option to the list. # Let's take [app] / source.exclude_patterns. # Instead of doing: # #[app] #source.exclude_patterns = license,data/audio/*.wav,data/images/original/* # # This can be translated into: # #[app:source.exclude_patterns] #license #data/audio/*.wav #data/images/original/* # # ----------------------------------------------------------------------------- # Profiles # # You can extend section / key with a profile # For example, you want to deploy a demo version of your application without # HD content. You could first change the title to add "(demo)" in the name # and extend the excluded directories to remove the HD content. # #[app@demo] #title = My Application (demo) # #[app:source.exclude_patterns@demo] #images/hd/* # # Then, invoke the command line with the "demo" profile: # #buildozer --profile demo android debug plyer-2.1.0/examples/barometer/main.py000066400000000000000000000033411433372044000177420ustar00rootroot00000000000000from kivy.app import App from kivy.clock import Clock from kivy.lang import Builder from kivy.properties import NumericProperty from kivy.properties import ObjectProperty from kivy.uix.boxlayout import BoxLayout Builder.load_string(''' #:import barometer plyer.barometer : barometer: barometer orientation: 'vertical' padding: '50dp' spacing: '20dp' BoxLayout: orientation: 'horizontal' size_hint_y: 0.3 Button: id: button_enable text: 'Enable' disabled: False on_release: root.enable() button_disable.disabled = not button_disable.disabled button_enable.disabled = not button_enable.disabled Button: id: button_disable text: 'Disable' disabled: True on_release: root.disable() button_disable.disabled = not button_disable.disabled button_enable.disabled = not button_enable.disabled Label: text: 'Current pressure:' + str(root.pressure) + ' hPa.' ''') class BarometerInterface(BoxLayout): '''Root Widget.''' barometer = ObjectProperty() pressure = NumericProperty() def enable(self): self.barometer.enable() Clock.schedule_interval(self.get_pressure, 1 / 20.) def disable(self): self.barometer.disable() Clock.unschedule(self.get_pressure) def get_pressure(self, dt): self.pressure = self.barometer.pressure or self.pressure class BarometerApp(App): def build(self): return BarometerInterface() def on_pause(self): return True if __name__ == "__main__": BarometerApp().run() plyer-2.1.0/examples/battery/000077500000000000000000000000001433372044000161355ustar00rootroot00000000000000plyer-2.1.0/examples/battery/buildozer.spec000066400000000000000000000130301433372044000210050ustar00rootroot00000000000000[app] # (str) Title of your application title = Plyer Battery Example # (str) Package name package.name = batteryexample # (str) Package domain (needed for android/ios packaging) package.domain = org.test # (str) Source code where the main.py live source.dir = . # (list) Source files to include (let empty to include all the files) source.include_exts = py,png,jpg,kv,atlas # (list) Source files to exclude (let empty to not exclude anything) #source.exclude_exts = spec # (list) List of directory to exclude (let empty to not exclude anything) #source.exclude_dirs = tests, bin # (list) List of exclusions using pattern matching #source.exclude_patterns = license,images/*/*.jpg # (str) Application versioning (method 1) #version.regex = __version__ = ['"](.*)['"] #version.filename = %(source.dir)s/main.py # (str) Application versioning (method 2) version = 1.0 # (list) Application requirements # comma seperated e.g. requirements = sqlite3,kivy requirements = kivy,plyer # (str) Custom source folders for requirements # Sets custom source for any requirements with recipes # requirements.source.kivy = ../../kivy # (list) Garden requirements #garden_requirements = # (str) Presplash of the application #presplash.filename = %(source.dir)s/data/presplash.png # (str) Icon of the application #icon.filename = %(source.dir)s/data/icon.png # (str) Supported orientation (one of landscape, portrait or all) orientation = portrait # (bool) Indicate if the application should be fullscreen or not fullscreen = 0 # # Android specific # # (list) Permissions android.permissions = BATTERY_STATS # (int) Android API to use #android.api = 14 # (int) Minimum API required (8 = Android 2.2 devices) #android.minapi = 8 # (int) Android SDK version to use #android.sdk = 21 # (str) Android NDK version to use #android.ndk = 9c # (bool) Use --private data storage (True) or --dir public storage (False) #android.private_storage = True # (str) Android NDK directory (if empty, it will be automatically downloaded.) #android.ndk_path = # (str) Android SDK directory (if empty, it will be automatically downloaded.) #android.sdk_path = # (str) python-for-android git clone directory (if empty, it will be automatically cloned from github) #android.p4a_dir = # (list) python-for-android whitelist #android.p4a_whitelist = # (str) Android entry point, default is ok for Kivy-based app #android.entrypoint = org.renpy.android.PythonActivity # (list) List of Java .jar files to add to the libs so that pyjnius can access # their classes. Don't add jars that you do not need, since extra jars can slow # down the build process. Allows wildcards matching, for example: # OUYA-ODK/libs/*.jar #android.add_jars = foo.jar,bar.jar,path/to/more/*.jar # (list) List of Java files to add to the android project (can be java or a # directory containing the files) #android.add_src = # (str) python-for-android branch to use, if not master, useful to try # not yet merged features. #android.branch = master # (str) OUYA Console category. Should be one of GAME or APP # If you leave this blank, OUYA support will not be enabled #android.ouya.category = GAME # (str) Filename of OUYA Console icon. It must be a 732x412 png image. #android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png # (str) XML file to include as an intent filters in tag #android.manifest.intent_filters = # (list) Android additionnal libraries to copy into libs/armeabi #android.add_libs_armeabi = libs/android/*.so #android.add_libs_armeabi_v7a = libs/android-v7/*.so #android.add_libs_x86 = libs/android-x86/*.so #android.add_libs_mips = libs/android-mips/*.so # (bool) Indicate whether the screen should stay on # Don't forget to add the WAKE_LOCK permission if you set this to True #android.wakelock = False # (list) Android application meta-data to set (key=value format) #android.meta_data = # (list) Android library project to add (will be added in the # project.properties automatically.) #android.library_references = # # iOS specific # # (str) Name of the certificate to use for signing the debug version # Get a list of available identities: buildozer ios list_identities #ios.codesign.debug = "iPhone Developer: ()" # (str) Name of the certificate to use for signing the release version #ios.codesign.release = %(ios.codesign.debug)s [buildozer] # (int) Log level (0 = error only, 1 = info, 2 = debug (with command output)) log_level = 1 # (int) Display warning if buildozer is run as root (0 = False, 1 = True) warn_on_root = 1 # ----------------------------------------------------------------------------- # List as sections # # You can define all the "list" as [section:key]. # Each line will be considered as a option to the list. # Let's take [app] / source.exclude_patterns. # Instead of doing: # #[app] #source.exclude_patterns = license,data/audio/*.wav,data/images/original/* # # This can be translated into: # #[app:source.exclude_patterns] #license #data/audio/*.wav #data/images/original/* # # ----------------------------------------------------------------------------- # Profiles # # You can extend section / key with a profile # For example, you want to deploy a demo version of your application without # HD content. You could first change the title to add "(demo)" in the name # and extend the excluded directories to remove the HD content. # #[app@demo] #title = My Application (demo) # #[app:source.exclude_patterns@demo] #images/hd/* # # Then, invoke the command line with the "demo" profile: # #buildozer --profile demo android debug plyer-2.1.0/examples/battery/main.py000066400000000000000000000022371433372044000174370ustar00rootroot00000000000000from kivy.app import App from kivy.uix.boxlayout import BoxLayout from kivy.lang import Builder from kivy.properties import ObjectProperty from plyer import battery Builder.load_string(''' : lbl1: lbl1 lbl2: lbl2 FloatLayout: Button: size_hint_y: None pos_hint: {'y': .5} text: "Battery Status" on_press: root.get_status() BoxLayout: size_hint_y: None pos_hint: {'y': .1} Label: text: "Is Charging?" Label: id: lbl1 text: Label: text: "Percentage" Label: id: lbl2 text: ''') class BatteryInterface(BoxLayout): lbl1 = ObjectProperty() lbl2 = ObjectProperty() def get_status(self, *args): self.lbl1.text = str(battery.status['isCharging']) self.lbl2.text = str(battery.status['percentage']) + "%" class BatteryApp(App): def build(self): return BatteryInterface() def on_pause(self): return True if __name__ == "__main__": app = BatteryApp() app.run() plyer-2.1.0/examples/bluetooth/000077500000000000000000000000001433372044000164705ustar00rootroot00000000000000plyer-2.1.0/examples/bluetooth/buildozer.spec000066400000000000000000000152261433372044000213510ustar00rootroot00000000000000[app] # (str) Title of your application title = Bluetooth # (str) Package name package.name = plyerbluetooth # (str) Package domain (needed for android/ios packaging) package.domain = org.test # (str) Source code where the main.py live source.dir = . # (list) Source files to include (let empty to include all the files) source.include_exts = py,png,jpg,kv,atlas # (list) List of inclusions using pattern matching #source.include_patterns = assets/*,images/*.png # (list) Source files to exclude (let empty to not exclude anything) #source.exclude_exts = spec # (list) List of directory to exclude (let empty to not exclude anything) #source.exclude_dirs = tests, bin # (list) List of exclusions using pattern matching #source.exclude_patterns = license,images/*/*.jpg # (str) Application versioning (method 1) version = 0.1 # (str) Application versioning (method 2) # version.regex = __version__ = ['"](.*)['"] # version.filename = %(source.dir)s/main.py # (list) Application requirements # comma seperated e.g. requirements = sqlite3,kivy requirements = kivy,plyer # (str) Custom source folders for requirements # Sets custom source for any requirements with recipes # requirements.source.kivy = ../../kivy # (list) Garden requirements #garden_requirements = # (str) Presplash of the application #presplash.filename = %(source.dir)s/data/presplash.png # (str) Icon of the application #icon.filename = %(source.dir)s/data/icon.png # (str) Supported orientation (one of landscape, portrait or all) orientation = portrait # (list) List of service to declare #services = NAME:ENTRYPOINT_TO_PY,NAME2:ENTRYPOINT2_TO_PY # # OSX Specific # # # author = © Copyright Info # # Android specific # # (bool) Indicate if the application should be fullscreen or not fullscreen = 0 # (list) Permissions #android.permissions = INTERNET # (int) Android API to use #android.api = 19 # (int) Minimum API required #android.minapi = 9 # (int) Android SDK version to use #android.sdk = 20 # (str) Android NDK version to use #android.ndk = 9c # (bool) Use --private data storage (True) or --dir public storage (False) #android.private_storage = True # (str) Android NDK directory (if empty, it will be automatically downloaded.) #android.ndk_path = # (str) Android SDK directory (if empty, it will be automatically downloaded.) #android.sdk_path = # (str) ANT directory (if empty, it will be automatically downloaded.) #android.ant_path = # (str) python-for-android git clone directory (if empty, it will be automatically cloned from github) #android.p4a_dir = # (str) The directory in which python-for-android should look for your own build recipes (if any) #p4a.local_recipes = # (list) python-for-android whitelist #android.p4a_whitelist = # (bool) If True, then skip trying to update the Android sdk # This can be useful to avoid excess Internet downloads or save time # when an update is due and you just want to test/build your package # android.skip_update = False # (str) Bootstrap to use for android builds (android_new only) # android.bootstrap = sdl2 # (str) Android entry point, default is ok for Kivy-based app #android.entrypoint = org.renpy.android.PythonActivity # (list) List of Java .jar files to add to the libs so that pyjnius can access # their classes. Don't add jars that you do not need, since extra jars can slow # down the build process. Allows wildcards matching, for example: # OUYA-ODK/libs/*.jar #android.add_jars = foo.jar,bar.jar,path/to/more/*.jar # (list) List of Java files to add to the android project (can be java or a # directory containing the files) #android.add_src = # (str) python-for-android branch to use, if not master, useful to try # not yet merged features. #android.branch = master # (str) OUYA Console category. Should be one of GAME or APP # If you leave this blank, OUYA support will not be enabled #android.ouya.category = GAME # (str) Filename of OUYA Console icon. It must be a 732x412 png image. #android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png # (str) XML file to include as an intent filters in tag #android.manifest.intent_filters = # (list) Android additionnal libraries to copy into libs/armeabi #android.add_libs_armeabi = libs/android/*.so #android.add_libs_armeabi_v7a = libs/android-v7/*.so #android.add_libs_x86 = libs/android-x86/*.so #android.add_libs_mips = libs/android-mips/*.so # (bool) Indicate whether the screen should stay on # Don't forget to add the WAKE_LOCK permission if you set this to True #android.wakelock = False # (list) Android application meta-data to set (key=value format) #android.meta_data = # (list) Android library project to add (will be added in the # project.properties automatically.) #android.library_references = # (str) Android logcat filters to use #android.logcat_filters = *:S python:D # (bool) Copy library instead of making a libpymodules.so #android.copy_libs = 1 # # iOS specific # # (str) Path to a custom kivy-ios folder #ios.kivy_ios_dir = ../kivy-ios # (str) Name of the certificate to use for signing the debug version # Get a list of available identities: buildozer ios list_identities #ios.codesign.debug = "iPhone Developer: ()" # (str) Name of the certificate to use for signing the release version #ios.codesign.release = %(ios.codesign.debug)s [buildozer] # (int) Log level (0 = error only, 1 = info, 2 = debug (with command output)) log_level = 2 # (int) Display warning if buildozer is run as root (0 = False, 1 = True) warn_on_root = 1 # (str) Path to build artifact storage, absolute or relative to spec file # build_dir = ./.buildozer # (str) Path to build output (i.e. .apk, .ipa) storage # bin_dir = ./bin # ----------------------------------------------------------------------------- # List as sections # # You can define all the "list" as [section:key]. # Each line will be considered as a option to the list. # Let's take [app] / source.exclude_patterns. # Instead of doing: # #[app] #source.exclude_patterns = license,data/audio/*.wav,data/images/original/* # # This can be translated into: # #[app:source.exclude_patterns] #license #data/audio/*.wav #data/images/original/* # # ----------------------------------------------------------------------------- # Profiles # # You can extend section / key with a profile # For example, you want to deploy a demo version of your application without # HD content. You could first change the title to add "(demo)" in the name # and extend the excluded directories to remove the HD content. # #[app@demo] #title = My Application (demo) # #[app:source.exclude_patterns@demo] #images/hd/* # # Then, invoke the command line with the "demo" profile: # #buildozer --profile demo android debug plyer-2.1.0/examples/bluetooth/main.py000066400000000000000000000020041433372044000177620ustar00rootroot00000000000000from kivy.app import App from kivy.lang import Builder from kivy.properties import ObjectProperty from kivy.properties import StringProperty from kivy.uix.boxlayout import BoxLayout Builder.load_string(''' #:import bluetooth plyer.bluetooth : bluetooth: bluetooth orientation: 'vertical' padding: '50dp' spacing: '20dp' BoxLayout: orientation: 'horizontal' size_hint_y: 0.3 Button: text: 'Get Bluetooth status' on_release: root.get_info() Label: text: str(root.text) Label: text: str(root.info) ''') class BluetoothInterface(BoxLayout): '''Root Widget.''' info = ObjectProperty() text = StringProperty() text = "Bluetooth: " def get_info(self): self.info = str(self.bluetooth.info) class BluetoothApp(App): def build(self): return BluetoothInterface() def on_pause(self): return True if __name__ == "__main__": BluetoothApp().run() plyer-2.1.0/examples/brightness/000077500000000000000000000000001433372044000166335ustar00rootroot00000000000000plyer-2.1.0/examples/brightness/buildozer.spec000077500000000000000000000171621433372044000215200ustar00rootroot00000000000000[app] # (str) Title of your application title = Brightness # (str) Package name package.name = plyerbrightness # (str) Package domain (needed for android/ios packaging) package.domain = org.test # (str) Source code where the main.py live source.dir = . # (list) Source files to include (let empty to include all the files) source.include_exts = py,png,jpg,kv,atlas # (list) List of inclusions using pattern matching #source.include_patterns = assets/*,images/*.png # (list) Source files to exclude (let empty to not exclude anything) #source.exclude_exts = spec # (list) List of directory to exclude (let empty to not exclude anything) #source.exclude_dirs = tests, bin # (list) List of exclusions using pattern matching #source.exclude_patterns = license,images/*/*.jpg # (str) Application versioning (method 1) version = 0.1 # (str) Application versioning (method 2) # version.regex = __version__ = ['"](.*)['"] # version.filename = %(source.dir)s/main.py # (list) Application requirements # comma seperated e.g. requirements = sqlite3,kivy requirements = kivy,plyer # (str) Custom source folders for requirements # Sets custom source for any requirements with recipes # requirements.source.kivy = ../../kivy # (list) Garden requirements #garden_requirements = # (str) Presplash of the application #presplash.filename = %(source.dir)s/data/presplash.png # (str) Icon of the application #icon.filename = %(source.dir)s/data/icon.png # (str) Supported orientation (one of landscape, portrait or all) orientation = portrait # (list) List of service to declare #services = NAME:ENTRYPOINT_TO_PY,NAME2:ENTRYPOINT2_TO_PY # # OSX Specific # # # author = © Copyright Info # change the major version of python used by the app osx.python_version = 3 # Kivy version to use osx.kivy_version = 1.9.1 # # Android specific # # (bool) Indicate if the application should be fullscreen or not fullscreen = 0 # (string) Presplash background color (for new android toolchain) # Supported formats are: #RRGGBB #AARRGGBB or one of the following names: # red, blue, green, black, white, gray, cyan, magenta, yellow, lightgray, # darkgray, grey, lightgrey, darkgrey, aqua, fuchsia, lime, maroon, navy, # olive, purple, silver, teal. #android.presplash_color = #FFFFFF # (list) Permissions android.permissions = WRITE_SETTINGS # (int) Android API to use #android.api = 19 # (int) Minimum API required #android.minapi = 9 # (int) Android SDK version to use #android.sdk = 20 # (str) Android NDK version to use #android.ndk = 9c # (bool) Use --private data storage (True) or --dir public storage (False) #android.private_storage = True # (str) Android NDK directory (if empty, it will be automatically downloaded.) #android.ndk_path = # (str) Android SDK directory (if empty, it will be automatically downloaded.) #android.sdk_path = # (str) ANT directory (if empty, it will be automatically downloaded.) #android.ant_path = # (bool) If True, then skip trying to update the Android sdk # This can be useful to avoid excess Internet downloads or save time # when an update is due and you just want to test/build your package #android.skip_update = True # (str) Android entry point, default is ok for Kivy-based app #android.entrypoint = org.renpy.android.PythonActivity # (list) Pattern to whitelist for the whole project #android.whitelist = # (str) Path to a custom whitelist file #android.whitelist_src = # (str) Path to a custom blacklist file #android.blacklist_src = # (list) List of Java .jar files to add to the libs so that pyjnius can access # their classes. Don't add jars that you do not need, since extra jars can slow # down the build process. Allows wildcards matching, for example: # OUYA-ODK/libs/*.jar #android.add_jars = foo.jar,bar.jar,path/to/more/*.jar # (list) List of Java files to add to the android project (can be java or a # directory containing the files) #android.add_src = # (list) Android AAR archives to add (currently works only with sdl2_gradle # bootstrap) #android.add_aars = # (list) Gradle dependencies to add (currently works only with sdl2_gradle # bootstrap) #android.gradle_dependencies = # (str) python-for-android branch to use, defaults to master #p4a.branch = master # (str) OUYA Console category. Should be one of GAME or APP # If you leave this blank, OUYA support will not be enabled #android.ouya.category = GAME # (str) Filename of OUYA Console icon. It must be a 732x412 png image. #android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png # (str) XML file to include as an intent filters in tag #android.manifest.intent_filters = # (list) Android additionnal libraries to copy into libs/armeabi #android.add_libs_armeabi = libs/android/*.so #android.add_libs_armeabi_v7a = libs/android-v7/*.so #android.add_libs_x86 = libs/android-x86/*.so #android.add_libs_mips = libs/android-mips/*.so # (bool) Indicate whether the screen should stay on # Don't forget to add the WAKE_LOCK permission if you set this to True #android.wakelock = False # (list) Android application meta-data to set (key=value format) #android.meta_data = # (list) Android library project to add (will be added in the # project.properties automatically.) #android.library_references = # (str) Android logcat filters to use #android.logcat_filters = *:S python:D # (bool) Copy library instead of making a libpymodules.so #android.copy_libs = 1 # (str) The Android arch to build for, choices: armeabi-v7a, arm64-v8a, x86 android.arch = armeabi-v7a # # Python for android (p4a) specific # # (str) python-for-android git clone directory (if empty, it will be automatically cloned from github) #p4a.source_dir = # (str) The directory in which python-for-android should look for your own build recipes (if any) #p4a.local_recipes = # (str) Filename to the hook for p4a #p4a.hook = # (str) Bootstrap to use for android builds # p4a.bootstrap = sdl2 # # iOS specific # # (str) Path to a custom kivy-ios folder #ios.kivy_ios_dir = ../kivy-ios # (str) Name of the certificate to use for signing the debug version # Get a list of available identities: buildozer ios list_identities #ios.codesign.debug = "iPhone Developer: ()" # (str) Name of the certificate to use for signing the release version #ios.codesign.release = %(ios.codesign.debug)s [buildozer] # (int) Log level (0 = error only, 1 = info, 2 = debug (with command output)) log_level = 2 # (int) Display warning if buildozer is run as root (0 = False, 1 = True) warn_on_root = 1 # (str) Path to build artifact storage, absolute or relative to spec file # build_dir = ./.buildozer # (str) Path to build output (i.e. .apk, .ipa) storage # bin_dir = ./bin # ----------------------------------------------------------------------------- # List as sections # # You can define all the "list" as [section:key]. # Each line will be considered as a option to the list. # Let's take [app] / source.exclude_patterns. # Instead of doing: # #[app] #source.exclude_patterns = license,data/audio/*.wav,data/images/original/* # # This can be translated into: # #[app:source.exclude_patterns] #license #data/audio/*.wav #data/images/original/* # # ----------------------------------------------------------------------------- # Profiles # # You can extend section / key with a profile # For example, you want to deploy a demo version of your application without # HD content. You could first change the title to add "(demo)" in the name # and extend the excluded directories to remove the HD content. # #[app@demo] #title = My Application (demo) # #[app:source.exclude_patterns@demo] #images/hd/* # # Then, invoke the command line with the "demo" profile: # #buildozer --profile demo android debug plyer-2.1.0/examples/brightness/main.py000077500000000000000000000015341433372044000201370ustar00rootroot00000000000000from kivy.app import App from kivy.uix.boxlayout import BoxLayout from kivy.lang import Builder from plyer import brightness Builder.load_string(''' : orientation: 'vertical' Label: text: 'Adjust the slider to increase \\n or decrease the brightness' Slider: id: slider min: 0 max: 100 value: root.get_current_brightness() on_value: root.set_brightness(slider.value) Label: text: 'Current brightness = ' + str(slider.value) ''') class BrightnessInterface(BoxLayout): def set_brightness(self, level): brightness.set_level(level) def get_current_brightness(self): return brightness.current_level() class BrightnessApp(App): def build(self): return BrightnessInterface() if __name__ == '__main__': BrightnessApp().run() plyer-2.1.0/examples/call/000077500000000000000000000000001433372044000153765ustar00rootroot00000000000000plyer-2.1.0/examples/call/buildozer.spec000066400000000000000000000115731433372044000202600ustar00rootroot00000000000000[app] # (str) Title of your application title = Plyer Call Example # (str) Package name package.name = callexample # (str) Package domain (needed for android/ios packaging) package.domain = org.test # (str) Source code where the main.py live source.dir = . # (list) Source files to include (let empty to include all the files) source.include_exts = py,png,jpg,kv,atlas # (list) Source files to exclude (let empty to not exclude anything) #source.exclude_exts = spec # (list) List of directory to exclude (let empty to not exclude anything) #source.exclude_dirs = tests, bin # (list) List of exclusions using pattern matching #source.exclude_patterns = license,images/*/*.jpg # (str) Application versioning (method 1) # version.regex = __version__ = '(.*)' # version.filename = %(source.dir)s/main.py # (str) Application versioning (method 2) version = 1.0.1 # (list) Application requirements requirements = kivy, plyer # (str) Presplash of the application #presplash.filename = %(source.dir)s/data/presplash.png # (str) Icon of the application #icon.filename = %(source.dir)s/data/icon.png # (str) Supported orientation (one of landscape, portrait or all) orientation = portrait # (bool) Indicate if the application should be fullscreen or not fullscreen = 0 # # Android specific # # (list) Permissions android.permissions = CALL_PHONE # (int) Android API to use #android.api = 14 # (int) Minimum API required (8 = Android 2.2 devices) #android.minapi = 8 # (int) Android SDK version to use #android.sdk = 21 # (str) Android NDK version to use #android.ndk = 9 # (bool) Use --private data storage (True) or --dir public storage (False) #android.private_storage = True # (str) Android NDK directory (if empty, it will be automatically downloaded.) #android.ndk_path = # (str) Android SDK directory (if empty, it will be automatically downloaded.) #android.sdk_path = # (str) Android entry point, default is ok for Kivy-based app #android.entrypoint = org.renpy.android.PythonActivity # (list) List of Java .jar files to add to the libs so that pyjnius can access # their classes. Don't add jars that you do not need, since extra jars can slow # down the build process. Allows wildcards matching, for example: # OUYA-ODK/libs/*.jar #android.add_jars = foo.jar,bar.jar,path/to/more/*.jar # (list) List of Java files to add to the android project (can be java or a # directory containing the files) #android.add_src = # (str) python-for-android branch to use, if not master, useful to try # not yet merged features. #android.branch = master # (str) OUYA Console category. Should be one of GAME or APP # If you leave this blank, OUYA support will not be enabled #android.ouya.category = GAME # (str) Filename of OUYA Console icon. It must be a 732x412 png image. #android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png # (str) XML file to include as an intent filters in tag #android.manifest.intent_filters = # (list) Android additionnal libraries to copy into libs/armeabi #android.add_libs_armeabi = libs/android/*.so # (bool) Indicate whether the screen should stay on # Don't forget to add the WAKE_LOCK permission if you set this to True #android.wakelock = False # (list) Android application meta-data to set (key=value format) #android.meta_data = # (list) Android library project to add (will be added in the # project.properties automatically.) #android.library_references = # # iOS specific # # (str) Name of the certificate to use for signing the debug version # Get a list of available identities: buildozer ios list_identities #ios.codesign.debug = "iPhone Developer: ()" # (str) Name of the certificate to use for signing the release version #ios.codesign.release = %(ios.codesign.debug)s [buildozer] # (int) Log level (0 = error only, 1 = info, 2 = debug (with command output)) log_level = 2 # ----------------------------------------------------------------------------- # List as sections # # You can define all the "list" as [section:key]. # Each line will be considered as a option to the list. # Let's take [app] / source.exclude_patterns. # Instead of doing: # # [app] # source.exclude_patterns = license,data/audio/*.wav,data/images/original/* # # This can be translated into: # # [app:source.exclude_patterns] # license # data/audio/*.wav # data/images/original/* # # ----------------------------------------------------------------------------- # Profiles # # You can extend section / key with a profile # For example, you want to deploy a demo version of your application without # HD content. You could first change the title to add "(demo)" in the name # and extend the excluded directories to remove the HD content. # # [app@demo] # title = My Application (demo) # # [app:source.exclude_patterns@demo] # images/hd/* # # Then, invoke the command line with the "demo" profile: # # buildozer --profile demo android debug plyer-2.1.0/examples/call/main.py000066400000000000000000000024211433372044000166730ustar00rootroot00000000000000from kivy.app import App from kivy.uix.boxlayout import BoxLayout from kivy.uix.button import Button from kivy.lang import Builder from kivy.properties import StringProperty from plyer import call Builder.load_string(''' #: import Platform kivy.utils.platform : orientation: 'vertical' Label: BoxLayout: size_hint_y: None size: (400,100) TextInput: id: number hint_text: "Enter Number" multiline: False MakeCallButton: tel: number.text text: 'Make call via this app' on_release: self.call() Label: text: "OR" DialCallButton: size_hint_y: None size: (400,100) disabled: True if Platform == 'ios' else False text: "Dial call via phone" on_release: self.dial() Label: ''') class CallInterface(BoxLayout): pass class DialCallButton(Button): def dial(self, *args): call.dialcall() class MakeCallButton(Button): tel = StringProperty() def call(self, *args): call.makecall(tel=self.tel) class CallApp(App): def build(self): return CallInterface() def on_pause(self): return True if __name__ == "__main__": app = CallApp() app.run() plyer-2.1.0/examples/camera/000077500000000000000000000000001433372044000157135ustar00rootroot00000000000000plyer-2.1.0/examples/camera/basic/000077500000000000000000000000001433372044000167745ustar00rootroot00000000000000plyer-2.1.0/examples/camera/basic/README000066400000000000000000000007321433372044000176560ustar00rootroot00000000000000Basic camera example. Default picture is saved as /sdcard/org.test.cameraexample/enter_file_name_here.jpg Python-for-android compilation: ./distribute.sh -m 'kivy plyer' cd dist/default ./build.py --org.test.cameraexample --name "Kivy Camera Example" \ --dir /path/to/plyer/examples/camera/basic --version 1.0 \ debug installd Buildozer: cd /path/to/plyer/examples/camera/basic # edit the buildozer.spec if required, then buildozer android debug deploy run plyer-2.1.0/examples/camera/basic/buildozer.spec000066400000000000000000000120141433372044000216450ustar00rootroot00000000000000[app] # (str) Title of your application title = Kivy Camera Example # (str) Package name package.name = cameraexample # (str) Package domain (needed for android/ios packaging) package.domain = org.test # (str) Source code where the main.py live source.dir = . # (list) Source files to include (let empty to include all the files) source.include_exts = py,png,jpg,kv,atlas # (list) Source files to exclude (let empty to not exclude anything) #source.exclude_exts = spec # (list) List of directory to exclude (let empty to not exclude anything) #source.exclude_dirs = tests, bin # (list) List of exclusions using pattern matching #source.exclude_patterns = license,images/*/*.jpg # (str) Application versioning (method 1) # version.regex = __version__ = '(.*)' # version.filename = %(source.dir)s/main.py # (str) Application versioning (method 2) version = 1.0 # (list) Application requirements # android library is also required to run this app on Android platform # for android device -> requirements = plyer,kivy,android requirements = plyer,kivy # (str) Presplash of the application #presplash.filename = %(source.dir)s/data/presplash.png # (str) Icon of the application #icon.filename = %(source.dir)s/data/icon.png # (str) Supported orientation (one of landscape, portrait or all) orientation = portrait # (bool) Indicate if the application should be fullscreen or not fullscreen = 0 # # Android specific # # (list) Permissions # android.permissions = WRITE_EXTERNAL_STORAGE # (int) Android API to use #android.api = 14 # (int) Minimum API required (8 = Android 2.2 devices) #android.minapi = 8 # (int) Android SDK version to use #android.sdk = 21 # (str) Android NDK version to use #android.ndk = 9 # (bool) Use --private data storage (True) or --dir public storage (False) #android.private_storage = True # (str) Android NDK directory (if empty, it will be automatically downloaded.) #android.ndk_path = # (str) Android SDK directory (if empty, it will be automatically downloaded.) #android.sdk_path = # (str) Android entry point, default is ok for Kivy-based app #android.entrypoint = org.renpy.android.PythonActivity # (list) List of Java .jar files to add to the libs so that pyjnius can access # their classes. Don't add jars that you do not need, since extra jars can slow # down the build process. Allows wildcards matching, for example: # OUYA-ODK/libs/*.jar #android.add_jars = foo.jar,bar.jar,path/to/more/*.jar # (list) List of Java files to add to the android project (can be java or a # directory containing the files) #android.add_src = # (str) python-for-android branch to use, if not master, useful to try # not yet merged features. #android.branch = master # (str) OUYA Console category. Should be one of GAME or APP # If you leave this blank, OUYA support will not be enabled #android.ouya.category = GAME # (str) Filename of OUYA Console icon. It must be a 732x412 png image. #android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png # (str) XML file to include as an intent filters in tag #android.manifest.intent_filters = # (list) Android additionnal libraries to copy into libs/armeabi #android.add_libs_armeabi = libs/android/*.so # (bool) Indicate whether the screen should stay on # Don't forget to add the WAKE_LOCK permission if you set this to True #android.wakelock = False # (list) Android application meta-data to set (key=value format) #android.meta_data = # (list) Android library project to add (will be added in the # project.properties automatically.) #android.library_references = # # iOS specific # # (str) Name of the certificate to use for signing the debug version # Get a list of available identities: buildozer ios list_identities #ios.codesign.debug = "iPhone Developer: ()" # (str) Name of the certificate to use for signing the release version #ios.codesign.release = %(ios.codesign.debug)s [buildozer] # (int) Log level (0 = error only, 1 = info, 2 = debug (with command output)) log_level = 2 # ----------------------------------------------------------------------------- # List as sections # # You can define all the "list" as [section:key]. # Each line will be considered as a option to the list. # Let's take [app] / source.exclude_patterns. # Instead of doing: # # [app] # source.exclude_patterns = license,data/audio/*.wav,data/images/original/* # # This can be translated into: # # [app:source.exclude_patterns] # license # data/audio/*.wav # data/images/original/* # # ----------------------------------------------------------------------------- # Profiles # # You can extend section / key with a profile # For example, you want to deploy a demo version of your application without # HD content. You could first change the title to add "(demo)" in the name # and extend the excluded directories to remove the HD content. # # [app@demo] # title = My Application (demo) # # [app:source.exclude_patterns@demo] # images/hd/* # # Then, invoke the command line with the "demo" profile: # # buildozer --profile demo android debug plyer-2.1.0/examples/camera/basic/camerademo.kv000066400000000000000000000017301433372044000214340ustar00rootroot00000000000000#:kivy 1.8.0 : FloatLayout: Label: id: path_label text: 'Working Directory:' pos_hint: {'x': 0.25, 'y': 0.7} size_hint: 0.5, 0.1 TextInput: id: filename_text text: 'enter_file_name_here.jpg' pos_hint: {'x': 0.25, 'y': .6} size_hint: 0.5, 0.1 multiline: False Button: text: 'Take picture from camera!' pos_hint: {'x': 0.25, 'y': .3} size_hint: 0.5, 0.2 on_press: root.do_capture() : size_hint: .7, .4 title: "Attention" BoxLayout: orientation: 'vertical' padding: 10 spacing: 20 Label: id: message_label size_hint_y: 0.4 text: "Label" Button: text: 'Dismiss' size_hint_y: 0.4 on_press: root.dismiss() plyer-2.1.0/examples/camera/basic/main.py000066400000000000000000000032651433372044000203000ustar00rootroot00000000000000""" Basic camera example Default picture is saved as /sdcard/org.test.cameraexample/enter_file_name_here.jpg """ from os import getcwd from os.path import exists from kivy.app import App from kivy.uix.floatlayout import FloatLayout from kivy.uix.popup import Popup import kivy from plyer import camera kivy.require('1.8.0') class CameraDemo(FloatLayout): def __init__(self): super().__init__() self.cwd = getcwd() + "/" self.ids.path_label.text = self.cwd def do_capture(self): filepath = self.cwd + self.ids.filename_text.text if exists(filepath): popup = MsgPopup("Picture with this name already exists!") popup.open() return False try: camera.take_picture(filename=filepath, on_complete=self.camera_callback) except NotImplementedError: popup = MsgPopup( "This feature has not yet been implemented for this platform.") popup.open() def camera_callback(self, filepath): if exists(filepath): popup = MsgPopup("Picture saved!") popup.open() else: popup = MsgPopup("Could not save your picture!") popup.open() class CameraDemoApp(App): def __init__(self): super().__init__() self.demo = None def build(self): self.demo = CameraDemo() return self.demo def on_pause(self): return True def on_resume(self): pass class MsgPopup(Popup): def __init__(self, msg): super().__init__() self.ids.message_label.text = msg if __name__ == '__main__': CameraDemoApp().run() plyer-2.1.0/examples/compass/000077500000000000000000000000001433372044000161305ustar00rootroot00000000000000plyer-2.1.0/examples/compass/README000066400000000000000000000005711433372044000170130ustar00rootroot00000000000000Compass example. Python-for-android compilation: ./distribute.sh -m 'kivy plyer' cd dist/default ./build.py --package org.test.compassexample --name "Kivy Compass" \ --dir /path/to/plyer/examples/compass --version 1.0 \ debug installd Buildozer: cd /path/to/plyer/examples/compass # edit the buildozer.spec if required, then buildozer android debug deploy run plyer-2.1.0/examples/compass/buildozer.spec000066400000000000000000000171561433372044000210150ustar00rootroot00000000000000[app] # (str) Title of your application title = Compass # (str) Package name package.name = plyercompass # (str) Package domain (needed for android/ios packaging) package.domain = org.test # (str) Source code where the main.py live source.dir = . # (list) Source files to include (let empty to include all the files) source.include_exts = py,png,jpg,kv,atlas # (list) List of inclusions using pattern matching #source.include_patterns = assets/*,images/*.png # (list) Source files to exclude (let empty to not exclude anything) #source.exclude_exts = spec # (list) List of directory to exclude (let empty to not exclude anything) #source.exclude_dirs = tests, bin # (list) List of exclusions using pattern matching #source.exclude_patterns = license,images/*/*.jpg # (str) Application versioning (method 1) version = 0.1 # (str) Application versioning (method 2) # version.regex = __version__ = ['"](.*)['"] # version.filename = %(source.dir)s/main.py # (list) Application requirements # comma seperated e.g. requirements = sqlite3,kivy requirements = kivy,hostpython3 # (str) Custom source folders for requirements # Sets custom source for any requirements with recipes # requirements.source.kivy = ../../kivy # (list) Garden requirements #garden_requirements = # (str) Presplash of the application #presplash.filename = %(source.dir)s/data/presplash.png # (str) Icon of the application #icon.filename = %(source.dir)s/data/icon.png # (str) Supported orientation (one of landscape, portrait or all) orientation = portrait # (list) List of service to declare #services = NAME:ENTRYPOINT_TO_PY,NAME2:ENTRYPOINT2_TO_PY # # OSX Specific # # # author = © Copyright Info # change the major version of python used by the app osx.python_version = 3 # Kivy version to use osx.kivy_version = 1.9.1 # # Android specific # # (bool) Indicate if the application should be fullscreen or not fullscreen = 0 # (string) Presplash background color (for new android toolchain) # Supported formats are: #RRGGBB #AARRGGBB or one of the following names: # red, blue, green, black, white, gray, cyan, magenta, yellow, lightgray, # darkgray, grey, lightgrey, darkgrey, aqua, fuchsia, lime, maroon, navy, # olive, purple, silver, teal. #android.presplash_color = #FFFFFF # (list) Permissions #android.permissions = INTERNET # (int) Android API to use #android.api = 19 # (int) Minimum API required #android.minapi = 9 # (int) Android SDK version to use #android.sdk = 20 # (str) Android NDK version to use #android.ndk = 9c # (bool) Use --private data storage (True) or --dir public storage (False) #android.private_storage = True # (str) Android NDK directory (if empty, it will be automatically downloaded.) #android.ndk_path = # (str) Android SDK directory (if empty, it will be automatically downloaded.) #android.sdk_path = # (str) ANT directory (if empty, it will be automatically downloaded.) #android.ant_path = # (bool) If True, then skip trying to update the Android sdk # This can be useful to avoid excess Internet downloads or save time # when an update is due and you just want to test/build your package # android.skip_update = False # (str) Android entry point, default is ok for Kivy-based app #android.entrypoint = org.renpy.android.PythonActivity # (list) Pattern to whitelist for the whole project #android.whitelist = # (str) Path to a custom whitelist file #android.whitelist_src = # (str) Path to a custom blacklist file #android.blacklist_src = # (list) List of Java .jar files to add to the libs so that pyjnius can access # their classes. Don't add jars that you do not need, since extra jars can slow # down the build process. Allows wildcards matching, for example: # OUYA-ODK/libs/*.jar #android.add_jars = foo.jar,bar.jar,path/to/more/*.jar # (list) List of Java files to add to the android project (can be java or a # directory containing the files) #android.add_src = # (list) Android AAR archives to add (currently works only with sdl2_gradle # bootstrap) #android.add_aars = # (list) Gradle dependencies to add (currently works only with sdl2_gradle # bootstrap) #android.gradle_dependencies = # (str) python-for-android branch to use, defaults to master #p4a.branch = stable # (str) OUYA Console category. Should be one of GAME or APP # If you leave this blank, OUYA support will not be enabled #android.ouya.category = GAME # (str) Filename of OUYA Console icon. It must be a 732x412 png image. #android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png # (str) XML file to include as an intent filters in tag #android.manifest.intent_filters = # (list) Android additionnal libraries to copy into libs/armeabi #android.add_libs_armeabi = libs/android/*.so #android.add_libs_armeabi_v7a = libs/android-v7/*.so #android.add_libs_x86 = libs/android-x86/*.so #android.add_libs_mips = libs/android-mips/*.so # (bool) Indicate whether the screen should stay on # Don't forget to add the WAKE_LOCK permission if you set this to True #android.wakelock = False # (list) Android application meta-data to set (key=value format) #android.meta_data = # (list) Android library project to add (will be added in the # project.properties automatically.) #android.library_references = # (str) Android logcat filters to use #android.logcat_filters = *:S python:D # (bool) Copy library instead of making a libpymodules.so #android.copy_libs = 1 # (str) The Android arch to build for, choices: armeabi-v7a, arm64-v8a, x86 android.arch = armeabi-v7a # # Python for android (p4a) specific # # (str) python-for-android git clone directory (if empty, it will be automatically cloned from github) #p4a.source_dir = # (str) The directory in which python-for-android should look for your own build recipes (if any) #p4a.local_recipes = # (str) Filename to the hook for p4a #p4a.hook = # (str) Bootstrap to use for android builds # p4a.bootstrap = sdl2 # # iOS specific # # (str) Path to a custom kivy-ios folder #ios.kivy_ios_dir = ../kivy-ios # (str) Name of the certificate to use for signing the debug version # Get a list of available identities: buildozer ios list_identities #ios.codesign.debug = "iPhone Developer: ()" # (str) Name of the certificate to use for signing the release version #ios.codesign.release = %(ios.codesign.debug)s [buildozer] # (int) Log level (0 = error only, 1 = info, 2 = debug (with command output)) log_level = 1 # (int) Display warning if buildozer is run as root (0 = False, 1 = True) warn_on_root = 1 # (str) Path to build artifact storage, absolute or relative to spec file # build_dir = ./.buildozer # (str) Path to build output (i.e. .apk, .ipa) storage # bin_dir = ./bin # ----------------------------------------------------------------------------- # List as sections # # You can define all the "list" as [section:key]. # Each line will be considered as a option to the list. # Let's take [app] / source.exclude_patterns. # Instead of doing: # #[app] #source.exclude_patterns = license,data/audio/*.wav,data/images/original/* # # This can be translated into: # #[app:source.exclude_patterns] #license #data/audio/*.wav #data/images/original/* # # ----------------------------------------------------------------------------- # Profiles # # You can extend section / key with a profile # For example, you want to deploy a demo version of your application without # HD content. You could first change the title to add "(demo)" in the name # and extend the excluded directories to remove the HD content. # #[app@demo] #title = My Application (demo) # #[app:source.exclude_patterns@demo] #images/hd/* # # Then, invoke the command line with the "demo" profile: # #buildozer --profile demo android debug plyer-2.1.0/examples/compass/main.py000066400000000000000000000067071433372044000174400ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- from kivy.app import App from kivy.clock import Clock from kivy.lang import Builder from kivy.properties import NumericProperty from kivy.properties import ObjectProperty from kivy.uix.boxlayout import BoxLayout interface = Builder.load_string(''' #:import facade plyer.compass : facade: facade orientation: 'vertical' padding: '20dp' spacing: '10dp' BoxLayout: orientation: 'vertical' BoxLayout: orientation: 'horizontal' size_hint: 1, .1 Button: id: enable_button text: 'Enable Sensor' disabled: False on_release: root.enable() disable_button.disabled = not disable_button.disabled enable_button.disabled = not enable_button.disabled Button: id: disable_button text: 'Disable Sensor' disabled: True on_release: root.disable() disable_button.disabled = not disable_button.disabled enable_button.disabled = not enable_button.disabled BoxLayout: orientation: 'vertical' Label: text: "Earth's Magnetic Field" Label: text: 'including hard iron calibration' Label: text: '(' + str(root.x_calib) + ',' Label: text: str(root.y_calib) + ',' Label: text: str(root.z_calib) + ')' Label: text: "Earth's Magnetic Field" Label: text: 'w/o hard iron calibration' Label: text: '(' + str(root.x_field) + ',' Label: text: str(root.y_field) + ',' Label: text: str(root.z_field) + ')' Label: text: 'Hard Iron Calibration' Label: text: '(' + str(root.x_iron) + ',' Label: text: str(root.y_iron) + ',' Label: text: str(root.z_iron) + ')' Label: text: 'All the values are in μT' ''') class CompassInterface(BoxLayout): x_calib = NumericProperty(0) y_calib = NumericProperty(0) z_calib = NumericProperty(0) x_field = NumericProperty(0) y_field = NumericProperty(0) z_field = NumericProperty(0) x_iron = NumericProperty(0) y_iron = NumericProperty(0) z_iron = NumericProperty(0) facade = ObjectProperty() def enable(self): self.facade.enable() Clock.schedule_interval(self.get_field, 1 / 20.) Clock.schedule_interval(self.get_field_uncalib, 1 / 20.) def disable(self): self.facade.disable() Clock.unschedule(self.get_field) Clock.unschedule(self.get_field_uncalib) def get_field(self, dt): if self.facade.field != (None, None, None): self.x_calib, self.y_calib, self.z_calib = self.facade.field def get_field_uncalib(self, dt): if self.facade.field_uncalib != (None, None, None, None, None, None): self.x_field, self.y_field, self.z_field, self.x_iron,\ self.y_iron, self.z_iron = self.facade.field_uncalib class CompassTestApp(App): def build(self): return CompassInterface() if __name__ == '__main__': CompassTestApp().run() plyer-2.1.0/examples/email/000077500000000000000000000000001433372044000155525ustar00rootroot00000000000000plyer-2.1.0/examples/email/buildozer.spec000066400000000000000000000115601433372044000204300ustar00rootroot00000000000000[app] # (str) Title of your application title = Plyer email test # (str) Package name package.name = email # (str) Package domain (needed for android/ios packaging) package.domain = org.test # (str) Source code where the main.py live source.dir = . # (list) Source files to include (let empty to include all the files) source.include_exts = py,png,jpg,kv,atlas # (list) Source files to exclude (let empty to not exclude anything) #source.exclude_exts = spec # (list) List of directory to exclude (let empty to not exclude anything) #source.exclude_dirs = tests, bin # (list) List of exclusions using pattern matching #source.exclude_patterns = license,images/*/*.jpg # (str) Application versioning (method 1) # version.regex = __version__ = '(.*)' # version.filename = %(source.dir)s/main.py # (str) Application versioning (method 2) version = 1.0 # (list) Application requirements requirements = plyer,kivy # (str) Presplash of the application #presplash.filename = %(source.dir)s/data/presplash.png # (str) Icon of the application #icon.filename = %(source.dir)s/data/icon.png # (str) Supported orientation (one of landscape, portrait or all) orientation = portrait # (bool) Indicate if the application should be fullscreen or not fullscreen = 0 # # Android specific # # (list) Permissions #android.permissions = INTERNET # (int) Android API to use #android.api = 14 # (int) Minimum API required (8 = Android 2.2 devices) #android.minapi = 8 # (int) Android SDK version to use #android.sdk = 21 # (str) Android NDK version to use #android.ndk = 9 # (bool) Use --private data storage (True) or --dir public storage (False) #android.private_storage = True # (str) Android NDK directory (if empty, it will be automatically downloaded.) #android.ndk_path = # (str) Android SDK directory (if empty, it will be automatically downloaded.) #android.sdk_path = # (str) Android entry point, default is ok for Kivy-based app #android.entrypoint = org.renpy.android.PythonActivity # (list) List of Java .jar files to add to the libs so that pyjnius can access # their classes. Don't add jars that you do not need, since extra jars can slow # down the build process. Allows wildcards matching, for example: # OUYA-ODK/libs/*.jar #android.add_jars = foo.jar,bar.jar,path/to/more/*.jar # (list) List of Java files to add to the android project (can be java or a # directory containing the files) #android.add_src = # (str) python-for-android branch to use, if not master, useful to try # not yet merged features. #android.branch = master # (str) OUYA Console category. Should be one of GAME or APP # If you leave this blank, OUYA support will not be enabled #android.ouya.category = GAME # (str) Filename of OUYA Console icon. It must be a 732x412 png image. #android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png # (str) XML file to include as an intent filters in tag #android.manifest.intent_filters = # (list) Android additionnal libraries to copy into libs/armeabi #android.add_libs_armeabi = libs/android/*.so # (bool) Indicate whether the screen should stay on # Don't forget to add the WAKE_LOCK permission if you set this to True #android.wakelock = False # (list) Android application meta-data to set (key=value format) #android.meta_data = # (list) Android library project to add (will be added in the # project.properties automatically.) #android.library_references = # # iOS specific # # (str) Name of the certificate to use for signing the debug version # Get a list of available identities: buildozer ios list_identities #ios.codesign.debug = "iPhone Developer: ()" # (str) Name of the certificate to use for signing the release version #ios.codesign.release = %(ios.codesign.debug)s [buildozer] # (int) Log level (0 = error only, 1 = info, 2 = debug (with command output)) log_level = 2 # ----------------------------------------------------------------------------- # List as sections # # You can define all the "list" as [section:key]. # Each line will be considered as a option to the list. # Let's take [app] / source.exclude_patterns. # Instead of doing: # # [app] # source.exclude_patterns = license,data/audio/*.wav,data/images/original/* # # This can be translated into: # # [app:source.exclude_patterns] # license # data/audio/*.wav # data/images/original/* # # ----------------------------------------------------------------------------- # Profiles # # You can extend section / key with a profile # For example, you want to deploy a demo version of your application without # HD content. You could first change the title to add "(demo)" in the name # and extend the excluded directories to remove the HD content. # # [app@demo] # title = My Application (demo) # # [app:source.exclude_patterns@demo] # images/hd/* # # Then, invoke the command line with the "demo" profile: # # buildozer --profile demo android debug plyer-2.1.0/examples/email/main.py000066400000000000000000000031211433372044000170450ustar00rootroot00000000000000 from kivy.app import App from kivy.uix.boxlayout import BoxLayout from kivy.uix.button import Button from kivy.lang import Builder from kivy.properties import StringProperty, BooleanProperty from plyer import email Builder.load_string(''' : orientation: 'vertical' BoxLayout: Label: text: 'Recipient:' TextInput: id: recipient BoxLayout: Label: text: 'Subject:' TextInput: id: subject BoxLayout: Label: text: 'text' TextInput: id: text BoxLayout: Label: text: 'create chooser?' CheckBox: id: create_chooser IntentButton: email_recipient: recipient.text email_subject: subject.text email_text: text.text create_chooser: create_chooser.active text: 'Send email' size_hint_y: None height: sp(40) on_release: self.send_email() ''') class EmailInterface(BoxLayout): pass class IntentButton(Button): email_recipient = StringProperty() email_subject = StringProperty() email_text = StringProperty() create_chooser = BooleanProperty() def send_email(self, *args): email.send(recipient=self.email_recipient, subject=self.email_subject, text=self.email_text, create_chooser=self.create_chooser) class EmailApp(App): def build(self): return EmailInterface() def on_pause(self): return True if __name__ == "__main__": EmailApp().run() plyer-2.1.0/examples/filechooser/000077500000000000000000000000001433372044000167655ustar00rootroot00000000000000plyer-2.1.0/examples/filechooser/main.py000066400000000000000000000032351433372044000202660ustar00rootroot00000000000000''' Example of an Android filechooser. ''' from textwrap import dedent from plyer import filechooser from kivy.app import App from kivy.lang import Builder from kivy.properties import ListProperty from kivy.uix.button import Button class FileChoose(Button): ''' Button that triggers 'filechooser.open_file()' and processes the data response from filechooser Activity. ''' selection = ListProperty([]) def choose(self): ''' Call plyer filechooser API to run a filechooser Activity. ''' filechooser.open_file(on_selection=self.handle_selection) def handle_selection(self, selection): ''' Callback function for handling the selection response from Activity. ''' self.selection = selection def on_selection(self, *a, **k): ''' Update TextInput.text after FileChoose.selection is changed via FileChoose.handle_selection. ''' App.get_running_app().root.ids.result.text = str(self.selection) class ChooserApp(App): ''' Application class with root built in KV. ''' def build(self): return Builder.load_string(dedent(''' : BoxLayout: BoxLayout: orientation: 'vertical' TextInput: id: result text: '' hint_text: 'selected path' FileChoose: size_hint_y: 0.1 on_release: self.choose() text: 'Select a file' ''')) if __name__ == '__main__': ChooserApp().run() plyer-2.1.0/examples/flash/000077500000000000000000000000001433372044000155605ustar00rootroot00000000000000plyer-2.1.0/examples/flash/buildozer.spec000066400000000000000000000130321433372044000204320ustar00rootroot00000000000000[app] # (str) Title of your application title = Plyer Flash Example # (str) Package name package.name = flashexample # (str) Package domain (needed for android/ios packaging) package.domain = org.test # (str) Source code where the main.py live source.dir = . # (list) Source files to include (let empty to include all the files) source.include_exts = py,png,jpg,kv,atlas # (list) Source files to exclude (let empty to not exclude anything) #source.exclude_exts = spec # (list) List of directory to exclude (let empty to not exclude anything) #source.exclude_dirs = tests, bin # (list) List of exclusions using pattern matching #source.exclude_patterns = license,images/*/*.jpg # (str) Application versioning (method 1) #version.regex = __version__ = ['"](.*)['"] #version.filename = %(source.dir)s/main.py # (str) Application versioning (method 2) version = 1.0 # (list) Application requirements # comma seperated e.g. requirements = sqlite3,kivy requirements = kivy, plyer # (str) Custom source folders for requirements # Sets custom source for any requirements with recipes # requirements.source.kivy = ../../kivy # (list) Garden requirements #garden_requirements = # (str) Presplash of the application #presplash.filename = %(source.dir)s/data/presplash.png # (str) Icon of the application #icon.filename = %(source.dir)s/data/icon.png # (str) Supported orientation (one of landscape, portrait or all) orientation = portrait # (bool) Indicate if the application should be fullscreen or not fullscreen = 0 # # Android specific # # (list) Permissions android.permissions = CAMERA, FLASHLIGHT # (int) Android API to use #android.api = 14 # (int) Minimum API required (8 = Android 2.2 devices) #android.minapi = 8 # (int) Android SDK version to use #android.sdk = 21 # (str) Android NDK version to use #android.ndk = 9c # (bool) Use --private data storage (True) or --dir public storage (False) #android.private_storage = True # (str) Android NDK directory (if empty, it will be automatically downloaded.) #android.ndk_path = # (str) Android SDK directory (if empty, it will be automatically downloaded.) #android.sdk_path = # (str) python-for-android git clone directory (if empty, it will be automatically cloned from github) #android.p4a_dir = # (list) python-for-android whitelist #android.p4a_whitelist = # (str) Android entry point, default is ok for Kivy-based app #android.entrypoint = org.renpy.android.PythonActivity # (list) List of Java .jar files to add to the libs so that pyjnius can access # their classes. Don't add jars that you do not need, since extra jars can slow # down the build process. Allows wildcards matching, for example: # OUYA-ODK/libs/*.jar #android.add_jars = foo.jar,bar.jar,path/to/more/*.jar # (list) List of Java files to add to the android project (can be java or a # directory containing the files) #android.add_src = # (str) python-for-android branch to use, if not master, useful to try # not yet merged features. #android.branch = master # (str) OUYA Console category. Should be one of GAME or APP # If you leave this blank, OUYA support will not be enabled #android.ouya.category = GAME # (str) Filename of OUYA Console icon. It must be a 732x412 png image. #android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png # (str) XML file to include as an intent filters in tag #android.manifest.intent_filters = # (list) Android additionnal libraries to copy into libs/armeabi #android.add_libs_armeabi = libs/android/*.so #android.add_libs_armeabi_v7a = libs/android-v7/*.so #android.add_libs_x86 = libs/android-x86/*.so #android.add_libs_mips = libs/android-mips/*.so # (bool) Indicate whether the screen should stay on # Don't forget to add the WAKE_LOCK permission if you set this to True #android.wakelock = False # (list) Android application meta-data to set (key=value format) #android.meta_data = # (list) Android library project to add (will be added in the # project.properties automatically.) #android.library_references = # # iOS specific # # (str) Name of the certificate to use for signing the debug version # Get a list of available identities: buildozer ios list_identities #ios.codesign.debug = "iPhone Developer: ()" # (str) Name of the certificate to use for signing the release version #ios.codesign.release = %(ios.codesign.debug)s [buildozer] # (int) Log level (0 = error only, 1 = info, 2 = debug (with command output)) log_level = 1 # (int) Display warning if buildozer is run as root (0 = False, 1 = True) warn_on_root = 1 # ----------------------------------------------------------------------------- # List as sections # # You can define all the "list" as [section:key]. # Each line will be considered as a option to the list. # Let's take [app] / source.exclude_patterns. # Instead of doing: # #[app] #source.exclude_patterns = license,data/audio/*.wav,data/images/original/* # # This can be translated into: # #[app:source.exclude_patterns] #license #data/audio/*.wav #data/images/original/* # # ----------------------------------------------------------------------------- # Profiles # # You can extend section / key with a profile # For example, you want to deploy a demo version of your application without # HD content. You could first change the title to add "(demo)" in the name # and extend the excluded directories to remove the HD content. # #[app@demo] #title = My Application (demo) # #[app:source.exclude_patterns@demo] #images/hd/* # # Then, invoke the command line with the "demo" profile: # #buildozer --profile demo android debug plyer-2.1.0/examples/flash/main.py000066400000000000000000000013471433372044000170630ustar00rootroot00000000000000from kivy.app import App from kivy.uix.boxlayout import BoxLayout from kivy.lang import Builder from plyer import flash Builder.load_string(''' : Button: text: "Turn On" on_release: root.turn_on() Button: text: "Turn off" on_release: root.turn_off() Button: text: "Release" on_release: root.release() ''') class FlashInterface(BoxLayout): def turn_on(self): flash.on() def turn_off(self): flash.off() def release(self): flash.release() class FlashApp(App): def build(self): return FlashInterface() def on_pause(self): return True if __name__ == "__main__": app = FlashApp() app.run() plyer-2.1.0/examples/gps/000077500000000000000000000000001433372044000152545ustar00rootroot00000000000000plyer-2.1.0/examples/gps/README000066400000000000000000000005471433372044000161420ustar00rootroot00000000000000Python-for-android compilation: ./distribute.sh -m 'kivy plyer' cd dist/default ./build.py --package org.test.gps --name "GPS example" \ --private ~/path/to/plyer/examples/gps --version 1 \ --permission ACCESS_FINE_LOCATION \ --permission ACCESS_COARSE_LOCATION \ --permission INTERNET \ --window \ --orientation portrait \ debug installd plyer-2.1.0/examples/gps/buildozer.spec000066400000000000000000000116641433372044000201370ustar00rootroot00000000000000[app] # (str) Title of your application title = Plyer GPS example # (str) Package name package.name = gpsexample # (str) Package domain (needed for android/ios packaging) package.domain = org.test # (str) Source code where the main.py live source.dir = . # (list) Source files to include (let empty to include all the files) source.include_exts = py,png,jpg,kv,atlas # (list) Source files to exclude (let empty to not exclude anything) #source.exclude_exts = spec # (list) List of directory to exclude (let empty to not exclude anything) #source.exclude_dirs = tests, bin # (list) List of exclusions using pattern matching #source.exclude_patterns = license,images/*/*.jpg # (str) Application versioning (method 1) # version.regex = __version__ = '(.*)' # version.filename = %(source.dir)s/main.py # (str) Application versioning (method 2) version = 1.0 # (list) Application requirements requirements = python3, kivy, plyer, android # (str) Presplash of the application #presplash.filename = %(source.dir)s/data/presplash.png # (str) Icon of the application #icon.filename = %(source.dir)s/data/icon.png # (str) Supported orientation (one of landscape, portrait or all) orientation = portrait # (bool) Indicate if the application should be fullscreen or not fullscreen = 0 # # Android specific # # (list) Permissions android.permissions = INTERNET,ACCESS_FINE_LOCATION,ACCESS_COARSE_LOCATION # (int) Android API to use #android.api = 14 # (int) Minimum API required (8 = Android 2.2 devices) #android.minapi = 8 # (int) Android SDK version to use #android.sdk = 21 # (str) Android NDK version to use #android.ndk = 9 # (bool) Use --private data storage (True) or --dir public storage (False) #android.private_storage = True # (str) Android NDK directory (if empty, it will be automatically downloaded.) #android.ndk_path = # (str) Android SDK directory (if empty, it will be automatically downloaded.) #android.sdk_path = # (str) Android entry point, default is ok for Kivy-based app #android.entrypoint = org.renpy.android.PythonActivity # (list) List of Java .jar files to add to the libs so that pyjnius can access # their classes. Don't add jars that you do not need, since extra jars can slow # down the build process. Allows wildcards matching, for example: # OUYA-ODK/libs/*.jar #android.add_jars = foo.jar,bar.jar,path/to/more/*.jar # (list) List of Java files to add to the android project (can be java or a # directory containing the files) #android.add_src = # (str) python-for-android branch to use, if not master, useful to try # not yet merged features. #android.branch = master # (str) OUYA Console category. Should be one of GAME or APP # If you leave this blank, OUYA support will not be enabled #android.ouya.category = GAME # (str) Filename of OUYA Console icon. It must be a 732x412 png image. #android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png # (str) XML file to include as an intent filters in tag #android.manifest.intent_filters = # (list) Android additionnal libraries to copy into libs/armeabi #android.add_libs_armeabi = libs/android/*.so # (bool) Indicate whether the screen should stay on # Don't forget to add the WAKE_LOCK permission if you set this to True #android.wakelock = False # (list) Android application meta-data to set (key=value format) #android.meta_data = # (list) Android library project to add (will be added in the # project.properties automatically.) #android.library_references = # # iOS specific # # (str) Name of the certificate to use for signing the debug version # Get a list of available identities: buildozer ios list_identities #ios.codesign.debug = "iPhone Developer: ()" # (str) Name of the certificate to use for signing the release version #ios.codesign.release = %(ios.codesign.debug)s [buildozer] # (int) Log level (0 = error only, 1 = info, 2 = debug (with command output)) log_level = 2 # ----------------------------------------------------------------------------- # List as sections # # You can define all the "list" as [section:key]. # Each line will be considered as a option to the list. # Let's take [app] / source.exclude_patterns. # Instead of doing: # # [app] # source.exclude_patterns = license,data/audio/*.wav,data/images/original/* # # This can be translated into: # # [app:source.exclude_patterns] # license # data/audio/*.wav # data/images/original/* # # ----------------------------------------------------------------------------- # Profiles # # You can extend section / key with a profile # For example, you want to deploy a demo version of your application without # HD content. You could first change the title to add "(demo)" in the name # and extend the excluded directories to remove the HD content. # # [app@demo] # title = My Application (demo) # # [app:source.exclude_patterns@demo] # images/hd/* # # Then, invoke the command line with the "demo" profile: # # buildozer --profile demo android debug plyer-2.1.0/examples/gps/main.py000066400000000000000000000057561433372044000165670ustar00rootroot00000000000000from kivy.lang import Builder from plyer import gps from kivy.app import App from kivy.properties import StringProperty from kivy.clock import mainthread from kivy.utils import platform kv = ''' BoxLayout: orientation: 'vertical' Label: text: app.gps_location Label: text: app.gps_status BoxLayout: size_hint_y: None height: '48dp' padding: '4dp' ToggleButton: text: 'Start' if self.state == 'normal' else 'Stop' on_state: app.start(1000, 0) if self.state == 'down' else \ app.stop() ''' class GpsTest(App): gps_location = StringProperty() gps_status = StringProperty('Click Start to get GPS location updates') def request_android_permissions(self): """ Since API 23, Android requires permission to be requested at runtime. This function requests permission and handles the response via a callback. The request will produce a popup if permissions have not already been been granted, otherwise it will do nothing. """ from android.permissions import request_permissions, Permission def callback(permissions, results): """ Defines the callback to be fired when runtime permission has been granted or denied. This is not strictly required, but added for the sake of completeness. """ if all([res for res in results]): print("callback. All permissions granted.") else: print("callback. Some permissions refused.") request_permissions([Permission.ACCESS_COARSE_LOCATION, Permission.ACCESS_FINE_LOCATION], callback) # # To request permissions without a callback, do: # request_permissions([Permission.ACCESS_COARSE_LOCATION, # Permission.ACCESS_FINE_LOCATION]) def build(self): try: gps.configure(on_location=self.on_location, on_status=self.on_status) except NotImplementedError: import traceback traceback.print_exc() self.gps_status = 'GPS is not implemented for your platform' if platform == "android": print("gps.py: Android detected. Requesting permissions") self.request_android_permissions() return Builder.load_string(kv) def start(self, minTime, minDistance): gps.start(minTime, minDistance) def stop(self): gps.stop() @mainthread def on_location(self, **kwargs): self.gps_location = '\n'.join([ '{}={}'.format(k, v) for k, v in kwargs.items()]) @mainthread def on_status(self, stype, status): self.gps_status = 'type={}\n{}'.format(stype, status) def on_pause(self): gps.stop() return True def on_resume(self): gps.start(1000, 0) pass if __name__ == '__main__': GpsTest().run() plyer-2.1.0/examples/gravity/000077500000000000000000000000001433372044000161505ustar00rootroot00000000000000plyer-2.1.0/examples/gravity/buildozer.spec000066400000000000000000000117701433372044000210310ustar00rootroot00000000000000[app] # (str) Title of your application title = Plyer Gravity Example # (str) Package name package.name = plyergravity # (str) Package domain (needed for android/ios packaging) package.domain = org.test # (str) Source code where the main.py live source.dir = . # (list) Source files to include (let empty to include all the files) source.include_exts = py,png,jpg,kv,atlas # (list) Source files to exclude (let empty to not exclude anything) #source.exclude_exts = spec # (list) List of directory to exclude (let empty to not exclude anything) #source.exclude_dirs = tests, bin # (list) List of exclusions using pattern matching #source.exclude_patterns = license,images/*/*.jpg # (str) Application versioning (method 1) # version.regex = __version__ = '(.*)' # version.filename = %(source.dir)s/main.py # (str) Application versioning (method 2) version = 0.1 # (list) Application requirements requirements = plyer,kivy # (str) Presplash of the application #presplash.filename = %(source.dir)s/data/presplash.png # (str) Icon of the application #icon.filename = %(source.dir)s/data/icon.png # (str) Supported orientation (one of landscape, portrait or all) orientation = portrait # (bool) Indicate if the application should be fullscreen or not fullscreen = 0 # # Android specific # # (list) Permissions #android.permissions = INTERNET # (int) Android API to use # android.api = 18 # (int) Minimum API required (8 = Android 2.2 devices) #android.minapi = 8 # (int) Android SDK version to use #android.sdk = 21 # (str) Android NDK version to use #android.ndk = 9 # (bool) Use --private data storage (True) or --dir public storage (False) #android.private_storage = True # (str) Android NDK directory (if empty, it will be automatically downloaded.) #android.ndk_path = # (str) Android SDK directory (if empty, it will be automatically downloaded.) #android.sdk_path = # (str) python-for-android git clone directory (if empty, it will be automatically cloned from github) #android.p4a_dir = # (str) Android entry point, default is ok for Kivy-based app #android.entrypoint = org.renpy.android.PythonActivity # (list) List of Java .jar files to add to the libs so that pyjnius can access # their classes. Don't add jars that you do not need, since extra jars can slow # down the build process. Allows wildcards matching, for example: # OUYA-ODK/libs/*.jar #android.add_jars = foo.jar,bar.jar,path/to/more/*.jar # (list) List of Java files to add to the android project (can be java or a # directory containing the files) #android.add_src = # (str) python-for-android branch to use, if not master, useful to try # not yet merged features. #android.branch = master # (str) OUYA Console category. Should be one of GAME or APP # If you leave this blank, OUYA support will not be enabled #android.ouya.category = GAME # (str) Filename of OUYA Console icon. It must be a 732x412 png image. #android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png # (str) XML file to include as an intent filters in tag #android.manifest.intent_filters = # (list) Android additionnal libraries to copy into libs/armeabi #android.add_libs_armeabi = libs/android/*.so # (bool) Indicate whether the screen should stay on # Don't forget to add the WAKE_LOCK permission if you set this to True #android.wakelock = False # (list) Android application meta-data to set (key=value format) #android.meta_data = # (list) Android library project to add (will be added in the # project.properties automatically.) #android.library_references = # # iOS specific # # (str) Name of the certificate to use for signing the debug version # Get a list of available identities: buildozer ios list_identities #ios.codesign.debug = "iPhone Developer: ()" # (str) Name of the certificate to use for signing the release version #ios.codesign.release = %(ios.codesign.debug)s [buildozer] # (int) Log level (0 = error only, 1 = info, 2 = debug (with command output)) log_level = 2 # ----------------------------------------------------------------------------- # List as sections # # You can define all the "list" as [section:key]. # Each line will be considered as a option to the list. # Let's take [app] / source.exclude_patterns. # Instead of doing: # # [app] # source.exclude_patterns = license,data/audio/*.wav,data/images/original/* # # This can be translated into: # # [app:source.exclude_patterns] # license # data/audio/*.wav # data/images/original/* # # ----------------------------------------------------------------------------- # Profiles # # You can extend section / key with a profile # For example, you want to deploy a demo version of your application without # HD content. You could first change the title to add "(demo)" in the name # and extend the excluded directories to remove the HD content. # # [app@demo] # title = My Application (demo) # # [app:source.exclude_patterns@demo] # images/hd/* # # Then, invoke the command line with the "demo" profile: # # buildozer --profile demo android debug plyer-2.1.0/examples/gravity/main.py000066400000000000000000000036141433372044000174520ustar00rootroot00000000000000from kivy.app import App from kivy.clock import Clock from kivy.lang import Builder from kivy.uix.boxlayout import BoxLayout from plyer import gravity Builder.load_string(''' : orientation: 'vertical' Label: id: x_label text: 'X: ' Label: id: y_label text: 'Y: ' Label: id: z_label text: 'Z: ' Label: id: status text: '' BoxLayout: size_hint_y: None height: '48dp' padding: '4dp' ToggleButton: id: toggle_button text: 'Start Gravity Sensor' on_press: root.do_toggle() ''') class GravityInterface(BoxLayout): def __init__(self): super().__init__() self.sensorEnabled = False def do_toggle(self): try: if not self.sensorEnabled: gravity.enable() Clock.schedule_interval(self.get_gravity, 1 / 20.) self.sensorEnabled = True self.ids.toggle_button.text = "Stop Gravity Sensor" else: gravity.disable() Clock.unschedule(self.get_gravity) self.sensorEnabled = False self.ids.toggle_button.text = "Start Gravity Sensor" except NotImplementedError: import traceback traceback.print_exc() status = "Gravity sensor is not implemented " \ "for your platform" self.ids.status.text = status def get_gravity(self, dt): val = gravity.gravity if not val == (None, None, None): self.ids.x_label.text = "X: " + str(val[0]) self.ids.y_label.text = "Y: " + str(val[1]) self.ids.z_label.text = "Z: " + str(val[2]) class GravityApp(App): def build(self): return GravityInterface() if __name__ == '__main__': GravityApp().run() plyer-2.1.0/examples/gyroscope/000077500000000000000000000000001433372044000164755ustar00rootroot00000000000000plyer-2.1.0/examples/gyroscope/buildozer.spec000066400000000000000000000152261433372044000213560ustar00rootroot00000000000000[app] # (str) Title of your application title = Gyroscope # (str) Package name package.name = plyergyroscope # (str) Package domain (needed for android/ios packaging) package.domain = org.test # (str) Source code where the main.py live source.dir = . # (list) Source files to include (let empty to include all the files) source.include_exts = py,png,jpg,kv,atlas # (list) List of inclusions using pattern matching #source.include_patterns = assets/*,images/*.png # (list) Source files to exclude (let empty to not exclude anything) #source.exclude_exts = spec # (list) List of directory to exclude (let empty to not exclude anything) #source.exclude_dirs = tests, bin # (list) List of exclusions using pattern matching #source.exclude_patterns = license,images/*/*.jpg # (str) Application versioning (method 1) version = 0.1 # (str) Application versioning (method 2) # version.regex = __version__ = ['"](.*)['"] # version.filename = %(source.dir)s/main.py # (list) Application requirements # comma seperated e.g. requirements = sqlite3,kivy requirements = kivy,plyer # (str) Custom source folders for requirements # Sets custom source for any requirements with recipes # requirements.source.kivy = ../../kivy # (list) Garden requirements #garden_requirements = # (str) Presplash of the application #presplash.filename = %(source.dir)s/data/presplash.png # (str) Icon of the application #icon.filename = %(source.dir)s/data/icon.png # (str) Supported orientation (one of landscape, portrait or all) orientation = portrait # (list) List of service to declare #services = NAME:ENTRYPOINT_TO_PY,NAME2:ENTRYPOINT2_TO_PY # # OSX Specific # # # author = © Copyright Info # # Android specific # # (bool) Indicate if the application should be fullscreen or not fullscreen = 0 # (list) Permissions #android.permissions = INTERNET # (int) Android API to use #android.api = 19 # (int) Minimum API required #android.minapi = 9 # (int) Android SDK version to use #android.sdk = 20 # (str) Android NDK version to use #android.ndk = 9c # (bool) Use --private data storage (True) or --dir public storage (False) #android.private_storage = True # (str) Android NDK directory (if empty, it will be automatically downloaded.) #android.ndk_path = # (str) Android SDK directory (if empty, it will be automatically downloaded.) #android.sdk_path = # (str) ANT directory (if empty, it will be automatically downloaded.) #android.ant_path = # (str) python-for-android git clone directory (if empty, it will be automatically cloned from github) #android.p4a_dir = # (str) The directory in which python-for-android should look for your own build recipes (if any) #p4a.local_recipes = # (list) python-for-android whitelist #android.p4a_whitelist = # (bool) If True, then skip trying to update the Android sdk # This can be useful to avoid excess Internet downloads or save time # when an update is due and you just want to test/build your package # android.skip_update = False # (str) Bootstrap to use for android builds (android_new only) # android.bootstrap = sdl2 # (str) Android entry point, default is ok for Kivy-based app #android.entrypoint = org.renpy.android.PythonActivity # (list) List of Java .jar files to add to the libs so that pyjnius can access # their classes. Don't add jars that you do not need, since extra jars can slow # down the build process. Allows wildcards matching, for example: # OUYA-ODK/libs/*.jar #android.add_jars = foo.jar,bar.jar,path/to/more/*.jar # (list) List of Java files to add to the android project (can be java or a # directory containing the files) #android.add_src = # (str) python-for-android branch to use, if not master, useful to try # not yet merged features. #android.branch = master # (str) OUYA Console category. Should be one of GAME or APP # If you leave this blank, OUYA support will not be enabled #android.ouya.category = GAME # (str) Filename of OUYA Console icon. It must be a 732x412 png image. #android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png # (str) XML file to include as an intent filters in tag #android.manifest.intent_filters = # (list) Android additionnal libraries to copy into libs/armeabi #android.add_libs_armeabi = libs/android/*.so #android.add_libs_armeabi_v7a = libs/android-v7/*.so #android.add_libs_x86 = libs/android-x86/*.so #android.add_libs_mips = libs/android-mips/*.so # (bool) Indicate whether the screen should stay on # Don't forget to add the WAKE_LOCK permission if you set this to True #android.wakelock = False # (list) Android application meta-data to set (key=value format) #android.meta_data = # (list) Android library project to add (will be added in the # project.properties automatically.) #android.library_references = # (str) Android logcat filters to use #android.logcat_filters = *:S python:D # (bool) Copy library instead of making a libpymodules.so #android.copy_libs = 1 # # iOS specific # # (str) Path to a custom kivy-ios folder #ios.kivy_ios_dir = ../kivy-ios # (str) Name of the certificate to use for signing the debug version # Get a list of available identities: buildozer ios list_identities #ios.codesign.debug = "iPhone Developer: ()" # (str) Name of the certificate to use for signing the release version #ios.codesign.release = %(ios.codesign.debug)s [buildozer] # (int) Log level (0 = error only, 1 = info, 2 = debug (with command output)) log_level = 2 # (int) Display warning if buildozer is run as root (0 = False, 1 = True) warn_on_root = 1 # (str) Path to build artifact storage, absolute or relative to spec file # build_dir = ./.buildozer # (str) Path to build output (i.e. .apk, .ipa) storage # bin_dir = ./bin # ----------------------------------------------------------------------------- # List as sections # # You can define all the "list" as [section:key]. # Each line will be considered as a option to the list. # Let's take [app] / source.exclude_patterns. # Instead of doing: # #[app] #source.exclude_patterns = license,data/audio/*.wav,data/images/original/* # # This can be translated into: # #[app:source.exclude_patterns] #license #data/audio/*.wav #data/images/original/* # # ----------------------------------------------------------------------------- # Profiles # # You can extend section / key with a profile # For example, you want to deploy a demo version of your application without # HD content. You could first change the title to add "(demo)" in the name # and extend the excluded directories to remove the HD content. # #[app@demo] #title = My Application (demo) # #[app:source.exclude_patterns@demo] #images/hd/* # # Then, invoke the command line with the "demo" profile: # #buildozer --profile demo android debug plyer-2.1.0/examples/gyroscope/main.py000066400000000000000000000066041433372044000200010ustar00rootroot00000000000000from kivy.app import App from kivy.clock import Clock from kivy.lang import Builder from kivy.properties import NumericProperty from kivy.properties import ObjectProperty from kivy.uix.boxlayout import BoxLayout interface = Builder.load_string(''' #:import facade plyer.gyroscope : facade: facade orientation: 'vertical' padding: '20dp' spacing: '10dp' BoxLayout: orientation: 'vertical' BoxLayout: orientation: 'horizontal' size_hint: 1, .1 Button: id: enable_button text: 'Enable Sensor' disabled: False on_release: root.enable() disable_button.disabled = not disable_button.disabled enable_button.disabled = not enable_button.disabled Button: id: disable_button text: 'Disable Sensor' disabled: True on_release: root.disable() disable_button.disabled = not disable_button.disabled enable_button.disabled = not enable_button.disabled BoxLayout: orientation: 'vertical' Label: text: 'Rate of rotation' Label: text: 'including drift compensation' Label: text: '(' + str(root.x_calib) + ',' Label: text: str(root.y_calib) + ',' Label: text: str(root.z_calib) + ')' Label: text: 'Rate of rotation' Label: text: 'w/o drift compensation' Label: text: '(' + str(root.x_speed) + ',' Label: text: str(root.y_speed) + ',' Label: text: str(root.z_speed) + ')' Label: text: 'Estimated Drift' Label: text: '(' + str(root.x_drift) + ',' Label: text: str(root.y_drift) + ',' Label: text: str(root.z_drift) + ')' ''') class GyroscopeInterface(BoxLayout): x_calib = NumericProperty(0) y_calib = NumericProperty(0) z_calib = NumericProperty(0) x_speed = NumericProperty(0) y_speed = NumericProperty(0) z_speed = NumericProperty(0) x_drift = NumericProperty(0) y_drift = NumericProperty(0) z_drift = NumericProperty(0) facade = ObjectProperty() def enable(self): self.facade.enable() Clock.schedule_interval(self.get_rotation, 1 / 20.) Clock.schedule_interval(self.get_rotation_uncalib, 1 / 20.) def disable(self): self.facade.disable() Clock.unschedule(self.get_rotation) Clock.unschedule(self.get_rotation_uncalib) def get_rotation(self, dt): if self.facade.rotation != (None, None, None): self.x_calib, self.y_calib, self.z_calib = self.facade.rotation def get_rotation_uncalib(self, dt): empty = tuple([None for i in range(6)]) if self.facade.rotation_uncalib != empty: self.x_speed, self.y_speed, self.z_speed, self.x_drift,\ self.y_drift, self.z_drift = self.facade.rotation_uncalib class GyroscopeTestApp(App): def build(self): return GyroscopeInterface() if __name__ == "__main__": GyroscopeTestApp().run() plyer-2.1.0/examples/humidity/000077500000000000000000000000001433372044000163175ustar00rootroot00000000000000plyer-2.1.0/examples/humidity/buildozer.spec000066400000000000000000000147031433372044000211770ustar00rootroot00000000000000[app] # (str) Title of your application title = PlyerHumidity # (str) Package name package.name = plyer.humidity # (str) Package domain (needed for android/ios packaging) package.domain = org.test # (str) Source code where the main.py live source.dir = . # (list) Source files to include (let empty to include all the files) source.include_exts = py,png,jpg,kv,atlas # (list) List of inclusions using pattern matching #source.include_patterns = assets/*,images/*.png # (list) Source files to exclude (let empty to not exclude anything) #source.exclude_exts = spec # (list) List of directory to exclude (let empty to not exclude anything) #source.exclude_dirs = tests, bin # (list) List of exclusions using pattern matching #source.exclude_patterns = license,images/*/*.jpg # (str) Application versioning (method 1) version = 0.1 # (str) Application versioning (method 2) # version.regex = __version__ = ['"](.*)['"] # version.filename = %(source.dir)s/main.py # (list) Application requirements # comma seperated e.g. requirements = sqlite3,kivy requirements = kivy,plyer # (str) Custom source folders for requirements # Sets custom source for any requirements with recipes # requirements.source.kivy = ../../kivy # (list) Garden requirements #garden_requirements = # (str) Presplash of the application #presplash.filename = %(source.dir)s/data/presplash.png # (str) Icon of the application #icon.filename = %(source.dir)s/data/icon.png # (str) Supported orientation (one of landscape, portrait or all) orientation = portrait # (list) List of service to declare #services = NAME:ENTRYPOINT_TO_PY,NAME2:ENTRYPOINT2_TO_PY # # OSX Specific # # # author = © Copyright Info # # Android specific # # (bool) Indicate if the application should be fullscreen or not fullscreen = 0 # (list) Permissions #android.permissions = INTERNET # (int) Android API to use android.api = 19 # (int) Minimum API required android.minapi = 19 # (int) Android SDK version to use android.sdk = 24 # (str) Android NDK version to use android.ndk = 9c # (bool) Use --private data storage (True) or --dir public storage (False) #android.private_storage = True # (str) Android NDK directory (if empty, it will be automatically downloaded.) #android.ndk_path = # (str) Android SDK directory (if empty, it will be automatically downloaded.) #android.sdk_path = # (str) ANT directory (if empty, it will be automatically downloaded.) #android.ant_path = # (str) python-for-android git clone directory (if empty, it will be automatically cloned from github) #android.p4a_dir = # (list) python-for-android whitelist #android.p4a_whitelist = # (bool) If True, then skip trying to update the Android sdk # This can be useful to avoid excess Internet downloads or save time # when an update is due and you just want to test/build your package # android.skip_update = False # (str) Android entry point, default is ok for Kivy-based app #android.entrypoint = org.renpy.android.PythonActivity # (list) List of Java .jar files to add to the libs so that pyjnius can access # their classes. Don't add jars that you do not need, since extra jars can slow # down the build process. Allows wildcards matching, for example: # OUYA-ODK/libs/*.jar #android.add_jars = foo.jar,bar.jar,path/to/more/*.jar # (list) List of Java files to add to the android project (can be java or a # directory containing the files) #android.add_src = # (str) python-for-android branch to use, if not master, useful to try # not yet merged features. #android.branch = master # (str) OUYA Console category. Should be one of GAME or APP # If you leave this blank, OUYA support will not be enabled #android.ouya.category = GAME # (str) Filename of OUYA Console icon. It must be a 732x412 png image. #android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png # (str) XML file to include as an intent filters in tag #android.manifest.intent_filters = # (list) Android additionnal libraries to copy into libs/armeabi #android.add_libs_armeabi = libs/android/*.so #android.add_libs_armeabi_v7a = libs/android-v7/*.so #android.add_libs_x86 = libs/android-x86/*.so #android.add_libs_mips = libs/android-mips/*.so # (bool) Indicate whether the screen should stay on # Don't forget to add the WAKE_LOCK permission if you set this to True #android.wakelock = False # (list) Android application meta-data to set (key=value format) #android.meta_data = # (list) Android library project to add (will be added in the # project.properties automatically.) #android.library_references = # (str) Android logcat filters to use #android.logcat_filters = *:S python:D # (bool) Copy library instead of making a libpymodules.so #android.copy_libs = 1 # # iOS specific # # (str) Path to a custom kivy-ios folder #ios.kivy_ios_dir = ../kivy-ios # (str) Name of the certificate to use for signing the debug version # Get a list of available identities: buildozer ios list_identities #ios.codesign.debug = "iPhone Developer: ()" # (str) Name of the certificate to use for signing the release version #ios.codesign.release = %(ios.codesign.debug)s [buildozer] # (int) Log level (0 = error only, 1 = info, 2 = debug (with command output)) log_level = 2 # (int) Display warning if buildozer is run as root (0 = False, 1 = True) warn_on_root = 1 # (str) Path to build artifact storage, absolute or relative to spec file # build_dir = ./.buildozer # (str) Path to build output (i.e. .apk, .ipa) storage # bin_dir = ./bin # ----------------------------------------------------------------------------- # List as sections # # You can define all the "list" as [section:key]. # Each line will be considered as a option to the list. # Let's take [app] / source.exclude_patterns. # Instead of doing: # #[app] #source.exclude_patterns = license,data/audio/*.wav,data/images/original/* # # This can be translated into: # #[app:source.exclude_patterns] #license #data/audio/*.wav #data/images/original/* # # ----------------------------------------------------------------------------- # Profiles # # You can extend section / key with a profile # For example, you want to deploy a demo version of your application without # HD content. You could first change the title to add "(demo)" in the name # and extend the excluded directories to remove the HD content. # #[app@demo] #title = My Application (demo) # #[app:source.exclude_patterns@demo] #images/hd/* # # Then, invoke the command line with the "demo" profile: # #buildozer --profile demo android debug plyer-2.1.0/examples/humidity/main.py000066400000000000000000000032621433372044000176200ustar00rootroot00000000000000from kivy.app import App from kivy.clock import Clock from kivy.lang import Builder from kivy.properties import NumericProperty from kivy.properties import ObjectProperty from kivy.uix.boxlayout import BoxLayout Builder.load_string(''' #:import facade plyer.humidity : facade: facade orientation: 'vertical' padding: '50dp' spacing: '20dp' BoxLayout: orientation: 'horizontal' size_hint_y: 0.3 Button: id: button_enable text: 'Enable' disabled: False on_release: root.enable() button_disable.disabled = not button_disable.disabled button_enable.disabled = not button_enable.disabled Button: id: button_disable text: 'Disable' disabled: True on_release: root.disable() button_disable.disabled = not button_disable.disabled button_enable.disabled = not button_enable.disabled Label: text: 'Humidity: ' + str(root.humidity) ''') class HumidityInterface(BoxLayout): '''Root Widget.''' humidity = NumericProperty(0) facade = ObjectProperty() def enable(self): self.facade.enable() Clock.schedule_interval(self.tell, 1 / 20.) def disable(self): self.facade.disable() Clock.unschedule(self.tell) def tell(self, dt): if self.facade.tell is not None: self.humidity = self.facade.tell class HumidityApp(App): def build(self): return HumidityInterface() def on_pause(self): return True if __name__ == "__main__": HumidityApp().run() plyer-2.1.0/examples/light/000077500000000000000000000000001433372044000155725ustar00rootroot00000000000000plyer-2.1.0/examples/light/buildozer.spec000066400000000000000000000152341433372044000204520ustar00rootroot00000000000000[app] # (str) Title of your application title = Plyer Light Example # (str) Package name package.name = plyerlight # (str) Package domain (needed for android/ios packaging) package.domain = org.test # (str) Source code where the main.py live source.dir = . # (list) Source files to include (let empty to include all the files) source.include_exts = py,png,jpg,kv,atlas # (list) List of inclusions using pattern matching #source.include_patterns = assets/*,images/*.png # (list) Source files to exclude (let empty to not exclude anything) #source.exclude_exts = spec # (list) List of directory to exclude (let empty to not exclude anything) #source.exclude_dirs = tests, bin # (list) List of exclusions using pattern matching #source.exclude_patterns = license,images/*/*.jpg # (str) Application versioning (method 1) version = 0.1 # (str) Application versioning (method 2) # version.regex = __version__ = ['"](.*)['"] # version.filename = %(source.dir)s/main.py # (list) Application requirements # comma seperated e.g. requirements = sqlite3,kivy requirements = kivy,plyer # (str) Custom source folders for requirements # Sets custom source for any requirements with recipes # requirements.source.kivy = ../../kivy # (list) Garden requirements #garden_requirements = # (str) Presplash of the application #presplash.filename = %(source.dir)s/data/presplash.png # (str) Icon of the application #icon.filename = %(source.dir)s/data/icon.png # (str) Supported orientation (one of landscape, portrait or all) orientation = portrait # (list) List of service to declare #services = NAME:ENTRYPOINT_TO_PY,NAME2:ENTRYPOINT2_TO_PY # # OSX Specific # # # author = © Copyright Info # # Android specific # # (bool) Indicate if the application should be fullscreen or not fullscreen = 0 # (list) Permissions #android.permissions = INTERNET # (int) Android API to use #android.api = 19 # (int) Minimum API required #android.minapi = 9 # (int) Android SDK version to use #android.sdk = 20 # (str) Android NDK version to use #android.ndk = 9c # (bool) Use --private data storage (True) or --dir public storage (False) #android.private_storage = True # (str) Android NDK directory (if empty, it will be automatically downloaded.) #android.ndk_path = # (str) Android SDK directory (if empty, it will be automatically downloaded.) #android.sdk_path = # (str) ANT directory (if empty, it will be automatically downloaded.) #android.ant_path = # (str) python-for-android git clone directory (if empty, it will be automatically cloned from github) #android.p4a_dir = # (str) The directory in which python-for-android should look for your own build recipes (if any) #p4a.local_recipes = # (list) python-for-android whitelist #android.p4a_whitelist = # (bool) If True, then skip trying to update the Android sdk # This can be useful to avoid excess Internet downloads or save time # when an update is due and you just want to test/build your package # android.skip_update = False # (str) Bootstrap to use for android builds (android_new only) # android.bootstrap = sdl2 # (str) Android entry point, default is ok for Kivy-based app #android.entrypoint = org.renpy.android.PythonActivity # (list) List of Java .jar files to add to the libs so that pyjnius can access # their classes. Don't add jars that you do not need, since extra jars can slow # down the build process. Allows wildcards matching, for example: # OUYA-ODK/libs/*.jar #android.add_jars = foo.jar,bar.jar,path/to/more/*.jar # (list) List of Java files to add to the android project (can be java or a # directory containing the files) #android.add_src = # (str) python-for-android branch to use, if not master, useful to try # not yet merged features. #android.branch = master # (str) OUYA Console category. Should be one of GAME or APP # If you leave this blank, OUYA support will not be enabled #android.ouya.category = GAME # (str) Filename of OUYA Console icon. It must be a 732x412 png image. #android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png # (str) XML file to include as an intent filters in tag #android.manifest.intent_filters = # (list) Android additionnal libraries to copy into libs/armeabi #android.add_libs_armeabi = libs/android/*.so #android.add_libs_armeabi_v7a = libs/android-v7/*.so #android.add_libs_x86 = libs/android-x86/*.so #android.add_libs_mips = libs/android-mips/*.so # (bool) Indicate whether the screen should stay on # Don't forget to add the WAKE_LOCK permission if you set this to True #android.wakelock = False # (list) Android application meta-data to set (key=value format) #android.meta_data = # (list) Android library project to add (will be added in the # project.properties automatically.) #android.library_references = # (str) Android logcat filters to use #android.logcat_filters = *:S python:D # (bool) Copy library instead of making a libpymodules.so #android.copy_libs = 1 # # iOS specific # # (str) Path to a custom kivy-ios folder #ios.kivy_ios_dir = ../kivy-ios # (str) Name of the certificate to use for signing the debug version # Get a list of available identities: buildozer ios list_identities #ios.codesign.debug = "iPhone Developer: ()" # (str) Name of the certificate to use for signing the release version #ios.codesign.release = %(ios.codesign.debug)s [buildozer] # (int) Log level (0 = error only, 1 = info, 2 = debug (with command output)) log_level = 2 # (int) Display warning if buildozer is run as root (0 = False, 1 = True) warn_on_root = 1 # (str) Path to build artifact storage, absolute or relative to spec file # build_dir = ./.buildozer # (str) Path to build output (i.e. .apk, .ipa) storage # bin_dir = ./bin # ----------------------------------------------------------------------------- # List as sections # # You can define all the "list" as [section:key]. # Each line will be considered as a option to the list. # Let's take [app] / source.exclude_patterns. # Instead of doing: # #[app] #source.exclude_patterns = license,data/audio/*.wav,data/images/original/* # # This can be translated into: # #[app:source.exclude_patterns] #license #data/audio/*.wav #data/images/original/* # # ----------------------------------------------------------------------------- # Profiles # # You can extend section / key with a profile # For example, you want to deploy a demo version of your application without # HD content. You could first change the title to add "(demo)" in the name # and extend the excluded directories to remove the HD content. # #[app@demo] #title = My Application (demo) # #[app:source.exclude_patterns@demo] #images/hd/* # # Then, invoke the command line with the "demo" profile: # #buildozer --profile demo android debug plyer-2.1.0/examples/light/main.py000066400000000000000000000033211433372044000170670ustar00rootroot00000000000000from kivy.app import App from kivy.clock import Clock from kivy.lang import Builder from kivy.properties import NumericProperty from kivy.properties import ObjectProperty from kivy.uix.boxlayout import BoxLayout Builder.load_string(''' #:import light plyer.light : light: light orientation: 'vertical' padding: '50dp' spacing: '50dp' BoxLayout: orientation: 'horizontal' size_hint_y: 0.3 Button: id: button_enable text: 'Enable' disabled: False on_release: root.enable() button_disable.disabled = not button_disable.disabled button_enable.disabled = not button_enable.disabled Button: id: button_disable text: 'Disable' disabled: True on_release: root.disable() button_disable.disabled = not button_disable.disabled button_enable.disabled = not button_enable.disabled Label: text: 'Current illumination:' + str(root.illumination) + ' lx.' ''') class LightInterface(BoxLayout): '''Root Widget.''' light = ObjectProperty() illumination = NumericProperty() def enable(self): self.light.enable() Clock.schedule_interval(self.get_illumination, 1 / 20.) def disable(self): self.light.disable() Clock.unschedule(self.get_illumination) def get_illumination(self, dt): self.illumination = self.light.illumination or self.illumination class LightApp(App): def build(self): return LightInterface() def on_pause(self): return True if __name__ == '__main__': LightApp().run() plyer-2.1.0/examples/notification/000077500000000000000000000000001433372044000171515ustar00rootroot00000000000000plyer-2.1.0/examples/notification/buildozer.spec000066400000000000000000000115771433372044000220370ustar00rootroot00000000000000[app] # (str) Title of your application title = Plyer Notification Example # (str) Package name package.name = notiexample # (str) Package domain (needed for android/ios packaging) package.domain = org.test # (str) Source code where the main.py live source.dir = . # (list) Source files to include (let empty to include all the files) source.include_exts = py,png,jpg,kv,atlas # (list) Source files to exclude (let empty to not exclude anything) #source.exclude_exts = spec # (list) List of directory to exclude (let empty to not exclude anything) #source.exclude_dirs = tests, bin # (list) List of exclusions using pattern matching #source.exclude_patterns = license,images/*/*.jpg # (str) Application versioning (method 1) # version.regex = __version__ = '(.*)' # version.filename = %(source.dir)s/main.py # (str) Application versioning (method 2) version = 1.0 # (list) Application requirements requirements = plyer,kivy # (str) Presplash of the application #presplash.filename = %(source.dir)s/data/presplash.png # (str) Icon of the application #icon.filename = %(source.dir)s/data/icon.png # (str) Supported orientation (one of landscape, portrait or all) orientation = portrait # (bool) Indicate if the application should be fullscreen or not fullscreen = 0 # # Android specific # # (list) Permissions android.permissions = INTERNET # (int) Android API to use #android.api = 14 # (int) Minimum API required (8 = Android 2.2 devices) #android.minapi = 8 # (int) Android SDK version to use #android.sdk = 21 # (str) Android NDK version to use #android.ndk = 9 # (bool) Use --private data storage (True) or --dir public storage (False) #android.private_storage = True # (str) Android NDK directory (if empty, it will be automatically downloaded.) #android.ndk_path = # (str) Android SDK directory (if empty, it will be automatically downloaded.) #android.sdk_path = # (str) Android entry point, default is ok for Kivy-based app #android.entrypoint = org.renpy.android.PythonActivity # (list) List of Java .jar files to add to the libs so that pyjnius can access # their classes. Don't add jars that you do not need, since extra jars can slow # down the build process. Allows wildcards matching, for example: # OUYA-ODK/libs/*.jar #android.add_jars = foo.jar,bar.jar,path/to/more/*.jar # (list) List of Java files to add to the android project (can be java or a # directory containing the files) #android.add_src = # (str) python-for-android branch to use, if not master, useful to try # not yet merged features. #android.branch = master # (str) OUYA Console category. Should be one of GAME or APP # If you leave this blank, OUYA support will not be enabled #android.ouya.category = GAME # (str) Filename of OUYA Console icon. It must be a 732x412 png image. #android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png # (str) XML file to include as an intent filters in tag #android.manifest.intent_filters = # (list) Android additionnal libraries to copy into libs/armeabi #android.add_libs_armeabi = libs/android/*.so # (bool) Indicate whether the screen should stay on # Don't forget to add the WAKE_LOCK permission if you set this to True #android.wakelock = False # (list) Android application meta-data to set (key=value format) #android.meta_data = # (list) Android library project to add (will be added in the # project.properties automatically.) #android.library_references = # # iOS specific # # (str) Name of the certificate to use for signing the debug version # Get a list of available identities: buildozer ios list_identities #ios.codesign.debug = "iPhone Developer: ()" # (str) Name of the certificate to use for signing the release version #ios.codesign.release = %(ios.codesign.debug)s [buildozer] # (int) Log level (0 = error only, 1 = info, 2 = debug (with command output)) log_level = 2 # ----------------------------------------------------------------------------- # List as sections # # You can define all the "list" as [section:key]. # Each line will be considered as a option to the list. # Let's take [app] / source.exclude_patterns. # Instead of doing: # # [app] # source.exclude_patterns = license,data/audio/*.wav,data/images/original/* # # This can be translated into: # # [app:source.exclude_patterns] # license # data/audio/*.wav # data/images/original/* # # ----------------------------------------------------------------------------- # Profiles # # You can extend section / key with a profile # For example, you want to deploy a demo version of your application without # HD content. You could first change the title to add "(demo)" in the name # and extend the excluded directories to remove the HD content. # # [app@demo] # title = My Application (demo) # # [app:source.exclude_patterns@demo] # images/hd/* # # Then, invoke the command line with the "demo" profile: # # buildozer --profile demo android debug plyer-2.1.0/examples/notification/main.py000066400000000000000000000023341433372044000204510ustar00rootroot00000000000000from os.path import join, dirname, realpath import kivy from kivy.app import App from kivy.uix.boxlayout import BoxLayout from plyer import notification from plyer.utils import platform kivy.require('1.8.0') class NotificationDemo(BoxLayout): def do_notify(self, mode='normal'): title = self.ids.notification_title.text message = self.ids.notification_text.text ticker = self.ids.ticker_text.text kwargs = {'title': title, 'message': message, 'ticker': ticker} if mode == 'fancy': kwargs['app_name'] = "Plyer Notification Example" if platform == "win": kwargs['app_icon'] = join(dirname(realpath(__file__)), 'plyer-icon.ico') kwargs['timeout'] = 4 else: kwargs['app_icon'] = join(dirname(realpath(__file__)), 'plyer-icon.png') elif mode == 'toast': kwargs['toast'] = True notification.notify(**kwargs) class NotificationDemoApp(App): def build(self): return NotificationDemo() def on_pause(self): return True if __name__ == '__main__': NotificationDemoApp().run() plyer-2.1.0/examples/notification/notificationdemo.kv000066400000000000000000000015701433372044000230510ustar00rootroot00000000000000#:kivy 1.8.0 : orientation: 'vertical' Widget: BoxLayout: orientation: 'horizontal' size_hint: 1, None TextInput: id: notification_title text: 'Put title here' size_hint: 1, None TextInput: id: notification_text text: 'Put message here' size_hint: 1, None TextInput: id: ticker_text text: 'New notification' size_hint: 1, None Button: text: 'Toast Notification' size_hint: 1, None on_release: root.do_notify(mode='toast') Button: text: 'Simple Notification' size_hint: 1, None on_release: root.do_notify(mode='normal') Button: text: 'Fancy Notification' size_hint: 1, None on_release: root.do_notify(mode='fancy') Widget: plyer-2.1.0/examples/notification/plyer-icon.ico000066400000000000000000000764461433372044000217470ustar00rootroot00000000000000 hF 00 %V@@ (B:(  @KOeaI q ; e9 G 9 ;GI-=~~qЭe#? VV!oee;o{{#GiiCM"Gee3=||qLL?ttK Wmm==ann=KʇttOCxx[-a( @  'IuE+w [}Gk [;M 5Aw 3o i=!%/A 3os.#.' O/#32 $%%)"+ ?+ I ==7wwqp c3#1I5 3' ҝa-)9?7^^iا_m 36i353 ee_k?E_ 9);;=اmYY{]885qeall׿)pp_y7xxy LL_ڹi~~W !!="wwY{{/``rr)HHU9``) 995sLLY1 ! (('C׽ ~~Y 1aay;;HH5  ((%-& ! 995AAG ??3s``?AK](0` %?k)om i  ] a iI  gO{U?! ?E# S 5m(-;A59Gy?2'&/ A*+G 7_,'9#2%5}@E 5a60#/' /2#.#  IwE(%+%') I''/=2A/ I '%%!+"+!//+'i Eaaxx &#K''!= O15- I!BBĵXmC1+'  9 Y593 % ;w!1++/C;9 5__eauw 5-1593 GYSk S57IUU}eas||vyo A ;+ //]~~˿}‹ >CO PPg˳sq3qq!hh{c_oM//_Y 22gcWk<<'#owi BB  [[u KK_ 55k}! QQ;/HH} +"mmqCC{JJ >>{{44gO ..[ ;3# uu%ppG XX ]] ==s + KK{3 9EEu ttUDDyKK}BB% ;EE'ttbb II# SS7$cc  GU!pp/{{a;76 yyV sQ3 (@ B/i/+yyA  iwio3M a ]q gC+q mU U Y a[] OIAEU79} ?A; #C W  W G&/9? C aG 1[9/+%1 7 Q _5 A  mgwR:#,))/ U 1%4'5 5W[Q3#8#5%4' ? 1;F  O  a?/#1#0'2'  C 9,%0# = )y/)#-#,')) =!''%;] /k)!%%+%&'#+"+  U I+)$% ]#91k S7'"%&'/#+- 11++ ?g"::O NNaayy]C)'#)A U335-+ [)TTI#35)) ! ; _=57 -G# ]]ѣuC-1+)#' C W79 93 %Wu{wU!+++-9C9793!'ffkaiw8>M3-/359;%1vvݩ[S_o}~ =1575 !  <ѭ"Bq1ϊ>o [DԋpUPGȇGu003W}'_:)}=4at?RS)|_O3QՁ-n"BBM <Ӟ{z^,hIP A K9g^a{cS%]Vu#Z_ϗ"ܖH5րZҥla18?Lc(Ļ" k;cTó",װ f&h[-aڵ MuD×clan״i4x>Ĵ5? EN89ɉ8NӺ@.u0 =#sMtR""JD8(=bNw AX̝KD188 %.ZD6(KQ ׭ѣ|D0ɱȸq_(jg8+' ,v4`W^Ʋ-eƍ,Q,[/X@eTZ׉@;\E -ZDݫR!u̚E1|`YE^ "ܺf ?lth /-Teq:H~G,>OTߣRoY,,n"w{ioxo6CEy9S+.{UvX,l׽%hZ _)qR o!:nq@oâMe}g6瞺pa4♙$N;ư5t9Tբ*.VOO™iiȘ1+!5@6k-}I#Æ15xlG;,ld̞]S#BS҅J1DRpL}7uKO':t(23y[k"qJ٩ :L9sJ/N)6 r%T9s8Fd5NN[Coz@ԓY5_j͇՚J@PVs?VIMbXMuALz:))Q/tbFfUz:eZx[7_UY-t8]x!3/Ʋ:r^ҿvZ3gzQ~I|5q|_B{d'/ ci&r*z]ggs80\KP5> b1p>}K&1H=iw[1ed 6%B3x:1e;e>š4Y`iMxmra2ΘQ1Dֲ(p8hC\788Bof%M׼yb48ƏIcئ5i@n[/\ bzn b{t1_}uٔ!Cȴ,o\'Hd6y@PoAnNʆk!R2#`ԥx F@S@/pKu L$>+9#Puɱ88^D#u'V֫ymBUC8Z]​񸷴TCQK@:=G-$ ߕ֍zȍ+. 8J$dQ#B+Ƭ 9a G=y>:^_/V9c C}#(`;Roӆq(TA, TSt8Z@U'tSpQ㉬,rNՔC=~S]5"8#J)OgR M<$_,6颋n1"K`4q1ߤW7,-nc|ĢE^x`] 1$B '?\ӉZ쨩cJjv5/@> , 6jzQ+r~ؼaс[e?l`UTpjQE%sVD231qDZn79ݺqrzY aUUal٦e eb@ˑcd2nm .&RJm$cp 0 &M2P{eUVlh@A9,S6& ,…P;DexyL[+ jkq8jj`J. cB!j:B"W\z)s 7qJz(6`\qEVQCFO>a1T+[~?'1PXq2ψ_%W\(F-_vYZI$/Q^Nƍ1Z`yn\駇.wgtW /U"srVTpo /?ohf aN7F`Dzq("YeD/ʼnj2XSXH͌i HѣKGil[֚だ1mzPThtybe!3gtVv`$"k^3Hgc#@w z >,R* KGk eiij2NmˆTud^p8W1tԚxz:oL}I}XDzmJaf͢<4&E" kB)e̲e4B8n%Sk|23fRJzk^Q(dJ~Xrʕk "nzA\O>I:W3T^۶86= (w8ˏO<.+M*hӆJEUI&L=̡7pk].2EԳ͓~/?9'ǵ|9ݔ"X"XѨZ|0~ Kh՚/.X 5s&@laG"ARJL._N0DZhH.nÆsN VOUjYA~A@/cњrYN _M33ךpom-.ceLZhU3-@M]+2mb+U5Ys6@R]ˬ+}:Q۠5m^z 0>l iv,X\̏a$%R"|MA͟ʁuDY DDny_R8fme ~Hx<4Z|9t IFj @pYkךmR;o֬ O(.⬳ S8*A_uSmAcpWTPѮO;}Fg%tj ZS?"MŘ14hMR?_ŲC.B{"O?-.M.9s8HDfx(xP5֕qs@M!(\=znฎYwꩬ>*1OgUm-Ab.jpoi͕XZFl܈s:/Xipq2%1:ḼN_& ͜X O"7r!SyO'8ݐȇ6#&äx֭…\ hMcOQHGA"2K ;bl'üi ŋ?XT $$PmgMyoܱ90L)5mx<}P*T}I6U HڶyT7ےb*r ftI $m-!pyw0@ఊ~10f::'vsK!L媸DJA~0i` E Ņ{BCI啯u"Ԅ/Kj_Yhşm1񳬌koG@ q9#*M|[q?Fv"+ j.J"2xBᷖ-C, *j\@}r;/l Gjyxnڞ _2j\"O8UYMqE o ).rQ  !nY Z$ QێZ$5!gU>}h{;Vv`5kfUwΌٹB#0q#ѵq0 7cLA?Ɲ$sa"׊K\9{6- پY…D0r (YQ; RuZ9퐑Q3tu8P.r|>6 ϗܱdbBo4^A"EZz39K[;ÝmT&d8eJ5:$q; ݻ=]N Q !cw,R]"#E)^y NצX̘AIdf6!]&Naȋlj;(p iiC1.c@Rzʒӓ$}7+t9o_`JKѩ+f'=&=4lN8<ǃ(ӮA>bV)E7W9eͷVo Eg׮hL;1J᪨Byhov%Wqqŝ.W8c/G"A&x:=FY@>г1Ԥ*/}FqcЃx채ET$2Kkzb\vNDK~*Ex0N'Rvcc(ך !]kJ"NFG6i<;sg Hx5r$EEEOk[|ѧ*o4 gN\tqH\Xvя l6 ۳i@ WR"ӏV'ӄ|„/4l"p$w`lKO'*66k`1dD߰ve]/}Ig^{-}b1"n7"p }P#Ma@`FQh Y[]Sm".({_p Ʊ܍;-IL͕%$;tkQÁЁA؜-w͓XEڂ?^&I7۶i=wx pӉ;.;j%kyTn>{&>]Տ|3"dt'fw*4G 8Dtϯ;q8 j [rbFk㫪X/ub7,a[7Ѿ}r/IB)YΊ_m2h uYǮDtUZQ[#1c"#T$״ghM[$pQalؓyک :kXÁe #y'777R ZزڷGZ:&ڡh]H"I"A煅ێ%56Oy@֭<׾ܿs&?g#tbޝ Cu!`FBT76[oڨQFHz }>,vcj&`E%BR8#eMsة ]C3X~?cyKBr }|ϲ\.UòlW":{!z5ה_ׯ(1ڷgLj|nga>4@䩫cݔ)p=lg1GVvII7Ĉh4Dcbz:l$yn{1M߱vDӔvZ霝.W2,\#U;c8k ?uRQY/޾o {'T_?GvfPԉ*aKrM@mZM_kO٧Jc઩a7yo_&J&p "o~5`h 4kֶ_:D ;99Dal!A "Uws&x,EKS܂x-MeL QZͥXn 5"T8(e;VM@]zt~Q&4Aʙ|M{RD[Qzo~S6G@Bk,8@^gv1 ~7O`V13~P &ir$@N:(@r#ԪxCDdъo,Món_ܹ3@КÑ,fdPw|[Ɍ|6lu vHl:?uY`@mp8(~0 Q5o 6:>{>?U&e8 AlxIjhcAa rs`I:gH$ڄΖm7( ۴r+f3v]22٘$}2M('hm ) ˝-Ug 껸/"5@b?w|CIDATpր^8orHn%8akI ym&U;.Nj@k͗SQ87 IVsm%sYǑ#">Ѹ?4 "϶IFw^,zj3,dYQw.1҃X셫wL}Τvp~@v dT \1doBOŪNyUD<&ۙt:=w$, Bڇ"t tag #android.manifest.intent_filters = # (list) Android additionnal libraries to copy into libs/armeabi #android.add_libs_armeabi = libs/android/*.so # (bool) Indicate whether the screen should stay on # Don't forget to add the WAKE_LOCK permission if you set this to True #android.wakelock = False # (list) Android application meta-data to set (key=value format) #android.meta_data = # (list) Android library project to add (will be added in the # project.properties automatically.) #android.library_references = # # iOS specific # # (str) Name of the certificate to use for signing the debug version # Get a list of available identities: buildozer ios list_identities #ios.codesign.debug = "iPhone Developer: ()" # (str) Name of the certificate to use for signing the release version #ios.codesign.release = %(ios.codesign.debug)s [buildozer] # (int) Log level (0 = error only, 1 = info, 2 = debug (with command output)) log_level = 2 # ----------------------------------------------------------------------------- # List as sections # # You can define all the "list" as [section:key]. # Each line will be considered as a option to the list. # Let's take [app] / source.exclude_patterns. # Instead of doing: # # [app] # source.exclude_patterns = license,data/audio/*.wav,data/images/original/* # # This can be translated into: # # [app:source.exclude_patterns] # license # data/audio/*.wav # data/images/original/* # # ----------------------------------------------------------------------------- # Profiles # # You can extend section / key with a profile # For example, you want to deploy a demo version of your application without # HD content. You could first change the title to add "(demo)" in the name # and extend the excluded directories to remove the HD content. # # [app@demo] # title = My Application (demo) # # [app:source.exclude_patterns@demo] # images/hd/* # # Then, invoke the command line with the "demo" profile: # # buildozer --profile demo android debug plyer-2.1.0/examples/orientation/main.py000066400000000000000000000022221433372044000203120ustar00rootroot00000000000000 from kivy.base import runTouchApp from kivy.lang import Builder interface = Builder.load_string(''' #:import orientation plyer.orientation : text_size: self.size valign: 'middle' halign: 'center' BoxLayout: orientation: 'horizontal' GridLayout: size_hint_x: 2 cols: 2 WrapButton: text: 'portrait' on_release: orientation.set_portrait() WrapButton: text: 'portrait reverse' on_release: orientation.set_portrait(reverse=True) WrapButton: text: 'landscape' on_release: orientation.set_landscape() WrapButton: text: 'landscape reverse' on_release: orientation.set_landscape(reverse=True) WrapButton: text: 'free sensor' on_release: orientation.set_sensor(mode='any') Widget: WrapButton: text: 'landscape sensor' on_release: orientation.set_sensor(mode='landscape') WrapButton: text: 'portrait sensor' on_release: orientation.set_sensor(mode='portrait') ''') runTouchApp(interface) plyer-2.1.0/examples/proximity/000077500000000000000000000000001433372044000165275ustar00rootroot00000000000000plyer-2.1.0/examples/proximity/buildozer.spec000066400000000000000000000152361433372044000214110ustar00rootroot00000000000000[app] # (str) Title of your application title = Plyer Proximity Example # (str) Package name package.name = plyer.proximity # (str) Package domain (needed for android/ios packaging) package.domain = org.test # (str) Source code where the main.py live source.dir = . # (list) Source files to include (let empty to include all the files) source.include_exts = py,png,jpg,kv,atlas # (list) List of inclusions using pattern matching #source.include_patterns = assets/*,images/*.png # (list) Source files to exclude (let empty to not exclude anything) #source.exclude_exts = spec # (list) List of directory to exclude (let empty to not exclude anything) #source.exclude_dirs = tests, bin # (list) List of exclusions using pattern matching #source.exclude_patterns = license,images/*/*.jpg # (str) Application versioning (method 1) version = 0.1 # (str) Application versioning (method 2) # version.regex = __version__ = ['"](.*)['"] # version.filename = %(source.dir)s/main.py # (list) Application requirements # comma seperated e.g. requirements = sqlite3,kivy requirements = kivy, plyer # (str) Custom source folders for requirements # Sets custom source for any requirements with recipes #requirements.source.plyer = # (list) Garden requirements #garden_requirements = # (str) Presplash of the application #presplash.filename = %(source.dir)s/data/presplash.png # (str) Icon of the application #icon.filename = %(source.dir)s/data/icon.png # (str) Supported orientation (one of landscape, portrait or all) orientation = portrait # (list) List of service to declare #services = NAME:ENTRYPOINT_TO_PY,NAME2:ENTRYPOINT2_TO_PY # # OSX Specific # # # author = © Copyright Info # # Android specific # # (bool) Indicate if the application should be fullscreen or not fullscreen = 0 # (list) Permissions #android.permissions = INTERNET # (int) Android API to use #android.api = 19 # (int) Minimum API required #android.minapi = 9 # (int) Android SDK version to use #android.sdk = 20 # (str) Android NDK version to use #android.ndk = 9c # (bool) Use --private data storage (True) or --dir public storage (False) #android.private_storage = True # (str) Android NDK directory (if empty, it will be automatically downloaded.) #android.ndk_path = # (str) Android SDK directory (if empty, it will be automatically downloaded.) #android.sdk_path = # (str) ANT directory (if empty, it will be automatically downloaded.) #android.ant_path = # (str) python-for-android git clone directory (if empty, it will be automatically cloned from github) #android.p4a_dir = # (str) The directory in which python-for-android should look for your own build recipes (if any) #p4a.local_recipes = # (list) python-for-android whitelist #android.p4a_whitelist = # (bool) If True, then skip trying to update the Android sdk # This can be useful to avoid excess Internet downloads or save time # when an update is due and you just want to test/build your package # android.skip_update = False # (str) Bootstrap to use for android builds (android_new only) # android.bootstrap = sdl2 # (str) Android entry point, default is ok for Kivy-based app #android.entrypoint = org.renpy.android.PythonActivity # (list) List of Java .jar files to add to the libs so that pyjnius can access # their classes. Don't add jars that you do not need, since extra jars can slow # down the build process. Allows wildcards matching, for example: # OUYA-ODK/libs/*.jar #android.add_jars = foo.jar,bar.jar,path/to/more/*.jar # (list) List of Java files to add to the android project (can be java or a # directory containing the files) #android.add_src = # (str) python-for-android branch to use, if not master, useful to try # not yet merged features. #android.branch = master # (str) OUYA Console category. Should be one of GAME or APP # If you leave this blank, OUYA support will not be enabled #android.ouya.category = GAME # (str) Filename of OUYA Console icon. It must be a 732x412 png image. #android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png # (str) XML file to include as an intent filters in tag #android.manifest.intent_filters = # (list) Android additionnal libraries to copy into libs/armeabi #android.add_libs_armeabi = libs/android/*.so #android.add_libs_armeabi_v7a = libs/android-v7/*.so #android.add_libs_x86 = libs/android-x86/*.so #android.add_libs_mips = libs/android-mips/*.so # (bool) Indicate whether the screen should stay on # Don't forget to add the WAKE_LOCK permission if you set this to True #android.wakelock = False # (list) Android application meta-data to set (key=value format) #android.meta_data = # (list) Android library project to add (will be added in the # project.properties automatically.) #android.library_references = # (str) Android logcat filters to use #android.logcat_filters = *:S python:D # (bool) Copy library instead of making a libpymodules.so #android.copy_libs = 1 # # iOS specific # # (str) Path to a custom kivy-ios folder #ios.kivy_ios_dir = ../kivy-ios # (str) Name of the certificate to use for signing the debug version # Get a list of available identities: buildozer ios list_identities #ios.codesign.debug = "iPhone Developer: ()" # (str) Name of the certificate to use for signing the release version #ios.codesign.release = %(ios.codesign.debug)s [buildozer] # (int) Log level (0 = error only, 1 = info, 2 = debug (with command output)) log_level = 2 # (int) Display warning if buildozer is run as root (0 = False, 1 = True) warn_on_root = 1 # (str) Path to build artifact storage, absolute or relative to spec file # build_dir = ./.buildozer # (str) Path to build output (i.e. .apk, .ipa) storage # bin_dir = ./bin # ----------------------------------------------------------------------------- # List as sections # # You can define all the "list" as [section:key]. # Each line will be considered as a option to the list. # Let's take [app] / source.exclude_patterns. # Instead of doing: # #[app] #source.exclude_patterns = license,data/audio/*.wav,data/images/original/* # # This can be translated into: # #[app:source.exclude_patterns] #license #data/audio/*.wav #data/images/original/* # # ----------------------------------------------------------------------------- # Profiles # # You can extend section / key with a profile # For example, you want to deploy a demo version of your application without # HD content. You could first change the title to add "(demo)" in the name # and extend the excluded directories to remove the HD content. # #[app@demo] #title = My Application (demo) # #[app:source.exclude_patterns@demo] #images/hd/* # # Then, invoke the command line with the "demo" profile: # #buildozer --profile demo android debug plyer-2.1.0/examples/proximity/main.py000066400000000000000000000035731433372044000200350ustar00rootroot00000000000000from kivy.app import App from kivy.clock import Clock from kivy.lang import Builder from kivy.properties import BooleanProperty from kivy.properties import ObjectProperty from kivy.uix.boxlayout import BoxLayout Builder.load_string(''' #:import proximity plyer.proximity : proximity: proximity orientation: 'vertical' padding: '50dp' spacing: '20dp' BoxLayout: orientation: 'horizontal' size_hint_y: 0.3 Button: id: button_enable text: 'Enable' disabled: False on_release: root.enable() button_disable.disabled = not button_disable.disabled button_enable.disabled = not button_enable.disabled Button: id: button_disable text: 'Disable' disabled: True on_release: root.disable() button_disable.disabled = not button_disable.disabled button_enable.disabled = not button_enable.disabled Label: text: 'Does Proximity Sensor detect something?' Label: text: 'Yes' if root.is_near else 'No' Widget: Label: text: 'Cover with your hand' Label: text: 'a top part of phone to see result.' ''') class ProximityInterface(BoxLayout): '''Root Widget.''' proximity = ObjectProperty() is_near = BooleanProperty(False) def enable(self): self.proximity.enable() Clock.schedule_interval(self.get_proxime, 1 / 20.) def disable(self): self.proximity.disable() Clock.unschedule(self.get_proxime) def get_proxime(self, dt): self.is_near = self.proximity.proximity class ProximityApp(App): def build(self): return ProximityInterface() def on_pause(self): return True if __name__ == "__main__": ProximityApp().run() plyer-2.1.0/examples/screenshot/000077500000000000000000000000001433372044000166405ustar00rootroot00000000000000plyer-2.1.0/examples/screenshot/main.py000066400000000000000000000012241433372044000201350ustar00rootroot00000000000000from kivy.app import App from kivy.lang import Builder from kivy.uix.boxlayout import BoxLayout Builder.load_string(''' #:import screenshot plyer.screenshot : orientation: 'vertical' padding: '50dp' spacing: '20dp' Label: size_hint_y: None height: sp(40) text: 'Screenshot Location: ' + str(screenshot.file_path) Button: text: 'Capture Screenshot' on_release: screenshot.capture() ''') class ScreenshotDemo(BoxLayout): '''Root Widget.''' class ScreenshotApp(App): def build(self): return ScreenshotDemo() if __name__ == "__main__": ScreenshotApp().run() plyer-2.1.0/examples/sms/000077500000000000000000000000001433372044000152655ustar00rootroot00000000000000plyer-2.1.0/examples/sms/buildozer.spec000066400000000000000000000120331433372044000201370ustar00rootroot00000000000000[app] # (str) Title of your application title = Plyer sms Test # (str) Package name package.name = plyersms # (str) Package domain (needed for android/ios packaging) package.domain = org.test # (str) Source code where the main.py live source.dir = . # (list) Source files to include (let empty to include all the files) source.include_exts = py,png,jpg,kv,atlas # (list) Source files to exclude (let empty to not exclude anything) #source.exclude_exts = spec # (list) List of directory to exclude (let empty to not exclude anything) #source.exclude_dirs = tests, bin # (list) List of exclusions using pattern matching #source.exclude_patterns = license,images/*/*.jpg # (str) Application versioning (method 1) #version.regex = __version__ = '(.*)' #version.filename = %(source.dir)s/main.py # (str) Application versioning (method 2) version = 0.1 # (list) Application requirements requirements = plyer,kivy # (list) Garden requirements #garden_requirements = # (str) Presplash of the application #presplash.filename = %(source.dir)s/data/presplash.png # (str) Icon of the application #icon.filename = %(source.dir)s/data/icon.png # (str) Supported orientation (one of landscape, portrait or all) orientation = portrait # (bool) Indicate if the application should be fullscreen or not fullscreen = 0 # # Android specific # # (list) Permissions android.permissions = SEND_SMS # (int) Android API to use #android.api = 14 # (int) Minimum API required (8 = Android 2.2 devices) #android.minapi = 8 # (int) Android SDK version to use #android.sdk = 21 # (str) Android NDK version to use #android.ndk = 9c # (bool) Use --private data storage (True) or --dir public storage (False) #android.private_storage = True # (str) Android NDK directory (if empty, it will be automatically downloaded.) #android.ndk_path = # (str) Android SDK directory (if empty, it will be automatically downloaded.) #android.sdk_path = # (str) python-for-android git clone directory (if empty, it will be automatically cloned from github) #android.p4a_dir = # (str) Android entry point, default is ok for Kivy-based app #android.entrypoint = org.renpy.android.PythonActivity # (list) List of Java .jar files to add to the libs so that pyjnius can access # their classes. Don't add jars that you do not need, since extra jars can slow # down the build process. Allows wildcards matching, for example: # OUYA-ODK/libs/*.jar #android.add_jars = foo.jar,bar.jar,path/to/more/*.jar # (list) List of Java files to add to the android project (can be java or a # directory containing the files) #android.add_src = # (str) python-for-android branch to use, if not master, useful to try # not yet merged features. #android.branch = master # (str) OUYA Console category. Should be one of GAME or APP # If you leave this blank, OUYA support will not be enabled #android.ouya.category = GAME # (str) Filename of OUYA Console icon. It must be a 732x412 png image. #android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png # (str) XML file to include as an intent filters in tag #android.manifest.intent_filters = # (list) Android additionnal libraries to copy into libs/armeabi #android.add_libs_armeabi = libs/android/*.so # (bool) Indicate whether the screen should stay on # Don't forget to add the WAKE_LOCK permission if you set this to True #android.wakelock = False # (list) Android application meta-data to set (key=value format) #android.meta_data = # (list) Android library project to add (will be added in the # project.properties automatically.) #android.library_references = # # iOS specific # # (str) Name of the certificate to use for signing the debug version # Get a list of available identities: buildozer ios list_identities #ios.codesign.debug = "iPhone Developer: ()" # (str) Name of the certificate to use for signing the release version #ios.codesign.release = %(ios.codesign.debug)s [buildozer] # (int) Log level (0 = error only, 1 = info, 2 = debug (with command output)) log_level = 2 # ----------------------------------------------------------------------------- # List as sections # # You can define all the "list" as [section:key]. # Each line will be considered as a option to the list. # Let's take [app] / source.exclude_patterns. # Instead of doing: # # [app] # source.exclude_patterns = license,data/audio/*.wav,data/images/original/* # # This can be translated into: # # [app:source.exclude_patterns] # license # data/audio/*.wav # data/images/original/* # # ----------------------------------------------------------------------------- # Profiles # # You can extend section / key with a profile # For example, you want to deploy a demo version of your application without # HD content. You could first change the title to add "(demo)" in the name # and extend the excluded directories to remove the HD content. # # [app@demo] # title = My Application (demo) # # [app:source.exclude_patterns@demo] # images/hd/* # # Then, invoke the command line with the "demo" profile: # # buildozer --profile demo android debugplyer-2.1.0/examples/sms/main.py000066400000000000000000000023061433372044000165640ustar00rootroot00000000000000from kivy.app import App from kivy.uix.boxlayout import BoxLayout from kivy.uix.button import Button from kivy.lang import Builder from kivy.properties import StringProperty from plyer import sms Builder.load_string(''' : orientation: 'vertical' BoxLayout: size_hint_y: None height: sp(30) Label: text: 'Recipient:' TextInput: id: recipient multiline: False on_text_validate: message.focus = True BoxLayout: Label: text: 'Message:' TextInput: id: message IntentButton: sms_recipient: recipient.text sms_message: message.text text: 'Send SMS' size_hint_y: None height: sp(40) on_release: self.send_sms() ''') class SmsInterface(BoxLayout): pass class IntentButton(Button): sms_recipient = StringProperty() sms_message = StringProperty() def send_sms(self, *args): sms.send(recipient=self.sms_recipient, message=self.sms_message) class SmsApp(App): def build(self): return SmsInterface() def on_pause(self): return True if __name__ == "__main__": SmsApp().run() plyer-2.1.0/examples/spatialorientation/000077500000000000000000000000001433372044000203745ustar00rootroot00000000000000plyer-2.1.0/examples/spatialorientation/buildozer.spec000066400000000000000000000152571433372044000232610ustar00rootroot00000000000000[app] # (str) Title of your application title = Plyer spatial orientation # (str) Package name package.name = plyerspatialorientation # (str) Package domain (needed for android/ios packaging) package.domain = org.test # (str) Source code where the main.py live source.dir = . # (list) Source files to include (let empty to include all the files) source.include_exts = py,png,jpg,kv,atlas # (list) List of inclusions using pattern matching #source.include_patterns = assets/*,images/*.png # (list) Source files to exclude (let empty to not exclude anything) #source.exclude_exts = spec # (list) List of directory to exclude (let empty to not exclude anything) #source.exclude_dirs = tests, bin # (list) List of exclusions using pattern matching #source.exclude_patterns = license,images/*/*.jpg # (str) Application versioning (method 1) version = 0.1 # (str) Application versioning (method 2) # version.regex = __version__ = ['"](.*)['"] # version.filename = %(source.dir)s/main.py # (list) Application requirements # comma seperated e.g. requirements = sqlite3,kivy requirements = kivy,plyer # (str) Custom source folders for requirements # Sets custom source for any requirements with recipes # requirements.source.kivy = ../../kivy # (list) Garden requirements #garden_requirements = # (str) Presplash of the application #presplash.filename = %(source.dir)s/data/presplash.png # (str) Icon of the application #icon.filename = %(source.dir)s/data/icon.png # (str) Supported orientation (one of landscape, portrait or all) orientation = portrait # (list) List of service to declare #services = NAME:ENTRYPOINT_TO_PY,NAME2:ENTRYPOINT2_TO_PY # # OSX Specific # # # author = © Copyright Info # # Android specific # # (bool) Indicate if the application should be fullscreen or not fullscreen = 0 # (list) Permissions #android.permissions = INTERNET # (int) Android API to use #android.api = 19 # (int) Minimum API required #android.minapi = 9 # (int) Android SDK version to use #android.sdk = 20 # (str) Android NDK version to use #android.ndk = 9c # (bool) Use --private data storage (True) or --dir public storage (False) #android.private_storage = True # (str) Android NDK directory (if empty, it will be automatically downloaded.) #android.ndk_path = # (str) Android SDK directory (if empty, it will be automatically downloaded.) #android.sdk_path = # (str) ANT directory (if empty, it will be automatically downloaded.) #android.ant_path = # (str) python-for-android git clone directory (if empty, it will be automatically cloned from github) #android.p4a_dir = # (str) The directory in which python-for-android should look for your own build recipes (if any) #p4a.local_recipes = # (list) python-for-android whitelist #android.p4a_whitelist = # (bool) If True, then skip trying to update the Android sdk # This can be useful to avoid excess Internet downloads or save time # when an update is due and you just want to test/build your package # android.skip_update = False # (str) Bootstrap to use for android builds (android_new only) # android.bootstrap = sdl2 # (str) Android entry point, default is ok for Kivy-based app #android.entrypoint = org.renpy.android.PythonActivity # (list) List of Java .jar files to add to the libs so that pyjnius can access # their classes. Don't add jars that you do not need, since extra jars can slow # down the build process. Allows wildcards matching, for example: # OUYA-ODK/libs/*.jar #android.add_jars = foo.jar,bar.jar,path/to/more/*.jar # (list) List of Java files to add to the android project (can be java or a # directory containing the files) #android.add_src = # (str) python-for-android branch to use, if not master, useful to try # not yet merged features. #android.branch = master # (str) OUYA Console category. Should be one of GAME or APP # If you leave this blank, OUYA support will not be enabled #android.ouya.category = GAME # (str) Filename of OUYA Console icon. It must be a 732x412 png image. #android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png # (str) XML file to include as an intent filters in tag #android.manifest.intent_filters = # (list) Android additionnal libraries to copy into libs/armeabi #android.add_libs_armeabi = libs/android/*.so #android.add_libs_armeabi_v7a = libs/android-v7/*.so #android.add_libs_x86 = libs/android-x86/*.so #android.add_libs_mips = libs/android-mips/*.so # (bool) Indicate whether the screen should stay on # Don't forget to add the WAKE_LOCK permission if you set this to True #android.wakelock = False # (list) Android application meta-data to set (key=value format) #android.meta_data = # (list) Android library project to add (will be added in the # project.properties automatically.) #android.library_references = # (str) Android logcat filters to use #android.logcat_filters = *:S python:D # (bool) Copy library instead of making a libpymodules.so #android.copy_libs = 1 # # iOS specific # # (str) Path to a custom kivy-ios folder #ios.kivy_ios_dir = ../kivy-ios # (str) Name of the certificate to use for signing the debug version # Get a list of available identities: buildozer ios list_identities #ios.codesign.debug = "iPhone Developer: ()" # (str) Name of the certificate to use for signing the release version #ios.codesign.release = %(ios.codesign.debug)s [buildozer] # (int) Log level (0 = error only, 1 = info, 2 = debug (with command output)) log_level = 2 # (int) Display warning if buildozer is run as root (0 = False, 1 = True) warn_on_root = 1 # (str) Path to build artifact storage, absolute or relative to spec file # build_dir = ./.buildozer # (str) Path to build output (i.e. .apk, .ipa) storage # bin_dir = ./bin # ----------------------------------------------------------------------------- # List as sections # # You can define all the "list" as [section:key]. # Each line will be considered as a option to the list. # Let's take [app] / source.exclude_patterns. # Instead of doing: # #[app] #source.exclude_patterns = license,data/audio/*.wav,data/images/original/* # # This can be translated into: # #[app:source.exclude_patterns] #license #data/audio/*.wav #data/images/original/* # # ----------------------------------------------------------------------------- # Profiles # # You can extend section / key with a profile # For example, you want to deploy a demo version of your application without # HD content. You could first change the title to add "(demo)" in the name # and extend the excluded directories to remove the HD content. # #[app@demo] #title = My Application (demo) # #[app:source.exclude_patterns@demo] #images/hd/* # # Then, invoke the command line with the "demo" profile: # #buildozer --profile demo android debug plyer-2.1.0/examples/spatialorientation/main.py000066400000000000000000000043111433372044000216710ustar00rootroot00000000000000from kivy.app import App from kivy.clock import Clock from kivy.lang import Builder from kivy.properties import NumericProperty from kivy.properties import ObjectProperty from kivy.uix.boxlayout import BoxLayout interface = Builder.load_string(''' #:import facade plyer.spatialorientation : facade: facade orientation: 'vertical' padding: '20dp' spacing: '10dp' BoxLayout: orientation: 'vertical' BoxLayout: orientation: 'horizontal' Button: id: enable_button text: 'Enable Sensor' disabled: False on_release: root.enable_listener() disable_button.disabled = not disable_button.disabled enable_button.disabled = not enable_button.disabled Button: id: disable_button text: 'Disable Sensor' disabled: True on_release: root.disable_listener() disable_button.disabled = not disable_button.disabled enable_button.disabled = not enable_button.disabled BoxLayout: orientation: 'vertical' Label: text: 'Azimuth: ' + str(root.azimuth) + ' radians' Label: text: 'Pitch: ' + str(root.pitch) + ' radians' Label: text: 'Roll: ' + str(root.roll) + ' radians' ''') class SpOrientationInterface(BoxLayout): pitch = NumericProperty(0) azimuth = NumericProperty(0) roll = NumericProperty(0) facade = ObjectProperty() def enable_listener(self): self.facade.enable_listener() Clock.schedule_interval(self.get_orientation, 1 / 20.) def disable_listener(self): self.facade.disable_listener() Clock.unschedule(self.get_orientation) def get_orientation(self, dt): if self.facade.orientation != (None, None, None): self.azimuth, self.pitch, self.roll = self.facade.orientation class SpOrientationTestApp(App): def build(self): return SpOrientationInterface() if __name__ == "__main__": SpOrientationTestApp().run() plyer-2.1.0/examples/speech2text/000077500000000000000000000000001433372044000167215ustar00rootroot00000000000000plyer-2.1.0/examples/speech2text/buildozer.spec000066400000000000000000000130571433372044000216020ustar00rootroot00000000000000[app] # (str) Title of your application title = Plyer Speech Recognition # (str) Package name package.name = plyer.speech.recognition # (str) Package domain (needed for android/ios packaging) package.domain = org.test # (str) Source code where the main.py live source.dir = . # (list) Source files to include (let empty to include all the files) source.include_exts = py,png,jpg,kv,atlas # (list) Source files to exclude (let empty to not exclude anything) #source.exclude_exts = spec # (list) List of directory to exclude (let empty to not exclude anything) #source.exclude_dirs = tests, bin # (list) List of exclusions using pattern matching #source.exclude_patterns = license,images/*/*.jpg # (str) Application versioning (method 1) # version.regex = __version__ = ['"](.*)['"] # version.filename = %(source.dir)s/main.py # (str) Application versioning (method 2) version = 1.0 # (list) Application requirements # comma seperated e.g. requirements = sqlite3,kivy requirements = kivy,plyer # (str) Custom source folders for requirements # Sets custom source for any requirements with recipes # requirements.source.kivy = ../../kivy # (list) Garden requirements #garden_requirements = # (str) Presplash of the application #presplash.filename = %(source.dir)s/data/presplash.png # (str) Icon of the application #icon.filename = %(source.dir)s/data/icon.png # (str) Supported orientation (one of landscape, portrait or all) orientation = portrait # (bool) Indicate if the application should be fullscreen or not fullscreen = 1 # # Android specific # # (list) Permissions android.permissions = INTERNET,RECORD_AUDIO # (int) Android API to use #android.api = 14 # (int) Minimum API required (8 = Android 2.2 devices) #android.minapi = 8 # (int) Android SDK version to use #android.sdk = 21 # (str) Android NDK version to use #android.ndk = 9c # (bool) Use --private data storage (True) or --dir public storage (False) #android.private_storage = True # (str) Android NDK directory (if empty, it will be automatically downloaded.) #android.ndk_path = # (str) Android SDK directory (if empty, it will be automatically downloaded.) #android.sdk_path = # (str) python-for-android git clone directory (if empty, it will be automatically cloned from github) #android.p4a_dir = # (list) python-for-android whitelist #android.p4a_whitelist = # (str) Android entry point, default is ok for Kivy-based app #android.entrypoint = org.renpy.android.PythonActivity # (list) List of Java .jar files to add to the libs so that pyjnius can access # their classes. Don't add jars that you do not need, since extra jars can slow # down the build process. Allows wildcards matching, for example: # OUYA-ODK/libs/*.jar #android.add_jars = foo.jar,bar.jar,path/to/more/*.jar # (list) List of Java files to add to the android project (can be java or a # directory containing the files) #android.add_src = # (str) python-for-android branch to use, if not master, useful to try # not yet merged features. #android.branch = master # (str) OUYA Console category. Should be one of GAME or APP # If you leave this blank, OUYA support will not be enabled #android.ouya.category = GAME # (str) Filename of OUYA Console icon. It must be a 732x412 png image. #android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png # (str) XML file to include as an intent filters in tag #android.manifest.intent_filters = # (list) Android additionnal libraries to copy into libs/armeabi #android.add_libs_armeabi = libs/android/*.so #android.add_libs_armeabi_v7a = libs/android-v7/*.so #android.add_libs_x86 = libs/android-x86/*.so #android.add_libs_mips = libs/android-mips/*.so # (bool) Indicate whether the screen should stay on # Don't forget to add the WAKE_LOCK permission if you set this to True #android.wakelock = False # (list) Android application meta-data to set (key=value format) #android.meta_data = # (list) Android library project to add (will be added in the # project.properties automatically.) #android.library_references = # # iOS specific # # (str) Name of the certificate to use for signing the debug version # Get a list of available identities: buildozer ios list_identities #ios.codesign.debug = "iPhone Developer: ()" # (str) Name of the certificate to use for signing the release version #ios.codesign.release = %(ios.codesign.debug)s [buildozer] # (int) Log level (0 = error only, 1 = info, 2 = debug (with command output)) log_level = 2 # (int) Display warning if buildozer is run as root (0 = False, 1 = True) warn_on_root = 1 # ----------------------------------------------------------------------------- # List as sections # # You can define all the "list" as [section:key]. # Each line will be considered as a option to the list. # Let's take [app] / source.exclude_patterns. # Instead of doing: # #[app] #source.exclude_patterns = license,data/audio/*.wav,data/images/original/* # # This can be translated into: # #[app:source.exclude_patterns] #license #data/audio/*.wav #data/images/original/* # # ----------------------------------------------------------------------------- # Profiles # # You can extend section / key with a profile # For example, you want to deploy a demo version of your application without # HD content. You could first change the title to add "(demo)" in the name # and extend the excluded directories to remove the HD content. # #[app@demo] #title = My Application (demo) # #[app:source.exclude_patterns@demo] #images/hd/* # # Then, invoke the command line with the "demo" profile: # #buildozer --profile demo android debug plyer-2.1.0/examples/speech2text/main.py000066400000000000000000000035031433372044000202200ustar00rootroot00000000000000from kivy.app import App from kivy.clock import Clock from kivy.lang import Builder from kivy.uix.boxlayout import BoxLayout from plyer import stt Builder.load_string(''' #:import stt plyer.stt : orientation: 'vertical' Label: size_hint_y: None height: sp(40) text: 'Is supported: %s' % stt.exist() Label: size_hint_y: None height: sp(40) text: 'Possible Matches' TextInput: id: results hint_text: 'results (auto stop)' TextInput: id: partial hint_text: 'partial results (manual stop)' TextInput: id: errors hint_text: 'errors' Button: id: start_button text: 'Start Listening' on_release: root.start_listening() ''') class SpeechInterface(BoxLayout): '''Root Widget.''' def start_listening(self): if stt.listening: self.stop_listening() return start_button = self.ids.start_button start_button.text = 'Stop' self.ids.results.text = '' self.ids.partial.text = '' stt.start() Clock.schedule_interval(self.check_state, 1 / 5) def stop_listening(self): start_button = self.ids.start_button start_button.text = 'Start Listening' stt.stop() self.update() Clock.unschedule(self.check_state) def check_state(self, dt): # if the recognizer service stops, change UI if not stt.listening: self.stop_listening() def update(self): self.ids.partial.text = '\n'.join(stt.partial_results) self.ids.results.text = '\n'.join(stt.results) class SpeechApp(App): def build(self): return SpeechInterface() def on_pause(self): return True if __name__ == "__main__": SpeechApp().run() plyer-2.1.0/examples/storagepath/000077500000000000000000000000001433372044000170045ustar00rootroot00000000000000plyer-2.1.0/examples/storagepath/buildozer.spec000066400000000000000000000171611433372044000216650ustar00rootroot00000000000000[app] # (str) Title of your application title = Storage Path # (str) Package name package.name = plyerstoragepath # (str) Package domain (needed for android/ios packaging) package.domain = org.test # (str) Source code where the main.py live source.dir = . # (list) Source files to include (let empty to include all the files) source.include_exts = py,png,jpg,kv,atlas # (list) List of inclusions using pattern matching #source.include_patterns = assets/*,images/*.png # (list) Source files to exclude (let empty to not exclude anything) #source.exclude_exts = spec # (list) List of directory to exclude (let empty to not exclude anything) #source.exclude_dirs = tests, bin # (list) List of exclusions using pattern matching #source.exclude_patterns = license,images/*/*.jpg # (str) Application versioning (method 1) version = 0.1 # (str) Application versioning (method 2) # version.regex = __version__ = ['"](.*)['"] # version.filename = %(source.dir)s/main.py # (list) Application requirements # comma seperated e.g. requirements = sqlite3,kivy requirements = kivy,plyer # (str) Custom source folders for requirements # Sets custom source for any requirements with recipes # requirements.source.kivy = ../../kivy # (list) Garden requirements #garden_requirements = # (str) Presplash of the application #presplash.filename = %(source.dir)s/data/presplash.png # (str) Icon of the application #icon.filename = %(source.dir)s/data/icon.png # (str) Supported orientation (one of landscape, portrait or all) orientation = portrait # (list) List of service to declare #services = NAME:ENTRYPOINT_TO_PY,NAME2:ENTRYPOINT2_TO_PY # # OSX Specific # # # author = © Copyright Info # change the major version of python used by the app osx.python_version = 3 # Kivy version to use osx.kivy_version = 1.9.1 # # Android specific # # (bool) Indicate if the application should be fullscreen or not fullscreen = 0 # (string) Presplash background color (for new android toolchain) # Supported formats are: #RRGGBB #AARRGGBB or one of the following names: # red, blue, green, black, white, gray, cyan, magenta, yellow, lightgray, # darkgray, grey, lightgrey, darkgrey, aqua, fuchsia, lime, maroon, navy, # olive, purple, silver, teal. #android.presplash_color = #FFFFFF # (list) Permissions #android.permissions = INTERNET # (int) Android API to use #android.api = 19 # (int) Minimum API required #android.minapi = 9 # (int) Android SDK version to use #android.sdk = 20 # (str) Android NDK version to use #android.ndk = 9c # (bool) Use --private data storage (True) or --dir public storage (False) #android.private_storage = True # (str) Android NDK directory (if empty, it will be automatically downloaded.) #android.ndk_path = # (str) Android SDK directory (if empty, it will be automatically downloaded.) #android.sdk_path = # (str) ANT directory (if empty, it will be automatically downloaded.) #android.ant_path = # (bool) If True, then skip trying to update the Android sdk # This can be useful to avoid excess Internet downloads or save time # when an update is due and you just want to test/build your package # android.skip_update = False # (str) Android entry point, default is ok for Kivy-based app #android.entrypoint = org.renpy.android.PythonActivity # (list) Pattern to whitelist for the whole project #android.whitelist = # (str) Path to a custom whitelist file #android.whitelist_src = # (str) Path to a custom blacklist file #android.blacklist_src = # (list) List of Java .jar files to add to the libs so that pyjnius can access # their classes. Don't add jars that you do not need, since extra jars can slow # down the build process. Allows wildcards matching, for example: # OUYA-ODK/libs/*.jar #android.add_jars = foo.jar,bar.jar,path/to/more/*.jar # (list) List of Java files to add to the android project (can be java or a # directory containing the files) #android.add_src = # (list) Android AAR archives to add (currently works only with sdl2_gradle # bootstrap) #android.add_aars = # (list) Gradle dependencies to add (currently works only with sdl2_gradle # bootstrap) #android.gradle_dependencies = # (str) python-for-android branch to use, defaults to master #p4a.branch = stable # (str) OUYA Console category. Should be one of GAME or APP # If you leave this blank, OUYA support will not be enabled #android.ouya.category = GAME # (str) Filename of OUYA Console icon. It must be a 732x412 png image. #android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png # (str) XML file to include as an intent filters in tag #android.manifest.intent_filters = # (list) Android additionnal libraries to copy into libs/armeabi #android.add_libs_armeabi = libs/android/*.so #android.add_libs_armeabi_v7a = libs/android-v7/*.so #android.add_libs_x86 = libs/android-x86/*.so #android.add_libs_mips = libs/android-mips/*.so # (bool) Indicate whether the screen should stay on # Don't forget to add the WAKE_LOCK permission if you set this to True #android.wakelock = False # (list) Android application meta-data to set (key=value format) #android.meta_data = # (list) Android library project to add (will be added in the # project.properties automatically.) #android.library_references = # (str) Android logcat filters to use #android.logcat_filters = *:S python:D # (bool) Copy library instead of making a libpymodules.so #android.copy_libs = 1 # (str) The Android arch to build for, choices: armeabi-v7a, arm64-v8a, x86 android.arch = armeabi-v7a # # Python for android (p4a) specific # # (str) python-for-android git clone directory (if empty, it will be automatically cloned from github) #p4a.source_dir = # (str) The directory in which python-for-android should look for your own build recipes (if any) #p4a.local_recipes = # (str) Filename to the hook for p4a #p4a.hook = # (str) Bootstrap to use for android builds # p4a.bootstrap = sdl2 # # iOS specific # # (str) Path to a custom kivy-ios folder #ios.kivy_ios_dir = ../kivy-ios # (str) Name of the certificate to use for signing the debug version # Get a list of available identities: buildozer ios list_identities #ios.codesign.debug = "iPhone Developer: ()" # (str) Name of the certificate to use for signing the release version #ios.codesign.release = %(ios.codesign.debug)s [buildozer] # (int) Log level (0 = error only, 1 = info, 2 = debug (with command output)) log_level = 2 # (int) Display warning if buildozer is run as root (0 = False, 1 = True) warn_on_root = 1 # (str) Path to build artifact storage, absolute or relative to spec file # build_dir = ./.buildozer # (str) Path to build output (i.e. .apk, .ipa) storage # bin_dir = ./bin # ----------------------------------------------------------------------------- # List as sections # # You can define all the "list" as [section:key]. # Each line will be considered as a option to the list. # Let's take [app] / source.exclude_patterns. # Instead of doing: # #[app] #source.exclude_patterns = license,data/audio/*.wav,data/images/original/* # # This can be translated into: # #[app:source.exclude_patterns] #license #data/audio/*.wav #data/images/original/* # # ----------------------------------------------------------------------------- # Profiles # # You can extend section / key with a profile # For example, you want to deploy a demo version of your application without # HD content. You could first change the title to add "(demo)" in the name # and extend the excluded directories to remove the HD content. # #[app@demo] #title = My Application (demo) # #[app:source.exclude_patterns@demo] #images/hd/* # # Then, invoke the command line with the "demo" profile: # #buildozer --profile demo android debug plyer-2.1.0/examples/storagepath/main.py000066400000000000000000000033111433372044000203000ustar00rootroot00000000000000''' Storage Path Example. ''' from kivy.lang import Builder from kivy.app import App from kivy.uix.boxlayout import BoxLayout Builder.load_string(''' #: import storagepath plyer.storagepath : BoxLayout: orientation: 'vertical' BoxLayout: Button: text: 'Home' on_press: label.text = str(storagepath.get_home_dir()) Button: text: 'External Storage' on_press: label.text = str(storagepath.get_external_storage_dir()) BoxLayout: Button: text: 'Root' on_press: label.text = str(storagepath.get_root_dir()) Button: text: 'Documents' on_press: label.text = str(storagepath.get_documents_dir()) BoxLayout: Button: text: 'Downloads' on_press: label.text = str(storagepath.get_downloads_dir()) Button: text: 'Videos' on_press: label.text = str(storagepath.get_videos_dir()) BoxLayout: Button: text: 'Music' on_press: label.text = str(storagepath.get_music_dir()) Button: text: 'Pictures' on_press: label.text = str(storagepath.get_pictures_dir()) Button: text: 'Applications' on_press: label.text = str(storagepath.get_application_dir()) Label: id: label ''') class StoragePathInterface(BoxLayout): pass class StoragePathApp(App): def build(self): return StoragePathInterface() if __name__ == "__main__": StoragePathApp().run() plyer-2.1.0/examples/temperature/000077500000000000000000000000001433372044000170205ustar00rootroot00000000000000plyer-2.1.0/examples/temperature/buildozer.spec000066400000000000000000000152501433372044000216760ustar00rootroot00000000000000[app] # (str) Title of your application title = Plyer Temperature Example # (str) Package name package.name = plyertemperature # (str) Package domain (needed for android/ios packaging) package.domain = org.test # (str) Source code where the main.py live source.dir = . # (list) Source files to include (let empty to include all the files) source.include_exts = py,png,jpg,kv,atlas # (list) List of inclusions using pattern matching #source.include_patterns = assets/*,images/*.png # (list) Source files to exclude (let empty to not exclude anything) #source.exclude_exts = spec # (list) List of directory to exclude (let empty to not exclude anything) #source.exclude_dirs = tests, bin # (list) List of exclusions using pattern matching #source.exclude_patterns = license,images/*/*.jpg # (str) Application versioning (method 1) version = 0.1 # (str) Application versioning (method 2) # version.regex = __version__ = ['"](.*)['"] # version.filename = %(source.dir)s/main.py # (list) Application requirements # comma seperated e.g. requirements = sqlite3,kivy requirements = kivy,plyer # (str) Custom source folders for requirements # Sets custom source for any requirements with recipes # requirements.source.kivy = ../../kivy # (list) Garden requirements #garden_requirements = # (str) Presplash of the application #presplash.filename = %(source.dir)s/data/presplash.png # (str) Icon of the application #icon.filename = %(source.dir)s/data/icon.png # (str) Supported orientation (one of landscape, portrait or all) orientation = portrait # (list) List of service to declare #services = NAME:ENTRYPOINT_TO_PY,NAME2:ENTRYPOINT2_TO_PY # # OSX Specific # # # author = © Copyright Info # # Android specific # # (bool) Indicate if the application should be fullscreen or not fullscreen = 0 # (list) Permissions #android.permissions = INTERNET # (int) Android API to use #android.api = 19 # (int) Minimum API required #android.minapi = 9 # (int) Android SDK version to use #android.sdk = 20 # (str) Android NDK version to use #android.ndk = 9c # (bool) Use --private data storage (True) or --dir public storage (False) #android.private_storage = True # (str) Android NDK directory (if empty, it will be automatically downloaded.) #android.ndk_path = # (str) Android SDK directory (if empty, it will be automatically downloaded.) #android.sdk_path = # (str) ANT directory (if empty, it will be automatically downloaded.) #android.ant_path = # (str) python-for-android git clone directory (if empty, it will be automatically cloned from github) #android.p4a_dir = # (str) The directory in which python-for-android should look for your own build recipes (if any) #p4a.local_recipes = # (list) python-for-android whitelist #android.p4a_whitelist = # (bool) If True, then skip trying to update the Android sdk # This can be useful to avoid excess Internet downloads or save time # when an update is due and you just want to test/build your package # android.skip_update = False # (str) Bootstrap to use for android builds (android_new only) # android.bootstrap = sdl2 # (str) Android entry point, default is ok for Kivy-based app #android.entrypoint = org.renpy.android.PythonActivity # (list) List of Java .jar files to add to the libs so that pyjnius can access # their classes. Don't add jars that you do not need, since extra jars can slow # down the build process. Allows wildcards matching, for example: # OUYA-ODK/libs/*.jar #android.add_jars = foo.jar,bar.jar,path/to/more/*.jar # (list) List of Java files to add to the android project (can be java or a # directory containing the files) #android.add_src = # (str) python-for-android branch to use, if not master, useful to try # not yet merged features. #android.branch = master # (str) OUYA Console category. Should be one of GAME or APP # If you leave this blank, OUYA support will not be enabled #android.ouya.category = GAME # (str) Filename of OUYA Console icon. It must be a 732x412 png image. #android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png # (str) XML file to include as an intent filters in tag #android.manifest.intent_filters = # (list) Android additionnal libraries to copy into libs/armeabi #android.add_libs_armeabi = libs/android/*.so #android.add_libs_armeabi_v7a = libs/android-v7/*.so #android.add_libs_x86 = libs/android-x86/*.so #android.add_libs_mips = libs/android-mips/*.so # (bool) Indicate whether the screen should stay on # Don't forget to add the WAKE_LOCK permission if you set this to True #android.wakelock = False # (list) Android application meta-data to set (key=value format) #android.meta_data = # (list) Android library project to add (will be added in the # project.properties automatically.) #android.library_references = # (str) Android logcat filters to use #android.logcat_filters = *:S python:D # (bool) Copy library instead of making a libpymodules.so #android.copy_libs = 1 # # iOS specific # # (str) Path to a custom kivy-ios folder #ios.kivy_ios_dir = ../kivy-ios # (str) Name of the certificate to use for signing the debug version # Get a list of available identities: buildozer ios list_identities #ios.codesign.debug = "iPhone Developer: ()" # (str) Name of the certificate to use for signing the release version #ios.codesign.release = %(ios.codesign.debug)s [buildozer] # (int) Log level (0 = error only, 1 = info, 2 = debug (with command output)) log_level = 2 # (int) Display warning if buildozer is run as root (0 = False, 1 = True) warn_on_root = 1 # (str) Path to build artifact storage, absolute or relative to spec file # build_dir = ./.buildozer # (str) Path to build output (i.e. .apk, .ipa) storage # bin_dir = ./bin # ----------------------------------------------------------------------------- # List as sections # # You can define all the "list" as [section:key]. # Each line will be considered as a option to the list. # Let's take [app] / source.exclude_patterns. # Instead of doing: # #[app] #source.exclude_patterns = license,data/audio/*.wav,data/images/original/* # # This can be translated into: # #[app:source.exclude_patterns] #license #data/audio/*.wav #data/images/original/* # # ----------------------------------------------------------------------------- # Profiles # # You can extend section / key with a profile # For example, you want to deploy a demo version of your application without # HD content. You could first change the title to add "(demo)" in the name # and extend the excluded directories to remove the HD content. # #[app@demo] #title = My Application (demo) # #[app:source.exclude_patterns@demo] #images/hd/* # # Then, invoke the command line with the "demo" profile: # #buildozer --profile demo android debug plyer-2.1.0/examples/temperature/main.py000066400000000000000000000034021433372044000203150ustar00rootroot00000000000000from kivy.app import App from kivy.clock import Clock from kivy.lang import Builder from kivy.properties import NumericProperty from kivy.properties import ObjectProperty from kivy.uix.boxlayout import BoxLayout Builder.load_string(''' #:import temperature plyer.temperature : temperature: temperature orientation: 'vertical' padding: '50dp' spacing: '20dp' BoxLayout: orientation: 'horizontal' size_hint_y: 0.3 Button: id: button_enable text: 'Enable' disabled: False on_release: root.enable() button_disable.disabled = not button_disable.disabled button_enable.disabled = not button_enable.disabled Button: id: button_disable text: 'Disable' disabled: True on_release: root.disable() button_disable.disabled = not button_disable.disabled button_enable.disabled = not button_enable.disabled Label: text: 'Current air temperature: ' + str(root.temp) + ' degrees C.' ''') class TemperatureInterface(BoxLayout): '''Root Widget.''' temperature = ObjectProperty() temp = NumericProperty() def enable(self): self.temperature.enable() Clock.schedule_interval(self.get_temperature, 1 / 20.) def disable(self): self.temperature.disable() Clock.unschedule(self.get_temperature) def get_temperature(self, dt): self.temp = self.temperature.temperature or self.temp class TemperatureApp(App): def build(self): return TemperatureInterface() def on_pause(self): return True if __name__ == "__main__": TemperatureApp().run() plyer-2.1.0/examples/text2speech/000077500000000000000000000000001433372044000167215ustar00rootroot00000000000000plyer-2.1.0/examples/text2speech/buildozer.spec000066400000000000000000000115771433372044000216070ustar00rootroot00000000000000[app] # (str) Title of your application title = Plyer Text2Speech Example # (str) Package name package.name = ttsexample # (str) Package domain (needed for android/ios packaging) package.domain = org.test # (str) Source code where the main.py live source.dir = . # (list) Source files to include (let empty to include all the files) source.include_exts = py,png,jpg,kv,atlas # (list) Source files to exclude (let empty to not exclude anything) #source.exclude_exts = spec # (list) List of directory to exclude (let empty to not exclude anything) #source.exclude_dirs = tests, bin # (list) List of exclusions using pattern matching #source.exclude_patterns = license,images/*/*.jpg # (str) Application versioning (method 1) # version.regex = __version__ = '(.*)' # version.filename = %(source.dir)s/main.py # (str) Application versioning (method 2) version = 1.0 # (list) Application requirements requirements = plyer,kivy # (str) Presplash of the application #presplash.filename = %(source.dir)s/data/presplash.png # (str) Icon of the application #icon.filename = %(source.dir)s/data/icon.png # (str) Supported orientation (one of landscape, portrait or all) orientation = portrait # (bool) Indicate if the application should be fullscreen or not fullscreen = 0 # # Android specific # # (list) Permissions # android.permissions = INTERNET # (int) Android API to use #android.api = 14 # (int) Minimum API required (8 = Android 2.2 devices) #android.minapi = 8 # (int) Android SDK version to use #android.sdk = 21 # (str) Android NDK version to use #android.ndk = 9 # (bool) Use --private data storage (True) or --dir public storage (False) #android.private_storage = True # (str) Android NDK directory (if empty, it will be automatically downloaded.) #android.ndk_path = # (str) Android SDK directory (if empty, it will be automatically downloaded.) #android.sdk_path = # (str) Android entry point, default is ok for Kivy-based app #android.entrypoint = org.renpy.android.PythonActivity # (list) List of Java .jar files to add to the libs so that pyjnius can access # their classes. Don't add jars that you do not need, since extra jars can slow # down the build process. Allows wildcards matching, for example: # OUYA-ODK/libs/*.jar #android.add_jars = foo.jar,bar.jar,path/to/more/*.jar # (list) List of Java files to add to the android project (can be java or a # directory containing the files) #android.add_src = # (str) python-for-android branch to use, if not master, useful to try # not yet merged features. #android.branch = master # (str) OUYA Console category. Should be one of GAME or APP # If you leave this blank, OUYA support will not be enabled #android.ouya.category = GAME # (str) Filename of OUYA Console icon. It must be a 732x412 png image. #android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png # (str) XML file to include as an intent filters in tag #android.manifest.intent_filters = # (list) Android additionnal libraries to copy into libs/armeabi #android.add_libs_armeabi = libs/android/*.so # (bool) Indicate whether the screen should stay on # Don't forget to add the WAKE_LOCK permission if you set this to True #android.wakelock = False # (list) Android application meta-data to set (key=value format) #android.meta_data = # (list) Android library project to add (will be added in the # project.properties automatically.) #android.library_references = # # iOS specific # # (str) Name of the certificate to use for signing the debug version # Get a list of available identities: buildozer ios list_identities #ios.codesign.debug = "iPhone Developer: ()" # (str) Name of the certificate to use for signing the release version #ios.codesign.release = %(ios.codesign.debug)s [buildozer] # (int) Log level (0 = error only, 1 = info, 2 = debug (with command output)) log_level = 2 # ----------------------------------------------------------------------------- # List as sections # # You can define all the "list" as [section:key]. # Each line will be considered as a option to the list. # Let's take [app] / source.exclude_patterns. # Instead of doing: # # [app] # source.exclude_patterns = license,data/audio/*.wav,data/images/original/* # # This can be translated into: # # [app:source.exclude_patterns] # license # data/audio/*.wav # data/images/original/* # # ----------------------------------------------------------------------------- # Profiles # # You can extend section / key with a profile # For example, you want to deploy a demo version of your application without # HD content. You could first change the title to add "(demo)" in the name # and extend the excluded directories to remove the HD content. # # [app@demo] # title = My Application (demo) # # [app:source.exclude_patterns@demo] # images/hd/* # # Then, invoke the command line with the "demo" profile: # # buildozer --profile demo android debug plyer-2.1.0/examples/text2speech/main.py000066400000000000000000000011361433372044000202200ustar00rootroot00000000000000import kivy from kivy.app import App from kivy.uix.boxlayout import BoxLayout from kivy.uix.popup import Popup from plyer import tts kivy.require('1.8.0') class Text2SpeechDemo(BoxLayout): def do_read(self): try: tts.speak(self.ids.notification_text.text) except NotImplementedError: popup = ErrorPopup() popup.open() class Text2SpeechDemoApp(App): def build(self): return Text2SpeechDemo() def on_pause(self): return True class ErrorPopup(Popup): pass if __name__ == '__main__': Text2SpeechDemoApp().run() plyer-2.1.0/examples/text2speech/text2speechdemo.kv000066400000000000000000000012651433372044000223720ustar00rootroot00000000000000#:kivy 1.8.0 : BoxLayout: orientation: 'vertical' padding: 20 TextInput: id: notification_text text: 'Put message here' Button: text: 'Read' size_hint_y: 0.2 on_press: root.do_read() : size_hint: .7, .4 title: "Error" BoxLayout: orientation: 'vertical' padding: 10 spacing: 20 Label: size_hint_y: 0.4 text: "This feature has not yet been implemented in Plyer." Button: text: 'Dismiss' size_hint_y: 0.4 on_press: root.dismiss() plyer-2.1.0/examples/uniqueid/000077500000000000000000000000001433372044000163065ustar00rootroot00000000000000plyer-2.1.0/examples/uniqueid/buildozer.spec000066400000000000000000000152251433372044000211660ustar00rootroot00000000000000[app] # (str) Title of your application title = Unique ID # (str) Package name package.name = plyeruniqueid # (str) Package domain (needed for android/ios packaging) package.domain = org.test # (str) Source code where the main.py live source.dir = . # (list) Source files to include (let empty to include all the files) source.include_exts = py,png,jpg,kv,atlas # (list) List of inclusions using pattern matching #source.include_patterns = assets/*,images/*.png # (list) Source files to exclude (let empty to not exclude anything) #source.exclude_exts = spec # (list) List of directory to exclude (let empty to not exclude anything) #source.exclude_dirs = tests, bin # (list) List of exclusions using pattern matching #source.exclude_patterns = license,images/*/*.jpg # (str) Application versioning (method 1) version = 0.1 # (str) Application versioning (method 2) # version.regex = __version__ = ['"](.*)['"] # version.filename = %(source.dir)s/main.py # (list) Application requirements # comma seperated e.g. requirements = sqlite3,kivy requirements = kivy,plyer # (str) Custom source folders for requirements # Sets custom source for any requirements with recipes # requirements.source.kivy = ../../kivy # (list) Garden requirements #garden_requirements = # (str) Presplash of the application #presplash.filename = %(source.dir)s/data/presplash.png # (str) Icon of the application #icon.filename = %(source.dir)s/data/icon.png # (str) Supported orientation (one of landscape, portrait or all) orientation = portrait # (list) List of service to declare #services = NAME:ENTRYPOINT_TO_PY,NAME2:ENTRYPOINT2_TO_PY # # OSX Specific # # # author = © Copyright Info # # Android specific # # (bool) Indicate if the application should be fullscreen or not fullscreen = 0 # (list) Permissions #android.permissions = INTERNET # (int) Android API to use #android.api = 19 # (int) Minimum API required #android.minapi = 9 # (int) Android SDK version to use #android.sdk = 20 # (str) Android NDK version to use #android.ndk = 9c # (bool) Use --private data storage (True) or --dir public storage (False) #android.private_storage = True # (str) Android NDK directory (if empty, it will be automatically downloaded.) #android.ndk_path = # (str) Android SDK directory (if empty, it will be automatically downloaded.) #android.sdk_path = # (str) ANT directory (if empty, it will be automatically downloaded.) #android.ant_path = # (str) python-for-android git clone directory (if empty, it will be automatically cloned from github) #android.p4a_dir = # (str) The directory in which python-for-android should look for your own build recipes (if any) #p4a.local_recipes = # (list) python-for-android whitelist #android.p4a_whitelist = # (bool) If True, then skip trying to update the Android sdk # This can be useful to avoid excess Internet downloads or save time # when an update is due and you just want to test/build your package # android.skip_update = False # (str) Bootstrap to use for android builds (android_new only) # android.bootstrap = sdl2 # (str) Android entry point, default is ok for Kivy-based app #android.entrypoint = org.renpy.android.PythonActivity # (list) List of Java .jar files to add to the libs so that pyjnius can access # their classes. Don't add jars that you do not need, since extra jars can slow # down the build process. Allows wildcards matching, for example: # OUYA-ODK/libs/*.jar #android.add_jars = foo.jar,bar.jar,path/to/more/*.jar # (list) List of Java files to add to the android project (can be java or a # directory containing the files) #android.add_src = # (str) python-for-android branch to use, if not master, useful to try # not yet merged features. #android.branch = master # (str) OUYA Console category. Should be one of GAME or APP # If you leave this blank, OUYA support will not be enabled #android.ouya.category = GAME # (str) Filename of OUYA Console icon. It must be a 732x412 png image. #android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png # (str) XML file to include as an intent filters in tag #android.manifest.intent_filters = # (list) Android additionnal libraries to copy into libs/armeabi #android.add_libs_armeabi = libs/android/*.so #android.add_libs_armeabi_v7a = libs/android-v7/*.so #android.add_libs_x86 = libs/android-x86/*.so #android.add_libs_mips = libs/android-mips/*.so # (bool) Indicate whether the screen should stay on # Don't forget to add the WAKE_LOCK permission if you set this to True #android.wakelock = False # (list) Android application meta-data to set (key=value format) #android.meta_data = # (list) Android library project to add (will be added in the # project.properties automatically.) #android.library_references = # (str) Android logcat filters to use #android.logcat_filters = *:S python:D # (bool) Copy library instead of making a libpymodules.so #android.copy_libs = 1 # # iOS specific # # (str) Path to a custom kivy-ios folder #ios.kivy_ios_dir = ../kivy-ios # (str) Name of the certificate to use for signing the debug version # Get a list of available identities: buildozer ios list_identities #ios.codesign.debug = "iPhone Developer: ()" # (str) Name of the certificate to use for signing the release version #ios.codesign.release = %(ios.codesign.debug)s [buildozer] # (int) Log level (0 = error only, 1 = info, 2 = debug (with command output)) log_level = 2 # (int) Display warning if buildozer is run as root (0 = False, 1 = True) warn_on_root = 1 # (str) Path to build artifact storage, absolute or relative to spec file # build_dir = ./.buildozer # (str) Path to build output (i.e. .apk, .ipa) storage # bin_dir = ./bin # ----------------------------------------------------------------------------- # List as sections # # You can define all the "list" as [section:key]. # Each line will be considered as a option to the list. # Let's take [app] / source.exclude_patterns. # Instead of doing: # #[app] #source.exclude_patterns = license,data/audio/*.wav,data/images/original/* # # This can be translated into: # #[app:source.exclude_patterns] #license #data/audio/*.wav #data/images/original/* # # ----------------------------------------------------------------------------- # Profiles # # You can extend section / key with a profile # For example, you want to deploy a demo version of your application without # HD content. You could first change the title to add "(demo)" in the name # and extend the excluded directories to remove the HD content. # #[app@demo] #title = My Application (demo) # #[app:source.exclude_patterns@demo] #images/hd/* # # Then, invoke the command line with the "demo" profile: # #buildozer --profile demo android debug plyer-2.1.0/examples/uniqueid/main.py000066400000000000000000000023631433372044000176100ustar00rootroot00000000000000from kivy.app import App from kivy.lang import Builder from kivy.properties import ObjectProperty from kivy.properties import StringProperty from kivy.uix.boxlayout import BoxLayout from plyer.utils import platform Builder.load_string(''' #:import uniqueid plyer.uniqueid : uniqueid: uniqueid orientation: 'vertical' padding: '50dp' spacing: '20dp' BoxLayout: orientation: 'horizontal' size_hint_y: 0.3 Button: text: 'Get Unique ID' on_release: root.get_uid() Label: text: str(root.text) Label: text: str(root.uid) ''') class UniqueIDInterface(BoxLayout): '''Root Widget.''' uniqueid = ObjectProperty() uid = StringProperty() text = StringProperty() if platform == "android": text = "Android ID: " elif platform == "ios": text = "UUID: " elif platform == "win": text = "Machine GUID: " else: text = "Serial Number: " def get_uid(self): self.uid = self.uniqueid.id or self.uid class UniqueIDApp(App): def build(self): return UniqueIDInterface() def on_pause(self): return True if __name__ == "__main__": UniqueIDApp().run() plyer-2.1.0/examples/vibrator/000077500000000000000000000000001433372044000163135ustar00rootroot00000000000000plyer-2.1.0/examples/vibrator/buildozer.spec000066400000000000000000000117621433372044000211750ustar00rootroot00000000000000[app] # (str) Title of your application title = Plyer vibrate test # (str) Package name package.name = plyervibrate # (str) Package domain (needed for android/ios packaging) package.domain = org.test # (str) Source code where the main.py live source.dir = . # (list) Source files to include (let empty to include all the files) source.include_exts = py,png,jpg,kv,atlas # (list) Source files to exclude (let empty to not exclude anything) #source.exclude_exts = spec # (list) List of directory to exclude (let empty to not exclude anything) #source.exclude_dirs = tests, bin # (list) List of exclusions using pattern matching #source.exclude_patterns = license,images/*/*.jpg # (str) Application versioning (method 1) # version.regex = __version__ = '(.*)' # version.filename = %(source.dir)s/main.py # (str) Application versioning (method 2) version = 0.1 # (list) Application requirements requirements = plyer,kivy # (str) Presplash of the application #presplash.filename = %(source.dir)s/data/presplash.png # (str) Icon of the application #icon.filename = %(source.dir)s/data/icon.png # (str) Supported orientation (one of landscape, portrait or all) orientation = portrait # (bool) Indicate if the application should be fullscreen or not fullscreen = 0 # # Android specific # # (list) Permissions android.permissions = VIBRATE # (int) Android API to use #android.api = 14 # (int) Minimum API required (8 = Android 2.2 devices) #android.minapi = 8 # (int) Android SDK version to use #android.sdk = 21 # (str) Android NDK version to use #android.ndk = 9 # (bool) Use --private data storage (True) or --dir public storage (False) #android.private_storage = True # (str) Android NDK directory (if empty, it will be automatically downloaded.) #android.ndk_path = # (str) Android SDK directory (if empty, it will be automatically downloaded.) #android.sdk_path = # (str) python-for-android git clone directory (if empty, it will be automatically cloned from github) #android.p4a_dir = # (str) Android entry point, default is ok for Kivy-based app #android.entrypoint = org.renpy.android.PythonActivity # (list) List of Java .jar files to add to the libs so that pyjnius can access # their classes. Don't add jars that you do not need, since extra jars can slow # down the build process. Allows wildcards matching, for example: # OUYA-ODK/libs/*.jar #android.add_jars = foo.jar,bar.jar,path/to/more/*.jar # (list) List of Java files to add to the android project (can be java or a # directory containing the files) #android.add_src = # (str) python-for-android branch to use, if not master, useful to try # not yet merged features. #android.branch = master # (str) OUYA Console category. Should be one of GAME or APP # If you leave this blank, OUYA support will not be enabled #android.ouya.category = GAME # (str) Filename of OUYA Console icon. It must be a 732x412 png image. #android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png # (str) XML file to include as an intent filters in tag #android.manifest.intent_filters = # (list) Android additionnal libraries to copy into libs/armeabi #android.add_libs_armeabi = libs/android/*.so # (bool) Indicate whether the screen should stay on # Don't forget to add the WAKE_LOCK permission if you set this to True #android.wakelock = False # (list) Android application meta-data to set (key=value format) #android.meta_data = # (list) Android library project to add (will be added in the # project.properties automatically.) #android.library_references = # # iOS specific # # (str) Name of the certificate to use for signing the debug version # Get a list of available identities: buildozer ios list_identities #ios.codesign.debug = "iPhone Developer: ()" # (str) Name of the certificate to use for signing the release version #ios.codesign.release = %(ios.codesign.debug)s [buildozer] # (int) Log level (0 = error only, 1 = info, 2 = debug (with command output)) log_level = 2 # ----------------------------------------------------------------------------- # List as sections # # You can define all the "list" as [section:key]. # Each line will be considered as a option to the list. # Let's take [app] / source.exclude_patterns. # Instead of doing: # # [app] # source.exclude_patterns = license,data/audio/*.wav,data/images/original/* # # This can be translated into: # # [app:source.exclude_patterns] # license # data/audio/*.wav # data/images/original/* # # ----------------------------------------------------------------------------- # Profiles # # You can extend section / key with a profile # For example, you want to deploy a demo version of your application without # HD content. You could first change the title to add "(demo)" in the name # and extend the excluded directories to remove the HD content. # # [app@demo] # title = My Application (demo) # # [app:source.exclude_patterns@demo] # images/hd/* # # Then, invoke the command line with the "demo" profile: # # buildozer --profile demo android debug plyer-2.1.0/examples/vibrator/main.py000066400000000000000000000021451433372044000176130ustar00rootroot00000000000000from kivy.app import App from kivy.lang import Builder from kivy.uix.boxlayout import BoxLayout Builder.load_string(''' #:import vibrator plyer.vibrator : orientation: 'vertical' Label: size_hint_y: None height: sp(40) text: 'vibrator exists: ' + str(vibrator.exists()) Button: text: 'vibrate 10s' on_release: vibrator.vibrate(10) Button: text: 'vibrate 1s' on_release: vibrator.vibrate(1) Button: text: 'vibrate 0.1s' on_release: vibrator.vibrate(0.1) Button: text: 'cancel vibration' on_release: vibrator.cancel() TextInput: id: ti text: '0.5,0.5,1,2,0.1,0.1,0.1,0.1,0.1,0.1' Button: text: 'vibrate pattern' on_release: vibrator.pattern([float(n) for n in ti.text.split(',')]) ''') class VibrationInterface(BoxLayout): '''Root Widget.''' pass class VibrationApp(App): def build(self): return VibrationInterface() def on_pause(self): return True if __name__ == "__main__": VibrationApp().run() plyer-2.1.0/examples/wifi/000077500000000000000000000000001433372044000154215ustar00rootroot00000000000000plyer-2.1.0/examples/wifi/main.py000066400000000000000000000075341433372044000167300ustar00rootroot00000000000000from kivy.app import App from kivy.lang import Builder from kivy.uix.button import Button from kivy.uix.label import Label from kivy.uix.popup import Popup from kivy.uix.boxlayout import BoxLayout from plyer import wifi from functools import partial Builder.load_string(''' : orientation: 'vertical' padding: '30dp' spacing: '20dp' GridLayout: cols: 2 padding: 20 spacing: 20 size_hint: 1,.4 Button: text: "Disconnect" on_release: root.disconnect() TextInput: id: password hint_text: "Password" disabled: True Label: size_hint_y: None height: sp(20) text: 'Wifi enabled: ' + str(root.is_enabled()) BoxLayout: orientation: 'horizontal' size_hint_y: 0.3 Button: id: wifi_button size_hint_y: None height: sp(35) text: 'Enable Wifi / Start Scanning' on_release: root.start_wifi() Button: id: stop_wifi_button size_hint_y: None height: sp(35) disabled: True text: 'Disable Wifi' on_release: root.stop_wifi() BoxLayout: id: scan_layout orientation: 'vertical' Label: size_hint_x: 1 size_hint_y: None valign: 'middle' height: '35dp' text: 'Scan Results' ''') class WifiInterface(BoxLayout): param = {} def _create_popup(self, title, content): return Popup( title=title, content=Label(text=content), size_hint=(.8, 1), auto_dismiss=True ) def start_wifi(self): wifi_button = self.ids['wifi_button'] wifi_button.text = 'Showing Scan Results' wifi_button.on_release = self.show_wifi_scans wifi.start_scanning() stop_wifi_button = self.ids['stop_wifi_button'] stop_wifi_button.disabled = False text_inpt = self.ids['password'] text_inpt.disabled = False def stop_wifi(self): stop_wifi_button = self.ids['stop_wifi_button'] stop_wifi_button.disabled = True wifi_button = self.ids['wifi_button'] wifi_button.text = 'Enable Wifi' wifi_button.on_release = self.start_wifi wifi.disable() self.ids['scan_layout'].clear_widgets() text_inpt = self.ids['password'] text_inpt.disabled = False def start_scanning(self): wifi.start_scanning() def show_wifi_scans(self): stack = self.ids['scan_layout'] stack.clear_widgets() wifi_scans = wifi.names.keys() for name in wifi_scans: content = "" items = wifi._get_network_info(name) for key, value in items.items(): content += "{}: {} \n".format(key, value) popup = self._create_popup(name, content) boxl = BoxLayout(orientation='horizontal') button = Button( text=name, size_hint=(1, 1), height='40dp', on_release=popup.open, ) button_connect = Button( text="Connect", size_hint_x=.2, on_release=partial(self.connect, name)) boxl.add_widget(button) boxl.add_widget(button_connect) stack.add_widget(boxl) def is_enabled(self): return wifi.is_enabled() def disconnect(self): wifi.disconnect() def connect(self, network_name, instance): self.param['password'] = self.ids['password'].text wifi.connect(network_name, self.param) class WifiApp(App): def build(self): return WifiInterface() def on_pause(self): return True if __name__ == "__main__": WifiApp().run() plyer-2.1.0/plyer/000077500000000000000000000000001433372044000140005ustar00rootroot00000000000000plyer-2.1.0/plyer/__init__.py000066400000000000000000000077451433372044000161260ustar00rootroot00000000000000''' Plyer ===== ''' __all__ = ( 'accelerometer', 'audio', 'barometer', 'battery', 'bluetooth', 'brightness', 'call', 'camera', 'compass', 'cpu', 'email', 'filechooser', 'flash', 'gps', 'gravity', 'gyroscope', 'humidity', 'irblaster', 'keystore', 'light', 'notification', 'orientation', 'processors', 'proximity', 'screenshot', 'sms', 'spatialorientation', 'storagepath', 'stt', 'temperature', 'tts', 'uniqueid', 'vibrator', 'wifi', 'devicename' ) __version__ = '2.1.0' from plyer import facades from plyer.utils import Proxy #: Accelerometer proxy to :class:`plyer.facades.Accelerometer` accelerometer = Proxy('accelerometer', facades.Accelerometer) #: Keyring proxy to :class::`plyer.facades.Keystore` keystore = Proxy('keystore', facades.Keystore) #: Audio proxy to :class:`plyer.facades.Audio` audio = Proxy('audio', facades.Audio) #: Barometer proxy to :class:`plyer.facades.Barometer` barometer = Proxy('barometer', facades.Barometer) #: Battery proxy to :class:`plyer.facades.Battery` battery = Proxy('battery', facades.Battery) #: Call proxy to :class `plyer.facades.Call` call = Proxy('call', facades.Call) #: 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) #: Gravity proxy to :class:`plyer.facades.Gravity` gravity = Proxy('gravity', facades.Gravity) #: Gyroscope proxy to :class:`plyer.facades.Gyroscope` gyroscope = Proxy('gyroscope', facades.Gyroscope) #: IrBlaster proxy to :class:`plyer.facades.IrBlaster` irblaster = Proxy('irblaster', facades.IrBlaster) #: Light proxy to :class:`plyer.facades.Light` light = Proxy('light', facades.Light) #: Orientation proxy to :class:`plyer.facades.Orientation` orientation = Proxy('orientation', facades.Orientation) #: Notification proxy to :class:`plyer.facades.Notification` notification = Proxy('notification', facades.Notification) #: Proximity proxy to :class:`plyer.facades.Proximity` proximity = Proxy('proximity', facades.Proximity) #: Sms proxy to :class:`plyer.facades.Sms` sms = Proxy('sms', facades.Sms) #: Speech proxy to :class:`plyer.facades.STT` stt = Proxy('stt', facades.STT) #: 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) #: Flash proxy to :class:`plyer.facades.Flash` flash = Proxy('flash', facades.Flash) #: Wifi proxy to :class:`plyer.facades.Wifi` wifi = Proxy('wifi', facades.Wifi) #: Temperature proxy to :class:`plyer.facades.Temperature` temperature = Proxy('temperature', facades.Temperature) #: Humidity proxy to :class:`plyer.facades.Humidity` humidity = Proxy('humidity', facades.Humidity) #: SpatialOrientation proxy to :class:`plyer.facades.SpatialOrientation` spatialorientation = Proxy('spatialorientation', facades.SpatialOrientation) #: Brightness proxy to :class:`plyer.facades.Brightness` brightness = Proxy('brightness', facades.Brightness) #: StoragePath proxy to :class:`plyer.facades.StoragePath` storagepath = Proxy('storagepath', facades.StoragePath) #: Bluetooth proxy to :class:`plyer.facades.Bluetooth` bluetooth = Proxy('bluetooth', facades.Bluetooth) #: Processors proxy to :class:`plyer.facades.Processors` processors = Proxy('processors', facades.Processors) #: Processors proxy to :class:`plyer.facades.CPU` cpu = Proxy('cpu', facades.CPU) #: Screenshot proxy to :class:`plyer.facades.Screenshot` screenshot = Proxy('screenshot', facades.Screenshot) #: devicename proxy to :class:`plyer.facades.DeviceName` devicename = Proxy('devicename', facades.DeviceName) plyer-2.1.0/plyer/facades/000077500000000000000000000000001433372044000153665ustar00rootroot00000000000000plyer-2.1.0/plyer/facades/__init__.py000066400000000000000000000040501433372044000174760ustar00rootroot00000000000000''' Facades ======= Interface of all the features available. ''' __all__ = ('Accelerometer', 'Audio', 'Barometer', 'Battery', 'Call', 'Camera', 'Compass', 'Email', 'FileChooser', 'GPS', 'Gravity', 'Gyroscope', 'IrBlaster', 'Light', 'Orientation', 'Notification', 'Proximity', 'Sms', 'TTS', 'UniqueID', 'Vibrator', 'Wifi', 'Flash', 'CPU', 'Temperature', 'Humidity', 'SpatialOrientation', 'Brightness', 'Processors', 'StoragePath', 'Keystore', 'Bluetooth', 'Screenshot', 'STT', 'DeviceName') from plyer.facades.accelerometer import Accelerometer from plyer.facades.audio import Audio from plyer.facades.barometer import Barometer from plyer.facades.battery import Battery from plyer.facades.call import Call 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.flash import Flash from plyer.facades.gps import GPS from plyer.facades.gravity import Gravity from plyer.facades.gyroscope import Gyroscope from plyer.facades.irblaster import IrBlaster from plyer.facades.light import Light from plyer.facades.proximity import Proximity from plyer.facades.orientation import Orientation from plyer.facades.notification import Notification from plyer.facades.sms import Sms from plyer.facades.stt import STT from plyer.facades.tts import TTS from plyer.facades.uniqueid import UniqueID from plyer.facades.vibrator import Vibrator from plyer.facades.wifi import Wifi from plyer.facades.temperature import Temperature from plyer.facades.humidity import Humidity from plyer.facades.spatialorientation import SpatialOrientation from plyer.facades.brightness import Brightness from plyer.facades.keystore import Keystore from plyer.facades.storagepath import StoragePath from plyer.facades.bluetooth import Bluetooth from plyer.facades.processors import Processors from plyer.facades.cpu import CPU from plyer.facades.screenshot import Screenshot from plyer.facades.devicename import DeviceName plyer-2.1.0/plyer/facades/accelerometer.py000066400000000000000000000031321433372044000205510ustar00rootroot00000000000000''' Accelerometer ============ The accelerometer is a motion sensor that detects the change (delta) in movement relative to the current device orientation, in three dimensions along the x, y, and z axis. The :class:`Accelerometer` provides access to public methods to use accelerometer of your device. Simple Examples --------------- To enable accelerometer:: >>> from plyer import accelerometer >>> accelerometer.enable() To disable accelerometer:: >>> accelerometer.disable() To get the acceleration:: >>> accelerometer.acceleration (-10.048464775085449, 6.825869083404541, 7.7260890007019043) Supported Plaforms ------------------ Android, iOS, OS X, Linux ''' class Accelerometer: ''' 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() plyer-2.1.0/plyer/facades/audio.py000066400000000000000000000035671433372044000170540ustar00rootroot00000000000000''' Audio ===== The :class:`Audio` is used for recording audio. Default path for recording is set in platform implementation. .. note:: On Android the `RECORD_AUDIO`, `WAKE_LOCK` permissions are needed. Simple Examples --------------- To get the file path:: >>> audio.file_path '/sdcard/testrecorder.3gp' To set the file path:: >>> import os >>> current_list = os.listdir('.') ['/sdcard/testrecorder.3gp', '/sdcard/testrecorder1.3gp', '/sdcard/testrecorder2.3gp', '/sdcard/testrecorder3.3gp'] >>> file_path = current_list[2] >>> audio.file_path = file_path To start recording:: >>> from plyer import audio >>> audio.start() To stop recording:: >>> audio.stop() To play recording:: >>> audio.play() Supported Platforms ------------------- Android, Windows, macOS ''' class Audio: ''' Audio facade. ''' state = 'ready' _file_path = '' def __init__(self, file_path=None): super().__init__() self._file_path = file_path or self._file_path def start(self): ''' Start record. ''' self._start() self.state = 'recording' def stop(self): ''' Stop record. ''' self._stop() self.state = 'ready' def play(self): ''' Play current recording. ''' self._play() self.state = 'playing' @property def file_path(self): return self._file_path @file_path.setter def file_path(self, location): ''' Location of the recording. ''' assert isinstance(location, str), 'Location must be string or unicode' self._file_path = location # private def _start(self): raise NotImplementedError() def _stop(self): raise NotImplementedError() def _play(self): raise NotImplementedError() plyer-2.1.0/plyer/facades/barometer.py000066400000000000000000000015431433372044000177230ustar00rootroot00000000000000class Barometer: '''Barometer facade. Barometer sensor is used to measure the ambient air pressure in hPa. With method `enable` you can turn on pressure sensor and 'disable' method stops the sensor. Use property `pressure` to get current air pressure in hPa. .. versionadded:: 1.2.5 Supported Platforms:: Android, iOS ''' @property def pressure(self): '''Current air pressure in hPa.''' return self._get_pressure() def _get_pressure(self, **kwargs): raise NotImplementedError() def _enable(self, **kwargs): raise NotImplementedError() def enable(self): '''Enable barometer sensor.''' self._enable() def _disable(self, **kwargs): raise NotImplementedError() def disable(self): '''Disable barometer sensor.''' self._disable() plyer-2.1.0/plyer/facades/battery.py000066400000000000000000000021541433372044000174140ustar00rootroot00000000000000''' Battery ======= The :class:`Battery` provides information about the battery of your device. .. note:: On Android the `BATTERY_STATS` permission is needed. Simple Example --------------- To get battery status:: >>> from plyer import battery >>> battery.status {'percentage': 82.0, 'isCharging': False} Supported Platforms ------------------- Android, iOS, Windows, OS X, Linux ''' class Battery: ''' 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): ''' Public method for filling battery.status via platform-specific API in plyer.platforms. ''' return self._get_state() # private def _get_state(self): raise NotImplementedError() plyer-2.1.0/plyer/facades/bluetooth.py000066400000000000000000000013511433372044000177450ustar00rootroot00000000000000'''Bluetooth facade. Returns the following: * Bluetooth info Simple Example -------------- To get the bluetooth status info:: todo: will be extended to get additional bluetooth info todo: will be extended to allow bluetooth connections etc. >>> from plyer import bluetooth >>> bluetooth 'on' or 'off' Supported Platforms ------------------- Android, OS X ''' class Bluetooth: ''' Bluetooth facade. ''' @property def info(self): ''' Property that returns the info (currently status) of the bluetooth. ''' return self.get_info() def get_info(self): return self._get_info() # private def _get_info(self, **kwargs): raise NotImplementedError() plyer-2.1.0/plyer/facades/brightness.py000077500000000000000000000023651433372044000201210ustar00rootroot00000000000000''' Brightness ========== This API helps you to control the brightness of your primary display screen. The :class:`Brightness` provides access to public methods to control the brightness of screen. NOTE:: For Android, make sure to add permission, WRITE_SETTINGS Simple Examples --------------- To know the current brightness level of device:: >>> from plyer import brightness >>> brightness.current_level() To set the brightness level to half of maximum:: >>> from plyer import brightness >>> brightness.set_level(50) Supported Platforms ------------------- Android, iOS, Linux ''' class Brightness: ''' Brightness facade. ''' def current_level(self): ''' Know the current level of device's brightness. ''' return self._current_level() def set_level(self, level): ''' Adjust the brightness of the screen. Minimum brightness level:: 1 Maximum brightness level:: 100 :param level: New level of brightness between 1 and 100 :type level: int ''' return self._set_level(level) # private def _set_level(self, level): raise NotImplementedError() def _current_level(self): raise NotImplementedError() plyer-2.1.0/plyer/facades/call.py000066400000000000000000000017531433372044000166610ustar00rootroot00000000000000''' Call ==== The :class:`Call` provides access to calling feature of your device. .. note:: - On Android your app needs the `CALL_PHONE` or `CALL_PRIVILEGED` permission in order to make calls. - Dialing call feature in not supported yet in iOS devices. Simple Examples --------------- To make call:: >>> from plyer import call >>> tel = 9999222299 >>> call.makecall(tel=tel) To dial call:: >>> call.dialcall() Supported Platforms ------------------- Android, iOS ''' class Call: ''' Call facade. ''' def makecall(self, tel): ''' Make calls using your device. :param tel: The reciever :type tel: number ''' self._makecall(tel=tel) def dialcall(self): ''' Opens dialing interface. ''' self._dialcall() # private def _makecall(self, **kwargs): raise NotImplementedError() def _dialcall(self, **kwargs): raise NotImplementedError() plyer-2.1.0/plyer/facades/camera.py000066400000000000000000000045101433372044000171700ustar00rootroot00000000000000''' Camera ====== The :class:`Camera` is to capture pictures and make videos. .. note:: - On Android the `CAMERA` , `WRITE_EXTERNAL_STORAGE`, `READ_EXTERNAL_STORAGE` permissions are needed. Simple Examples --------------- Setup callback function. >>> from os.path import exists, join >>> from plyer import camera >>> def camera_callback(filepath): >>> if(exists(filepath)): >>> print "saved" >>> else: >>> print "unable to save." >>> filepath = 'path/to/your/file' >>> # e.g: filepath = join(App.get_running_app().user_data_dir, file_name) To take picture:: >>> file_name = "test.jpg" >>> camera.take_picture(filename=file_name, >>> on_complete=camera_callback) Ta take a video:: >>> file_name = "test.mp4" >>> camera.take_video(filename=file_name, >>> on_complete=camera_callback) Supported Platforms ------------------- Android, iOS ''' class Camera: ''' 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() plyer-2.1.0/plyer/facades/compass.py000066400000000000000000000047541433372044000174170ustar00rootroot00000000000000''' Compass ======= The :class:`Compass` provides access to public methods to use compass of your device. Simple Examples --------------- To enable compass:: >>> from plyer import compass >>> compass.enable() To disable compass:: >>> compass.disable() To get the field:: >>> compass.field() (-23.721826553344727, -5.7114701271057129, -36.749668121337891) To get the uncalibrated field along with iron bias estimation:: >>> compass.field_uncalib() (a,b,c,x,y,z) # a,b,c denote the Geomagnetic field strength # (without hard iron calibration) along the three axes. # x,y,z denote the Iron bias estimation along the three axes. Supported Platforms ------------------- Android, iOS ''' class Compass: '''Compass facade. .. versionadded:: 1.2.0 ''' @property def orientation(self): ''' WARNING:: This property is deprecated after API level 8. Use `compass.field` instead. 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() @property def field(self): ''' .. versionadded:: 1.3.1 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() @property def field_uncalib(self): ''' .. versionadded:: 1.3.1 Property that returns the current value of Uncalibrated Magnetic Field (without hard iron calibration) along with the iron bias estimation along the three axes. ''' return self.get_field_uncalib() 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() def get_field_uncalib(self): ''' .. versionadded:: 1.3.1 ''' return self._get_field_uncalib() # private def _enable(self): raise NotImplementedError() def _disable(self): raise NotImplementedError() def _get_orientation(self): raise NotImplementedError() def _get_field_uncalib(self): raise NotImplementedError() plyer-2.1.0/plyer/facades/cpu.py000066400000000000000000000036671433372044000165430ustar00rootroot00000000000000''' CPU === Simple Example --------------- To get CPU count:: >>> from plyer import cpu >>> # 1 socket, 1 core per socket, 2 threads per core >>> cpu.sockets # 1 CPU socket (or slot) 1 >>> cpu.physical # 1 CPU socket * 1 core per socket 1 >>> cpu.logical # 1 CPU socket * 1 core per socket * 2 threads per core 2 Supported Platforms ------------------- MacOS Linux Windows ''' class CPU: ''' Facade providing info about sockets, physical and logical number of processors. ''' @property def sockets(self): ''' Property that contains the count of CPU sockets. ''' return self._sockets() @property def physical(self): ''' Property that contains the total number of physical cores (max core count) in the system. .. note:: `sockets * cores per socket` ''' return self._physical() @property def logical(self): ''' Property that contains the total number of logical cores (max thread count) in the system. .. note:: `sockets * cores per socket * threads per core` ''' return self._logical() @property def cache(self): ''' Property that contains the count of L1, L2, L3 caches in the system as a dictionary `{'L1': int, 'L2': int, 'L3': int}`. ''' return self._cache() @property def numa(self): ''' Property that contains the count of NUMA nodes in the system. .. note:: https://en.wikipedia.org/wiki/Non-uniform_memory_access ''' return self._numa() # private def _sockets(self): raise NotImplementedError() def _physical(self): raise NotImplementedError() def _logical(self): raise NotImplementedError() def _cache(self): raise NotImplementedError() def _numa(self): raise NotImplementedError() plyer-2.1.0/plyer/facades/devicename.py000066400000000000000000000014321433372044000200400ustar00rootroot00000000000000'''DeviceName facade. Returns the following depending on the platform: * **Android**: Android Device name * **Linux**: Hostname of the machine * **OS X**: Hostname of the machine * **Windows**: Hostname of the machine Simple Example -------------- To get the Device Name:: >>> from plyer import devicename >>> devicename.device_name 'Oneplus 3' .. versionadded:: 2.1.0 - first release Supported Platforms ------------------- Android, Windows, OS X, Linux ''' class DeviceName: ''' DeviceName facade. ''' @property def device_name(self): ''' Property that returns the device name of the platform. ''' return self._get_device_name() # private def _get_device_name(self): raise NotImplementedError() plyer-2.1.0/plyer/facades/email.py000066400000000000000000000026641433372044000170370ustar00rootroot00000000000000''' Email ===== The :class:`Email` provides access to public methods to use email of your device. .. note:: On Android `INTERNET` permission is needed. Simple Examples --------------- To send an e-mail:: >>> from plyer import email >>> recipient = 'abc@gmail.com' >>> subject = 'Hi' >>> text = 'This is an example.' >>> create_chooser = False >>> email.send(recipient=recipient, subject=subject, text=text, create_chooser=create_chooser) >>> # opens email interface where user can change the content. Supported Platforms ------------------- Android, iOS, Windows, OS X, Linux ''' class Email: ''' 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() plyer-2.1.0/plyer/facades/filechooser.py000066400000000000000000000052431433372044000202460ustar00rootroot00000000000000''' 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) * **on_selection** *(func)*: Callback for fetching the selection. Important: these methods will return only after user interaction. Use threads or you will stop the mainloop if your app has one. .. versionchanged:: 1.4.0 Added Android implementation for open_file() Added ``on_selection`` kwarg for callback function Supported Plaforms ------------------ Android, iOS, macOS, Linux, Windows ''' class FileChooser: ''' File Chooser facade. ''' 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) # private def _file_selection_dialog(self, **kwargs): raise NotImplementedError() plyer-2.1.0/plyer/facades/flash.py000066400000000000000000000023171433372044000170400ustar00rootroot00000000000000# coding=utf-8 ''' Flash ===== The :class:`Flash` provides access to public methods to use flash of your device. .. note:: In android you need CAMERA, FLASHLIGHT permissions to access flash. .. versionadded:: 1.2.5 This can be used to activate the flash of your camera on Android and iOS. Simple Examples --------------- To turn on flash:: >>> from plyer import flash >>> flash.on() To turn off flash:: >>> flash.off() To release flash:: >>> flash.release() Supported Platforms ------------------- Android, iOS ''' class Flash: """ Flash facade. """ def on(self): """ Activate the flash """ self._on() def off(self): """ Deactiavte the flash """ self._off() def release(self): """ Release any access to the Flash / Camera. Call this when you're done using the Flash. This will release the Camera, and stop any process. Next call to `_on` will reactivate it. """ self._release() # private def _on(self): raise NotImplementedError() def _off(self): raise NotImplementedError() def _release(self): pass plyer-2.1.0/plyer/facades/gps.py000066400000000000000000000051131433372044000165310ustar00rootroot00000000000000''' GPS ==== .. versionadded:: 1.1 .. note:: On Android `INTERNET`, `ACCESS_FINE_LOCATION`, `ACCESS_COARSE_LOCATION` permissions are needed. .. note:: On iOS `NSLocationWhenInUseUsageDescription` key is required for app to display geolocation usage permission prompt. Key can be added in Xcode target `info` section or in ``Resources/-info.plist``. App background mode (`on_pause`) also must be supported. 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() Supported Platforms ------------------- Android, iOS ''' class GPS: ''' GPS facade. ''' 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, minTime=1000, minDistance=1): ''' Start the GPS location updates. Expects 2 parameters: minTime: milliseconds. (float) minDistance: meters. (float) ''' self._start(minTime=minTime, minDistance=minDistance) def stop(self): ''' Stop the GPS location updates. ''' self._stop() # private def _configure(self): raise NotImplementedError() def _start(self, **kwargs): raise NotImplementedError() def _stop(self): raise NotImplementedError() plyer-2.1.0/plyer/facades/gravity.py000066400000000000000000000015131433372044000174250ustar00rootroot00000000000000class Gravity: '''Gravity facade. .. versionadded:: 1.2.5 Supported Platforms:: Android ''' @property def gravity(self): '''Property that returns values of the current gravity force as a (x, y, z) tuple. Returns (None, None, None) if no data is currently available. ''' return self._get_gravity() def enable(self): '''Activate the gravity sensor. Throws an error if the hardware is not available or not implemented on. ''' self._enable() def disable(self): '''Disable the gravity sensor. ''' self._disable() # private def _enable(self): raise NotImplementedError() def _disable(self): raise NotImplementedError() def _get_gravity(self): raise NotImplementedError() plyer-2.1.0/plyer/facades/gyroscope.py000066400000000000000000000062031433372044000177530ustar00rootroot00000000000000''' Gyroscope ============ The gyroscope measures the rate of rotation around a device's x, y, and z axis. The :class:`Gyroscope` provides access to public methods to use gyroscope of your device. Simple Examples --------------- To enable gyroscope:: >>> from plyer import gyroscope >>> gyroscope.enable() To disable gyroscope:: >>> gyroscope.disable() To get the rate of rotation along the three axes:: >>> gyroscope.rotation (-0.0034587313421070576, -0.0073830625042319298, 0.0046892408281564713) To get the uncalibrated rate of rotation along the three axes along with the drift compensation:: >>> gyroscope.rotation_uncalib () where the first three values show the rate of rotation w/o drift compensation and the last three show the estimated drift along the three axes. Supported Platforms ------------------- Android, iOS ''' class Gyroscope: ''' Gyroscope facade. .. versionadded:: 1.3.1 ''' @property def rotation(self): ''' Property that returns the rate of rotation around the device's local X, Y and Z axis. Along x-axis: angular speed around the X axis Along y-axis: angular speed around the Y axis Along z-axis: angular speed around the Z axis Returns (None, None, None) if no data is currently available. ''' return self.get_orientation() @property def rotation_uncalib(self): ''' Property that returns the current rate of rotation around the X, Y and Z axis. An estimation of the drift on each axis is reported as well. Along x-axis: angular speed (w/o drift compensation) around the X axis Along y-axis: angular speed (w/o drift compensation) around the Y axis Along z-axis: angular speed (w/o drift compensation) around the Z axis Along x-axis: estimated drift around X axis Along y-axis: estimated drift around Y axis Along z-axis: estimated drift around Z axis Returns (None, None, None, None, None, None) if no data is currently available. ''' return self.get_rotation_uncalib() @property def orientation(self): ''' WARNING:: This property is deprecated after API Level 8. Use `gyroscope.rotation` instead. 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() def get_rotation_uncalib(self): return self._get_rotation_uncalib() # private def _enable(self): raise NotImplementedError() def _disable(self): raise NotImplementedError() def _get_orientation(self): raise NotImplementedError() def _get_rotation_uncalib(self): raise NotImplementedError() plyer-2.1.0/plyer/facades/humidity.py000066400000000000000000000014741433372044000176020ustar00rootroot00000000000000class Humidity: '''Humidity facade. Humidity sensor returns value of humidity. With method `enable` you can turn on Humidity sensor and 'disable' method stops the sensor. Use property `tell` to get humidity value. Supported Platforms ------------------- Android ''' @property def tell(self): '''Current humidity''' return self._get_humidity() def enable(self): '''Enable Humidity sensor.''' self._enable() def disable(self): '''Disable Humidity sensor.''' self._disable() # private def _get_humidity(self, **kwargs): raise NotImplementedError() def _enable(self, **kwargs): raise NotImplementedError() def _disable(self, **kwargs): raise NotImplementedError() plyer-2.1.0/plyer/facades/irblaster.py000066400000000000000000000047671433372044000177450ustar00rootroot00000000000000''' IrBlaster ============ The :class:`IrBlaster` provides access to public methods by which your device can act as a remote and could be used to control your TV, AC, Music Player, Projectors, Set top box or anything that can be controlled by a remote. .. note:: - On Android your app needs the TRANSMIT_IR permission which allows an application to use the device's IR transmitter, If available. Simple Examples --------------- To get transmit an IR sequence:: >>> from plyer import irblaster >>> irblaster.transmit(frequency, pattern, mode) To get frequencies:: >>> irblaster.frequencies To check if IrBlaster exists:: >>> irblaster.exists() True/False Supported Platforms ------------------- Android ''' class IrBlaster: ''' 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 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 exists(self): ''' Check if the device has an infrared emitter. ''' return self._exists() # private def _get_frequencies(self): raise NotImplementedError() def _transmit(self, frequency, pattern, mode): raise NotImplementedError() def _exists(self): raise NotImplementedError() plyer-2.1.0/plyer/facades/keystore.py000066400000000000000000000015451433372044000176120ustar00rootroot00000000000000''' Keystore ======= The :class:`Keystore` provides a mechanism for securing/storing cryptographic keys (such as user credentials) in a container. Typically needed to support authentication APIs such as OAuth2 .. note:: Typically needed to support authentication APIs such as OAuth2 Supported Platforms ------------------- Android, iOS, Windows, OS X, Linux --------------- ''' class Keystore: ''' Keystore facade ''' def set_key(self, servicename, key, value, **kwargs): self._set_key(servicename, key, value, **kwargs) def _set_key(self, servicename, key, value, **kwargs): raise NotImplementedError() def get_key(self, servicename, key, **kwargs): return self._get_key(servicename, key) def _get_key(self, servicename, key, **kwargs): raise NotImplementedError() plyer-2.1.0/plyer/facades/light.py000066400000000000000000000016331433372044000170520ustar00rootroot00000000000000class Light: '''Light facade. Light sensor measures the ambient light level(illumination) in lx. Common uses include controlling screen brightness. With method `enable` you can turn on the sensor and `disable` method stops the sensor. Use property `illumination` to get current illumination in lx. .. versionadded:: 1.2.5 Supported Platforms:: Android ''' @property def illumination(self): '''Current illumination in lx.''' return self._get_illumination() def enable(self): '''Enable light sensor.''' self._enable() def disable(self): '''Disable light sensor.''' self._disable() # private def _get_illumination(self, **kwargs): raise NotImplementedError() def _enable(self, **kwargs): raise NotImplementedError() def _disable(self, **kwargs): raise NotImplementedError() plyer-2.1.0/plyer/facades/notification.py000066400000000000000000000052111433372044000204250ustar00rootroot00000000000000''' Notification ============ The :class:`Notification` provides access to public methods to create notifications. Simple Examples --------------- To send notification:: >>> from plyer import notification >>> title = 'plyer' >>> message = 'This is an example.' >>> notification.notify(title=title, message=message) Android toast notification:: >>> from plyer import notification >>> notification.notify(message='hello', toast=True) Android simple notification:: >>> from plyer import notification >>> notification.notify(message='hello', toast=True) Notification with custom icon:: >>> from plyer import notification >>> notification.notify(title='title', message='hello', app_icon=) .. versionadded:: 1.0.0 .. versionadded:: 1.4.0 Add implementation of primitive Android popup-like notification (toast) .. versionchanged:: 1.4.0 Android implementation now supports custom icons for notifications. ''' class Notification: ''' Notification facade. ''' def notify(self, title='', message='', app_name='', app_icon='', timeout=10, ticker='', toast=False, hints={}): ''' 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 :param ticker: text to display on status bar as the notification arrives :param toast: simple Android message instead of full notification :param hints: Optional hints that can be used to pass along extra instructions on Linux. (See https://specifications.freedesktop.org/notification-spec/latest/ar01s08.html) # noqa: E501 :type title: str :type message: str :type app_name: str :type app_icon: str :type timeout: int :type ticker: str :type toast: bool :type hints: dict .. note:: When called on Windows, ``app_icon`` has to be a path to a file in .ICO format. .. versionadded:: 1.0.0 .. versionchanged:: 1.4.0 Add 'toast' keyword argument ''' self._notify( title=title, message=message, app_icon=app_icon, app_name=app_name, timeout=timeout, ticker=ticker, toast=toast, hints=hints ) # private def _notify(self, **kwargs): raise NotImplementedError("No usable implementation found!") plyer-2.1.0/plyer/facades/orientation.py000066400000000000000000000035261433372044000203010ustar00rootroot00000000000000''' Orientation ========== The :class:`Orientation` provides access to public methods to set orientation of your device. .. 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 Simple Examples --------------- To set landscape:: >>> from plyer import orientation >>> orientation.set_landscape() To set portrait:: >>> orientation.set_portrait() To set sensor:: >>> orientation.set_sensor() Supported Platforms ------------------- Android, Linux ''' class Orientation: ''' Orientation facade. ''' 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_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_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) # private def _set_landscape(self, **kwargs): raise NotImplementedError() def _set_portrait(self, **kwargs): raise NotImplementedError() def _set_sensor(self, **kwargs): raise NotImplementedError() plyer-2.1.0/plyer/facades/processors.py000066400000000000000000000016461433372044000201510ustar00rootroot00000000000000''' Number of Processors ======= The :class:`Processors` provides a information on the number of processors in a system .. note:: Deprecated in favor of `cpu` Simple Example --------------- To get processors status:: >>> from plyer import processors >>> processors.status {'Number_of_Processors': '4'} Supported Platforms ------------------- Linux ''' class Processors: ''' Number of Processors info facade. ''' @property def status(self): ''' Property that contains a dict with the following fields: * **Number_of_Processors** *(int)*: Number of Processors in the system .. 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() def _get_state(self): raise NotImplementedError() plyer-2.1.0/plyer/facades/proximity.py000066400000000000000000000022601433372044000200040ustar00rootroot00000000000000class Proximity: '''Proximity facade. The proximity sensor is commonly used to determine distance whether phone is close to your head. Commonly is used when you have a call and you stick your phone with your head. Then screen of phone turns off. Use method `enable` to turn on proximity sensor and method `disable` for turn off. To check if some object (or your head) is near sensor check values from property `proximity`. It returns `True` when object is close. .. versionadded:: 1.2.5 Supported Platforms::Android ''' @property def proximity(self): '''Return True or False depending if there is an object or not. :return: True if there is an object. Otherwise False. ''' return self._get_proximity() def _enable(self, **kwargs): raise NotImplementedError() def enable(self): '''Enable the proximity sensor. ''' self._enable() def _disable(self, **kwargs): raise NotImplementedError() def disable(self): '''Disable the proximity sensor. ''' self._disable() def _get_proximity(self): raise NotImplementedError() plyer-2.1.0/plyer/facades/screenshot.py000066400000000000000000000020061433372044000201130ustar00rootroot00000000000000''' Screenshot ========== The :class:`Screenshot` is used for capturing a digital image of what is currently visible on the monitor. The default path for taking screenshot is set in each platform implementation. Simple Examples --------------- To get the file path:: >>> screenshot.file_path '/sdcard/test.jpg' To set the file path:: >>> screenshot.file_path = '/Users/OSXUser/Pictures/screenshot.png' To take screenshot:: >>> from plyer import screenshot >>> screenshot.capture() ''' class Screenshot: ''' Screenshot facade. ''' _file_path = '' def __init__(self, file_path=None): self._file_path = file_path def capture(self): self._capture() @property def file_path(self): return self._file_path @file_path.setter def file_path(self, location): ''' Location of the screenshot. ''' self._file_path = location # private def _capture(self, **kwargs): raise NotImplementedError() plyer-2.1.0/plyer/facades/sms.py000066400000000000000000000016001433372044000165370ustar00rootroot00000000000000''' Sms ==== The :class:`Sms` provides access to sending Sms from your device. .. note:: On Android your app needs the SEND_SMS permission in order to send sms messages. .. versionadded:: 1.2.0 Simple Examples --------------- To send sms:: >>> from plyer import sms >>> recipient = 9999222299 >>> message = 'This is an example.' >>> sms.send(recipient=recipient, message=message) Supported Platforms ------------------- Android, iOS ''' class Sms: ''' Sms facade. ''' def send(self, recipient, message): ''' Send SMS or open SMS interface. :param recipient: The receiver :param message: the message :type recipient: number :type message: str ''' self._send(recipient=recipient, message=message) # private def _send(self, **kwargs): raise NotImplementedError() plyer-2.1.0/plyer/facades/spatialorientation.py000066400000000000000000000031641433372044000216550ustar00rootroot00000000000000# coding=utf-8 class SpatialOrientation: '''Spatial Orientation facade. Computes the device's orientation based on the rotation matrix. .. versionadded:: 1.3.1 ''' @property def orientation(self): '''Property that returns values of the current device orientation as a (azimuth, pitch, roll) tuple. Azimuth, angle of rotation about the -z axis. This value represents the angle between the device's y axis and the magnetic north pole. The range of values is -π to π. Pitch, angle of rotation about the x axis. This value represents the angle between a plane parallel to the device's screen and a plane parallel to the ground. The range of values is -π to π. Roll, angle of rotation about the y axis. This value represents the angle between a plane perpendicular to the device's screen and a plane perpendicular to the ground. The range of values is -π/2 to π/2. Returns (None, None, None) if no data is currently available. Supported Platforms:: Android ''' return self._get_orientation() or (None, None, None) def _get_orientation(self): raise NotImplementedError() def enable_listener(self): '''Enable the orientation sensor. ''' self._enable_listener() def _enable_listener(self, **kwargs): raise NotImplementedError() def disable_listener(self): '''Disable the orientation sensor. ''' self._disable_listener() def _disable_listener(self, **kwargs): raise NotImplementedError() plyer-2.1.0/plyer/facades/storagepath.py000066400000000000000000000065061433372044000202700ustar00rootroot00000000000000''' Storage Path ============ The StorgePath API can be used to gain access to standard storage locations across platforms such as home directory, root directory, external storage directory, documents, downloads, etc. The :class:`StoragePath` provides access to public methods to access standard storage locations. Simple Examples --------------- To get the path of user's home directory:: >>> from plyer import storagepath >>> storagepath.get_home_dir() To get the path of standard downloads directory:: >>> from plyer import storagepath >>> storagepath.get_downloads_dir() To get the path of directory holding application files:: >>> from plyer import storagepath >>> storagepath.get_application_dir() ''' class StoragePath: ''' StoragePath facade. ''' def get_home_dir(self): ''' Get the path of home directory of current user. ''' return self._get_home_dir() def get_external_storage_dir(self): ''' Get the path of primary shared or external storage directory. ''' return self._get_external_storage_dir() def get_sdcard_dir(self): ''' Get the path of external SD card. .. versionadded:: 1.4.0 ''' return self._get_sdcard_dir() def get_root_dir(self): ''' Get the path of root of the "system" partition holding the core OS. ''' return self._get_root_dir() def get_documents_dir(self): ''' Get the path of standard directory in which to place documents that have been created by the user. ''' return self._get_documents_dir() def get_downloads_dir(self): ''' Get the path of standard directory in which to place files that have been downloaded by the user. ''' return self._get_downloads_dir() def get_videos_dir(self): ''' Get the path of standard directory in which to place videos that are available to the user. ''' return self._get_videos_dir() def get_music_dir(self): ''' Get the path of standard directory in which to place any audio files that should be in the regular list of music for the user. ''' return self._get_music_dir() def get_pictures_dir(self): ''' Standard directory in which to place pictures that are available to the user. ''' return self._get_pictures_dir() def get_application_dir(self): ''' Get the path of the directory holding application files. ''' return self._get_application_dir() # private def _get_home_dir(self): raise NotImplementedError() def _get_external_storage_dir(self): raise NotImplementedError() def _get_sdcard_dir(self): raise NotImplementedError() def _get_root_dir(self): raise NotImplementedError() def _get_documents_dir(self): raise NotImplementedError() def _get_downloads_dir(self): raise NotImplementedError() def _get_videos_dir(self): raise NotImplementedError() def _get_music_dir(self): raise NotImplementedError() def _get_pictures_dir(self): raise NotImplementedError() def _get_application_dir(self): raise NotImplementedError() plyer-2.1.0/plyer/facades/stt.py000066400000000000000000000107311433372044000165540ustar00rootroot00000000000000''' Speech to Text ============== .. versionadded:: 1.4.0 Speech Recognition facade. In order to check that your device supports voice recognition use method `exist`. Variable `language` indicates which language will be used to match words from voice. Use `start` to start voice recognition immediately and `stop` to stop. .. note:: Needed permissions for Android: `RECORD_AUDIO` (and `INTERNET` if you want online voice recognition API to be used) .. note:: On Android platform, after execute `start` method you can hear BEEP! Mute sound in order to disable it. .. note:: For Android implementation to work there has to be an application with `android.speech.RecognitionService` implementation present in the system. Mostly it's `com.google.android.googlequicksearchbox` or "Google" application (the search bar with the launcher widget). Offline Speech Recognition on Android ------------------------------------- Requires any application that provides an `android.speech.RecognitionService` implementation to the other apps. One of such applications is on a lot of devices preinstalled Google (quick search box). The API prefers offline recognition, but should be able to switch to online alternative in case you don't have a language package installed (`INTERNET` permission necessary). You can enable offline speech recognition this way (Android 8.1): * open the `Settings` app * choose `Language & Input` / `Language & Keyboard` (Samsung might include it in the `General` category) * choose `On-Screen keyboard` or `Voice search` * choose `Google Keyboard` * choose `Offline Speech recognition` * download language package if you don't have one already Simple Examples --------------- To start listening:: >>> from plyer import stt >>> stt.start() To retrieve partial results while listening:: >>> assert stt.listening >>> print(stt.partial_results) To stop listening:: >>> stt.stop() To retrieve results after the listening stopped:: >>> print(stt.results) ''' class STT: ''' Speech to text facade. ''' _language = 'en-US' ''' Default language in which platform will try to recognize voice. In order to change language pick one from list by using `supported_languages` method. ''' _supported_languages = [ 'en-US', 'pl-PL' ] results = [] ''' List of sentences found while listening. It may consist of many similar and possible sentences that was recognition program. ''' errors = [] ''' List of errors found while listening. ''' partial_results = [] ''' List of results found while the listener is still being active. ''' prefer_offline = True ''' Preference whether to use offline language package necessary for each platform dependant implementation or online API. ''' listening = False ''' Current state of listening. ''' @property def supported_languages(self): ''' Return list of supported languages used in recognition. ''' return self._supported_languages @property def language(self): ''' Return current language. ''' return self._language @language.setter def language(self, lang): ''' Set current language. Value can not be set if it's not supported. See `supported_languages` to get what language you can set. .. note:: We obviously can't check each language, therefore if you find that a specific language is available to you and the only limitation is our check for the internally defined `supported_languages`, feel free to open a pull request for adding your language to the list. ''' if lang in self.supported_languages: self._language = lang # public methods def start(self): ''' Start listening. ''' self.results = [] self.partial_results = [] self._start() self.listening = True def stop(self): ''' Stop listening. ''' self._stop() self.listening = False def exist(self): ''' Returns a boolean for speech recognition availability. ''' return self._exist() # private methods def _start(self): raise NotImplementedError def _stop(self): raise NotImplementedError def _exist(self): raise NotImplementedError plyer-2.1.0/plyer/facades/temperature.py000066400000000000000000000016771433372044000203100ustar00rootroot00000000000000# coding=utf-8 class Temperature: '''Temperature facade. Temperature sensor is used to measure the ambient room temperature in degrees Celsius (°C) With method `enable` you can turn on temperature sensor and 'disable' method stops the sensor. Use property `temperature` to get ambient air temperature in degree C. .. versionadded:: 1.2.5 Supported Platforms:: Android ''' @property def temperature(self): '''Current air temperature in degree C.''' return self._get_temperature() def enable(self): '''Enable temperature sensor.''' self._enable() def disable(self): '''Disable temperature sensor.''' self._disable() # private def _get_temperature(self, **kwargs): raise NotImplementedError() def _enable(self, **kwargs): raise NotImplementedError() def _disable(self, **kwargs): raise NotImplementedError() plyer-2.1.0/plyer/facades/tts.py000066400000000000000000000012201433372044000165450ustar00rootroot00000000000000''' TTS ==== The :class:`TTS` provides provides access to public methods to use text to speech of your device. Simple Examples --------------- To speak:: >>> from plyer import tts >>> tts.speak(message=message) Supported Platforms ------------------- Android, iOS, Windows, OS X, Linux ''' class TTS: ''' 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() plyer-2.1.0/plyer/facades/uniqueid.py000066400000000000000000000017561433372044000175740ustar00rootroot00000000000000'''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 * **iOS**: UUID Simple Example -------------- To get the unique ID:: >>> from plyer import uniqueid >>> uniqueid.id '1b1a7a4958e2a845' .. versionadded:: 1.2.0 .. versionchanged:: 1.2.4 On Android returns Android ID instead of IMEI. Supported Platforms ------------------- Android, iOS, Windows, OS X, Linux ''' class UniqueID: ''' UniqueID facade. ''' @property def id(self): ''' Property that returns the unique id of the platform. ''' return self.get_uid() def get_uid(self): ''' Public method for receiving unique ID via platform-specific API in plyer.platforms. ''' return self._get_uid() # private def _get_uid(self): raise NotImplementedError() plyer-2.1.0/plyer/facades/vibrator.py000066400000000000000000000042251433372044000175730ustar00rootroot00000000000000''' Vibrator ======= The :class:`Vibrator` provides access to public methods to use vibrator of your device. .. note:: On Android your app needs the VIBRATE permission to access the vibrator. Simple Examples --------------- To vibrate your device:: >>> from plyer import vibrator >>> time=2 >>> vibrator.vibrate(time=time) To set a pattern:: >>> vibrator.pattern(pattern=pattern, repeat=repeat) To know whether vibrator exists or not:: >>> vibrator.exists() To cancel vibration:: >>> vibrator.cancel() Supported Platforms ------------------- Android, iOS ''' class Vibrator: ''' Vibration facade. ''' 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 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 exists(self): ''' Check if the device has a vibrator. Returns True or False. ''' return self._exists() def cancel(self): ''' Cancels any current vibration, and stops the vibrator. ''' self._cancel() # private def _vibrate(self, **kwargs): raise NotImplementedError() def _pattern(self, **kwargs): raise NotImplementedError() def _exists(self, **kwargs): raise NotImplementedError() def _cancel(self, **kwargs): raise NotImplementedError() plyer-2.1.0/plyer/facades/wifi.py000066400000000000000000000101111433372044000166700ustar00rootroot00000000000000''' Wifi Facade. ============= The :class:`Wifi` is to provide access to the wifi of your mobile/ desktop devices. It currently supports `connecting`, `disconnecting`, `scanning`, `getting available wifi network list` and `getting network information`. Simple examples --------------- To enable/ turn on wifi scanning:: >>> from plyer import wifi >>> wifi.start_scanning() Once the wifi is enabled/ turned on, then this command starts to scan all the nearby available wifi networks. To get network info:: >>> from plyer import wifi >>> wifi.start_scanning() >>> return wifi.get_network_info(name) Returns network details of the network who's name/ssid is provided in the `name` parameter. To connect to a network:: >>> from plyer import wifi >>> wifi.start_scanning() >>> wifi.connect(network, parameters) This connects to the network who's name/ssid is provided under `network` parameter and along with other necessary methods for connection which depends upon platform to platform. please visit following files for more details about requirements of `paramaters` argument in `connect` method: plyer/platforms/win/wifi.py plyer/platforms/macosx/wifi.py plyer/platforms/win/wifi.py To disconnect from wifi:: >>> from plyer import wifi >>> wifi.disconnect() This disconnects your device from any wifi network. To get available wifi networks:: >>> from plyer import wifi >>> wifi.start_scanning() >>> return wifi.get_available_wifi() This returns all the available wifi networks near the device. Supported Platforms ------------------- Windows, OS X, Linux Ex: 6 ---------- from plyer import wifi wifi.enable() This enables wifi device. Ex: 7 ---------- from plyer import wifi wifi.disable() This disable wifi device ''' class Wifi: ''' Wifi Facade. ''' def is_enabled(self): ''' Return enabled status of WiFi hardware. ''' return self._is_enabled() def is_connected(self, interface=None): ''' Return connection state of WiFi interface. .. versionadded:: 1.4.0 ''' return self._is_connected(interface=interface) @property def interfaces(self): ''' List all available WiFi interfaces. .. versionadded:: 1.4.0 ''' raise NotImplementedError() def start_scanning(self, interface=None): ''' Turn on scanning. ''' return self._start_scanning(interface=interface) def get_network_info(self, name): ''' Return a dictionary of specified network. ''' return self._get_network_info(name=name) def get_available_wifi(self): ''' Returns a list of all the available wifi. ''' return self._get_available_wifi() def connect(self, network, parameters, interface=None): ''' Method to connect to some network. ''' self._connect( network=network, parameters=parameters, interface=interface ) def disconnect(self, interface=None): ''' To disconnect from some network. ''' self._disconnect(interface=interface) def enable(self): ''' Wifi interface power state is set to "ON". ''' self._enable() def disable(self): ''' Wifi interface power state is set to "OFF". ''' self._disable() # private def _is_enabled(self): raise NotImplementedError() def _is_connected(self, interface=None): raise NotImplementedError() def _start_scanning(self, interface=None): raise NotImplementedError() def _get_network_info(self, **kwargs): raise NotImplementedError() def _get_available_wifi(self): raise NotImplementedError() def _connect(self, **kwargs): raise NotImplementedError() def _disconnect(self, interface=None): raise NotImplementedError() def _enable(self): raise NotImplementedError() def _disable(self): raise NotImplementedError() plyer-2.1.0/plyer/platforms/000077500000000000000000000000001433372044000160075ustar00rootroot00000000000000plyer-2.1.0/plyer/platforms/__init__.py000066400000000000000000000000001433372044000201060ustar00rootroot00000000000000plyer-2.1.0/plyer/platforms/android/000077500000000000000000000000001433372044000174275ustar00rootroot00000000000000plyer-2.1.0/plyer/platforms/android/__init__.py000066400000000000000000000007771433372044000215530ustar00rootroot00000000000000from os import environ from jnius import autoclass ANDROID_VERSION = autoclass('android.os.Build$VERSION') SDK_INT = ANDROID_VERSION.SDK_INT try: from android import config ns = config.JAVA_NAMESPACE except (ImportError, AttributeError): ns = 'org.renpy.android' if 'PYTHON_SERVICE_ARGUMENT' in environ: PythonService = autoclass(ns + '.PythonService') activity = PythonService.mService else: PythonActivity = autoclass(ns + '.PythonActivity') activity = PythonActivity.mActivity plyer-2.1.0/plyer/platforms/android/accelerometer.py000066400000000000000000000041161433372044000226150ustar00rootroot00000000000000''' 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().__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().__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().__del__() def instance(): return AndroidAccelerometer() plyer-2.1.0/plyer/platforms/android/audio.py000066400000000000000000000031721433372044000211050ustar00rootroot00000000000000from 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().__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() plyer-2.1.0/plyer/platforms/android/barometer.py000066400000000000000000000034651433372044000217710ustar00rootroot00000000000000from jnius import autoclass from jnius import cast from jnius import java_method from jnius import PythonJavaClass from plyer.facades import Barometer from plyer.platforms.android import activity ActivityInfo = autoclass('android.content.pm.ActivityInfo') Context = autoclass('android.content.Context') Sensor = autoclass('android.hardware.Sensor') SensorManager = autoclass('android.hardware.SensorManager') class BarometerSensorListener(PythonJavaClass): __javainterfaces__ = ['android/hardware/SensorEventListener'] def __init__(self): super().__init__() service = activity.getSystemService(Context.SENSOR_SERVICE) self.SensorManager = cast('android.hardware.SensorManager', service) self.sensor = self.SensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE) self.value = 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.value = event.values[0] @java_method('(Landroid/hardware/Sensor;I)V') def onAccuracyChanged(self, sensor, accuracy): pass class AndroidBarometer(Barometer): listener = None def _get_pressure(self): if self.listener and self.listener.value: pressure = self.listener.value return pressure def _enable(self): if not self.listener: self.listener = BarometerSensorListener() self.listener.enable() def _disable(self): if self.listener: self.listener.disable() delattr(self, 'listener') def instance(): return AndroidBarometer() plyer-2.1.0/plyer/platforms/android/battery.py000066400000000000000000000024461433372044000214610ustar00rootroot00000000000000''' Module of Android API for plyer.battery. ''' 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): ''' Implementation of Android battery API. ''' def _get_state(self): status = {"isCharging": None, "percentage": None} ifilter = IntentFilter(Intent.ACTION_BATTERY_CHANGED) battery_status = cast( 'android.content.Intent', activity.registerReceiver(None, ifilter) ) query = battery_status.getIntExtra(BatteryManager.EXTRA_STATUS, -1) is_charging = query == BatteryManager.BATTERY_STATUS_CHARGING is_full = query == BatteryManager.BATTERY_STATUS_FULL level = battery_status.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) scale = battery_status.getIntExtra(BatteryManager.EXTRA_SCALE, -1) percentage = (level / float(scale)) * 100 status['isCharging'] = is_charging or is_full status['percentage'] = percentage return status def instance(): ''' Instance for facade proxy. ''' return AndroidBattery() plyer-2.1.0/plyer/platforms/android/bluetooth.py000066400000000000000000000012341433372044000220060ustar00rootroot00000000000000''' Module of Android API for plyer.bluetooth. ''' from jnius import autoclass from plyer.platforms.android import activity from plyer.facades import Bluetooth Global = autoclass('android.provider.Settings$Global') class AndroidBluetooth(Bluetooth): ''' Implementation of Android Bluetooth API. ''' def _get_info(self): bluetooth_enabled = Global.getString( activity.getContentResolver(), Global.BLUETOOTH_ON ) status = 'off' if bluetooth_enabled: status = 'on' return status def instance(): ''' Instance for facade proxy. ''' return AndroidBluetooth() plyer-2.1.0/plyer/platforms/android/brightness.py000077500000000000000000000014461433372044000221610ustar00rootroot00000000000000''' Android Brightness ------------------ ''' from jnius import autoclass from plyer.facades import Brightness from android import mActivity System = autoclass('android.provider.Settings$System') class AndroidBrightness(Brightness): def _current_level(self): System.putInt( mActivity.getContentResolver(), System.SCREEN_BRIGHTNESS_MODE, System.SCREEN_BRIGHTNESS_MODE_MANUAL) cr_level = System.getInt( mActivity.getContentResolver(), System.SCREEN_BRIGHTNESS) return (cr_level / 255.) * 100 def _set_level(self, level): System.putInt( mActivity.getContentResolver(), System.SCREEN_BRIGHTNESS, (level / 100.) * 255) def instance(): return AndroidBrightness() plyer-2.1.0/plyer/platforms/android/call.py000066400000000000000000000011521433372044000207130ustar00rootroot00000000000000''' Android Call ----------- ''' from jnius import autoclass from plyer.facades import Call from plyer.platforms.android import activity Intent = autoclass('android.content.Intent') uri = autoclass('android.net.Uri') class AndroidCall(Call): def _makecall(self, **kwargs): intent = Intent(Intent.ACTION_CALL) tel = kwargs.get('tel') intent.setData(uri.parse("tel:{}".format(tel))) activity.startActivity(intent) def _dialcall(self, **kwargs): intent_ = Intent(Intent.ACTION_DIAL) activity.startActivity(intent_) def instance(): return AndroidCall() plyer-2.1.0/plyer/platforms/android/camera.py000066400000000000000000000041641433372044000212360ustar00rootroot00000000000000import android import android.activity from os import remove from jnius import autoclass, cast from plyer.facades import Camera from plyer.platforms.android import activity Intent = autoclass('android.content.Intent') PythonActivity = autoclass('org.kivy.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._remove(self.filename) def _remove(self, fn): try: remove(fn) except OSError: pass def instance(): return AndroidCamera() plyer-2.1.0/plyer/platforms/android/compass.py000066400000000000000000000065201433372044000214510ustar00rootroot00000000000000''' 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 MFUSensorListener(PythonJavaClass): __javainterfaces__ = ['android/hardware/SensorEventListener'] def __init__(self): super().__init__() service = activity.getSystemService(Context.SENSOR_SERVICE) self.SensorManager = cast('android.hardware.SensorManager', service) self.sensor = self.SensorManager.getDefaultSensor( Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED) self.values = [None, None, None, 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[:6] @java_method('(Landroid/hardware/Sensor;I)V') def onAccuracyChanged(self, sensor, accuracy): pass class MagneticFieldSensorListener(PythonJavaClass): __javainterfaces__ = ['android/hardware/SensorEventListener'] def __init__(self): super().__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().__init__() self.bState = False def _enable(self): if (not self.bState): self.listenerm = MagneticFieldSensorListener() self.listenermu = MFUSensorListener() self.listenerm.enable() self.listenermu.enable() self.bState = True def _disable(self): if (self.bState): self.bState = False self.listenerm.disable() self.listenermu.disable() del self.listenerm del self.listenermu def _get_orientation(self): if (self.bState): return tuple(self.listenerm.values) else: return (None, None, None) def _get_field_uncalib(self): if (self.bState): return tuple(self.listenermu.values) else: return (None, None, None, None, None, None) def __del__(self): if self.bState: self._disable() super().__del__() def instance(): return AndroidCompass() plyer-2.1.0/plyer/platforms/android/devicename.py000066400000000000000000000014471433372044000221070ustar00rootroot00000000000000''' Module of Android API for plyer.devicename. ''' from jnius import autoclass from plyer.facades import DeviceName Build = autoclass('android.os.Build') class AndroidDeviceName(DeviceName): ''' Implementation of Android devicename API. ''' def _get_device_name(self): """ Method to get the device name aka model in an android environment. Changed the implementation from 'android.provider.Settings.Global' to 'android.os.Build' because 'android.provider.Settings.Global' was introduced in API 17 whereas 'android.os.Build' is present since API 1 Thereby making this method more backward compatible. """ return Build.MODEL def instance(): ''' Instance for facade proxy. ''' return AndroidDeviceName() plyer-2.1.0/plyer/platforms/android/email.py000066400000000000000000000030241433372044000210670ustar00rootroot00000000000000''' Module of Android API for plyer.email. ''' 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): ''' Implementation of Android email API. ''' 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(): ''' Instance for facade proxy. ''' return AndroidEmail() plyer-2.1.0/plyer/platforms/android/filechooser.py000066400000000000000000000357021433372044000223120ustar00rootroot00000000000000''' Android file chooser -------------------- Android runs ``Activity`` asynchronously via pausing our ``PythonActivity`` and starting a new one in the foreground. This means ``AndroidFileChooser._open_file()`` will always return the default value of ``AndroidFileChooser.selection`` i.e. ``None``. After the ``Activity`` (for us it's the file chooser ``Intent``) is completed, Android moves it to the background (or destroys or whatever is implemented) and pushes ``PythonActivity`` to the foreground. We have a custom listener for ``android.app.Activity.onActivityResult()`` via `android` package from `python-for-android` recipe, ``AndroidFileChooser._on_activity_result()`` which is called independently of any our action (we may call anything from our application in Python and this handler will be called nevertheless on each ``android.app.Activity`` result in the system). In the handler we check if the ``request_code`` matches the code passed to the ``Context.startActivityForResult()`` i.e. if the result from ``android.app.Activity`` is indeed meant for our ``PythonActivity`` and then we proceed. Since the ``android.app.Activity.onActivityResult()`` is the only way for us to intercept the result and we have a handler bound via ``android`` package, we need to get the path/file/... selection to the user the same way. Threading + ``Thread.join()`` or ``time.sleep()`` or any other kind of waiting for the result is not an option because: 1) ``android.app.Activity.onActivityResult()`` might remain unexecuted if the launched file chooser activity does not return the result (``Activity`` dies/freezes/etc). 2) Thread will be still waiting for the result e.g. an update of a value or to actually finish, however the result from the call of ``AndroidFileChooser._open_file()`` will be returned nevertheless and anything using that result will use an incorrect one i.e. the default value of ``AndroidFilechooser.selection`` (``None``). .. versionadded:: 1.4.0 ''' from os.path import join, basename from random import randint from android import activity, mActivity from jnius import autoclass, cast, JavaException from plyer.facades import FileChooser from plyer import storagepath Environment = autoclass("android.os.Environment") String = autoclass('java.lang.String') Intent = autoclass('android.content.Intent') Activity = autoclass('android.app.Activity') DocumentsContract = autoclass('android.provider.DocumentsContract') ContentUris = autoclass('android.content.ContentUris') Uri = autoclass('android.net.Uri') Long = autoclass('java.lang.Long') IMedia = autoclass('android.provider.MediaStore$Images$Media') VMedia = autoclass('android.provider.MediaStore$Video$Media') AMedia = autoclass('android.provider.MediaStore$Audio$Media') class AndroidFileChooser(FileChooser): ''' FileChooser implementation for Android using the built-in file browser via Intent. .. versionadded:: 1.4.0 ''' # filechooser activity <-> result pair identification select_code = None # default selection value selection = None # select multiple files multiple = False # mime types mime_type = { "doc": "application/msword", "docx": "application/vnd.openxmlformats-officedocument." + "wordprocessingml.document", "ppt": "application/vnd.ms-powerpoint", "pptx": "application/vnd.openxmlformats-officedocument." + "presentationml.presentation", "xls": "application/vnd.ms-excel", "xlsx": "application/vnd.openxmlformats-officedocument." + "spreadsheetml.sheet", "text": "text/*", "pdf": "application/pdf", "zip": "application/zip", "image": "image/*", "video": "video/*", "audio": "audio/*", "application": "application/*"} selected_mime_type = None def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.select_code = randint(123456, 654321) self.selection = None # bind a function for a response from filechooser activity activity.bind(on_activity_result=self._on_activity_result) @staticmethod def _handle_selection(selection): ''' Dummy placeholder for returning selection from ``android.app.Activity.onActivityResult()``. .. versionadded:: 1.4.0 ''' return selection def _open_file(self, **kwargs): ''' Running Android Activity is non-blocking and the only call that blocks is onActivityResult running in GUI thread .. versionadded:: 1.4.0 ''' # set up selection handler # startActivityForResult is async # onActivityResult is sync self._handle_selection = kwargs.pop( 'on_selection', self._handle_selection ) self.selected_mime_type = \ kwargs.pop("filters")[0] if "filters" in kwargs else "" # create Intent for opening file_intent = Intent(Intent.ACTION_GET_CONTENT) if not self.selected_mime_type or \ type(self.selected_mime_type) != str or \ self.selected_mime_type not in self.mime_type: file_intent.setType("*/*") else: file_intent.setType(self.mime_type[self.selected_mime_type]) file_intent.addCategory( Intent.CATEGORY_OPENABLE ) # use putExtra to allow multiple file selection if kwargs.get('multiple', self.multiple): file_intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, True) # start a new activity from PythonActivity # which creates a filechooser via intent mActivity.startActivityForResult( Intent.createChooser(file_intent, cast( 'java.lang.CharSequence', String("FileChooser") )), self.select_code ) def _on_activity_result(self, request_code, result_code, data): ''' Listener for ``android.app.Activity.onActivityResult()`` assigned via ``android.activity.bind()``. .. versionadded:: 1.4.0 ''' # not our response if request_code != self.select_code: return if result_code != Activity.RESULT_OK: # The action had been cancelled. return selection = [] # Process multiple URI if multiple files selected try: for count in range(data.getClipData().getItemCount()): ele = self._resolve_uri( data.getClipData().getItemAt(count).getUri()) or [] selection.append(ele) except Exception: selection = [self._resolve_uri(data.getData()), ] # return value to object self.selection = selection # return value via callback self._handle_selection(selection) @staticmethod def _handle_external_documents(uri): ''' Selection from the system filechooser when using ``Phone`` or ``Internal storage`` or ``SD card`` option from menu. .. versionadded:: 1.4.0 ''' file_id = DocumentsContract.getDocumentId(uri) file_type, file_name = file_id.split(':') # internal SD card mostly mounted as a files storage in phone internal = storagepath.get_external_storage_dir() # external (removable) SD card i.e. microSD external = storagepath.get_sdcard_dir() try: external_base = basename(external) except TypeError: external_base = basename(internal) # resolve sdcard path sd_card = internal # because external might have /storage/.../1 or other suffix # and file_type might be only a part of the real folder in /storage if file_type in external_base or external_base in file_type: sd_card = external elif file_type == "home": sd_card = join(Environment.getExternalStorageDirectory( ).getAbsolutePath(), Environment.DIRECTORY_DOCUMENTS) return join(sd_card, file_name) @staticmethod def _handle_media_documents(uri): ''' Selection from the system filechooser when using ``Images`` or ``Videos`` or ``Audio`` option from menu. .. versionadded:: 1.4.0 ''' file_id = DocumentsContract.getDocumentId(uri) file_type, file_name = file_id.split(':') selection = '_id=?' if file_type == 'image': uri = IMedia.EXTERNAL_CONTENT_URI elif file_type == 'video': uri = VMedia.EXTERNAL_CONTENT_URI elif file_type == 'audio': uri = AMedia.EXTERNAL_CONTENT_URI return file_name, selection, uri @staticmethod def _handle_downloads_documents(uri): ''' Selection from the system filechooser when using ``Downloads`` option from menu. Might not work all the time due to: 1) invalid URI: jnius.jnius.JavaException: JVM exception occurred: Unknown URI: content://downloads/public_downloads/1034 2) missing URI / android permissions jnius.jnius.JavaException: JVM exception occurred: Permission Denial: reading com.android.providers.downloads.DownloadProvider uri content://downloads/all_downloads/1034 from pid=2532, uid=10455 requires android.permission.ACCESS_ALL_DOWNLOADS, or grantUriPermission() Workaround: Selecting path from ``Phone`` -> ``Download`` -> ```` (or ``Internal storage``) manually. .. versionadded:: 1.4.0 ''' # known locations, differ between machines downloads = [ 'content://downloads/public_downloads', 'content://downloads/my_downloads', # all_downloads requires separate permission # android.permission.ACCESS_ALL_DOWNLOADS 'content://downloads/all_downloads' ] file_id = DocumentsContract.getDocumentId(uri) try_uris = [ ContentUris.withAppendedId( Uri.parse(down), Long.valueOf(file_id) ) for down in downloads ] # try all known Download folder uris # and handle JavaExceptions due to different locations # for content:// downloads or missing permission path = None for down in try_uris: try: path = AndroidFileChooser._parse_content( uri=down, projection=['_data'], selection=None, selection_args=None, sort_order=None ) except JavaException: import traceback traceback.print_exc() # we got a path, ignore the rest if path: break # alternative approach to Downloads by joining # all data items from Activity result if not path: for down in try_uris: try: path = AndroidFileChooser._parse_content( uri=down, projection=None, selection=None, selection_args=None, sort_order=None, index_all=True ) except JavaException: import traceback traceback.print_exc() # we got a path, ignore the rest if path: break return path def _resolve_uri(self, uri): ''' Resolve URI input from ``android.app.Activity.onActivityResult()``. .. versionadded:: 1.4.0 ''' uri_authority = uri.getAuthority() uri_scheme = uri.getScheme().lower() path = None file_name = None selection = None downloads = None # This does not allow file selected from google photos or gallery # or even any other file explorer to work # not a document URI, nothing to convert from # if not DocumentsContract.isDocumentUri(mActivity, uri): # return path if uri_authority == 'com.android.externalstorage.documents': return self._handle_external_documents(uri) # in case a user selects a file from 'Downloads' section # note: this won't be triggered if a user selects a path directly # e.g.: Phone -> Download -> elif uri_authority == 'com.android.providers.downloads.documents': path = downloads = self._handle_downloads_documents(uri) elif uri_authority == 'com.android.providers.media.documents': file_name, selection, uri = self._handle_media_documents(uri) # parse content:// scheme to path if uri_scheme == 'content' and not downloads: try: path = self._parse_content( uri=uri, projection=['_data'], selection=selection, selection_args=file_name, sort_order=None ) except JavaException: # handles array error for selection_args path = self._parse_content( uri=uri, projection=['_data'], selection=selection, selection_args=[file_name], sort_order=None ) # nothing to parse, file:// will return a proper path elif uri_scheme == 'file': path = uri.getPath() return path @staticmethod def _parse_content( uri, projection, selection, selection_args, sort_order, index_all=False ): ''' Parser for ``content://`` URI returned by some Android resources. .. versionadded:: 1.4.0 ''' result = None resolver = mActivity.getContentResolver() read = Intent.FLAG_GRANT_READ_URI_PERMISSION write = Intent.FLAG_GRANT_READ_URI_PERMISSION persist = Intent.FLAG_GRANT_READ_URI_PERMISSION # grant permission for our activity mActivity.grantUriPermission( mActivity.getPackageName(), uri, read | write | persist ) if not index_all: cursor = resolver.query( uri, projection, selection, selection_args, sort_order ) idx = cursor.getColumnIndex(projection[0]) if idx != -1 and cursor.moveToFirst(): result = cursor.getString(idx) else: result = [] cursor = resolver.query( uri, projection, selection, selection_args, sort_order ) while cursor.moveToNext(): for idx in range(cursor.getColumnCount()): result.append(cursor.getString(idx)) result = '/'.join(result) return result def _file_selection_dialog(self, **kwargs): mode = kwargs.pop('mode', None) if mode == 'open': self._open_file(**kwargs) def instance(): return AndroidFileChooser() plyer-2.1.0/plyer/platforms/android/flash.py000066400000000000000000000027471433372044000211100ustar00rootroot00000000000000# coding=utf-8 """ Flash ----- """ from plyer.facades import Flash from jnius import autoclass from plyer.platforms.android import activity Camera = autoclass("android.hardware.Camera") CameraParameters = autoclass("android.hardware.Camera$Parameters") SurfaceTexture = autoclass("android.graphics.SurfaceTexture") PackageManager = autoclass('android.content.pm.PackageManager') pm = activity.getPackageManager() flash_available = pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH) class AndroidFlash(Flash): _camera = None def _on(self): if self._camera is None: self._camera_open() if not self._camera: return self._camera.setParameters(self._f_on) def _off(self): if not self._camera: return self._camera.setParameters(self._f_off) def _release(self): if not self._camera: return self._camera.stopPreview() self._camera.release() self._camera = None def _camera_open(self): if not flash_available: return self._camera = Camera.open() self._f_on = Camera.getParameters() self._f_off = Camera.getParameters() self._f_on.setFlashMode(CameraParameters.FLASH_MODE_TORCH) self._f_off.setFlashMode(CameraParameters.FLASH_MODE_OFF) self._camera.startPreview() # Need this for Nexus 5 self._camera.setPreviewTexture(SurfaceTexture(0)) def instance(): return AndroidFlash() plyer-2.1.0/plyer/platforms/android/gps.py000066400000000000000000000052011433372044000205700ustar00rootroot00000000000000''' 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().__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(), accuracy=location.getAccuracy()) @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, **kwargs): min_time = kwargs.get('minTime') min_distance = kwargs.get('minDistance') providers = self._location_manager.getProviders(False).toArray() for provider in providers: self._location_manager.requestLocationUpdates( provider, min_time, # minTime, in milliseconds min_distance, # minDistance, in meters self._location_listener, Looper.getMainLooper()) def _stop(self): self._location_manager.removeUpdates(self._location_listener) def instance(): return AndroidGPS() plyer-2.1.0/plyer/platforms/android/gravity.py000066400000000000000000000040361433372044000214710ustar00rootroot00000000000000''' Android gravity --------------------- ''' from jnius import autoclass from jnius import cast from jnius import java_method from jnius import PythonJavaClass from plyer.facades import Gravity from plyer.platforms.android import activity Context = autoclass('android.content.Context') Sensor = autoclass('android.hardware.Sensor') SensorManager = autoclass('android.hardware.SensorManager') class GravitySensorListener(PythonJavaClass): __javainterfaces__ = ['android/hardware/SensorEventListener'] def __init__(self): super().__init__() service = activity.getSystemService(Context.SENSOR_SERVICE) self.SensorManager = cast('android.hardware.SensorManager', service) self.sensor = self.SensorManager.getDefaultSensor( Sensor.TYPE_GRAVITY ) 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): pass class AndroidGravity(Gravity): def __init__(self): super().__init__() self.state = False def _enable(self): if not self.state: self.listener = GravitySensorListener() self.listener.enable() self.state = True def _disable(self): if self.state: self.state = False self.listener.disable() del self.listener def _get_gravity(self): if self.state: return tuple(self.listener.values) else: return (None, None, None) def __del__(self): if self.state: self._disable() super().__del__() def instance(): return AndroidGravity() plyer-2.1.0/plyer/platforms/android/gyroscope.py000066400000000000000000000065411433372044000220210ustar00rootroot00000000000000''' 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().__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 GyroUncalibratedSensorListener(PythonJavaClass): __javainterfaces__ = ['android/hardware/SensorEventListener'] def __init__(self): super().__init__() service = activity.getSystemService(Context.SENSOR_SERVICE) self.SensorManager = cast('android.hardware.SensorManager', service) self.sensor = self.SensorManager.getDefaultSensor( Sensor.TYPE_GYROSCOPE_UNCALIBRATED) self.values = [None, None, None, 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[:6] @java_method('(Landroid/hardware/Sensor;I)V') def onAccuracyChanged(self, sensor, accuracy): pass class AndroidGyroscope(Gyroscope): def __init__(self): super().__init__() self.bState = False def _enable(self): if (not self.bState): self.listenerg = GyroscopeSensorListener() self.listenergu = GyroUncalibratedSensorListener() self.listenerg.enable() self.listenergu.enable() self.bState = True def _disable(self): if (self.bState): self.bState = False self.listenerg.disable() self.listenergu.disable() del self.listenerg del self.listenergu def _get_orientation(self): if (self.bState): return tuple(self.listenerg.values) else: return (None, None, None) def _get_rotation_uncalib(self): if (self.bState): return tuple(self.listenergu.values) else: return (None, None, None, None, None, None) def __del__(self): if self.bState: self._disable() super().__del__() def instance(): return AndroidGyroscope() plyer-2.1.0/plyer/platforms/android/humidity.py000066400000000000000000000064411433372044000216420ustar00rootroot00000000000000from jnius import autoclass from jnius import cast from jnius import java_method from jnius import PythonJavaClass from math import exp from plyer.facades import Humidity from plyer.platforms.android import activity ActivityInfo = autoclass('android.content.pm.ActivityInfo') Context = autoclass('android.content.Context') Sensor = autoclass('android.hardware.Sensor') SensorManager = autoclass('android.hardware.SensorManager') class RelativeHumiditySensorListener(PythonJavaClass): __javainterfaces__ = ['android/hardware/SensorEventListener'] def __init__(self): super().__init__() service = activity.getSystemService(Context.SENSOR_SERVICE) self.SensorManager = cast('android.hardware.SensorManager', service) self.sensor = self.SensorManager.getDefaultSensor( Sensor.TYPE_RELATIVE_HUMIDITY) self.value = 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.value = event.values[0] @java_method('(Landroid/hardware/Sensor;I)V') def onAccuracyChanged(self, sensor, accuracy): pass class AmbientTemperatureSensorListener(PythonJavaClass): __javainterfaces__ = ['android/hardware/SensorEventListener'] def __init__(self): super().__init__() service = activity.getSystemService(Context.SENSOR_SERVICE) self.SensorManager = cast('android.hardware.SensorManager', service) self.sensor = self.SensorManager.getDefaultSensor( Sensor.TYPE_AMBIENT_TEMPERATURE) self.value = 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.value = event.values[0] @java_method('(Landroid/hardware/Sensor;I)V') def onAccuracyChanged(self, sensor, accuracy): pass class AndroidHumidity(Humidity): def __init__(self): self.state = False def _get_humidity(self): if self.state: m = 17.62 Tn = 243.12 Ta = 216.7 Rh = self.listener_r.value Tc = self.listener_a.value A = 6.112 K = 273.15 humidity = (Ta * (Rh / 100) * A * exp(m * Tc / (Tn + Tc)) / (K + Tc)) return humidity def _enable(self): if not self.state: self.listener_r = RelativeHumiditySensorListener() self.listener_a = AmbientTemperatureSensorListener() self.listener_r.enable() self.listener_a.enable() self.state = True def _disable(self): if self.state: self.listener_r.disable() self.listener_a.disable() self.state = False delattr(self, 'listener_r') delattr(self, 'listener_a') def instance(): return AndroidHumidity() plyer-2.1.0/plyer/platforms/android/irblaster.py000066400000000000000000000031761433372044000217770ustar00rootroot00000000000000from 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() plyer-2.1.0/plyer/platforms/android/keystore.py000066400000000000000000000014031433372044000216440ustar00rootroot00000000000000from plyer.facades import Keystore from plyer.platforms.android import activity class AndroidKeystore(Keystore): def _set_key(self, servicename, key, value, **kwargs): mode = kwargs.get("mode", 0) settings = activity.getSharedPreferences(servicename, mode) editor = settings.edit() editor.putString(key, value) editor.commit() def _get_key(self, servicename, key, **kwargs): mode = kwargs.get("mode", 0) default = kwargs.get("default", "__None") settings = activity.getSharedPreferences(servicename, mode) ret = settings.getString(key, default) if ret == "__None": ret = None return ret def instance(): return AndroidKeystore() plyer-2.1.0/plyer/platforms/android/light.py000066400000000000000000000033331433372044000211120ustar00rootroot00000000000000from jnius import autoclass from jnius import cast from jnius import java_method from jnius import PythonJavaClass from plyer.facades import Light from plyer.platforms.android import activity Context = autoclass('android.content.Context') Sensor = autoclass('android.hardware.Sensor') SensorManager = autoclass('android.hardware.SensorManager') class LightSensorListener(PythonJavaClass): __javainterfaces__ = ['android/hardware/SensorEventListener'] def __init__(self): super().__init__() service = activity.getSystemService(Context.SENSOR_SERVICE) self.SensorManager = cast('android.hardware.SensorManager', service) self.sensor = self.SensorManager.getDefaultSensor(Sensor.TYPE_LIGHT) self.value = 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.value = event.values[0] @java_method('(Landroid/hardware/Sensor;I)V') def onAccuracyChanged(self, sensor, accuracy): pass class AndroidLight(Light): listener = None def _get_illumination(self): if self.listener and self.listener.value: light = self.listener.value return light def _enable(self): if not self.listener: self.listener = LightSensorListener() self.listener.enable() def _disable(self): if self.listener: self.listener.disable() delattr(self, 'listener') def instance(): return AndroidLight() plyer-2.1.0/plyer/platforms/android/notification.py000066400000000000000000000142151433372044000224720ustar00rootroot00000000000000''' Module of Android API for plyer.notification. .. versionadded:: 1.0.0 .. versionchanged:: 1.4.0 Fixed notifications not displaying due to missing NotificationChannel required by Android Oreo 8.0+ (API 26+). .. versionchanged:: 1.4.0 Added simple toaster notification. .. versionchanged:: 1.4.0 Fixed notifications not displaying big icons properly. Added option for custom big icon via `icon`. ''' from android import python_act from android.runnable import run_on_ui_thread from jnius import autoclass, cast 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') NotificationManager = autoclass('android.app.NotificationManager') PendingIntent = autoclass('android.app.PendingIntent') Intent = autoclass('android.content.Intent') Toast = autoclass('android.widget.Toast') BitmapFactory = autoclass('android.graphics.BitmapFactory') class AndroidNotification(Notification): ''' Implementation of Android notification API. .. versionadded:: 1.0.0 ''' def __init__(self): package_name = activity.getPackageName() self._ns = None self._channel_id = package_name pm = activity.getPackageManager() info = pm.getActivityInfo(activity.getComponentName(), 0) if info.icon == 0: # Take the application icon instead. info = pm.getApplicationInfo(package_name, 0) self._app_icon = info.icon def _get_notification_service(self): if not self._ns: self._ns = cast(NotificationManager, activity.getSystemService( Context.NOTIFICATION_SERVICE )) return self._ns def _build_notification_channel(self, name): ''' Create a NotificationChannel using channel id of the application package name (com.xyz, org.xyz, ...) and channel name same as the provided notification title if the API is high enough, otherwise do nothing. .. versionadded:: 1.4.0 ''' if SDK_INT < 26: return channel = autoclass('android.app.NotificationChannel') app_channel = channel( self._channel_id, name, NotificationManager.IMPORTANCE_DEFAULT ) self._get_notification_service().createNotificationChannel( app_channel ) return app_channel @run_on_ui_thread def _toast(self, message): ''' Display a popup-like small notification at the bottom of the screen. .. versionadded:: 1.4.0 ''' Toast.makeText( activity, cast('java.lang.CharSequence', AndroidString(message)), Toast.LENGTH_LONG ).show() def _set_icons(self, notification, icon=None): ''' Set the small application icon displayed at the top panel together with WiFi, battery percentage and time and the big optional icon (preferably PNG format with transparent parts) displayed directly in the notification body. .. versionadded:: 1.4.0 ''' app_icon = self._app_icon notification.setSmallIcon(app_icon) bitmap_icon = app_icon if icon is not None: bitmap_icon = BitmapFactory.decodeFile(icon) notification.setLargeIcon(bitmap_icon) elif icon == '': # we don't want the big icon set, # only the small one in the top panel pass else: bitmap_icon = BitmapFactory.decodeResource( python_act.getResources(), app_icon ) notification.setLargeIcon(bitmap_icon) def _build_notification(self, title): ''' .. versionadded:: 1.4.0 ''' if SDK_INT < 26: noti = NotificationBuilder(activity) else: self._channel = self._build_notification_channel(title) noti = NotificationBuilder(activity, self._channel_id) return noti @staticmethod def _set_open_behavior(notification): ''' Open the source application when user opens the notification. .. versionadded:: 1.4.0 ''' # create Intent that navigates back to the application app_context = activity.getApplication().getApplicationContext() notification_intent = Intent(app_context, python_act) # set flags to run our application Activity notification_intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) notification_intent.setAction(Intent.ACTION_MAIN) notification_intent.addCategory(Intent.CATEGORY_LAUNCHER) # get our application Activity pending_intent = PendingIntent.getActivity( app_context, 0, notification_intent, 0 ) notification.setContentIntent(pending_intent) notification.setAutoCancel(True) def _open_notification(self, notification): if SDK_INT >= 16: notification = notification.build() else: notification = notification.getNotification() self._get_notification_service().notify(0, notification) def _notify(self, **kwargs): noti = None message = kwargs.get('message').encode('utf-8') ticker = kwargs.get('ticker').encode('utf-8') title = AndroidString( kwargs.get('title', '').encode('utf-8') ) icon = kwargs.get('app_icon') # decide whether toast only or proper notification if kwargs.get('toast'): self._toast(message) return else: noti = self._build_notification(title) # set basic properties for notification noti.setContentTitle(title) noti.setContentText(AndroidString(message)) noti.setTicker(AndroidString(ticker)) # set additional flags for notification self._set_icons(noti, icon=icon) self._set_open_behavior(noti) # launch self._open_notification(noti) def instance(): ''' Instance for facade proxy. ''' return AndroidNotification() plyer-2.1.0/plyer/platforms/android/orientation.py000066400000000000000000000026111433372044000223340ustar00rootroot00000000000000from jnius import autoclass 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() plyer-2.1.0/plyer/platforms/android/proximity.py000066400000000000000000000037551433372044000220570ustar00rootroot00000000000000from jnius import autoclass from jnius import cast from jnius import java_method from jnius import PythonJavaClass from plyer.platforms.android import activity from plyer.facades import Proximity ActivityInfo = autoclass('android.content.pm.ActivityInfo') Context = autoclass('android.content.Context') Sensor = autoclass('android.hardware.Sensor') SensorManager = autoclass('android.hardware.SensorManager') class ProximitySensorListener(PythonJavaClass): __javainterfaces__ = ['android/hardware/SensorEventListener'] def __init__(self): super().__init__() service = activity.getSystemService(Context.SENSOR_SERVICE) self.SensorManager = cast('android.hardware.SensorManager', service) self.sensor = self.SensorManager.getDefaultSensor( Sensor.TYPE_PROXIMITY) self.value = 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.value = event.values[0] @java_method('(Landroid/hardware/Sensor;I)V') def onAccuracyChanged(self, sensor, accuracy): pass class AndroidProximity(Proximity): listener = None def _enable(self, **kwargs): if not self.listener: self.listener = ProximitySensorListener() self.listener.enable() def _disable(self, **kwargs): if self.listener: self.listener.disable() delattr(self, 'listener') def _get_proximity(self): if self.listener: value = self.listener.value # value is 0.0 when proxime sensor is covered. In other case # value is 5.0 because in smartphone, optical proximity sensors # are used. return value < 5.0 def instance(): return AndroidProximity() plyer-2.1.0/plyer/platforms/android/sms.py000066400000000000000000000007101433372044000206010ustar00rootroot00000000000000''' 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() plyer-2.1.0/plyer/platforms/android/spatialorientation.py000066400000000000000000000071551433372044000237220ustar00rootroot00000000000000from jnius import autoclass from jnius import cast from jnius import java_method from jnius import PythonJavaClass from plyer.platforms.android import activity from plyer.facades import SpatialOrientation 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().__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): pass class MagnetometerSensorListener(PythonJavaClass): __javainterfaces__ = ['android/hardware/SensorEventListener'] def __init__(self): super().__init__() service = activity.getSystemService(Context.SENSOR_SERVICE) self.SensorManager = cast('android.hardware.SensorManager', 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): pass class AndroidSpOrientation(SpatialOrientation): def __init__(self): self.state = False def _get_orientation(self): if self.state: rotation = [0] * 9 inclination = [0] * 9 gravity = [] geomagnetic = [] gravity = self.listener_a.values geomagnetic = self.listener_m.values if gravity[0] is not None and geomagnetic[0] is not None: ff_state = SensorManager.getRotationMatrix( rotation, inclination, gravity, geomagnetic ) if ff_state: values = [0, 0, 0] values = SensorManager.getOrientation( rotation, values ) return values def _enable_listener(self, **kwargs): if not self.state: self.listener_a = AccelerometerSensorListener() self.listener_m = MagnetometerSensorListener() self.listener_a.enable() self.listener_m.enable() self.state = True def _disable_listener(self, **kwargs): if self.state: self.listener_a.disable() self.listener_m.disable() self.state = False delattr(self, 'listener_a') delattr(self, 'listener_m') def instance(): return AndroidSpOrientation() plyer-2.1.0/plyer/platforms/android/storagepath.py000077500000000000000000000040471433372044000223320ustar00rootroot00000000000000''' Android Storage Path -------------------- ''' from os import listdir, access, R_OK from os.path import join from plyer.facades import StoragePath from jnius import autoclass from android import mActivity Environment = autoclass('android.os.Environment') Context = autoclass('android.content.Context') class AndroidStoragePath(StoragePath): def _get_home_dir(self): return Environment.getDataDirectory().getAbsolutePath() def _get_external_storage_dir(self): return Environment.getExternalStorageDirectory().getAbsolutePath() def _get_sdcard_dir(self): ''' .. versionadded:: 1.4.0 ''' # folder in /storage/ that is readable # and is not internal SD card path = None for folder in listdir('/storage'): folder = join('/storage', folder) if folder in self._get_external_storage_dir(): continue if not access(folder, R_OK): continue path = folder break return path def _get_root_dir(self): return Environment.getRootDirectory().getAbsolutePath() def _get_documents_dir(self): return Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_DOCUMENTS).getAbsolutePath() def _get_downloads_dir(self): return Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_DOWNLOADS).getAbsolutePath() def _get_videos_dir(self): return Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_MOVIES).getAbsolutePath() def _get_music_dir(self): return Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_MUSIC).getAbsolutePath() def _get_pictures_dir(self): return Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_PICTURES).getAbsolutePath() def _get_application_dir(self): return mActivity.getFilesDir().getParentFile().getParent() def instance(): return AndroidStoragePath() plyer-2.1.0/plyer/platforms/android/stt.py000066400000000000000000000164401433372044000206200ustar00rootroot00000000000000from android.runnable import run_on_ui_thread from jnius import autoclass from jnius import java_method from jnius import PythonJavaClass from plyer.facades import STT from plyer.platforms.android import activity ArrayList = autoclass('java.util.ArrayList') Bundle = autoclass('android.os.Bundle') Context = autoclass('android.content.Context') Intent = autoclass('android.content.Intent') RecognizerIntent = autoclass('android.speech.RecognizerIntent') RecognitionListener = autoclass('android.speech.RecognitionListener') SpeechRecognizer = autoclass('android.speech.SpeechRecognizer') SpeechResults = SpeechRecognizer.RESULTS_RECOGNITION class SpeechListener(PythonJavaClass): __javainterfaces__ = ['android/speech/RecognitionListener'] # class variables because PythonJavaClass class failed # to see them later in getters and setters _error_callback = None _result_callback = None _partial_result_callback = None _volume_callback = None def __init__(self): super().__init__() # overwrite class variables in the object self._error_callback = None self._result_callback = None self._partial_result_callback = None self._volume_callback = None # error handling @property def error_callback(self): return self._error_callback @error_callback.setter def error_callback(self, callback): ''' Set error callback. It is called when error occurs. :param callback: function with one parameter for error message ''' self._error_callback = callback # result handling @property def result_callback(self): return self._result_callback @result_callback.setter def result_callback(self, callback): ''' Set result callback. It is called when results are received. :param callback: function with one parameter for lists of strings ''' self._result_callback = callback @property def partial_result_callback(self): return self._partial_result_callback @partial_result_callback.setter def partial_result_callback(self, callback): ''' Set partial result callback. It is called when partial results are received while the listener is still in listening mode. :param callback: function with one parameter for lists of strings ''' self._partial_result_callback = callback # voice changes handling @property def volume_callback(self): return self._volume_callback @volume_callback.setter def volume_callback(self, callback): ''' Set volume voice callback. It is called when loudness of the voice changes. :param callback: function with one parameter for volume RMS dB (float). ''' self._volume_callback = callback # Implementation Java Interfaces @java_method('()V') def onBeginningOfSpeech(self): pass @java_method('([B)V') def onBufferReceived(self, buffer): pass @java_method('()V') def onEndOfSpeech(self): pass @java_method('(I)V') def onError(self, error): msg = '' if error == SpeechRecognizer.ERROR_AUDIO: msg = 'audio' if error == SpeechRecognizer.ERROR_CLIENT: msg = 'client' if error == SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS: msg = 'insufficient_permissions' if error == SpeechRecognizer.ERROR_NETWORK: msg = 'network' if error == SpeechRecognizer.ERROR_NETWORK_TIMEOUT: msg = 'network_timeout' if error == SpeechRecognizer.ERROR_NO_MATCH: msg = 'no_match' if error == SpeechRecognizer.ERROR_RECOGNIZER_BUSY: msg = 'recognizer_busy' if error == SpeechRecognizer.ERROR_SERVER: msg = 'server' if error == SpeechRecognizer.ERROR_SPEECH_TIMEOUT: msg = 'speech_timeout' if msg and self.error_callback: self.error_callback('error:' + msg) @java_method('(ILandroid/os/Bundle;)V') def onEvent(self, event_type, params): pass @java_method('(Landroid/os/Bundle;)V') def onPartialResults(self, results): texts = [] matches = results.getStringArrayList(SpeechResults) for match in matches.toArray(): if isinstance(match, bytes): match = match.decode('utf-8') texts.append(match) if texts and self.partial_result_callback: self.partial_result_callback(texts) @java_method('(Landroid/os/Bundle;)V') def onReadyForSpeech(self, params): pass @java_method('(Landroid/os/Bundle;)V') def onResults(self, results): texts = [] matches = results.getStringArrayList(SpeechResults) for match in matches.toArray(): if isinstance(match, bytes): match = match.decode('utf-8') texts.append(match) if texts and self.result_callback: self.result_callback(texts) @java_method('(F)V') def onRmsChanged(self, rmsdB): if self.volume_callback: self.volume_callback(rmsdB) class AndroidSpeech(STT): ''' Android Speech Implementation. Android class `SpeechRecognizer`'s listening deactivates automatically. Class methods `_on_error()`, `_on_result()` listeners. You can find documentation here: https://developer.android.com/reference/android/speech/RecognitionListener ''' def _on_error(self, msg): self.errors.append(msg) self.stop() def _on_result(self, messages): self.results.extend(messages) self.stop() def _on_partial(self, messages): self.partial_results.extend(messages) @run_on_ui_thread def _start(self): intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH) intent.putExtra( RecognizerIntent.EXTRA_CALLING_PACKAGE, activity.getPackageName() ) # language preferences intent.putExtra( RecognizerIntent.EXTRA_LANGUAGE_PREFERENCE, self.language ) intent.putExtra( RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH ) # results settings intent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 1000) intent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, True) if self.prefer_offline: intent.putExtra(RecognizerIntent.EXTRA_PREFER_OFFLINE, True) # listener and callbacks listener = SpeechListener() listener.error_callback = self._on_error listener.result_callback = self._on_result listener.partial_result_callback = self._on_partial # create recognizer and start self.speech = SpeechRecognizer.createSpeechRecognizer(activity) self.speech.setRecognitionListener(listener) self.speech.startListening(intent) @run_on_ui_thread def _stop(self): if not self.speech: return # stop listening self.speech.stopListening() # free object self.speech.destroy() self.speech = None def _exist(self): return bool( SpeechRecognizer.isRecognitionAvailable(activity) ) def instance(): return AndroidSpeech() plyer-2.1.0/plyer/platforms/android/temperature.py000066400000000000000000000035421433372044000223420ustar00rootroot00000000000000from jnius import autoclass from jnius import cast from jnius import java_method from jnius import PythonJavaClass from plyer.facades import Temperature from plyer.platforms.android import activity ActivityInfo = autoclass('android.content.pm.ActivityInfo') Context = autoclass('android.content.Context') Sensor = autoclass('android.hardware.Sensor') SensorManager = autoclass('android.hardware.SensorManager') class TemperatureSensorListener(PythonJavaClass): __javainterfaces__ = ['android/hardware/SensorEventListener'] def __init__(self): super().__init__() service = activity.getSystemService(Context.SENSOR_SERVICE) self.SensorManager = cast('android.hardware.SensorManager', service) self.sensor = self.SensorManager.getDefaultSensor( Sensor.TYPE_AMBIENT_TEMPERATURE) self.value = 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.value = event.values[0] @java_method('(Landroid/hardware/Sensor;I)V') def onAccuracyChanged(self, sensor, accuracy): pass class AndroidTemperature(Temperature): listener = None def _get_temperature(self): if self.listener and self.listener.value: temperature = self.listener.value return temperature def _enable(self): if not self.listener: self.listener = TemperatureSensorListener() self.listener.enable() def _disable(self): if self.listener: self.listener.disable() delattr(self, 'listener') def instance(): return AndroidTemperature() plyer-2.1.0/plyer/platforms/android/tts.py000066400000000000000000000016521433372044000206170ustar00rootroot00000000000000from 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) retries = 0 # First try rarely succeeds due to some timing issue message = kwargs.get('message') # first try for while loop speak_status = tts.speak( message, TextToSpeech.QUEUE_FLUSH, None ) # -1 indicates error. Let's wait and then try again while retries < 100 and speak_status == -1: sleep(0.1) retries += 1 speak_status = tts.speak( message, TextToSpeech.QUEUE_FLUSH, None ) def instance(): return AndroidTextToSpeech() plyer-2.1.0/plyer/platforms/android/uniqueid.py000066400000000000000000000010411433372044000216200ustar00rootroot00000000000000''' Module of Android API for plyer.uniqueid. ''' from jnius import autoclass from plyer.platforms.android import activity from plyer.facades import UniqueID Secure = autoclass('android.provider.Settings$Secure') class AndroidUniqueID(UniqueID): ''' Implementation of Android uniqueid API. ''' def _get_uid(self): return Secure.getString( activity.getContentResolver(), Secure.ANDROID_ID ) def instance(): ''' Instance for facade proxy. ''' return AndroidUniqueID() plyer-2.1.0/plyer/platforms/android/vibrator.py000066400000000000000000000034641433372044000216400ustar00rootroot00000000000000"""Implementation Vibrator for Android.""" from jnius import autoclass, cast from plyer.facades import Vibrator from plyer.platforms.android import activity from plyer.platforms.android import SDK_INT Context = autoclass("android.content.Context") vibrator_service = activity.getSystemService(Context.VIBRATOR_SERVICE) vibrator = cast("android.os.Vibrator", vibrator_service) if SDK_INT >= 26: VibrationEffect = autoclass("android.os.VibrationEffect") 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: if SDK_INT >= 26: vibrator.vibrate( VibrationEffect.createOneShot( int(1000 * time), VibrationEffect.DEFAULT_AMPLITUDE ) ) else: vibrator.vibrate(int(1000 * time)) def _pattern(self, pattern=None, repeat=None, **kwargs): pattern = [int(1000 * time) for time in pattern] if vibrator: if SDK_INT >= 26: vibrator.vibrate( VibrationEffect.createWaveform(pattern, repeat) ) else: vibrator.vibrate(pattern, repeat) def _exists(self, **kwargs): if SDK_INT >= 11: return vibrator.hasVibrator() elif 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() plyer-2.1.0/plyer/platforms/ios/000077500000000000000000000000001433372044000166015ustar00rootroot00000000000000plyer-2.1.0/plyer/platforms/ios/__init__.py000066400000000000000000000000001433372044000207000ustar00rootroot00000000000000plyer-2.1.0/plyer/platforms/ios/accelerometer.py000066400000000000000000000013751433372044000217730ustar00rootroot00000000000000''' 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().__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() plyer-2.1.0/plyer/platforms/ios/barometer.py000066400000000000000000000010561433372044000211350ustar00rootroot00000000000000''' iOS Barometer ------------- ''' from plyer.facades import Barometer from pyobjus import autoclass class iOSBarometer(Barometer): def __init__(self): super().__init__() self.bridge = autoclass('bridge').alloc().init() def _enable(self): self.bridge.startRelativeAltitude() def _disable(self): self.bridge.stopRelativeAltitude() def _get_pressure(self): ''' 1 kPa = 10 hPa ''' return ( self.bridge.pressure * 10) def instance(): return iOSBarometer() plyer-2.1.0/plyer/platforms/ios/battery.py000066400000000000000000000020721433372044000206260ustar00rootroot00000000000000''' Module of iOS API for plyer.battery. ''' 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): ''' Implementation of iOS battery API. ''' def __init__(self): super().__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: is_charging = None elif self.device.batteryState == 2: is_charging = True else: is_charging = False percentage = self.device.batteryLevel * 100. status['isCharging'] = is_charging status['percentage'] = percentage return status def instance(): ''' Instance for facade proxy. ''' return IOSBattery() plyer-2.1.0/plyer/platforms/ios/brightness.py000066400000000000000000000010501433372044000213170ustar00rootroot00000000000000''' iOS Brightness -------------- ''' from pyobjus import autoclass from plyer.facades import Brightness from pyobjus.dylib_manager import load_framework load_framework('/System/Library/Frameworks/UIKit.framework') UIScreen = autoclass('UIScreen') class iOSBrightness(Brightness): def __init__(self): self.screen = UIScreen.mainScreen() def _current_level(self): return self.screen.brightness * 100 def set_level(self, level): self.screen.brightness = level / 100 def instance(): return iOSBrightness() plyer-2.1.0/plyer/platforms/ios/call.py000066400000000000000000000011161433372044000200650ustar00rootroot00000000000000''' IOS Call ---------- ''' from plyer.facades import Call from pyobjus import autoclass, objc_str NSURL = autoclass('NSURL') NSString = autoclass('NSString') UIApplication = autoclass('UIApplication') class IOSCall(Call): def _makecall(self, **kwargs): tel = kwargs.get('tel') url = "tel://" + tel nsurl = NSURL.alloc().initWithString_(objc_str(url)) UIApplication.sharedApplication().openURL_(nsurl) def _dialcall(self, **kwargs): pass # Not possible, Access not provided by iPhone SDK def instance(): return IOSCall() plyer-2.1.0/plyer/platforms/ios/camera.py000066400000000000000000000025051433372044000204050ustar00rootroot00000000000000from os import remove from plyer.facades import Camera from plyer.utils import reify class iOSCamera(Camera): @reify def photos(self): # pyPhotoLibrary is a ios recipe/module that # interacts with the gallery and the camera on ios. from photolibrary import PhotosLibrary return PhotosLibrary() def _take_picture(self, on_complete, filename=None): assert on_complete is not None self.on_complete = on_complete self.filename = filename photos = self.photos if not photos.isCameraAvailable(): # no camera hardware return False photos.bind(on_image_captured=self.capture_callback) self._capture_filename = filename photos.capture_image(filename) return True def capture_callback(self, photolibrary): # Image was chosen # unbind self.photos.unbind(on_image_captured=self.capture_callback) if self.on_complete(self.filename): self._remove(self.filename) def _take_video(self, on_complete, filename=None): assert on_complete is not None raise NotImplementedError def _remove(self, fn): try: remove(fn) except OSError: print('Could not remove photo!') def instance(): return iOSCamera() plyer-2.1.0/plyer/platforms/ios/compass.py000066400000000000000000000020571433372044000206240ustar00rootroot00000000000000''' iOS Compass ----------- ''' from plyer.facades import Compass from pyobjus import autoclass class IosCompass(Compass): def __init__(self): super().__init__() self.bridge = autoclass('bridge').alloc().init() self.bridge.motionManager.setMagnetometerUpdateInterval_(0.1) self.bridge.motionManager.setDeviceMotionUpdateInterval_(0.1) def _enable(self): self.bridge.startMagnetometer() self.bridge.startDeviceMotionWithReferenceFrame() def _disable(self): self.bridge.stopMagnetometer() self.bridge.stopDeviceMotion() def _get_orientation(self): return ( self.bridge.mf_x, self.bridge.mf_y, self.bridge.mf_z) def _get_field_uncalib(self): return ( self.bridge.mg_x, self.bridge.mg_y, self.bridge.mg_z, self.bridge.mg_x - self.bridge.mf_x, self.bridge.mg_y - self.bridge.mf_y, self.bridge.mg_z - self.bridge.mf_z) def instance(): return IosCompass() plyer-2.1.0/plyer/platforms/ios/email.py000066400000000000000000000022711433372044000202440ustar00rootroot00000000000000''' Module of iOS API for plyer.email. ''' 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 IOSEmail(Email): ''' Implementation of iOS battery API. ''' 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(): ''' Instance for facade proxy. ''' return IOSEmail() plyer-2.1.0/plyer/platforms/ios/filechooser.py000066400000000000000000000052211433372044000214550ustar00rootroot00000000000000''' IOS file chooser -------------------- This module houses the iOS implementation of the plyer FileChooser. .. versionadded:: 1.4.4 ''' from plyer.facades import FileChooser from pyobjus import autoclass, protocol from pyobjus.dylib_manager import load_framework load_framework('/System/Library/Frameworks/Photos.framework') class IOSFileChooser(FileChooser): ''' FileChooser implementation for IOS using the built-in file browser via UIImagePickerController. .. versionadded:: 1.4.0 ''' def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._on_selection = None def _file_selection_dialog(self, *args, **kwargs): """ Function called when action is required, A "mode" parameter specifies which and is one of "open", "save" or "dir". """ self._on_selection = kwargs["on_selection"] if kwargs["mode"] == "open": self._open() else: raise NotImplementedError() def _get_picker(self): """ Return an instantiated and configured UIImagePickerController. """ picker = autoclass("UIImagePickerController") po = picker.alloc().init() po.sourceType = 0 po.delegate = self return po def _open(self): """ Launch the native iOS file browser. Upon selection, the `imagePickerController_didFinishPickingMediaWithInfo_` delegate is called where we close the file browser and handle the result. """ picker = self._get_picker() UIApplication = autoclass('UIApplication') vc = UIApplication.sharedApplication().keyWindow.rootViewController() vc.presentViewController_animated_completion_(picker, True, None) @protocol('UIImagePickerControllerDelegate') def imagePickerController_didFinishPickingMediaWithInfo_( self, image_picker, frozen_dict): """ Delegate which handles the result of the image selection process. """ image_picker.dismissViewControllerAnimated_completion_(True, None) # Note: We need to call this Objective C class as there is currently # no way to call a non-class function via pyobjus. And here, # we have to use the `UIImagePNGRepresentation` to get the png # representation. For this, please ensure you are using an # appropriate version of kivy-ios. native_image_picker = autoclass("NativeImagePicker").alloc().init() path = native_image_picker.writeToPNG_(frozen_dict) self._on_selection([path.UTF8String()]) def instance(): return IOSFileChooser() plyer-2.1.0/plyer/platforms/ios/flash.py000066400000000000000000000022301433372044000202450ustar00rootroot00000000000000# coding=utf-8 """ Flash ----- """ from plyer.facades import Flash from pyobjus import autoclass NSString = autoclass("NSString") AVCaptureDevice = autoclass("AVCaptureDevice") AVMediaTypeVideo = NSString.alloc().initWithUTF8String_("vide") AVCaptureTorchModeOff = 0 AVCaptureTorchModeOn = 1 class IosFlash(Flash): _camera = None def _on(self): if self._camera is None: self._camera_open() if not self._camera: return self._camera.lockForConfiguration_(None) try: self._camera.setTorchMode(AVCaptureTorchModeOn) finally: self._camera.unlockForConfiguration() def _off(self): if not self._camera: return self._camera.lockForConfiguration_(None) try: self._camera.setTorchMode(AVCaptureTorchModeOff) finally: self._camera.unlockForConfiguration() def _release(self): pass def _camera_open(self): device = AVCaptureDevice.defaultDeviceWithMediaType_(AVMediaTypeVideo) if not device: return self._camera = device def instance(): return IosFlash() plyer-2.1.0/plyer/platforms/ios/gps.py000066400000000000000000000051701433372044000177470ustar00rootroot00000000000000''' 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, **kwargs): 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_didChangeAuthorizationStatus_(self, manager, status): if self.on_status: s_status = '' provider_status = '' provider = 'standard-ios-provider' if status == 0: provider_status = 'provider-disabled' s_status = 'notDetermined' elif status == 1: provider_status = 'provider-enabled' s_status = 'restricted' elif status == 2: provider_status = 'provider-disabled' s_status = 'denied' elif status == 3: provider_status = 'provider-enabled' s_status = 'authorizedAlways' elif status == 4: provider_status = 'provider-enabled' s_status = 'authorizedWhenInUse' self.on_status(provider_status, '{}: {}'.format( provider, s_status)) @protocol('CLLocationManagerDelegate') def locationManager_didUpdateLocations_(self, manager, locations): location = manager.location description = location.description.UTF8String() split_description = description.split('<')[-1].split('>')[0].split(',') lat, lon = [float(coord) for coord in split_description] acc = float(description.split(' +/- ')[-1].split('m ')[0]) speed = location.speed altitude = location.altitude course = location.course self.on_location( lat=lat, lon=lon, speed=speed, bearing=course, altitude=altitude, accuracy=acc) def instance(): return IosGPS() plyer-2.1.0/plyer/platforms/ios/gravity.py000066400000000000000000000011061433372044000206360ustar00rootroot00000000000000''' iOS Gravity ----------- ''' from plyer.facades import Gravity from pyobjus import autoclass class iOSGravity(Gravity): def __init__(self): self.bridge = autoclass('bridge').alloc().init() self.bridge.motionManager.setDeviceMotionUpdateInterval_(0.1) def _enable(self): self.bridge.startDeviceMotion() def _disable(self): self.bridge.stopDeviceMotion() def _get_gravity(self): return ( self.bridge.g_x, self.bridge.g_y, self.bridge.g_z) def instance(): return iOSGravity() plyer-2.1.0/plyer/platforms/ios/gyroscope.py000066400000000000000000000026751433372044000211770ustar00rootroot00000000000000''' iOS Gyroscope --------------------- ''' from plyer.facades import Gyroscope from pyobjus import autoclass from pyobjus.dylib_manager import load_framework load_framework('/System/Library/Frameworks/UIKit.framework') UIDevice = autoclass('UIDevice') device = UIDevice.currentDevice() class IosGyroscope(Gyroscope): def __init__(self): super().__init__() self.bridge = autoclass('bridge').alloc().init() if int(device.systemVersion.UTF8String().split('.')[0]) <= 4: self.bridge.motionManager.setGyroscopeUpdateInterval_(0.1) else: self.bridge.motionManager.setGyroUpdateInterval_(0.1) self.bridge.motionManager.setDeviceMotionUpdateInterval_(0.1) def _enable(self): self.bridge.startGyroscope() self.bridge.startDeviceMotion() def _disable(self): self.bridge.stopGyroscope() self.bridge.stopDeviceMotion() def _get_orientation(self): return ( self.bridge.rotation_rate_x, self.bridge.rotation_rate_y, self.bridge.rotation_rate_z) def _get_rotation_uncalib(self): return ( self.bridge.gy_x, self.bridge.gy_y, self.bridge.gy_z, self.bridge.gy_x - self.bridge.rotation_rate_x, self.bridge.gy_y - self.bridge.rotation_rate_y, self.bridge.gy_z - self.bridge.rotation_rate_z) def instance(): return IosGyroscope() plyer-2.1.0/plyer/platforms/ios/keystore.py000066400000000000000000000011731433372044000210220ustar00rootroot00000000000000from plyer.facades import Keystore from pyobjus import autoclass, objc_str NSUserDefaults = autoclass('NSUserDefaults') class IosKeystore(Keystore): def _set_key(self, servicename, key, value, **kwargs): NSUserDefaults.standardUserDefaults().setObject_forKey_( objc_str(value), objc_str(key)) def _get_key(self, servicename, key, **kwargs): ret = NSUserDefaults.standardUserDefaults().stringForKey_( objc_str(key)) if ret is not None: return ret.UTF8String() else: return ret def instance(): return IosKeystore() plyer-2.1.0/plyer/platforms/ios/sms.py000066400000000000000000000021221433372044000177520ustar00rootroot00000000000000''' IOS Sms ---------- ''' from plyer.facades import Sms from pyobjus import autoclass, objc_str from pyobjus.dylib_manager import load_framework NSURL = autoclass('NSURL') NSString = autoclass('NSString') UIApplication = autoclass('UIApplication') load_framework('/System/Library/Frameworks/MessageUI.framework') class IOSSms(Sms): def _send(self, **kwargs): ''' This method provides sending messages to recipients. Expects 2 parameters in kwargs: - recipient: String type - message: String type Opens a message interface with recipient and message information. ''' recipient = kwargs.get('recipient') message = kwargs.get('message') url = "sms:" if recipient: # Apple has not supported multiple recipients yet. url += str(recipient) if message: # Apple has to supported it yet. pass nsurl = NSURL.alloc().initWithString_(objc_str(url)) UIApplication.sharedApplication().openURL_(nsurl) def instance(): return IOSSms() plyer-2.1.0/plyer/platforms/ios/spatialorientation.py000066400000000000000000000012541433372044000230660ustar00rootroot00000000000000''' iOS Spatial Orientation ----------------------- ''' from plyer.facades import SpatialOrientation from pyobjus import autoclass class iOSSpatialOrientation(SpatialOrientation): def __init__(self): self.bridge = autoclass('bridge').alloc().init() self.bridge.motionManager.setDeviceMotionUpdateInterval_(0.1) def _enable_listener(self): self.bridge.startDeviceMotion() def _disable_listener(self): self.bridge.stopDeviceMotion() def _get_orientation(self): return ( self.bridge.sp_yaw, self.bridge.sp_pitch, self.bridge.sp_roll) def instance(): return iOSSpatialOrientation() plyer-2.1.0/plyer/platforms/ios/storagepath.py000066400000000000000000000035431433372044000215010ustar00rootroot00000000000000''' iOS Storage Path -------------------- ''' from plyer.facades import StoragePath from pyobjus import autoclass import os NSFileManager = autoclass('NSFileManager') # Directory constants (NSSearchPathDirectory enumeration) NSApplicationDirectory = 1 NSDocumentDirectory = 9 NSDownloadsDirectory = 15 NSMoviesDirectory = 17 NSMusicDirectory = 18 NSPicturesDirectory = 19 class iOSStoragePath(StoragePath): def __init__(self): self.defaultManager = NSFileManager.defaultManager() def _get_home_dir(self): return os.path.expanduser('~/') def _get_external_storage_dir(self): return 'This feature is not implemented for this platform.' def _get_root_dir(self): return 'This feature is not implemented for this platform.' def _get_documents_dir(self): return self.defaultManager.URLsForDirectory_inDomains_( NSDocumentDirectory, 1).firstObject().absoluteString.UTF8String() def _get_downloads_dir(self): return self.defaultManager.URLsForDirectory_inDomains_( NSDownloadsDirectory, 1).firstObject().absoluteString.UTF8String() def _get_videos_dir(self): return self.defaultManager.URLsForDirectory_inDomains_( NSMoviesDirectory, 1).firstObject().absoluteString.UTF8String() def _get_music_dir(self): return self.defaultManager.URLsForDirectory_inDomains_( NSMusicDirectory, 1).firstObject().absoluteString.UTF8String() def _get_pictures_dir(self): return self.defaultManager.URLsForDirectory_inDomains_( NSPicturesDirectory, 1).firstObject().absoluteString.UTF8String() def _get_application_dir(self): return self.defaultManager.URLsForDirectory_inDomains_( NSApplicationDirectory, 1).firstObject().absoluteString.\ UTF8String() def instance(): return iOSStoragePath() plyer-2.1.0/plyer/platforms/ios/tts.py000066400000000000000000000020041433372044000177610ustar00rootroot00000000000000from 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().__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() plyer-2.1.0/plyer/platforms/ios/uniqueid.py000066400000000000000000000010641433372044000207770ustar00rootroot00000000000000''' Module of iOS API for plyer.uniqueid. ''' 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): ''' Implementation of iOS uniqueid API. ''' def _get_uid(self): uuid = UIDevice.currentDevice().identifierForVendor.UUIDString() return uuid.UTF8String() def instance(): ''' Instance for facade proxy. ''' return IOSUniqueID() plyer-2.1.0/plyer/platforms/ios/vibrator.py000066400000000000000000000016541433372044000210110ustar00rootroot00000000000000'''Implementation Vibrator for iOS. Install: Add AudioToolbox framework to your application. ''' import ctypes from plyer.facades import Vibrator class IosVibrator(Vibrator): '''iOS Vibrator class. iOS doesn't support any feature. All time, pattern, repetition are ignored. ''' def __init__(self): super().__init__() try: self._func = ctypes.CDLL(None).AudioServicesPlaySystemSound except AttributeError: self._func = None def _vibrate(self, time=None, **kwargs): # kSystemSoundID_Vibrate is 0x00000FFF self._func(0xFFF) def _pattern(self, pattern=None, repeat=None, **kwargs): self._vibrate() def _exists(self, **kwargs): return self._func is not None def _cancel(self, **kwargs): pass def instance(): '''Returns Vibrator :return: instance of class IosVibrator ''' return IosVibrator() plyer-2.1.0/plyer/platforms/linux/000077500000000000000000000000001433372044000171465ustar00rootroot00000000000000plyer-2.1.0/plyer/platforms/linux/__init__.py000066400000000000000000000000001433372044000212450ustar00rootroot00000000000000plyer-2.1.0/plyer/platforms/linux/accelerometer.py000066400000000000000000000015461433372044000223400ustar00rootroot00000000000000''' Linux accelerometer --------------------- ''' from plyer.facades import Accelerometer 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() plyer-2.1.0/plyer/platforms/linux/battery.py000066400000000000000000000056051433372044000212000ustar00rootroot00000000000000''' Module of Linux API for plyer.battery. ''' from math import floor from os import environ from os.path import exists, join from subprocess import Popen, PIPE from plyer.facades import Battery from plyer.utils import whereis_exe class LinuxBattery(Battery): ''' Implementation of Linux battery API via accessing the sysclass power_supply path from the kernel. ''' def _get_state(self): status = {"isCharging": None, "percentage": None} kernel_bat_path = join('/sys', 'class', 'power_supply', 'BAT0') uevent = join(kernel_bat_path, 'uevent') with open(uevent) as fle: lines = [ line.decode('utf-8').strip() for line in fle.readlines() ] output = { line.split('=')[0]: line.split('=')[1] for line in lines } is_charging = output['POWER_SUPPLY_STATUS'] == 'Charging' total = float(output['POWER_SUPPLY_CHARGE_FULL']) now = float(output['POWER_SUPPLY_CHARGE_NOW']) capacity = floor(now / total * 100) status['percentage'] = capacity status['isCharging'] = is_charging return status class UPowerBattery(Battery): ''' Implementation of UPower battery API. ''' def _get_state(self): # if no LANG specified, return empty string old_lang = environ.get('LANG', '') environ['LANG'] = 'C' status = {"isCharging": None, "percentage": None} # We are supporting only one battery now # this will fail if there is no object with such path, # however it's safer than 'upower -d' which provides # multiple unrelated 'state' and 'percentage' keywords dev = "/org/freedesktop/UPower/devices/battery_BAT0" upower_process = Popen( ["upower", "--show-info", dev], stdout=PIPE ) output = upower_process.communicate()[0].decode() environ['LANG'] = old_lang if not output: return status state = percentage = None for line in output.splitlines(): if 'state' in line: state = line.rpartition(':')[-1].strip() if 'percentage' in line: percentage = line.rpartition(':')[-1].strip()[:-1] # switching decimal comma to dot # (different LC_NUMERIC locale) percentage = float( percentage.replace(',', '.') ) if state: status['isCharging'] = state == "charging" status['percentage'] = percentage return status def instance(): ''' Instance for facade proxy. ''' import sys if whereis_exe('upower'): return UPowerBattery() sys.stderr.write("upower not found.") if exists(join('/sys', 'class', 'power_supply', 'BAT0')): return LinuxBattery() return Battery() plyer-2.1.0/plyer/platforms/linux/brightness.py000077500000000000000000000012271433372044000216750ustar00rootroot00000000000000''' Linux Brightness ---------------- ''' from plyer.facades import Brightness import subprocess import os class LinuxBrightness(Brightness): def __init__(self): if os.system("which xbacklight"): msg = ("It looks like 'xbacklight' is not installed. Try " "installing it with your distribution's package manager.") raise Exception(msg) def _current_level(self): cr_level = subprocess.check_output(["xbacklight", "-get"]) return str(cr_level) def _set_level(self, level): subprocess.call(["xbacklight", "-set", str(level)]) def instance(): return LinuxBrightness() plyer-2.1.0/plyer/platforms/linux/cpu.py000066400000000000000000000066251433372044000203200ustar00rootroot00000000000000''' Module of Linux API for plyer.cpu. ''' from os.path import join from os import environ, listdir from subprocess import Popen, PIPE from plyer.facades import CPU from plyer.utils import whereis_exe class LinuxCPU(CPU): ''' Implementation of Linux CPU API. ''' def _sockets(self): # physical CPU sockets (or slots) on motherboard sockets = [] # list of CPU ids from kernel # open Linux kernel data file for CPU with open('/proc/cpuinfo', 'rb') as fle: lines = fle.readlines() # go through the lines and obtain physical CPU ids for line in lines: line = line.decode('utf-8') if 'physical id' not in line: continue cpuid = line.split(':')[1].strip() sockets.append(cpuid) # total sockets is the length of unique CPU ids from kernel sockets = len(set(sockets)) return sockets def _physical(self): # cores physical = [] # list of CPU ids from kernel # open Linux kernel data file for CPU with open('/proc/cpuinfo', 'rb') as fle: lines = fle.readlines() # go through the lines and obtain CPU core ids for line in lines: line = line.decode('utf-8') if 'core id' not in line: continue cpuid = line.split(':')[1].strip() physical.append(cpuid) # total cores (socket * core per socket) # is the length of unique CPU core ids from kernel physical = len(set(physical)) return physical def _logical(self): # cores * threads logical = None old_lang = environ.get('LANG', '') environ['LANG'] = 'C' _logical = Popen(['nproc', '--all'], stdout=PIPE) output = _logical.communicate()[0].decode('utf-8').strip() if output: logical = int(output) environ['LANG'] = old_lang return logical def _cache(self): values = {key: 0 for key in ('L1', 'L2', 'L3')} cpu_path = join('/sys', 'devices', 'system', 'cpu') # get present cores from kernel device with open(join(cpu_path, 'present')) as fle: present = fle.read().decode('utf-8') present = present.strip().split('-') if len(present) == 2: present = range(int(present[1]) + 1) else: present = [present[0]] cores = ['cpu{}'.format(i) for i in present] for core in cores: indicies = [ # get 'indexN' files from 'cache' folder assuming # the filename is in range index0 to index99 # in case a wild 'index_whatevercontent' file appears fle for fle in listdir(join(cpu_path, core, 'cache')) if fle.startswith('index') and len(fle) <= len('index') + 2 ] for index in indicies: index_type = join(cpu_path, core, 'cache', index, 'level') with open(index_type, 'rb') as fle: cache_level = fle.read().decode('utf-8').strip() values['L{}'.format(cache_level)] += 1 return values @staticmethod def _numa(): return def instance(): ''' Instance for facade proxy. ''' import sys if whereis_exe('nproc'): return LinuxCPU() sys.stderr.write("nproc not found.") return CPU() plyer-2.1.0/plyer/platforms/linux/devicename.py000066400000000000000000000006061433372044000216220ustar00rootroot00000000000000''' Module of Linux API for plyer.devicename. ''' import socket from plyer.facades import DeviceName class LinuxDeviceName(DeviceName): ''' Implementation of Linux DeviceName API. ''' def _get_device_name(self): hostname = socket.gethostname() return hostname def instance(): ''' Instance for facade proxy. ''' return LinuxDeviceName() plyer-2.1.0/plyer/platforms/linux/email.py000066400000000000000000000020331433372044000206050ustar00rootroot00000000000000''' Module of Linux API for plyer.email. ''' 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 LinuxEmail(Email): ''' Implementation of Linux email API. ''' 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(): ''' Instance for facade proxy. ''' import sys if whereis_exe('xdg-open'): return LinuxEmail() sys.stderr.write("xdg-open not found.") return Email() plyer-2.1.0/plyer/platforms/linux/filechooser.py000066400000000000000000000167321433372044000220330ustar00rootroot00000000000000''' 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: '''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, *args, **kwargs): self._handle_selection = kwargs.pop( 'on_selection', self._handle_selection ) # Simulate Kivy's behavior for i in kwargs: setattr(self, i, kwargs[i]) @staticmethod def _handle_selection(selection): ''' Dummy placeholder for returning selection from chooser. ''' return selection _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().decode('utf8') return self._set_and_return_selection( self._split_output(out)) else: return self._set_and_return_selection(None) time.sleep(0.1) def _set_and_return_selection(self, value): self.selection = value self._handle_selection(value) return value 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 += [ "--getsavefilename", (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 (str(os.environ.get("DESKTOP_SESSION")).lower() == "trinity" 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 = self.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() plyer-2.1.0/plyer/platforms/linux/keystore.py000066400000000000000000000006561433372044000213740ustar00rootroot00000000000000try: import keyring except ImportError: raise NotImplementedError() from plyer.facades import Keystore class LinuxKeystore(Keystore): def _set_key(self, servicename, key, value, **kwargs): keyring.set_password(servicename, key, value) def _get_key(self, servicename, key, **kwargs): return keyring.get_password(servicename, key) def instance(): return LinuxKeystore() plyer-2.1.0/plyer/platforms/linux/notification.py000066400000000000000000000065631433372044000222200ustar00rootroot00000000000000''' Module of Linux API for plyer.notification. ''' import warnings import subprocess from plyer.facades import Notification from plyer.utils import whereis_exe import os class NotifyDesktopPortals(Notification): ''' Implementation of xdg-desktop-portals API. ''' def _notify(self, **kwargs): title = kwargs.get("title", "title") body = kwargs.get("message", "body") subprocess.run([ "gdbus", "call", "--session", "--dest", "org.freedesktop.portal.Desktop", "--object-path", "/org/freedesktop/portal/desktop", "--method", "org.freedesktop.portal.Notification.AddNotification", "", "{'title': <'" + title + "'>, 'body': <'" + body + "'>}" ], stdout=subprocess.DEVNULL) class NotifySendNotification(Notification): ''' Implementation of Linux notification API using notify-send binary. ''' def _notify(self, **kwargs): icon = kwargs.get('icon', '') title = kwargs.get('title', 'title') hint = kwargs.get('hint', 'string::') message = kwargs.get('message', 'body') category = kwargs.get('category', '') app_name = kwargs.get('app_name', '') urgency = kwargs.get('urgency', 'normal') expire_time = kwargs.get('expire_time', '0') notify_send_args = (title, message, "-i", icon, "-h", hint, "-u", urgency, "-c", category, "-a", app_name, "-t", expire_time) subprocess.call(["notify-send", *notify_send_args]) class NotifyDbus(Notification): ''' Implementation of Linux notification API using dbus library and dbus-python wrapper. ''' 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', 10) 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 * 1000 ) def instance(): ''' Instance for facade proxy. ''' if os.path.isdir("/app"): # Flatpak return NotifyDesktopPortals() try: import dbus # noqa: F401 return NotifyDbus() except ImportError: msg = ("The Python dbus package is not installed.\n" "Try installing it with your distribution's package manager, " "it is usually called python-dbus or python3-dbus, but you " "might have to try dbus-python instead, e.g. when using pip.") warnings.warn(msg) if whereis_exe('notify-send'): return NotifySendNotification() warnings.warn("notify-send not found.") return Notification() plyer-2.1.0/plyer/platforms/linux/orientation.py000066400000000000000000000015461433372044000220610ustar00rootroot00000000000000import subprocess as sb from plyer.facades import Orientation class LinuxOrientation(Orientation): def _set_landscape(self, **kwargs): self.rotate = 'normal' self.screen = sb.check_output( "xrandr -q | grep ' connected' | head -n 1 | cut -d ' ' -f1", shell=True ) self.screen = self.screen.decode('utf-8').split('\n')[0] sb.call(["xrandr", "--output", self.screen, "--rotate", self.rotate]) def _set_portrait(self, **kwargs): self.rotate = 'left' self.screen = sb.check_output( "xrandr -q | grep ' connected' | head -n 1 | cut -d ' ' -f1", shell=True ) self.screen = self.screen.decode('utf-8').split('\n')[0] sb.call(["xrandr", "--output", self.screen, "--rotate", self.rotate]) def instance(): return LinuxOrientation() plyer-2.1.0/plyer/platforms/linux/processors.py000066400000000000000000000014441433372044000217250ustar00rootroot00000000000000from subprocess import Popen, PIPE from plyer.facades import Processors from plyer.utils import whereis_exe from os import environ class LinuxProcessors(Processors): def _get_state(self): old_lang = environ.get('LANG') environ['LANG'] = 'C' status = {"Number_of_Processors": None} dev = "--all" nproc_process = Popen( ["nproc", dev], stdout=PIPE ) output = nproc_process.communicate()[0] environ['LANG'] = old_lang if not output: return status status['Number_of_Processors'] = output.rstrip() return status def instance(): import sys if whereis_exe('nproc'): return LinuxProcessors() sys.stderr.write("nproc not found.") return Processors() plyer-2.1.0/plyer/platforms/linux/screenshot.py000066400000000000000000000014671433372044000217050ustar00rootroot00000000000000import subprocess from os.path import join from plyer.facades import Screenshot from plyer.utils import whereis_exe from plyer.platforms.linux.storagepath import LinuxStoragePath class LinuxScreenshot(Screenshot): def __init__(self, file_path=None): default_path = join( LinuxStoragePath().get_pictures_dir(), 'screenshot.xwd' ) super().__init__(file_path or default_path) def _capture(self): # call xwd and redirect bytes from stdout to file with open(self.file_path, 'wb') as fle: subprocess.call([ # quiet, full screen root window 'xwd', '-silent', '-root', ], stdout=fle) def instance(): if whereis_exe('xwd'): return LinuxScreenshot() else: return Screenshot() plyer-2.1.0/plyer/platforms/linux/storagepath.py000077500000000000000000000037771433372044000220620ustar00rootroot00000000000000''' Linux Storage Path -------------------- ''' from plyer.facades import StoragePath from os.path import expanduser, dirname, abspath, join, exists # Default paths for each name USER_DIRS = "/.config/user-dirs.dirs" PATHS = { "DESKTOP": "Desktop", "DOCUMENTS": "Documents", "DOWNLOAD": "Downloads", "MUSIC": "Music", "PICTURES": "Pictures", "VIDEOS": "Videos" } class LinuxStoragePath(StoragePath): def _get_from_user_dirs(self, name): home_dir = self._get_home_dir() default = join(home_dir, PATHS[name]) user_dirs = join(home_dir, USER_DIRS) if not exists(user_dirs): return default with open(user_dirs, "r") as f: for line in f.readlines(): if line.startswith("XDG_" + name): return line.split('"')[1] return default def _get_home_dir(self): return expanduser('~') def _get_external_storage_dir(self): return "/media/" + self._get_home_dir().split("/")[-1] def _get_root_dir(self): return "/" def _get_documents_dir(self): directory = self._get_from_user_dirs("DOCUMENTS") return directory.replace("$HOME", self._get_home_dir()) def _get_downloads_dir(self): directory = self._get_from_user_dirs("DOWNLOAD") return directory.replace("$HOME", self._get_home_dir()) def _get_videos_dir(self): directory = self._get_from_user_dirs("VIDEOS") return directory.replace("$HOME", self._get_home_dir()) def _get_music_dir(self): directory = self._get_from_user_dirs("MUSIC") return directory.replace("$HOME", self._get_home_dir()) def _get_pictures_dir(self): directory = self._get_from_user_dirs("PICTURES") return directory.replace("$HOME", self._get_home_dir()) def _get_application_dir(self): return dirname(abspath(__name__)) def instance(): return LinuxStoragePath() plyer-2.1.0/plyer/platforms/linux/tts.py000066400000000000000000000011361433372044000203330ustar00rootroot00000000000000import 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, **kwargs): subprocess.call(["flite", "-t", kwargs.get('message'), "play"]) def instance(): if whereis_exe('espeak'): return EspeakTextToSpeech() elif whereis_exe('flite'): return FliteTextToSpeech() return TTS() plyer-2.1.0/plyer/platforms/linux/uniqueid.py000066400000000000000000000017701433372044000213500ustar00rootroot00000000000000''' Module of Linux API for plyer.uniqueid. ''' from os import environ from subprocess import Popen, PIPE from plyer.facades import UniqueID from plyer.utils import whereis_exe class LinuxUniqueID(UniqueID): ''' Implementation of Linux uniqueid API. ''' def _get_uid(self): old_lang = environ.get('LANG') environ['LANG'] = 'C' stdout = Popen( ["lshw", "-quiet"], stdout=PIPE, stderr=PIPE ).communicate()[0].decode('utf-8') output = u'' for line in stdout.splitlines(): if 'serial:' not in line: continue output = line break environ['LANG'] = old_lang or u'' result = None if output: result = output.split()[1] return result def instance(): ''' Instance for facade proxy. ''' import sys if whereis_exe('lshw'): return LinuxUniqueID() sys.stderr.write("lshw not found.") return UniqueID() plyer-2.1.0/plyer/platforms/linux/wifi.py000066400000000000000000000322401433372044000204570ustar00rootroot00000000000000''' .. note:: This facade depends on `nmcli` (Network Manager command line tool). It's found in most of the popular GNU/Linux distributions. Support for other backends is not provided yet. ''' from subprocess import Popen, PIPE, call from plyer.facades import Wifi from plyer.utils import whereis_exe, deprecated try: import wifi except ModuleNotFoundError as err: raise ModuleNotFoundError( "python-wifi not installed. try:" + "`pip install --user wifi`.") from err class NMCLIWifi(Wifi): ''' .. versionadded:: 1.4.0 ''' def __init__(self, *args, **kwargs): ''' .. versionadded:: 1.4.0 ''' super().__init__(*args, **kwargs) self.names = {} @property def interfaces(self): ''' Get all the available interfaces for WiFi. .. versionadded:: 1.4.0 Tested with nmcli 1.2.6. ''' if not self._is_enabled(): self._enable() # fetch the devices proc = Popen([ 'nmcli', '--terse', '--fields', 'DEVICE,TYPE', 'device' ], stdout=PIPE) lines = proc.communicate()[0].decode('utf-8').splitlines() # filter devices by type interfaces = [] for line in lines: # bad escape from nmcli's side :< line = line.replace('\\:', '$$') device, dtype = line.split(':') if dtype != 'wifi': continue interfaces.append(device.replace('$$', ':')) # return wifi interfaces return interfaces def _is_enabled(self): ''' Return the status of WiFi device. .. versionadded:: 1.4.0 Tested with nmcli 1.2.6. ''' output = Popen( ["nmcli", "radio", "wifi"], stdout=PIPE ).communicate()[0].decode('utf-8') if output.split()[0] == 'enabled': return True return False def _is_connected(self, interface=None): ''' Return whether a specified interface is connected to a WiFi network. .. versionadded:: 1.4.0 Tested with nmcli 1.2.6. ''' if not self._is_enabled(): self._enable() if not interface: interface = self.interfaces[0] # fetch all devices proc = Popen([ 'nmcli', '--terse', '--fields', 'DEVICE,TYPE,STATE', 'device' ], stdout=PIPE) lines = proc.communicate()[0].decode('utf-8').splitlines() # filter by wifi type and interface connected = False for line in lines: line = line.replace('\\:', '$$') device, dtype, state = line.split(':') device = device.replace('$$', ':') if dtype != 'wifi': continue if device != interface: continue if state == 'connected': connected = True return connected def _start_scanning(self, interface=None): ''' Start scanning for available Wi-Fi networks for the specified interface. .. versionadded:: 1.4.0 Tested with nmcli 1.2.6. ''' if not self._is_enabled(): self._enable() if not interface: interface = self.interfaces[0] # force rescan for fresh data call(['nmcli', 'device', 'wifi', 'rescan', 'ifname', interface]) # get properties fields = [ 'SSID', 'BSSID', 'MODE', 'CHAN', 'FREQ', 'BARS', 'RATE', 'SIGNAL', 'SECURITY' ] # fetch all networks for interface output = Popen([ 'nmcli', '--terse', '--fields', ','.join(fields), 'device', 'wifi', 'list', 'ifname', interface ], stdout=PIPE).communicate()[0].decode('utf-8') # parse output for line in output.splitlines(): line = line.replace('\\:', '$$') row = { field: value for field, value in zip(fields, line.split(':')) } row['BSSID'] = row['BSSID'].replace('$$', ':') self.names[row['SSID']] = row def _get_network_info(self, name): ''' Get all the network information by network's name (SSID). .. versionadded:: 1.4.0 Tested with nmcli 1.2.6. ''' if not self.names: self._start_scanning() ret_list = {} ret_list['ssid'] = self.names[name]['SSID'] ret_list['signal'] = self.names[name]['SIGNAL'] bars = len(self.names[name]['BARS']) ret_list['quality'] = '{}/100'.format(bars / 5.0 * 100) ret_list['frequency'] = self.names[name]['FREQ'] ret_list['bitrates'] = self.names[name]['RATE'] # wpa1, wpa2, wpa1 wpa2, wep, (none), perhaps something else security = self.names[name]['SECURITY'].lower() ret_list['encrypted'] = True if 'wpa2' in security: # wpa2, wpa2+wpa1 ret_list['encryption_type'] = 'wpa2' elif 'wpa' in security: ret_list['encryption_type'] = 'wpa' elif 'wep' in security: ret_list['encryption_type'] = 'wep' elif 'none' in security: ret_list['encrypted'] = False ret_list['encryption_type'] = 'none' else: ret_list['encryption_type'] = security ret_list['channel'] = int(self.names[name]['CHAN']) ret_list['address'] = self.names[name]['BSSID'] ret_list['mode'] = self.names[name]['MODE'] return ret_list def _get_available_wifi(self): ''' Return the names of all found networks. .. versionadded:: 1.4.0 Tested with nmcli 1.2.6. ''' if not self.names: self._start_scanning() return list(self.names.keys()) def _connect(self, network, parameters, interface=None): ''' Connect a specific interface to a WiFi network. Expects 2 parameters: - SSID of the network - parameters: dict - password: string or None .. versionadded:: 1.4.0 Tested with nmcli 1.2.6. ''' self._enable() if not interface: interface = self.interfaces[0] password = parameters.get('password') command = [ 'nmcli', 'device', 'wifi', 'connect', network, 'ifname', interface ] if password: command += ['password', password] call(command) def _disconnect(self, interface=None): ''' Disconnect a specific interface from a WiFi network. .. versionadded:: 1.4.0 Tested with nmcli 1.2.6. ''' if not self._is_enabled(): return if not interface: interface = self.interfaces[0] if self._nmcli_version() >= (1, 2, 6): call(['nmcli', 'device', 'disconnect', interface]) else: call(['nmcli', 'nm', 'enable', 'false']) def _enable(self): ''' Turn WiFi device on. .. versionadded:: 1.4.0 Tested with nmcli 1.2.6. ''' call(['nmcli', 'radio', 'wifi', 'on']) def _disable(self): ''' Turn WiFi device off. .. versionadded:: 1.4.0 Tested with nmcli 1.2.6. ''' call(['nmcli', 'radio', 'wifi', 'off']) def _nmcli_version(self): ''' Get nmcli version to prevent executing deprecated commands. .. versionadded:: 1.4.0 Tested with nmcli 1.2.6. ''' version = Popen(['nmcli', '-v'], stdout=PIPE) version = version.communicate()[0].decode('utf-8') while version and not version[0].isdigit(): version = version[1:] return tuple(map(int, (version.split('.')))) @deprecated class LinuxWifi(Wifi): ''' .. versionadded:: 1.2.5 ''' def __init__(self, *args, **kwargs): ''' .. versionadded:: 1.4.0 ''' super().__init__(*args, **kwargs) self.names = {} @property def interfaces(self): ''' .. versionadded:: 1.4.0 ''' proc = Popen([ 'nmcli', '--terse', '--fields', 'DEVICE,TYPE', 'device' ], stdout=PIPE) lines = proc.communicate()[0].decode('utf-8').splitlines() interfaces = [] for line in lines: device, dtype = line.split(':') if dtype != 'wifi': continue interfaces.append(device) return interfaces def _is_enabled(self): ''' Returns `True` if wifi is enabled else `False`. .. versionadded:: 1.2.5 .. versionchanged:: 1.3.2 nmcli output is properly decoded to unicode ''' enbl = Popen(["nmcli", "radio", "wifi"], stdout=PIPE, stderr=PIPE) if enbl.communicate()[0].split()[0].decode('utf-8') == "enabled": return True return False def _is_connected(self, interface=None): ''' .. versionadded:: 1.4.0 ''' if not interface: interface = self.interfaces[0] proc = Popen([ 'nmcli', '--terse', '--fields', 'DEVICE,TYPE,STATE', 'device' ], stdout=PIPE) lines = proc.communicate()[0].decode('utf-8').splitlines() connected = False for line in lines: device, dtype, state = line.split(':') if dtype != 'wifi': continue if device != interface: continue if state == 'connected': connected = True return connected def _start_scanning(self, interface=None): ''' Returns all the network information. .. versionadded:: 1.2.5 .. versionchanged:: 1.3.0 scan only if wifi is enabled ''' if not interface: interface = self.interfaces[0] if self._is_enabled(): list_ = list(wifi.Cell.all(interface)) for i in range(len(list_)): self.names[list_[i].ssid] = list_[i] else: raise Exception('Wifi not enabled.') def _get_network_info(self, name): ''' Starts scanning for available Wi-Fi networks and returns the available, devices. .. versionadded:: 1.2.5 ''' ret_list = {} ret_list['ssid'] = self.names[name].ssid ret_list['signal'] = self.names[name].signal ret_list['quality'] = self.names[name].quality ret_list['frequency'] = self.names[name].frequency ret_list['bitrates'] = self.names[name].bitrates ret_list['encrypted'] = self.names[name].encrypted ret_list['channel'] = self.names[name].channel ret_list['address'] = self.names[name].address ret_list['mode'] = self.names[name].mode if not ret_list['encrypted']: return ret_list else: ret_list['encryption_type'] = self.names[name].encryption_type return ret_list def _get_available_wifi(self): ''' Returns the name of available networks. .. versionadded:: 1.2.5 .. versionchanged:: 1.4.0 return a proper list of elements instead of dict_keys ''' return list(self.names.keys()) def _connect(self, network, parameters, interface=None): ''' Expects 2 parameters: - name/ssid of the network. - parameters: dict type - password: string or None .. versionadded:: 1.2.5 ''' if not interface: interface = self.interfaces[0] result = None try: self._enable() finally: password = parameters['password'] cell = self.names[network] result = wifi.Scheme.for_cell( interface, network, cell, password ) return result def _disconnect(self, interface=None): ''' Disconnect all the networks managed by Network manager. .. versionadded:: 1.2.5 ''' if not interface: interface = self.interfaces[0] if self._nmcli_version() >= (1, 2, 6): call(['nmcli', 'dev', 'disconnect', interface]) else: call(['nmcli', 'nm', 'enable', 'false']) def _enable(self): ''' Wifi interface power state is set to "ON". .. versionadded:: 1.3.2 ''' return call(['nmcli', 'radio', 'wifi', 'on']) def _disable(self): ''' Wifi interface power state is set to "OFF". .. versionadded:: 1.3.2 ''' return call(['nmcli', 'radio', 'wifi', 'off']) def _nmcli_version(self): ''' .. versionadded:: 1.3.2 ''' version = Popen(['nmcli', '-v'], stdout=PIPE) version = version.communicate()[0].decode('utf-8') while version and not version[0].isdigit(): version = version[1:] return tuple(map(int, (version.split('.')))) def instance(): if whereis_exe('nmcli'): return NMCLIWifi() return LinuxWifi() plyer-2.1.0/plyer/platforms/macosx/000077500000000000000000000000001433372044000173015ustar00rootroot00000000000000plyer-2.1.0/plyer/platforms/macosx/__init__.py000066400000000000000000000000001433372044000214000ustar00rootroot00000000000000plyer-2.1.0/plyer/platforms/macosx/accelerometer.py000066400000000000000000000010331433372044000224620ustar00rootroot00000000000000''' 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 Exception: 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() plyer-2.1.0/plyer/platforms/macosx/audio.py000066400000000000000000000052261433372044000207610ustar00rootroot00000000000000from os.path import join from pyobjus import autoclass from pyobjus.dylib_manager import INCLUDE, load_framework from plyer.facades import Audio from plyer.platforms.macosx.storagepath import OSXStoragePath load_framework(INCLUDE.Foundation) load_framework(INCLUDE.AVFoundation) AVAudioPlayer = autoclass("AVAudioPlayer") AVAudioRecorder = autoclass("AVAudioRecorder") AVAudioFormat = autoclass("AVAudioFormat") NSString = autoclass('NSString') NSURL = autoclass('NSURL') NSError = autoclass('NSError').alloc() class OSXAudio(Audio): def __init__(self, file_path=None): default_path = join( OSXStoragePath().get_music_dir(), 'audio.wav' ) super().__init__(file_path or default_path) self._recorder = None self._player = None self._current_file = None def _start(self): # Conversion of Python file path string to Objective-C NSString file_path_NSString = NSString.alloc() file_path_NSString = file_path_NSString.initWithUTF8String_( self._file_path ) # Definition of Objective-C NSURL object for the output record file # specified by NSString file path file_NSURL = NSURL.alloc() file_NSURL = file_NSURL.initWithString_(file_path_NSString) # Internal audio file format specification af = AVAudioFormat.alloc() af = af.initWithCommonFormat_sampleRate_channels_interleaved_( 1, 44100.0, 2, True ) # Audio recorder instance initialization with specified file NSURL # and audio file format self._recorder = AVAudioRecorder.alloc() self._recorder = self._recorder.initWithURL_format_error_( file_NSURL, af, NSError ) if not self._recorder: raise Exception(NSError.code, NSError.domain) self._recorder.record() # Setting the currently recorded file as current file # for using it as a parameter in audio player self._current_file = file_NSURL def _stop(self): if self._recorder: self._recorder.stop() self._recorder = None if self._player: self._player.stop() self._player = None def _play(self): # Audio player instance initialization with the file NSURL # of the last recorded audio file self._player = AVAudioPlayer.alloc() self._player = self._player.initWithContentsOfURL_error_( self._current_file, NSError ) if not self._player: raise Exception(NSError.code, NSError.domain) self._player.play() def instance(): return OSXAudio() plyer-2.1.0/plyer/platforms/macosx/battery.py000066400000000000000000000030061433372044000213240ustar00rootroot00000000000000''' Module of MacOS API for plyer.battery. ''' from os import environ from subprocess import Popen, PIPE from plyer.facades import Battery from plyer.utils import whereis_exe class OSXBattery(Battery): ''' Implementation of MacOS battery API. ''' 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 is_charging = max_capacity = current_capacity = None for line in output.decode('utf-8').splitlines(): if 'IsCharging' in line: is_charging = line.rpartition('=')[-1].strip() if 'MaxCapacity' in line: max_capacity = float(line.rpartition('=')[-1].strip()) if 'CurrentCapacity' in line: current_capacity = float(line.rpartition('=')[-1].strip()) if is_charging: status['isCharging'] = is_charging == "Yes" if current_capacity and max_capacity: status['percentage'] = 100.0 * current_capacity / max_capacity return status def instance(): ''' Instance for facade proxy. ''' import sys if whereis_exe('ioreg'): return OSXBattery() sys.stderr.write("ioreg not found.") return Battery() plyer-2.1.0/plyer/platforms/macosx/bluetooth.py000066400000000000000000000022731433372044000216640ustar00rootroot00000000000000''' Module of MacOS API for plyer.bluetooth. ''' from subprocess import Popen, PIPE from plyer.facades import Bluetooth from plyer.utils import whereis_exe from os import environ class OSXBluetooth(Bluetooth): ''' Implementation of MacOS bluetooth API. ''' def _get_info(self): old_lang = environ.get('LANG') environ['LANG'] = 'C' sys_profiler_process = Popen( ["system_profiler", "SPBluetoothDataType"], stdout=PIPE ) stdout = sys_profiler_process.communicate()[0].decode('utf-8') output = stdout.splitlines() lines = [] for line in output: if 'Bluetooth Power' not in line: continue lines.append(line) if old_lang is None: environ.pop('LANG') else: environ['LANG'] = old_lang if output and len(lines) == 1: return lines[0].split()[2] else: return None def instance(): ''' Instance for facade proxy. ''' import sys if whereis_exe('system_profiler'): return OSXBluetooth() sys.stderr.write("system_profiler not found.") return Bluetooth() plyer-2.1.0/plyer/platforms/macosx/cpu.py000066400000000000000000000023101433372044000204360ustar00rootroot00000000000000''' Module of MacOS API for plyer.cpu. ''' from subprocess import Popen, PIPE from plyer.facades import CPU from plyer.utils import whereis_exe class OSXCPU(CPU): ''' Implementation of MacOS CPU API. ''' @staticmethod def _sockets(): return def _physical(self): # cores physical = None _physical = Popen( ['sysctl', '-n', 'hw.physicalcpu_max'], stdout=PIPE ) output = _physical.communicate()[0].decode('utf-8').strip() if output: physical = int(output) return physical def _logical(self): # cores * threads logical = None _logical = Popen( ['sysctl', '-n', 'hw.logicalcpu_max'], stdout=PIPE ) output = _logical.communicate()[0].decode('utf-8').strip() if output: logical = int(output) return logical @staticmethod def _cache(): return @staticmethod def _numa(): return def instance(): ''' Instance for facade proxy. ''' import sys if whereis_exe('sysctl'): return OSXCPU() sys.stderr.write('sysctl not found.') return CPU() plyer-2.1.0/plyer/platforms/macosx/devicename.py000066400000000000000000000006041433372044000217530ustar00rootroot00000000000000''' Module of MacOSX API for plyer.devicename. ''' import socket from plyer.facades import DeviceName class OSXDeviceName(DeviceName): ''' Implementation of MacOSX DeviceName API. ''' def _get_device_name(self): hostname = socket.gethostname() return hostname def instance(): ''' Instance for facade proxy. ''' return OSXDeviceName() plyer-2.1.0/plyer/platforms/macosx/email.py000066400000000000000000000020231433372044000207370ustar00rootroot00000000000000''' Module of MacOS API for plyer.email. ''' 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): ''' Implementation of MacOS email API. ''' 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(): ''' Instance for facade proxy. ''' import sys if whereis_exe('open'): return MacOSXEmail() sys.stderr.write("open not found.") return Email() plyer-2.1.0/plyer/platforms/macosx/filechooser.py000066400000000000000000000072701433372044000221630ustar00rootroot00000000000000''' 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: '''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, *args, **kwargs): self._handle_selection = kwargs.pop( 'on_selection', self._handle_selection ) # Simulate Kivy's behavior for i in kwargs: setattr(self, i, kwargs[i]) @staticmethod def _handle_selection(selection): ''' Dummy placeholder for returning selection from chooser. ''' return selection def run(self): panel = None if self.mode in ("open", "dir", "dir_and_files"): panel = NSOpenPanel.openPanel() panel.setCanChooseDirectories_(self.mode != "open") panel.setCanChooseFiles_(self.mode != "dir") if self.multiple: panel.setAllowsMultipleSelection_(True) elif self.mode == "save": panel = NSSavePanel.savePanel() else: assert False, self.mode panel.setCanCreateDirectories_(True) panel.setShowsHiddenFiles_(self.show_hidden) if self.title: panel.setTitle_(objc_str(self.title)) # 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 self.filters: filthies = [] for f in self.filters: if type(f) == str: f = (None, f) for s in f[1:]: if not self.use_extensions: if s.strip().endswith("*"): continue pystr = s.strip().split("*")[-1].split(".")[-1] filthies.append(objc_str(pystr)) ftypes_arr = objc_arr(*filthies) # todo: switch to allowedContentTypes panel.setAllowedFileTypes_(ftypes_arr) panel.setAllowsOtherFileTypes_(not self.use_extensions) if self.path: url = NSURL.fileURLWithPath_(self.path) panel.setDirectoryURL_(url) selection = None if panel.runModal(): if self.mode == "save" or not self.multiple: selection = [panel.filename().UTF8String()] else: filename = panel.filenames() selection = [ filename.objectAtIndex_(x).UTF8String() for x in range(filename.count())] self._handle_selection(selection) return selection class MacOSXFileChooser(FileChooser): ''' FileChooser implementation for macOS using NSOpenPanel, NSSavePanel. ''' def _file_selection_dialog(self, **kwargs): return MacFileChooser(**kwargs).run() def instance(): return MacOSXFileChooser() plyer-2.1.0/plyer/platforms/macosx/keystore.py000066400000000000000000000006521433372044000215230ustar00rootroot00000000000000try: import keyring except ImportError: raise NotImplementedError() from plyer.facades import Keystore class OSXKeystore(Keystore): def _set_key(self, servicename, key, value, **kwargs): keyring.set_password(servicename, key, value) def _get_key(self, servicename, key, **kwargs): return keyring.get_password(servicename, key) def instance(): return OSXKeystore() plyer-2.1.0/plyer/platforms/macosx/libs/000077500000000000000000000000001433372044000202325ustar00rootroot00000000000000plyer-2.1.0/plyer/platforms/macosx/libs/__init__.py000066400000000000000000000000001433372044000223310ustar00rootroot00000000000000plyer-2.1.0/plyer/platforms/macosx/libs/osx_motion_sensor.py000066400000000000000000000061451433372044000244010ustar00rootroot00000000000000import ctypes from ctypes import ( Structure, cdll, sizeof, 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 = IOKit.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)]) plyer-2.1.0/plyer/platforms/macosx/libs/osx_paths.py000066400000000000000000000015551433372044000226220ustar00rootroot00000000000000import ctypes import os def NSIterateSearchPaths(directory): LibraryPath = ("/System/Library/Frameworks/CoreFoundation.framework/" "Versions/A/CoreFoundation") CoreFound = ctypes.cdll.LoadLibrary(LibraryPath) NSStartSearchPathEnumeration = CoreFound.NSStartSearchPathEnumeration NSGetNextSearchPathEnumeration = CoreFound.NSGetNextSearchPathEnumeration PATH_MAX = os.pathconf('/', os.pathconf_names['PC_PATH_MAX']) PATH_ENCODING = 'utf8' path_buffer = ctypes.create_string_buffer(PATH_MAX) # paths = [] <- fixme, possible list of paths in directory state = NSStartSearchPathEnumeration(directory, 1) while True: state = NSGetNextSearchPathEnumeration(state, path_buffer) if state == 0: break path = os.path.expanduser(path_buffer.value.decode(PATH_ENCODING)) return path plyer-2.1.0/plyer/platforms/macosx/notification.py000066400000000000000000000025771433372044000223540ustar00rootroot00000000000000''' Module of MacOS API for plyer.notification. ''' from plyer.facades import Notification from pyobjus import ( autoclass, protocol, objc_str, ObjcBOOL ) from pyobjus.dylib_manager import ( load_framework, INCLUDE ) load_framework(INCLUDE.AppKit) load_framework(INCLUDE.Foundation) NSUserNotification = autoclass('NSUserNotification') NSUserNotificationCenter = autoclass('NSUserNotificationCenter') class OSXNotification(Notification): ''' Implementation of MacOS notification API. ''' def _notify(self, **kwargs): title = kwargs.get('title', '') message = kwargs.get('message', '') app_name = kwargs.get('app_name', '') # app_icon, timeout, ticker are not supported (yet) notification = NSUserNotification.alloc().init() notification.setTitle_(objc_str(title)) notification.setSubtitle_(objc_str(app_name)) notification.setInformativeText_(objc_str(message)) usrnotifctr = NSUserNotificationCenter.defaultUserNotificationCenter() usrnotifctr.setDelegate_(self) usrnotifctr.deliverNotification_(notification) @protocol('NSUserNotificationCenterDelegate') def userNotificationCenter_shouldPresentNotification_( self, center, notification): return ObjcBOOL(True) def instance(): ''' Instance for facade proxy. ''' return OSXNotification() plyer-2.1.0/plyer/platforms/macosx/screenshot.py000066400000000000000000000012571433372044000220350ustar00rootroot00000000000000import subprocess from os.path import join from plyer.facades import Screenshot from plyer.utils import whereis_exe from plyer.platforms.macosx.storagepath import OSXStoragePath class OSXScreenshot(Screenshot): def __init__(self, file_path=None): default_path = join( OSXStoragePath().get_pictures_dir().replace('file://', ''), 'screenshot.png' ) super().__init__(file_path or default_path) def _capture(self): subprocess.call([ 'screencapture', self.file_path ]) def instance(): if whereis_exe('screencapture'): return OSXScreenshot() else: return Screenshot() plyer-2.1.0/plyer/platforms/macosx/storagepath.py000066400000000000000000000035741433372044000222050ustar00rootroot00000000000000''' MacOS X Storage Path -------------------- ''' from plyer.facades import StoragePath from pyobjus import autoclass NSFileManager = autoclass('NSFileManager') # Directory constants (NSSearchPathDirectory enumeration) NSApplicationDirectory = 1 NSDocumentDirectory = 9 NSDownloadsDirectory = 15 NSMoviesDirectory = 17 NSMusicDirectory = 18 NSPicturesDirectory = 19 class OSXStoragePath(StoragePath): def __init__(self): self.defaultManager = NSFileManager.defaultManager() def _get_home_dir(self): home_dir_NSURL = self.defaultManager.homeDirectoryForCurrentUser return home_dir_NSURL.absoluteString.UTF8String() def _get_external_storage_dir(self): return 'Method not implemented for current platform.' def _get_root_dir(self): return '/' def _get_documents_dir(self): return self.defaultManager.URLsForDirectory_inDomains_( NSDocumentDirectory, 1).firstObject().absoluteString.UTF8String() def _get_downloads_dir(self): return self.defaultManager.URLsForDirectory_inDomains_( NSDownloadsDirectory, 1).firstObject().absoluteString.UTF8String() def _get_videos_dir(self): return self.defaultManager.URLsForDirectory_inDomains_( NSMoviesDirectory, 1).firstObject().absoluteString.UTF8String() def _get_music_dir(self): return self.defaultManager.URLsForDirectory_inDomains_( NSMusicDirectory, 1).firstObject().absoluteString.UTF8String() def _get_pictures_dir(self): return self.defaultManager.URLsForDirectory_inDomains_( NSPicturesDirectory, 1).firstObject().absoluteString.UTF8String() def _get_application_dir(self): return self.defaultManager.URLsForDirectory_inDomains_( NSApplicationDirectory, 1 ).firstObject().absoluteString.UTF8String() def instance(): return OSXStoragePath() plyer-2.1.0/plyer/platforms/macosx/tts.py000066400000000000000000000011351433372044000204650ustar00rootroot00000000000000import 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() plyer-2.1.0/plyer/platforms/macosx/uniqueid.py000066400000000000000000000020571433372044000215020ustar00rootroot00000000000000''' Module of MacOS API for plyer.uniqueid. ''' from os import environ from subprocess import Popen, PIPE from plyer.facades import UniqueID from plyer.utils import whereis_exe class OSXUniqueID(UniqueID): ''' Implementation of MacOS uniqueid API. ''' 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] if old_lang is None: environ.pop('LANG') else: environ['LANG'] = old_lang result = None if output: result = output.split()[3][1:-1] return result def instance(): ''' Instance for facade proxy. ''' import sys if whereis_exe('ioreg'): return OSXUniqueID() sys.stderr.write("ioreg not found.") return UniqueID() plyer-2.1.0/plyer/platforms/macosx/wifi.py000066400000000000000000000121421433372044000206110ustar00rootroot00000000000000from pyobjus import autoclass from pyobjus.dylib_manager import load_framework, INCLUDE from plyer.facades import Wifi load_framework(INCLUDE.Foundation) load_framework(INCLUDE.CoreWLAN) CWInterface = autoclass('CWInterface') CWNetwork = autoclass('CWNetwork') CWWiFiClient = autoclass('CWWiFiClient') NSArray = autoclass('NSArray') NSDictionary = autoclass('NSDictionary') NSString = autoclass('NSString') class OSXWifi(Wifi): names = {} def _is_enabled(self): ''' Returns `True` if the Wifi is enabled else returns `False`. ''' return CWWiFiClient.sharedWiFiClient().interface().powerOn() def _get_network_info(self, name): ''' Returns all the network information. ''' accessNetworkType = self.names[name].accessNetworkType aggregateRSSI = self.names[name].aggregateRSSI beaconInterval = self.names[name].beaconInterval bssid = self.names[name].bssid.UTF8String() countryCode = self.names[name].countryCode hasInternet = self.names[name].hasInternet hasInterworkingIE = self.names[name].hasInterworkingIE hessid = self.names[name].hessid ibss = self.names[name].ibss isAdditionalStepRequiredForAccess = \ self.names[name].isAdditionalStepRequiredForAccess isCarPlayNetwork = self.names[name].isCarPlayNetwork isEmergencyServicesReachable = \ self.names[name].isEmergencyServicesReachable isPasspoint = self.names[name].isPasspoint isPersonalHotspot = self.names[name].isPersonalHotspot isUnauthenticatedEmergencyServiceAccessible = \ self.names[name].isUnauthenticatedEmergencyServiceAccessible noiseMeasurement = self.names[name].noiseMeasurement physicalLayerMode = self.names[name].physicalLayerMode rssiValue = self.names[name].rssiValue securityType = self.names[name].securityType ssid = self.names[name].ssid.UTF8String() supportsEasyConnect = self.names[name].supportsEasyConnect supportsWPS = self.names[name].supportsWPS venueGroup = self.names[name].venueGroup venueType = self.names[name].venueType return {'accessNetworkType': accessNetworkType, 'aggregateRSSI': aggregateRSSI, 'beaconInterval': beaconInterval, 'bssid': bssid, 'countryCode': countryCode, 'hasInternet': hasInternet, 'hasInterworkingIE': hasInterworkingIE, 'hessid': hessid, 'ibss': ibss, 'isAdditionalStepRequiredForAccess': isAdditionalStepRequiredForAccess, 'isCarPlayNetwork': isCarPlayNetwork, 'isEmergencyServicesReachable': isEmergencyServicesReachable, 'isPasspoint': isPasspoint, 'isPersonalHotspot': isPersonalHotspot, 'isUnauthenticatedEmergencyServiceAccessible': isUnauthenticatedEmergencyServiceAccessible, 'noiseMeasurement': noiseMeasurement, 'physicalLayerMode': physicalLayerMode, 'rssiValue': rssiValue, 'securityType': securityType, 'ssid': ssid, 'supportsEasyConnect': supportsEasyConnect, 'supportsWPS': supportsWPS, 'venueGroup': venueGroup, 'venueType': venueType} def _start_scanning(self): ''' Starts scanning for available Wi-Fi networks. ''' if self._is_enabled(): self.names = {} c = CWInterface.interface() scan = c.scanForNetworksWithName_error_(None, None) cnt = scan.allObjects().count() for i in range(cnt): self.names[ scan.allObjects().objectAtIndex_(i).ssid.UTF8String() ] = scan.allObjects().objectAtIndex_(i) else: raise Exception("Wifi not enabled.") def _get_available_wifi(self): ''' Returns the name of available networks. ''' return self.names.keys() def _connect(self, network, parameters): ''' Expects 2 parameters: - name/ssid of the network. - password: dict type ''' password = parameters['password'] network_object = self.names[network] CWInterface.interface().associateToNetwork_password_error_( network_object, password, None) return def _disconnect(self): ''' Disconnect from network. ''' CWInterface.interface().disassociate() return def _disable(self): ''' Wifi interface power state is set to "OFF". ''' interface = CWWiFiClient.sharedWiFiClient().interface() interface.setPower_error_(False, None) def _enable(self): ''' Wifi interface power state is set to "ON". ''' interface = CWWiFiClient.sharedWiFiClient().interface() interface.setPower_error_(True, None) def instance(): return OSXWifi() plyer-2.1.0/plyer/platforms/win/000077500000000000000000000000001433372044000166045ustar00rootroot00000000000000plyer-2.1.0/plyer/platforms/win/__init__.py000066400000000000000000000000001433372044000207030ustar00rootroot00000000000000plyer-2.1.0/plyer/platforms/win/audio.py000066400000000000000000000230731433372044000202640ustar00rootroot00000000000000''' Documentation: http://docs.microsoft.com/en-us/windows/desktop/Multimedia .. versionadded:: 1.4.0 ''' from os.path import join from ctypes import windll from ctypes import ( sizeof, c_void_p, c_ulonglong, c_ulong, c_wchar_p, byref, Structure, create_string_buffer ) from ctypes.wintypes import DWORD, UINT from plyer.facades import Audio from plyer.platforms.win.storagepath import WinStoragePath # DWORD_PTR i.e. ULONG_PTR, 32/64bit ULONG_PTR = c_ulonglong if sizeof(c_void_p) == 8 else c_ulong # device specific symbols MCI_OPEN = 0x803 MCI_OPEN_TYPE = 0x2000 MCI_OPEN_ELEMENT = 512 MCI_RECORD = 0x80F MCI_STOP = 0x808 MCI_SAVE = 0x813 MCI_PLAY = 0x806 MCI_CLOSE = 0x804 # recorder specific symbols MCI_FROM = 4 MCI_TO = 8 MCI_WAIT = 2 MCI_SAVE_FILE = 256 class MCI_OPEN_PARMS(Structure): ''' Struct for MCI_OPEN message parameters. .. versionadded:: 1.4.0 ''' _fields_ = [ ('mciOpenParms', ULONG_PTR), ('wDeviceID', UINT), ('lpstrDeviceType', c_wchar_p), ('lpstrElementName', c_wchar_p), ('lpstrAlias', c_wchar_p) ] class MCI_RECORD_PARMS(Structure): ''' Struct for MCI_RECORD message parameters. http://docs.microsoft.com/en-us/windows/desktop/Multimedia/mci-record-parms .. versionadded:: 1.4.0 ''' _fields_ = [ ('dwCallback', ULONG_PTR), ('dwFrom', DWORD), ('dwTo', DWORD) ] class MCI_SAVE_PARMS(Structure): ''' Struct for MCI_SAVE message parameters. http://docs.microsoft.com/en-us/windows/desktop/Multimedia/mci-save-parms .. versionadded:: 1.4.0 ''' _fields_ = [ ('dwCallback', ULONG_PTR), ('lpfilename', c_wchar_p) ] class MCI_PLAY_PARMS(Structure): ''' Struct for MCI_PLAY message parameters. http://docs.microsoft.com/en-us/windows/desktop/Multimedia/mci-play-parms .. versionadded:: 1.4.0 ''' _fields_ = [ ('dwCallback', ULONG_PTR), ('dwFrom', DWORD), ('dwTo', DWORD) ] def send_command(device, msg, flags, params): ''' Generic mciSendCommandW() wrapper with error handler. All parameters are required as for mciSendCommandW(). In case of no `params` passed, use `None`, that value won't be dereferenced. .. versionadded:: 1.4.0 ''' multimedia = windll.winmm send_command_w = multimedia.mciSendCommandW get_error = multimedia.mciGetErrorStringW # error text buffer # by API specification 128 is max, however the API sometimes # kind of does not respect the documented bounds and returns # more characters than buffer length...?! error_len = 128 # big enough to prevent API accidentally segfaulting error_text = create_string_buffer(error_len * 2) # open a recording device with a new file error_code = send_command_w( device, # device ID msg, flags, # reference to parameters structure or original value # in case of params=False/0/None/... byref(params) if params else params ) # handle error messages if any if error_code: # device did not open, raise an exception get_error(error_code, byref(error_text), error_len) error_text = error_text.raw.replace(b'\x00', b'').decode('utf-8') # either it can close already open device or it will fail because # the device is in non-closable state, but the end result is the same # and it makes no sense to parse MCI_CLOSE's error in this case send_command_w(device, MCI_CLOSE, 0, None) raise Exception(error_code, error_text) # return params struct because some commands write into it # to pass some values out of the local function scope return params class WinRecorder: ''' Generic wrapper for MCI_RECORD handling the filenames and device closing in the same approach like it is used for other platforms. .. versionadded:: 1.4.0 ''' def __init__(self, device, filename): self._device = device self._filename = filename @property def device(self): ''' Public property returning device ID. .. versionadded:: 1.4.0 ''' return self._device @property def filename(self): ''' Public property returning filename for current recording. .. versionadded:: 1.4.0 ''' return self._filename def record(self): ''' Start recording a WAV sound. .. versionadded:: 1.4.0 ''' send_command( device=self.device, msg=MCI_RECORD, flags=0, params=None ) def stop(self): ''' Stop recording and save the data to a file path self.filename. Wait until the file is written. Close the device afterwards. .. versionadded:: 1.4.0 ''' # stop the recording first send_command( device=self.device, msg=MCI_STOP, flags=MCI_WAIT, params=None ) # choose filename for the WAV file save_params = MCI_SAVE_PARMS() save_params.lpfilename = self.filename # save the sound data to a file and wait # until it ends writing to the file send_command( device=self.device, msg=MCI_SAVE, flags=MCI_SAVE_FILE | MCI_WAIT, params=save_params ) # close the recording device send_command( device=self.device, msg=MCI_CLOSE, flags=0, params=None ) class WinPlayer: ''' Generic wrapper for MCI_PLAY handling the device closing. .. versionadded:: 1.4.0 ''' def __init__(self, device): self._device = device @property def device(self): ''' Public property returning device ID. .. versionadded:: 1.4.0 ''' return self._device def play(self): ''' Start playing a WAV sound. .. versionadded:: 1.4.0 ''' play_params = MCI_PLAY_PARMS() play_params.dwFrom = 0 send_command( device=self.device, msg=MCI_PLAY, flags=MCI_FROM, params=play_params ) def stop(self): ''' Stop playing a WAV sound and close the device. .. versionadded:: 1.4.0 ''' send_command( device=self.device, msg=MCI_STOP, flags=MCI_WAIT, params=None ) # close the playing device send_command( device=self.device, msg=MCI_CLOSE, flags=0, params=None ) class WinAudio(Audio): ''' Windows implementation of audio recording and audio playing. .. versionadded:: 1.4.0 ''' def __init__(self, file_path=None): # default path unless specified otherwise default_path = join( WinStoragePath().get_music_dir(), 'audio.wav' ) super().__init__(file_path or default_path) self._recorder = None self._player = None self._current_file = None def _start(self): ''' Start recording a WAV sound in the background asynchronously. .. versionadded:: 1.4.0 ''' # clean everything before recording in case # there is a different device open self._stop() # create structure and set device parameters open_params = MCI_OPEN_PARMS() open_params.lpstrDeviceType = 'waveaudio' open_params.lpstrElementName = '' # open a new device for recording open_params = send_command( device=0, # device ID before opening msg=MCI_OPEN, # empty filename in lpstrElementName # device type in lpstrDeviceType flags=MCI_OPEN_ELEMENT | MCI_OPEN_TYPE, params=open_params ) # get recorder with device id and path for saving self._recorder = WinRecorder( device=open_params.wDeviceID, filename=self._file_path ) self._recorder.record() # Setting the currently recorded file as current file # for using it as a parameter in audio player self._current_file = self._recorder.filename def _stop(self): ''' Stop recording or playing of a WAV sound. .. versionadded:: 1.4.0 ''' if self._recorder: self._recorder.stop() self._recorder = None if self._player: self._player.stop() self._player = None def _play(self): ''' Play a WAV sound from a file. Prioritize latest recorded file before default file path from WinAudio. .. versionadded:: 1.4.0 ''' # create structure and set device parameters open_params = MCI_OPEN_PARMS() open_params.lpstrDeviceType = 'waveaudio' open_params.lpstrElementName = self._current_file or self._file_path # open a new device for playing open_params = send_command( device=0, # device ID before opening msg=MCI_OPEN, # existing filename in lpstrElementName # device type in lpstrDeviceType flags=MCI_OPEN_ELEMENT | MCI_OPEN_TYPE, params=open_params ) # get recorder with device id and path for saving self._player = WinPlayer(device=open_params.wDeviceID) self._player.play() def instance(): ''' Instance for facade proxy. ''' return WinAudio() plyer-2.1.0/plyer/platforms/win/battery.py000066400000000000000000000015001433372044000206240ustar00rootroot00000000000000''' Module of Windows API for plyer.battery. ''' from plyer.platforms.win.libs.batterystatus import battery_status from plyer.facades import Battery from ctypes.wintypes import BYTE class WinBattery(Battery): ''' Implementation of Windows battery API. ''' def _get_state(self): CHARGING = BYTE(8).value UNKNOWN_STATUS = BYTE(255).value status = {"isCharging": None, "percentage": None} query = battery_status() if not query: return status status["isCharging"] = (query["BatteryFlag"] != UNKNOWN_STATUS) and \ (query["BatteryFlag"] & CHARGING > 0) status["percentage"] = query["BatteryLifePercent"] return status def instance(): ''' Instance for facade proxy. ''' return WinBattery() plyer-2.1.0/plyer/platforms/win/cpu.py000066400000000000000000000147661433372044000177630ustar00rootroot00000000000000''' Module of Windows API for plyer.cpu. ''' from ctypes import ( c_ulonglong, c_ulong, byref, Structure, POINTER, Union, windll, create_string_buffer, sizeof, cast, c_void_p, c_uint32 ) from ctypes.wintypes import ( BYTE, DWORD, WORD ) from plyer.facades import CPU KERNEL = windll.kernel32 ERROR_INSUFFICIENT_BUFFER = 0x0000007A class CacheType: ''' Win API PROCESSOR_CACHE_TYPE enum. ''' unified = 0 instruction = 1 data = 2 trace = 3 class RelationshipType: ''' Win API LOGICAL_PROCESSOR_RELATIONSHIP enum. ''' processor_core = 0 # logical proc sharing single core numa_node = 1 # logical proc sharing single NUMA node cache = 2 # logical proc sharing cache processor_package = 3 # logical proc sharing physical package group = 4 # logical proc sharing processor group all = 0xffff # logical proc info for all groups class CacheDescriptor(Structure): ''' Win API CACHE_DESCRIPTOR struct. ''' _fields_ = [ ('Level', BYTE), ('Associativity', BYTE), ('LineSize', WORD), ('Size', DWORD), ('Type', DWORD) ] class ProcessorCore(Structure): ''' Win API ProcessorCore struct. ''' _fields_ = [('Flags', BYTE)] class NumaNode(Structure): ''' Win API NumaNode struct. ''' _fields_ = [('NodeNumber', DWORD)] class SystemLPIUnion(Union): ''' Win API SYSTEM_LOGICAL_PROCESSOR_INFORMATION union without name. ''' _fields_ = [ ('ProcessorCore', ProcessorCore), ('NumaNode', NumaNode), ('Cache', CacheDescriptor), ('Reserved', c_ulonglong) ] class SystemLPI(Structure): ''' Win API SYSTEM_LOGICAL_PROCESSOR_INFORMATION struct. ''' _fields_ = [ ('ProcessorMask', c_ulong), ('Relationship', c_ulong), ('LPI', SystemLPIUnion) ] class WinCPU(CPU): ''' Implementation of Windows CPU API. ''' @staticmethod def _countbits(mask): # make sure the correct ULONG_PTR size is used on 64bit # https://docs.microsoft.com/en-us/windows/ # desktop/WinProg/windows-data-types # note: not a pointer per-se, != PULONG_PTR ulong_ptr = c_ulonglong if sizeof(c_void_p) == 8 else c_ulong # note: c_ulonglong only on 64bit, otherwise c_ulong # DWORD == c_uint32 # https://docs.microsoft.com/en-us/windows/ # desktop/WinProg/windows-data-types lshift = c_uint32(sizeof(ulong_ptr) * 8 - 1) assert lshift.value in (31, 63), lshift # 32 or 64 bits - 1 lshift = lshift.value test = 1 << lshift assert test % 2 == 0, test count = 0 i = 0 while i <= lshift: i += 1 # do NOT remove!!! # test value has to be %2 == 0, # except the last case where the value is 1, # so that int(test) == int(float(test)) # and the mask bit is counted correctly assert test % 2 == 0 or float(test) == 1.0, test # https://stackoverflow.com/a/1746642/5994041 # note: useful to print(str(bin(int(...)))[2:]) count += 1 if (mask & int(test)) else 0 test /= 2 return count def _logprocinfo(self, relationship): get_logical_process_info = KERNEL.GetLogicalProcessorInformation # first call with no structure to get the real size of the required buff_length = c_ulong(0) result = get_logical_process_info(None, byref(buff_length)) assert not result, result error = KERNEL.GetLastError() assert error == ERROR_INSUFFICIENT_BUFFER, error assert buff_length, buff_length # create buffer from the real winapi buffer length buff = create_string_buffer(buff_length.value) # call again with buffer pointer + the same length as arguments result = get_logical_process_info(buff, byref(buff_length)) assert result, (result, KERNEL.GetLastError()) # memory size of one LPI struct in the array of LPI structs offset = sizeof(SystemLPI) # ok values = { key: 0 for key in ( 'relationship', 'mask', 'L1', 'L2', 'L3' ) } for i in range(0, buff_length.value, offset): slpi = cast( buff[i: i + offset], POINTER(SystemLPI) ).contents if slpi.Relationship != relationship: continue values['relationship'] += 1 values['mask'] += self._countbits(slpi.ProcessorMask) if slpi.LPI.Cache.Level == 1: values['L1'] += 1 elif slpi.LPI.Cache.Level == 2: values['L2'] += 1 elif slpi.LPI.Cache.Level == 3: values['L3'] += 1 return values def _sockets(self): # physical CPU sockets (or slots) on motherboard return self._logprocinfo( RelationshipType.processor_package )['relationship'] def _physical(self): # cores return self._logprocinfo( RelationshipType.processor_core )['relationship'] def _logical(self): # cores * threads # if hyperthreaded core -> more than one logical processor return self._logprocinfo( RelationshipType.processor_core )['mask'] def _cache(self): # L1, L2, L3 cache count result = self._logprocinfo( RelationshipType.cache ) return { key: result[key] for key in result if key in ('L1', 'L2', 'L3') } def _numa(self): # numa nodes return self._logprocinfo( RelationshipType.numa_node )['relationship'] def instance(): ''' Instance for facade proxy. ''' return WinCPU() # Resources: # GetLogicalProcessInformation # https://msdn.microsoft.com/en-us/library/ms683194(v=vs.85).aspx # SYSTEM_LOGICAL_PROCESSOR_INFORMATION # https://msdn.microsoft.com/en-us/library/ms686694(v=vs.85).aspx # LOGICAL_PROCESSOR_RELATIONSHIP enum (0 - 4, 0xffff) # https://msdn.microsoft.com/2ada52f0-70ec-4146-9ef7-9af3b08996f9 # CACHE_DESCRIPTOR struct # https://msdn.microsoft.com/38cfa605-831c-45ef-a99f-55f42b2b56e9 # PROCESSOR_CACHE_TYPE # https://msdn.microsoft.com/23044f67-e944-43c2-8c75-3d2fba87cb3c # C example # https://msdn.microsoft.com/en-us/904d2d35-f419-4e8f-a689-f39ed926644c plyer-2.1.0/plyer/platforms/win/devicename.py000066400000000000000000000006001433372044000212520ustar00rootroot00000000000000''' Module of Win API for plyer.devicename. ''' import socket from plyer.facades import DeviceName class WinDeviceName(DeviceName): ''' Implementation of Linux DeviceName API. ''' def _get_device_name(self): hostname = socket.gethostname() return hostname def instance(): ''' Instance for facade proxy. ''' return WinDeviceName() plyer-2.1.0/plyer/platforms/win/email.py000066400000000000000000000020441433372044000202450ustar00rootroot00000000000000''' Module of Windows API for plyer.email. ''' import os try: from urllib.parse import quote except ImportError: from urllib import quote from plyer.facades import Email class WindowsEmail(Email): ''' Implementation of Windows email API. ''' 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)) # WE + startfile are available only on Windows try: os.startfile(uri) except WindowsError: print("Warning: unable to find a program able to send emails.") def instance(): ''' Instance for facade proxy. ''' return WindowsEmail() plyer-2.1.0/plyer/platforms/win/filechooser.py000066400000000000000000000122651433372044000214660ustar00rootroot00000000000000''' Windows file chooser -------------------- ''' from plyer.facades import FileChooser from win32com.shell.shell import ( SHBrowseForFolder as browse, SHGetPathFromIDList as get_path ) from win32com.shell import shellcon import win32gui import win32con import pywintypes from os.path import dirname, splitext, join, isdir class Win32FileChooser: '''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 Known issues: * non-existins folders such as: Network, Control Panel, My Computer, Trash, Library and likes will raise a COM error. The path does not exist, nor a user can open from or save to such path. ''' path = None multiple = False filters = [] preview = False title = None icon = None show_hidden = False def __init__(self, *args, **kwargs): self._handle_selection = kwargs.pop( 'on_selection', self._handle_selection ) # Simulate Kivy's behavior for i in kwargs: setattr(self, i, kwargs[i]) @staticmethod def _handle_selection(selection): ''' Dummy placeholder for returning selection from chooser. ''' return selection def run(self): self.selection = [] try: if self.mode != "dir": args = {} if self.path: if isdir(self.path): args["InitialDir"] = self.path else: args["InitialDir"] = dirname(self.path) _, ext = splitext(self.path) args["File"] = self.path args["DefExt"] = ext and ext[1:] # no period args["Title"] = self.title if self.title else "Pick a file..." args["CustomFilter"] = 'Other file types\x00*.*\x00' args["FilterIndex"] = 1 file = "" if "File" in args: file = args["File"] args["File"] = file + ("\x00" * 4096) # e.g. open_file(filters=['*.txt', '*.py']) 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_OVERWRITEPROMPT flags |= win32con.OFN_HIDEREADONLY if self.multiple: flags |= win32con.OFN_ALLOWMULTISELECT flags |= win32con.OFN_EXPLORER if self.show_hidden: flags |= win32con.OFN_FORCESHOWHIDDEN args["Flags"] = flags try: if self.mode == "open": self.fname, _, _ = win32gui.GetOpenFileNameW(**args) elif self.mode == "save": self.fname, _, _ = win32gui.GetSaveFileNameW(**args) except pywintypes.error as e: # if canceled, it's not really an error if not e.winerror: self._handle_selection(self.selection) return self.selection raise if self.fname: if self.multiple: seq = str(self.fname).split("\x00") if len(seq) > 1: dir_n, base_n = seq[0], seq[1:] self.selection = [ join(dir_n, i) for i in base_n ] else: self.selection = seq else: self.selection = str(self.fname).split("\x00") else: # dir mode BIF_EDITBOX = shellcon.BIF_EDITBOX BIF_NEWDIALOGSTYLE = 0x00000040 # From http://goo.gl/UDqCqo pidl, name, images = browse( win32gui.GetDesktopWindow(), None, self.title if self.title else "Pick a folder...", BIF_NEWDIALOGSTYLE | BIF_EDITBOX, None, None ) # pidl is None when nothing is selected # and e.g. the dialog is closed afterwards with Cancel if pidl: self.selection = [str(get_path(pidl).decode('utf-8'))] except (RuntimeError, pywintypes.error, Exception): # ALWAYS! let user know what happened import traceback traceback.print_exc() self._handle_selection(self.selection) return self.selection class WinFileChooser(FileChooser): '''FileChooser implementation for Windows, using win3all. ''' def _file_selection_dialog(self, **kwargs): return Win32FileChooser(**kwargs).run() def instance(): return WinFileChooser() plyer-2.1.0/plyer/platforms/win/keystore.py000066400000000000000000000006501433372044000210240ustar00rootroot00000000000000try: import keyring except Exception: raise NotImplementedError() from plyer.facades import Keystore class WinKeystore(Keystore): def _set_key(self, servicename, key, value, **kwargs): keyring.set_password(servicename, key, value) def _get_key(self, servicename, key, **kwargs): return keyring.get_password(servicename, key) def instance(): return WinKeystore() plyer-2.1.0/plyer/platforms/win/libs/000077500000000000000000000000001433372044000175355ustar00rootroot00000000000000plyer-2.1.0/plyer/platforms/win/libs/__init__.py000066400000000000000000000000001433372044000216340ustar00rootroot00000000000000plyer-2.1.0/plyer/platforms/win/libs/balloontip.py000066400000000000000000000144221433372044000222550ustar00rootroot00000000000000# -- coding: utf-8 -- ''' Module of Windows API for creating taskbar balloon tip notification in the taskbar's tray notification area. ''' __all__ = ('WindowsBalloonTip', 'balloon_tip') import time import ctypes import atexit from threading import RLock from plyer.platforms.win.libs import win_api_defs 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: ''' Implementation of balloon tip notifications through Windows API. * Register Window class name: https://msdn.microsoft.com/en-us/library/windows/desktop/ms632596.aspx * Create an overlapped window using the registered class. - It's hidden everywhere in GUI unless ShowWindow(handle, SW_SHOW) function is called. * Show/remove a tray icon and a balloon tip notification. Each instance is a separate notification with different parameters. Can be used with Threads. ''' _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(): ''' Keep track of each created balloon tip notification names, so that they can be easily identified even from outside. Make sure the count is shared between all the instances i.e. use a lock, so that _count class variable is incremented safely when using balloon tip notifications with Threads. ''' 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, **kwargs): ''' The app_icon parameter, if given, is an .ICO file. ''' atexit.register(self.__del__) wnd_class_ex = win_api_defs.get_WNDCLASSEXW() class_name = 'PlyerTaskbar' + str(WindowsBalloonTip._get_unique_id()) 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( # dwExStyle, lpClassName, lpWindowName, dwStyle 0, class_atom, '', WS_OVERLAPPED, # x, y, nWidth, nHeight 0, 0, CW_USEDEFAULT, CW_USEDEFAULT, # hWndParent, hMenu, hInstance, lpParam 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 .ICO file for as balloon tip and tray 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(app_icon)) self._balloon_icon = self._hicon = hicon else: self._hicon = win_api_defs.LoadIconW( None, ctypes.cast(IDI_APPLICATION, win_api_defs.LPCWSTR) ) # show the notification self.notify(title, message, app_name) if timeout: time.sleep(timeout) def __del__(self): ''' Clean visible parts of the notification object, then free all resources allocated for creating the nofitication Window and icon. ''' 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. ''' # remove previous visible balloon tip nofitication if available 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): ''' Instance for balloon tip notification implementation. ''' WindowsBalloonTip(**kwargs) plyer-2.1.0/plyer/platforms/win/libs/batterystatus.py000066400000000000000000000010461433372044000230260ustar00rootroot00000000000000''' Module of Windows API helper for plyer.battery. ''' __all__ = ('battery_status') import ctypes from plyer.platforms.win.libs import win_api_defs def battery_status(): ''' Implementation of Windows system power status API for plyer.battery. ''' 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_ ) plyer-2.1.0/plyer/platforms/win/libs/wifi_defs.py000066400000000000000000000366411433372044000220600ustar00rootroot00000000000000''' Reference Methods, Structures and Documentation adapted from. https://msdn.microsoft.com/en-us/library/windows/desktop \ /ms705945%28v=vs.85%29.aspx ''' from ctypes import ( POINTER, FormatError, Structure, addressof, byref, c_bool, c_char, c_ubyte, c_uint, c_ulong, c_ushort, c_void_p, c_wchar, pointer, windll, ) from ctypes.wintypes import DWORD, HANDLE, LPCWSTR, ULONG from sys import exit as sys_exit def customresize(array, new_size): return ( array._type_ * new_size ).from_address( addressof(array) ) wlanapi = windll.LoadLibrary('wlanapi.dll') class GUID(Structure): _fields_ = [ ('Data1', c_ulong), ('Data2', c_ushort), ('Data3', c_ushort), ('Data4', c_ubyte * 8), ] # The WLAN_INTERFACE_STATE enumerated type indicates the state of an interface. WLAN_INTERFACE_STATE = c_uint (wlan_interface_state_not_ready, wlan_interface_state_connected, wlan_interface_state_ad_hoc_network_formed, wlan_interface_state_disconnecting, wlan_interface_state_disconnected, wlan_interface_state_associating, wlan_interface_state_discovering, wlan_interface_state_authenticating) = map(WLAN_INTERFACE_STATE, range(0, 8)) class WLAN_INTERFACE_INFO(Structure): ''' The WLAN_INTERFACE_STATE enumerated type indicates the state of an interface. ''' _fields_ = [ ("InterfaceGuid", GUID), ("strInterfaceDescription", c_wchar * 256), ("isState", WLAN_INTERFACE_STATE) ] class WLAN_INTERFACE_INFO_LIST(Structure): ''' The WLAN_INTERFACE_INFO_LIST structure contains an array of NIC interface information. ''' _fields_ = [ ("NumberOfItems", DWORD), ("Index", DWORD), ("InterfaceInfo", WLAN_INTERFACE_INFO * 1) ] DOT11_MAC_ADDRESS = c_ubyte * 6 WLAN_MAX_PHY_TYPE_NUMBER = 0x8 DOT11_SSID_MAX_LENGTH = 32 WLAN_REASON_CODE = DWORD DOT11_BSS_TYPE = c_uint (dot11_BSS_type_infrastructure, dot11_BSS_type_independent, dot11_BSS_type_any) = map(DOT11_BSS_TYPE, range(1, 4)) # The DOT11_PHY_TYPE enumeration defines an 802.11 PHY and media type. DOT11_PHY_TYPE = c_uint dot11_phy_type_unknown = 0 dot11_phy_type_any = 0 dot11_phy_type_fhss = 1 dot11_phy_type_dsss = 2 dot11_phy_type_irbaseband = 3 dot11_phy_type_ofdm = 4 dot11_phy_type_hrdsss = 5 dot11_phy_type_erp = 6 dot11_phy_type_ht = 7 dot11_phy_type_IHV_start = 0x80000000 dot11_phy_type_IHV_end = 0xffffffff # The DOT11_AUTH_ALGORITHM enumerated type defines a wireless # LAN authentication algorithm. DOT11_AUTH_ALGORITHM = c_uint DOT11_AUTH_ALGO_80211_OPEN = 1 DOT11_AUTH_ALGO_80211_SHARED_KEY = 2 DOT11_AUTH_ALGO_WPA = 3 DOT11_AUTH_ALGO_WPA_PSK = 4 DOT11_AUTH_ALGO_WPA_NONE = 5 DOT11_AUTH_ALGO_RSNA = 6 DOT11_AUTH_ALGO_RSNA_PSK = 7 DOT11_AUTH_ALGO_IHV_START = 0x80000000 DOT11_AUTH_ALGO_IHV_END = 0xffffffff # The DOT11_CIPHER_ALGORITHM enumerated type defines a cipher # algorithm for data encryption and decryption. DOT11_CIPHER_ALGORITHM = c_uint DOT11_CIPHER_ALGO_NONE = 0x00 DOT11_CIPHER_ALGO_WEP40 = 0x01 DOT11_CIPHER_ALGO_TKIP = 0x02 DOT11_CIPHER_ALGO_CCMP = 0x04 DOT11_CIPHER_ALGO_WEP104 = 0x05 DOT11_CIPHER_ALGO_WPA_USE_GROUP = 0x100 DOT11_CIPHER_ALGO_RSN_USE_GROUP = 0x100 DOT11_CIPHER_ALGO_WEP = 0x101 DOT11_CIPHER_ALGO_IHV_START = 0x80000000 DOT11_CIPHER_ALGO_IHV_END = 0xffffffff class DOT11_SSID(Structure): ''' A DOT11_SSID structure contains the SSID of an interface ''' _fields_ = [ ("SSIDLength", c_ulong), ("SSID", c_char * DOT11_SSID_MAX_LENGTH) ] # Enumerated type to define the code of connection. WLAN_CONNECTION_MODE = c_uint (wlan_connection_mode_profile, wlan_connection_mode_temporary_profile, wlan_connection_mode_discovery_secure, wlan_connection_mode_discovery_unsecure, wlan_connection_mode_auto, wlan_connection_mode_invalid) = map(WLAN_CONNECTION_MODE, range(0, 6)) class NDIS_OBJECT_HEADER(Structure): ''' This Structure packages the object type, version, and size information that is required in many NDIS (Netword Driver interface Specification) Structures. ''' _fields_ = [ ("Type", c_char), ("Revision", c_char), ("Size", c_ushort)] class DOT11_BSSID_LIST(Structure): ''' The DOT11_BSSID_LIST structure contains a list of basic service set (BSS) identifiers. ''' _fields_ = [ ("Header", NDIS_OBJECT_HEADER), ("uNumOfEntries", ULONG), ("uTotalNumOfEntries", ULONG), ("BSSIDs", DOT11_MAC_ADDRESS * 1) ] class WLAN_CONNECTION_PARAMETERS(Structure): ''' The WLAN_CONNECTION_PARAMETERS structure specifies the parameters used when using the WlanConnect function. ''' _fields_ = [ ("wlanConnectionMode", WLAN_CONNECTION_MODE), ("strProfile", LPCWSTR), ("pDot11Ssid", POINTER(DOT11_SSID)), ("pDesiredBssidList", POINTER(DOT11_BSSID_LIST)), ("dot11BssType", DOT11_BSS_TYPE), ("dwFlags", DWORD)] # The `WlanConnect` attempts to connect to a specific network. WlanConnect = wlanapi.WlanConnect WlanConnect.argtypes = (HANDLE, POINTER(GUID), POINTER(WLAN_CONNECTION_PARAMETERS), c_void_p) WlanConnect.restype = DWORD # The `WlanDisconnect` method disconnects an interface from its # current network. WlanDisconnect = wlanapi.WlanDisconnect WlanDisconnect.argtypes = (HANDLE, POINTER(GUID), c_void_p) WlanDisconnect.restype = DWORD # Opens a connection to the server. WlanOpenHandle = wlanapi.WlanOpenHandle WlanOpenHandle.argtypes = (DWORD, c_void_p, POINTER(DWORD), POINTER(HANDLE)) WlanOpenHandle.restype = DWORD # The WlanCloseHandle method closes the connection to the server. WlanCloseHandle = wlanapi.WlanCloseHandle WlanCloseHandle.argtypes = (HANDLE, c_void_p) WlanCloseHandle.restype = DWORD class WLAN_AVAILABLE_NETWORK(Structure): ''' The WLAN_INTERFACE_INFO structure contains information about a wireless LAN interface. ''' _fields_ = [ ("ProfileName", c_wchar * 256), ("dot11Ssid", DOT11_SSID), ("dot11BssType", DOT11_BSS_TYPE), ("NumberOfBssids", c_ulong), ("NetworkConnectable", c_bool), ("wlanNotConnectableReason", WLAN_REASON_CODE), ("NumberOfPhyTypes", c_ulong), ("dot11PhyTypes", DOT11_PHY_TYPE * WLAN_MAX_PHY_TYPE_NUMBER), ("MorePhyTypes", c_bool), ("wlanSignalQuality", c_ulong), ("SecurityEnabled", c_bool), ("dot11DefaultAuthAlgorithm", DOT11_AUTH_ALGORITHM), ("dot11DefaultCipherAlgorithm", DOT11_CIPHER_ALGORITHM), ("Flags", DWORD), ("Reserved", DWORD)] class WLAN_AVAILABLE_NETWORK_LIST(Structure): ''' The WLAN_INTERFACE_INFO_LIST structure contains an array of NIC interface information. ''' _fields_ = [ ("NumberOfItems", DWORD), ("Index", DWORD), ("Network", WLAN_AVAILABLE_NETWORK * 1)] # The WlanEnumInterfaces function enumerates all of the wireless LAN interfaces # currently enabled on the local computer. WlanEnumInterfaces = wlanapi.WlanEnumInterfaces WlanEnumInterfaces.argtypes = (HANDLE, c_void_p, POINTER(POINTER(WLAN_INTERFACE_INFO_LIST))) WlanEnumInterfaces.restype = DWORD # The WlanGetAvailableNetworkList function retrieves the list of available # networks on a wireless LAN interface. WlanGetAvailableNetworkList = wlanapi.WlanGetAvailableNetworkList WlanGetAvailableNetworkList.argtypes = (HANDLE, POINTER(GUID), DWORD, c_void_p, POINTER(POINTER( WLAN_AVAILABLE_NETWORK_LIST))) WlanGetAvailableNetworkList.restype = DWORD # The WlanFreeMemory function frees memory. Any memory returned from Native # Wifi functions must be freed. WlanFreeMemory = wlanapi.WlanFreeMemory WlanFreeMemory.argtypes = [c_void_p] wireless_interfaces = None available = None _dict = {} # Private methods. def _connect(network, parameters): ''' Attempts to connect to a specific network. ''' global _dict wireless_interface = _dict[network] wcp = WLAN_CONNECTION_PARAMETERS() connection_mode = parameters['connection_mode'] wcp.wlanConnectionMode = WLAN_CONNECTION_MODE(connection_mode) if connection_mode == 0 or connection_mode == 1: wcp.strProfile = LPCWSTR(parameters["profile"]) else: wcp.strProfile = None dot11Ssid = DOT11_SSID() try: dot11Ssid.SSID = parameters["ssid"] dot11Ssid.SSIDLength = len(parameters["ssid"]) except KeyError: dot11Ssid.SSID = network dot11Ssid.SSIDLength = len(network) wcp.pDot11Ssid = pointer(dot11Ssid) dot11bssid = DOT11_BSSID_LIST() bssid = parameters["bssidList"] dot11bssid.Header = bssid['Header'] dot11bssid.uNumOfEntries = bssid['uNumOfEntries'] dot11bssid.uTotalNumOfEntries = bssid['uTotalNumOfEntries'] dot11bssid.BSSIDs = bssid['BSSIDs'] wcp.pDesiredBssidList = pointer(dot11bssid) bssType = parameters["bssType"] wcp.dot11BssType = DOT11_BSS_TYPE(bssType) wcp.dwFlags = DWORD(parameters["flags"]) NegotiatedVersion = DWORD() ClientHandle = HANDLE() wlan = WlanOpenHandle(1, None, byref(NegotiatedVersion), byref(ClientHandle)) if wlan: sys_exit(FormatError(wlan)) pInterfaceList = pointer(WLAN_INTERFACE_INFO_LIST()) wlan = WlanEnumInterfaces(ClientHandle, None, byref(pInterfaceList)) if wlan: sys_exit(FormatError(wlan)) try: wlan = WlanConnect(ClientHandle, wireless_interface, wcp, None) if wlan: sys_exit(FormatError(wlan)) WlanCloseHandle(ClientHandle, None) finally: WlanFreeMemory(pInterfaceList) def _disconnect(): ''' To disconnect an interface form the current network. ''' NegotiatedVersion = DWORD() ClientHandle = HANDLE() wlan = WlanOpenHandle( 1, None, byref(NegotiatedVersion), byref(ClientHandle) ) if wlan: sys_exit(FormatError(wlan)) pInterfaceList = pointer(WLAN_INTERFACE_INFO_LIST()) wlan = WlanEnumInterfaces(ClientHandle, None, byref(pInterfaceList)) if wlan: sys_exit(FormatError(wlan)) result = None try: ifaces = customresize( pInterfaceList.contents.InterfaceInfo, pInterfaceList.contents.NumberOfItems ) # find each available network for each interface for iface in ifaces: wlan = WlanDisconnect( ClientHandle, byref(iface.InterfaceGuid), None ) if wlan: sys_exit(FormatError(wlan)) WlanCloseHandle(ClientHandle, None) finally: WlanFreeMemory(pInterfaceList) result = get_available_wifi() return result def _start_scanning(): ''' Private method for scanning and returns the available devices. ''' global available global wireless_interfaces NegotiatedVersion = DWORD() ClientHandle = HANDLE() wlan = WlanOpenHandle( 1, None, byref(NegotiatedVersion), byref(ClientHandle) ) if wlan: sys_exit(FormatError(wlan)) # find all wireless network interfaces pInterfaceList = pointer(WLAN_INTERFACE_INFO_LIST()) wlan = WlanEnumInterfaces(ClientHandle, None, byref(pInterfaceList)) if wlan: sys_exit(FormatError(wlan)) result = None try: ifaces = customresize( pInterfaceList.contents.InterfaceInfo, pInterfaceList.contents.NumberOfItems ) # find each available network for each interface wireless_interfaces = ifaces for iface in ifaces: pAvailableNetworkList = pointer(WLAN_AVAILABLE_NETWORK_LIST()) wlan = WlanGetAvailableNetworkList( ClientHandle, byref(iface.InterfaceGuid), 0, None, byref(pAvailableNetworkList) ) if wlan: sys_exit(FormatError(wlan)) try: avail_net_list = pAvailableNetworkList.contents networks = customresize( avail_net_list.Network, avail_net_list.NumberOfItems ) # Assigning the value of networks to the global variable # `available`, so it could be used in other methods. available = networks _make_dict() wlan = WlanDisconnect( ClientHandle, byref(iface.InterfaceGuid), None ) if wlan: sys_exit(FormatError(wlan)) WlanCloseHandle(ClientHandle, None) finally: WlanFreeMemory(pAvailableNetworkList) finally: WlanFreeMemory(pInterfaceList) result = get_available_wifi() return result def _get_network_info(name): ''' returns the list of the network selected in a dict. ''' global available global _dict net = _dict[name] dot11BssType = net.dot11BssType dot11DefaultAuthAlgorithm = net.dot11DefaultAuthAlgorithm dot11DefaultCipherAlgorithm = net.dot11DefaultCipherAlgorithm dot11PhyTypes = net.dot11PhyTypes[0] dot11Ssid = net.dot11Ssid wlanNotConnectableReason = net.wlanNotConnectableReason wlanSignalQuality = net.wlanSignalQuality return {"dot11BssType": dot11BssType, "dot11DefaultAuthAlgorithm": dot11DefaultAuthAlgorithm, "dot11DefaultCipherAlgorithm": dot11DefaultCipherAlgorithm, "dot11PhyTypes": dot11PhyTypes, "SSID": dot11Ssid.SSID, "SSIDLength": dot11Ssid.SSIDLength, "wlanNotConnectableReason": wlanNotConnectableReason, "wlanSignalQuality": wlanSignalQuality} def _make_dict(): ''' Prepares a dict so it could store network information. ''' global available global _dict _dict = {} for network in available: _dict[network.dot11Ssid.SSID.decode('utf-8')] = network def _get_available_wifi(): ''' returns the available wifi networks. ''' global _dict return _dict def _is_enabled(): ''' Reason for returning true is explained in widi facade. /plyer/facades/wifi.py ''' return True # public methods. def is_enabled(): ''' calls private method `_is_enabled` and returns the result. ''' return _is_enabled() def connect(network, parameters): ''' Connect to a network with following parameters. ''' _connect(network=network, parameters=parameters) def disconnect(): ''' Disconnect from a network. ''' _disconnect() def start_scanning(): ''' Start scanning for available wifi networks available. ''' return _start_scanning() def get_network_info(name): ''' return the wifi network info. ''' return _get_network_info(name=name) def get_available_wifi(): ''' return the available wifi networks available ''' return _get_available_wifi() plyer-2.1.0/plyer/platforms/win/libs/win_api_defs.py000077500000000000000000000152731433372044000225510ustar00rootroot00000000000000''' 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', 'get_PATH') 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(Structure): _fields_ = [ ('ACLineStatus', BYTE), ('BatteryFlag', BYTE), ('BatteryLifePercent', BYTE), ('Reserved1', BYTE), ('BatteryLifeTime', DWORD), ('BatteryFullLifeTime', DWORD), ] SystemPowerStatusP = POINTER(SYSTEM_POWER_STATUS) GetSystemPowerStatus = windll.kernel32.GetSystemPowerStatus GetSystemPowerStatus.argtypes = [SystemPowerStatusP] GetSystemPowerStatus.restype = BOOL class GUID_(Structure): _fields_ = [ ('Data1', DWORD), ('Data2', WORD), ('Data3', WORD), ('Data4', BYTE * 8) ] def __init__(self, uuid_): Structure.__init__(self) self.Data1, self.Data2, self.Data3, self.Data4[0], self.Data4[1], rest\ = uuid_.fields for i in range(2, 8): self.Data4[i] = rest >> (8 - i - 1) * 8 & 0xff _CoTaskMemFree = windll.ole32.CoTaskMemFree _CoTaskMemFree.restype = None _CoTaskMemFree.argtypes = [ctypes.c_void_p] _SHGetKnownFolderPath = windll.shell32.SHGetKnownFolderPath _SHGetKnownFolderPath.argtypes = [ POINTER(GUID_), DWORD, HANDLE, POINTER(ctypes.c_wchar_p) ] class PathNotFoundException(Exception): pass def get_PATH(folderid): fid = GUID_(folderid) pPath = ctypes.c_wchar_p() S_OK = 0 Result = _SHGetKnownFolderPath(ctypes.byref(fid), 0, None, ctypes.byref(pPath)) if Result != S_OK: raise PathNotFoundException() path = pPath.value _CoTaskMemFree(pPath) return path plyer-2.1.0/plyer/platforms/win/notification.py000066400000000000000000000007631433372044000216520ustar00rootroot00000000000000''' Module of Windows API for plyer.notification. ''' from threading import Thread as thread from plyer.facades import Notification from plyer.platforms.win.libs.balloontip import balloon_tip class WindowsNotification(Notification): ''' Implementation of Windows notification/balloon API. ''' def _notify(self, **kwargs): thread(target=balloon_tip, kwargs=kwargs).start() def instance(): ''' Instance for facade proxy. ''' return WindowsNotification() plyer-2.1.0/plyer/platforms/win/screenshot.py000066400000000000000000000065101433372044000213350ustar00rootroot00000000000000''' https://docs.microsoft.com/en-us/windows/desktop/api/wingdi/nf-wingdi-bitblt https://www.bugs.python.org/issue33656 ''' from os import getpid from os.path import join from ctypes import windll, c_int, addressof from win32gui import GetDesktopWindow, GetWindowDC from win32api import GetSystemMetrics from win32ui import CreateDCFromHandle, CreateBitmap from win32con import ( SM_CXVIRTUALSCREEN, SM_CYVIRTUALSCREEN, SM_XVIRTUALSCREEN, SM_YVIRTUALSCREEN, SRCCOPY ) from plyer.facades import Screenshot from plyer.platforms.win.storagepath import WinStoragePath class WinScreenshot(Screenshot): def __init__(self, file_path=None): default_path = join( WinStoragePath().get_pictures_dir(), 'screenshot.bmp' ) super().__init__(file_path or default_path) def _set_dpi_aware(self, value): try: windll.shcore.SetProcessDpiAwareness(value) except (AttributeError, OSError): print('Could not set DPI awareness.') def _dpi_aware(self): # make backup of DPI awareness value in case a user does not want # to use it, otherwise we'll cripple user's runtime try: process_handle = windll.kernel32.OpenProcess( 0, # no permissions False, # bInheritHandle getpid() ) aware = c_int() windll.shcore.GetProcessDpiAwareness( process_handle, addressof(aware) ) finally: # always close the handle! windll.kernel32.CloseHandle(process_handle) return bool(aware) def _capture(self): # make sure the process is DPI aware, otherwise the image # is only a part of the full monitor content # (necessary only on Win 8.1+) old_awareness = self._dpi_aware() self._set_dpi_aware(True) # get width and height of current monitor width = GetSystemMetrics(SM_CXVIRTUALSCREEN) height = GetSystemMetrics(SM_CYVIRTUALSCREEN) # get 'desktop' window handle handle_desktop = GetDesktopWindow() # get window graphic context handle handle_context = GetWindowDC(handle_desktop) # create device context dev_ctx = CreateDCFromHandle(handle_context) # create destination for original device context dest_ctx = dev_ctx.CreateCompatibleDC() # create bitmap compatible with desktop window device bmp = CreateBitmap() bmp.CreateCompatibleBitmap(dev_ctx, width, height) # select bitmap into destination device dest_ctx.SelectObject(bmp) # populate selected bitmap in destination device dest_ctx.BitBlt( (0, 0), # start point (x, y) (width, height), # size of rectangle dev_ctx, # source device ( GetSystemMetrics(SM_XVIRTUALSCREEN), GetSystemMetrics(SM_YVIRTUALSCREEN) ), # source rectangle, can be different monitor SRCCOPY # copy directly without filters ) # save bitmap to file bmp.SaveBitmapFile(dest_ctx, self.file_path) # return to the original state self._set_dpi_aware(old_awareness) def instance(): return WinScreenshot() plyer-2.1.0/plyer/platforms/win/storagepath.py000077500000000000000000000027031433372044000215040ustar00rootroot00000000000000''' Windows Storage Path -------------------- ''' from plyer.facades import StoragePath from os.path import expanduser from plyer.platforms.win.libs.win_api_defs import get_PATH from uuid import UUID class WinStoragePath(StoragePath): def _get_home_dir(self): return expanduser('~') def _get_external_storage_dir(self): ''' To do. ''' return "Method not implemented for current platform." def _get_root_dir(self): folderid = UUID('{F38BF404-1D43-42F2-9305-67DE0B28FC23}') return get_PATH(folderid) def _get_documents_dir(self): folderid = UUID('{FDD39AD0-238F-46AF-ADB4-6C85480369C7}') return get_PATH(folderid) def _get_downloads_dir(self): folderid = UUID('{374DE290-123F-4565-9164-39C4925E467B}') return get_PATH(folderid) def _get_videos_dir(self): folderid = UUID('{18989B1D-99B5-455B-841C-AB7C74E4DDFC}') return get_PATH(folderid) def _get_music_dir(self): folderid = UUID('{4BD8D571-6D19-48D3-BE97-422220080E43}') return get_PATH(folderid) def _get_pictures_dir(self): folderid = UUID('{33E28130-4E1E-4676-835A-98395C3BC3BB}') return get_PATH(folderid) def _get_application_dir(self): folderid = UUID('{905E63B6-C1BF-494E-B29C-65B732D3D21A}') return get_PATH(folderid) def instance(): return WinStoragePath() plyer-2.1.0/plyer/platforms/win/tts.py000066400000000000000000000005511433372044000177710ustar00rootroot00000000000000import 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() plyer-2.1.0/plyer/platforms/win/uniqueid.py000066400000000000000000000014211433372044000207770ustar00rootroot00000000000000''' Module of Windows API for plyer.uniqueid. ''' try: import _winreg as regedit except ImportError: try: import winreg as regedit except ImportError: raise NotImplementedError() from plyer.facades import UniqueID class WinUniqueID(UniqueID): ''' Implementation of Windows battery API. ''' def _get_uid(self): # Win XP+, REG QUERY KEY /V VALUE, case-insensitive handle = regedit.OpenKey( regedit.HKEY_LOCAL_MACHINE, r"SOFTWARE\\Microsoft\\Cryptography", 0, regedit.KEY_READ | regedit.KEY_WOW64_64KEY ) value, _ = regedit.QueryValueEx(handle, "MachineGuid") return value def instance(): ''' Instance for facade proxy. ''' return WinUniqueID() plyer-2.1.0/plyer/platforms/win/wifi.py000066400000000000000000000104741433372044000201220ustar00rootroot00000000000000import plyer.platforms.win.libs.wifi_defs as wifi_lib from plyer.facades import Wifi class WindowWifi(Wifi): names = {} def _is_enabled(self): ''' TODO: Implement this in future Couldn't find a nice implementation for this although NetworkInformation class could be used but ctypes doesn't supports class yet. It should look something like this. for item in NetworkInformation.getConnectionProfiles(): if item.IsWlanConnectionProfile: adapter_id = item.NetworkAdapter.NetworkAdapterId for item in NetworkInformation.GetLanIdentifiers(): if item.NetworkAdapterId == adapter_id: is_wifi_enabled = True return True/False Returning True for now to make it work. ''' return True def _get_network_info(self, name): ''' Returns all the network information. ''' return wifi_lib.get_network_info(name) def _start_scanning(self): ''' Starts scanning for available Wi-Fi networks and returns the available, devices. ''' if self._is_enabled(): self.names = wifi_lib.start_scanning() else: raise Exception('Wifi not Enabled.') def _get_available_wifi(self): ''' Returns the name of available networks. ''' return wifi_lib.get_available_wifi() def _connect(self, network, parameters): ''' Expects 2 parameters: - name/ssid of the network. - parameters: dict type - connection_mode: `https://msdn.microsoft.com/en-us/library/windows/desktop/ ms706844(v=vs.85).aspx` :between range [0, 5] - wlan_connection_mode_profile, wlan_connection_mode_temporary_profile, wlan_connection_mode_discovery_secure, wlan_connection_mode_discovery_unsecure, wlan_connection_mode_auto, wlan_connection_mode_invalid. - profile: if wlanConnectionMode = wlan_connection_mode_profile then profile = ssid if wlanConnectionMode = wlan_connection_mode_temporary_profile then profile = XML representation of the profile used for the connection if wlanConnectionMode = wlan_connection_mode_discovery_secure or wlan_connection_mode_discovery_unsecure then profile = None - ssid: optional (as network name and ssid are same) - bssidList `https://msdn.microsoft.com/en-us/library/windows/desktop/ ms705996(v=vs.85).aspx` - Header structure that contains the type, version, and, size information of an NDIS structure. - Type: NDSI_OBJECT_TYPE_DEFAULT - Revision: DOT11_BSSID_LIST_REVISION_1 - Size: sizeof(DOT11_BSSID_LIST) - uNumOfEntries The number of entries in this structure. - uTotalNumOfEntries The total number of entries supported. - BSSIDs `https://msdn.microsoft.com/en-us/library/windows/desktop/ bb427397(v=vs.85).aspx` A list of BSS identifiers. - bssType `https://msdn.microsoft.com/en-us/library/windows/desktop/ ms706001(v=vs.85).aspx` Constants: dot11_BSS_type_infrastructure = 1, dot11_BSS_type_independent = 2, dot11_BSS_type_any = 3 - flags Constant: WLAN_CONNECTION_HIDDEN_NETWORK value: 0x00000001 Constant: WLAN_CONNECTION_ADHOC_JOIN_ONLY value: 0x00000002 Constant: WLAN_CONNECTION_IGNORE_PRIVACY_BIT value: 0x00000004 Constant: WLAN_CONNECTION_EAPOL_PASSTHROUGH value: 0x00000008 - password ''' wifi_lib.connect(network, parameters) return def _disconnect(self): ''' Disconnect from network. ''' wifi_lib.disconnect() return def instance(): return WindowWifi() plyer-2.1.0/plyer/tests/000077500000000000000000000000001433372044000151425ustar00rootroot00000000000000plyer-2.1.0/plyer/tests/__init__.py000066400000000000000000000000001433372044000172410ustar00rootroot00000000000000plyer-2.1.0/plyer/tests/common.py000066400000000000000000000036721433372044000170140ustar00rootroot00000000000000''' Common objects for testing ========================== * :class:`PlatformTest` - used as a decorator, allows running a test function only on a specific platform (see `plyer.utils.platform`). * :func:`platform_import` - manual import of a platform specific class instead of using `plyer.facades.*` proxies. ''' import traceback from os import sep from os.path import normpath, splitdrive from plyer.utils import platform as plyer_platform class PlatformTest: ''' Class for the @PlatformTest decorator to prevent running tests calling platform dependent API on different platforms. ''' def __init__(self, platform): self.platform = platform def __call__(self, func): platform = self.platform if platform != plyer_platform: print("Skipping test '{}' - not on '{}'".format( func.__name__, platform )) func = self.eat return func @staticmethod def eat(*args, **kwargs): ''' Simply eat all positional and keyword arguments and return None as an empty function. ''' def platform_import(platform, module_name, whereis_exe=None): ''' Import platform API directly instead of through Proxy. ''' try: module = 'plyer.platforms.{}.{}'.format( platform, module_name ) mod = __import__(module, fromlist='.') except ImportError as exc: print(vars(exc)) traceback.print_exc() if whereis_exe: mod.whereis_exe = whereis_exe return mod def splitpath(path): ''' Split string path into a list of folders (+ file if available). ''' if path[0] == sep and path[1] != sep: path = path[1:] path = normpath(path).split(sep) else: drive, path = splitdrive(path) if path[0] == sep and path[1] != sep: path = path[1:] path = [drive, ] + normpath(path).split(sep) return path plyer-2.1.0/plyer/tests/images/000077500000000000000000000000001433372044000164075ustar00rootroot00000000000000plyer-2.1.0/plyer/tests/images/kivy32.ico000066400000000000000000000102761433372044000202400ustar00rootroot00000000000000  ( @  875L=<:<<:@?=@?=<;:=<:764M ++)1?>BA?BA?BA?BA?BA?BA?BA?BA?BA?>=;654N$#"=<:BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?>=;!! 10/AA@>BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?A@>542P<;9jBA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?:98l320>BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?654N875A@?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?A@>&%$A@>BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA??><653'BA?BA?BA?BA?BA?BA?BA?ddbBA?BA?BA?ba_BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?<;9G@?=BA?BA?BA?BA?BA?BA?ponBA?BA?ba_BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?@?=BA?BA?BA?BA?BA?BA?ponBA?ba_BA?BA?BA?BA?BA?BA?BA?BA?BA?BA??>?>@?=BA?BA?BA?BA?ponponBA?BA?BA?BA?@?=A@>qBA?BA?BA?BA?ponponmljBA?BA?BA?A@>A@>ABA?BA?BA?BA?ponddbponBA?BA?BA?BA?BA?BA?WWUmljwwuBA?BA?BA?@?=BBA?BA?BA?BA?ponddbBA?ponBA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?ponddbBA?BA?ponBA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?A@> BA?BA?BA?ddbddbBA?BA?BA?ddbBA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?>=;"BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?A@>A@>BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?;:8@?=2BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA??><2?><2BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?A@>a@?=1BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA??>=2BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA? BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?BA?A@> BA?@BA?pBA?A@>A@>BA?BA?pBA?@???plyer-2.1.0/plyer/tests/test_audio.py000066400000000000000000000050321433372044000176540ustar00rootroot00000000000000''' TestAudio ========= Tested platforms: * macOS * Windows .. versionadded:: 1.4.0 ''' import unittest import time from os import mkdir, remove, environ from os.path import join, expanduser, exists from plyer.tests.common import platform_import, PlatformTest class TestAudio(unittest.TestCase): ''' TestCase for plyer.audio. .. versionadded:: 1.4.0 ''' @PlatformTest('macosx') def test_audio_macosx(self): ''' Test macOS audio start, stop and play .. versionadded:: 1.4.0 ''' path = join(expanduser('~'), 'Music') if not exists(path): mkdir(path) audio = platform_import( platform='macosx', module_name='audio', ) self.assertIn('OSXAudio', dir(audio)) audio = audio.instance() self.assertIn('OSXAudio', str(audio)) self.assertFalse(exists(audio.file_path)) self.assertIsNone(audio.start()) time.sleep(0.5) self.assertIsNone(audio.stop()) self.assertIsNone(audio.play()) time.sleep(0.5) self.assertIsNone(audio.stop()) audio.file_path = audio.file_path.replace( 'file://', '' ) self.assertTrue(exists(audio.file_path)) remove(audio.file_path) @PlatformTest('win') def test_audio_win(self): ''' Test Windows audio start, stop and play .. versionadded:: 1.4.0 ''' if environ.get('APPVEYOR'): # Appveyor has no recording device installed # therefore the test will 100% fail # # error_code: 328 # message: # 'No wave device is installed that can record files in the current # format. To install a wave device, go to Control Panel, click P') return path = join(environ['USERPROFILE'], 'Music') if not exists(path): mkdir(path) audio = platform_import( platform='win', module_name='audio', ) self.assertIn('WinAudio', dir(audio)) audio = audio.instance() self.assertIn('WinAudio', str(audio)) self.assertFalse(exists(audio.file_path)) self.assertIsNone(audio.start()) time.sleep(0.5) self.assertIsNone(audio.stop()) self.assertIsNone(audio.play()) time.sleep(0.5) self.assertIsNone(audio.stop()) self.assertTrue(exists(audio.file_path)) remove(audio.file_path) if __name__ == '__main__': unittest.main() plyer-2.1.0/plyer/tests/test_battery.py000066400000000000000000000275631433372044000202420ustar00rootroot00000000000000''' TestBattery =========== Tested platforms: * Windows * Linux - upower, kernel sysclass * macOS - ioreg ''' import unittest from io import BytesIO from os.path import join from textwrap import dedent from mock import patch, Mock from plyer.tests.common import PlatformTest, platform_import class MockedKernelSysclass: ''' Mocked object used instead of Linux's sysclass for power_supply battery uevent. ''' @property def path(self): ''' Mocked path to Linux kernel sysclass. ''' return join('/sys', 'class', 'power_supply', 'BAT0') @property def charging(self): ''' Mocked battery charging status. ''' return u'Discharging' @property def percentage(self): ''' Mocked battery charge percentage. ''' return 89.0 @property def full(self): ''' Mocked full battery charge. ''' return 4764000 @property def now(self): ''' Calculated current mocked battery charge. ''' return self.percentage * self.full / 100.0 @property def uevent(self): ''' Mocked /sys/class/power_supply/BAT0 file. ''' return BytesIO(dedent(b'''\ POWER_SUPPLY_NAME=BAT0 POWER_SUPPLY_STATUS={} POWER_SUPPLY_PRESENT=1 POWER_SUPPLY_TECHNOLOGY=Li-ion POWER_SUPPLY_CYCLE_COUNT=0 POWER_SUPPLY_VOLTAGE_MIN_DESIGN=10800000 POWER_SUPPLY_VOLTAGE_NOW=12074000 POWER_SUPPLY_CURRENT_NOW=1584000 POWER_SUPPLY_CHARGE_FULL_DESIGN=5800000 POWER_SUPPLY_CHARGE_FULL={} POWER_SUPPLY_CHARGE_NOW={} POWER_SUPPLY_CAPACITY={} POWER_SUPPLY_CAPACITY_LEVEL=Normal POWER_SUPPLY_MODEL_NAME=1005HA POWER_SUPPLY_MANUFACTURER=ASUS POWER_SUPPLY_SERIAL_NUMBER=0 '''.decode('utf-8').format( self.charging, self.full, self.now, int(self.percentage) )).encode('utf-8')) class MockedUPower: ''' Mocked object used instead of 'upower' binary in the Linux specific API plyer.platforms.linux.battery. The same output structure is tested for the range of . .. note:: Extend the object with another data sample if it does not match. ''' min_version = '0.99.4' max_version = '0.99.4' values = { u'Device': u'/org/freedesktop/UPower/devices/battery_BAT0', u'native-path': u'BAT0', u'vendor': u'ASUS', u'model': u'1005HA', u'power supply': u'yes', u'updated': u'Thu 05 Jul 2018 23:15:01 PM CEST', u'has history': u'yes', u'has statistics': u'yes', u'battery': { u'present': u'yes', u'rechargeable': u'yes', u'state': u'discharging', u'warning-level': u'none', u'energy': u'48,708 Wh', u'energy-empty': u'0 Wh', u'energy-full': u'54,216 Wh', u'energy-full-design': u'62,64 Wh', u'energy-rate': u'7,722 W', u'voltage': u'11,916 V', u'time to empty': u'6,3 hours', u'percentage': u'89%', u'capacity': u'86,5517%', u'technology': u'lithium-ion', u'icon-name': u"'battery-full-symbolic" }, u'History (charge)': u'1530959637 89,000 discharging', u'History (rate)': u'1530958556 7,474 discharging' } data = str( ' native-path: {native-path}\n' ' vendor: {vendor}\n' ' model: {model}\n' ' power supply: {power supply}\n' ' updated: {updated}\n' ' has history: {has history}\n' ' has statistics: {has statistics}\n' ' battery\n' ' present: {battery[present]}\n' ' rechargeable: {battery[rechargeable]}\n' ' state: {battery[state]}\n' ' warning-level: {battery[warning-level]}\n' ' energy: {battery[energy]}\n' ' energy-empty: {battery[energy-empty]}\n' ' energy-full: {battery[energy-full]}\n' ' energy-full-design: {battery[energy-full-design]}\n' ' energy-rate: {battery[energy-rate]}\n' ' voltage: {battery[voltage]}\n' ' time to empty: {battery[time to empty]}\n' ' percentage: {battery[percentage]}\n' ' capacity: {battery[capacity]}\n' ' technology: {battery[technology]}\n' ' icon-name: {battery[icon-name]}\n' ' History (charge):\n' ' {History (charge)}\n' ' History (rate):\n' ' {History (rate)}\n' ).format(**values).encode('utf-8') # LinuxBattery calls decode() def __init__(self, *args, **kwargs): # only to ignore all args, kwargs pass @staticmethod def communicate(): ''' Mock Popen.communicate, so that 'upower' isn't used. ''' return (MockedUPower.data, ) @staticmethod def whereis_exe(binary): ''' Mock whereis_exe, so that it looks like Linux UPower binary is present on the system. ''' return binary == 'upower' @staticmethod def charging(): ''' Return charging bool from mocked data. ''' return MockedUPower.values['battery']['state'] == 'charging' @staticmethod def percentage(): ''' Return percentage from mocked data. ''' percentage = MockedUPower.values['battery']['percentage'][:-1] return float(percentage.replace(',', '.')) class MockedIOReg: ''' Mocked object used instead of Apple's ioreg. ''' values = { "MaxCapacity": "5023", "CurrentCapacity": "4222", "IsCharging": "No" } output = dedent( """+-o AppleSmartBattery {{ "TimeRemaining" = 585 "AvgTimeToEmpty" = 585 "InstantTimeToEmpty" = 761 "ExternalChargeCapable" = Yes "FullPathUpdated" = 1541845134 "CellVoltage" = (4109,4118,4099,0) "PermanentFailureStatus" = 0 "BatteryInvalidWakeSeconds" = 30 "AdapterInfo" = 0 "MaxCapacity" = {MaxCapacity} "Voltage" = 12326 "DesignCycleCount70" = 13 "Manufacturer" = "SWD" "Location" = 0 "CurrentCapacity" = {CurrentCapacity} "LegacyBatteryInfo" = {{"Amperage"=18446744073709551183,"Flags"=4,\ "Capacity"=5023,"Current"=4222,"Voltage"=12326,"Cycle Count"=40}} "FirmwareSerialNumber" = 1 "BatteryInstalled" = Yes "PackReserve" = 117 "CycleCount" = 40 "DesignCapacity" = 5088 "OperationStatus" = 58435 "ManufactureDate" = 19700 "AvgTimeToFull" = 65535 "BatterySerialNumber" = "1234567890ABCDEFGH" "BootPathUpdated" = 1541839734 "PostDischargeWaitSeconds" = 120 "Temperature" = 3038 "UserVisiblePathUpdated" = 1541845194 "InstantAmperage" = 18446744073709551249 "ManufacturerData" = <000000000> "FullyCharged" = No "MaxErr" = 1 "DeviceName" = "bq20z451" "IOGeneralInterest" = "IOCommand is not serializable" "Amperage" = 18446744073709551183 "IsCharging" = {IsCharging} "DesignCycleCount9C" = 1000 "PostChargeWaitSeconds" = 120 "ExternalConnected" = No }}""" ).format(**values).encode('utf-8') def __init__(self, *args, **kwargs): # only to ignore all args, kwargs pass @staticmethod def communicate(): ''' Mock Popen.communicate, so that 'ioreg' isn't used. ''' return (MockedIOReg.output, ) @staticmethod def whereis_exe(binary): ''' Mock whereis_exe, so that it looks like macOS ioreg binary is present on the system. ''' return binary == 'ioreg' @staticmethod def charging(): ''' Return charging bool from mocked data. ''' return MockedIOReg.values['IsCharging'] == 'Yes' @staticmethod def percentage(): ''' Return percentage from mocked data. ''' current_capacity = int(MockedIOReg.values['CurrentCapacity']) max_capacity = int(MockedIOReg.values['MaxCapacity']) percentage = 100.0 * current_capacity / max_capacity return percentage class TestBattery(unittest.TestCase): ''' TestCase for plyer.battery. ''' def test_battery_linux_upower(self): ''' Test mocked Linux UPower for plyer.battery. ''' battery = platform_import( platform='linux', module_name='battery', whereis_exe=MockedUPower.whereis_exe ) battery.Popen = MockedUPower battery = battery.instance() self.assertEqual( battery.status, { 'isCharging': MockedUPower.charging(), 'percentage': MockedUPower.percentage() } ) def test_battery_linux_kernel(self): ''' Test mocked Linux kernel sysclass for plyer.battery. ''' def false(*args, **kwargs): return False sysclass = MockedKernelSysclass() with patch(target='os.path.exists') as bat_path: # first call to trigger exists() call platform_import( platform='linux', module_name='battery', whereis_exe=false ).instance() bat_path.assert_called_once_with(sysclass.path) # exists() checked with sysclass path # set mock to proceed with this branch bat_path.return_value = True battery = platform_import( platform='linux', module_name='battery', whereis_exe=false ).instance() stub = Mock(return_value=sysclass.uevent) target = 'builtins.open' with patch(target=target, new=stub): self.assertEqual( battery.status, { 'isCharging': sysclass.charging == 'Charging', 'percentage': sysclass.percentage } ) @PlatformTest('win') def test_battery_win(self): ''' Test Windows API for plyer.battery. ''' battery = platform_import( platform='win', module_name='battery' ).instance() for key in ('isCharging', 'percentage'): self.assertIn(key, battery.status) self.assertIsNotNone(battery.status[key]) def test_battery_macosx(self): ''' Test macOS IOReg for plyer.battery. ''' battery = platform_import( platform='macosx', module_name='battery', whereis_exe=MockedIOReg.whereis_exe ) battery.Popen = MockedIOReg self.assertIn('OSXBattery', dir(battery)) battery = battery.instance() self.assertIn('OSXBattery', str(battery)) self.assertEqual( battery.status, { 'isCharging': MockedIOReg.charging(), 'percentage': MockedIOReg.percentage() } ) def test_battery_macosx_instance(self): ''' Test macOS instance for plyer.battery ''' def no_exe(*args, **kwargs): return battery = platform_import( platform='macosx', module_name='battery', whereis_exe=no_exe ) battery = battery.instance() self.assertNotIn('OSXBattery', str(battery)) self.assertIn('Battery', str(battery)) if __name__ == '__main__': unittest.main() plyer-2.1.0/plyer/tests/test_bluetooth.py000066400000000000000000000075031433372044000205650ustar00rootroot00000000000000''' TestBluetooth ============= Tested platforms: * macOS - system_profiler ''' import unittest from plyer.tests.common import platform_import from textwrap import dedent class MockedSystemProfiler: ''' Mocked object used instead of Apple's system_profiler ''' value = "On" output = dedent( """Bluetooth: Apple Bluetooth Software Version: 6.0.7f11 Hardware, Features, and Settings: Address: AA-00-BB-11-CC-22 Bluetooth Low Energy Supported: Yes Handoff Supported: Yes Instant Hot Spot Supported: Yes Manufacturer: Broadcom Transport: UART Chipset: 1234 Firmware Version: v00 c0000 Bluetooth Power: {} Auto Seek Pointing: On Remote wake: On Vendor ID: 0x0000 Product ID: 0x0000 HCI Version: (0x0) HCI Revision: 0x0000 LMP Version: (0x0) LMP Subversion: 0x0000 Auto Seek Keyboard: On Devices (Paired, Configured, etc.): iPhone: Address: AA-00-BB-11-CC-22 Major Type: Miscellaneous Minor Type: Unknown Services: Paired: No Configured: Yes Connected: No Class of Device: 0x00 0x00 0x0000 Services: Bluetooth File Transfer: Folder other devices can browse: ~/Public When receiving items: Accept all without warning State: Disabled Bluetooth File Exchange: Folder for accepted items: ~/Downloads When other items are accepted: Save to location When receiving items: Accept all without warning State: Disabled Bluetooth Internet Sharing: State: Disabled Incoming Serial Ports: Bluetooth-Incoming-Port: RFCOMM Channel: 3 Requires Authentication: No""" ).format(value).encode('utf-8') def __init__(self, *args, **kwargs): # only to ignore all args, kwargs pass @staticmethod def communicate(): ''' Mock Popen.communicate, so that 'system_profiler' isn't used. ''' return (MockedSystemProfiler.output, ) @staticmethod def whereis_exe(binary): ''' Mock whereis_exe, so that it looks like macOS system_profiler binary is present on the system. ''' return binary == 'system_profiler' @staticmethod def get_info(): ''' Return current bluetooth status from mocked output. ''' return MockedSystemProfiler.value class TestBluetooth(unittest.TestCase): ''' TestCase for plyer.bluetooth. ''' def test_bluetooth_macosx(self): ''' Test macOS system_profiler for plyer.bluetooth. ''' bluetooth = platform_import( platform='macosx', module_name='bluetooth', whereis_exe=MockedSystemProfiler.whereis_exe ) bluetooth.Popen = MockedSystemProfiler self.assertIn('OSXBluetooth', dir(bluetooth)) bluetooth = bluetooth.instance() self.assertIn('OSXBluetooth', str(bluetooth)) self.assertEqual( bluetooth.info, MockedSystemProfiler.get_info() ) def test_bluetooth_macosx_instance(self): ''' Test macOS instance for plyer.bluetooth. ''' def no_exe(*args, **kwargs): return bluetooth = platform_import( platform='macosx', module_name='bluetooth', whereis_exe=no_exe ) bluetooth = bluetooth.instance() self.assertNotIn('OSXBluetooth', str(bluetooth)) self.assertIn('Bluetooth', str(bluetooth)) if __name__ == '__main__': unittest.main() plyer-2.1.0/plyer/tests/test_cpu.py000066400000000000000000000165431433372044000173530ustar00rootroot00000000000000''' TestCPU ======= Tested platforms: * Windows * Linux - nproc ''' import unittest from os import environ from os.path import join from mock import patch, Mock from textwrap import dedent from plyer.tests.common import PlatformTest, platform_import, splitpath class MockedKernelCPU: def __init__(self, *args, **kwargs): self.fname = args[0] if args else '' self.cpu_path = join('/sys', 'devices', 'system', 'cpu') self.cores = 16 self.indicies = 4 def __enter__(self, *args): file_value = None cpu_path = self.cpu_path spath = splitpath(self.fname) if self.fname == join(cpu_path, 'present'): file_value = Mock() file_value.read.return_value = self.present elif spath[5] == 'cache' and spath[7] == 'level': file_value = Mock() # force bytes, because reading files as bytes file_value.read.return_value = str( self.index_types[spath[4]][spath[6]][spath[7]] ).encode('utf-8') return file_value def __exit__(self, *args): pass @property def present(self): rng = list(range(self.cores)) start = str(rng[0]) end = str(rng[-1]) if start == end: # cores == 1 --> b'0' value = str(start) else: # cores > 1 --> b'0-n' value = str('-'.join([start, end])) return value.encode('utf-8') @property def listdir(self): return ['index{}'.format(i) for i in range(self.indicies)] @property def index_types(self): # assign L1 to index0-1, L2 to 2, L3 to 3 types = {0: 1, 1: 1, 2: 2, 3: 3} return { 'cpu{}'.format(c): { 'index{}'.format(i): { 'level': types[i] } for i in range(self.indicies) } for c in range(self.cores) } class MockedNProc: ''' Mocked object used instead of 'nproc' binary in the Linux specific API plyer.platforms.linux.cpu. The same output structure is tested for the range of . .. note:: Extend the object with another data sample if it does not match. ''' min_version = '8.21' max_version = '8.21' logical_cores = 99 def __init__(self, *args, **kwargs): # only to ignore all args, kwargs pass @staticmethod def communicate(): ''' Mock Popen.communicate, so that 'nproc' isn't used. ''' return (str(MockedNProc.logical_cores).encode('utf-8'), ) @staticmethod def whereis_exe(binary): ''' Mock whereis_exe, so that it looks like Linux NProc binary is present on the system. ''' return binary == 'nproc' @staticmethod def logical(): ''' Return percentage from mocked data. ''' return int(MockedNProc.logical_cores) class MockedProcinfo: # docs: # https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git # /tree/arch/x86/kernel/cpu/proc.c sockets = 1 # physical id physical = 2 # core id threads_per_core = 2 # Intel specs document for i7-4500U logical = physical * threads_per_core # processor def __init__(self, *args, **kwargs): self.fname = args[0] if args else '' self.output = [] __step = 0 # 0,1,0,1 -> 0,0,1,1 for soc in range(self.sockets): for log in range(self.logical): if log != 0 and not log % self.physical: __step += 1 self.output.append((dedent( '''\ processor\t: {logical} vendor_id\t: GenuineIntel cpu family\t: 6 model\t\t: 69 model name\t: Intel(R) Core(TM) i7-4500U CPU @ 1.80GHz stepping\t: 1 microcode\t: 0x17 cpu MHz\t\t: 774.000 cache size\t: 4096 KB physical id\t: {socket} siblings\t: 4 core id\t\t: {physical} cpu cores\t: {threads_per_core} apicid\t\t: {logical} initial apicid\t: 0 fpu\t\t: yes fpu_exception\t: yes cpuid level\t: 13 wp\t\t: yes flags\t\t: fpu vme de pse tsc msr pae mce cx8 ... bogomips\t: 3591.40 clflush size\t: 64 cache_alignment\t: 64 address sizes\t: 39 bits physical, 48 bits virtual power management: \n''' )).format(**{ 'socket': soc, 'physical': __step, 'logical': log, 'threads_per_core': self.threads_per_core })) self.output = ''.join(self.output).encode('utf-8') def __enter__(self, *args): file_value = None if self.fname == '/proc/cpuinfo': file_value = Mock() file_value.readlines.return_value = self.output.split( '\n'.encode('utf-8') ) return file_value def __exit__(self, *args): pass class TestCPU(unittest.TestCase): ''' TestCase for plyer.cpu. ''' def test_cpu_linux_physical(self): cpu = platform_import( platform='linux', module_name='cpu', whereis_exe=lambda b: b == 'nproc' ).instance() stub = MockedProcinfo target = 'builtins.open' with patch(target=target, new=stub): sb = stub() self.assertEqual( cpu.physical, sb.physical ) def test_cpu_linux_logical(self): ''' Test mocked Linux NProc for plyer.cpu. ''' cpu = platform_import( platform='linux', module_name='cpu', whereis_exe=MockedNProc.whereis_exe ) cpu.Popen = MockedNProc cpu = cpu.instance() self.assertEqual( cpu.logical, MockedNProc.logical() ) @PlatformTest('linux') def test_cpu_linux_cache(self): cpu = platform_import( platform='linux', module_name='cpu', whereis_exe=lambda b: b == 'nproc' ).instance() stub = MockedKernelCPU target = 'builtins.open' sub_target = 'plyer.platforms.linux.cpu.listdir' with patch(target=target, new=stub): with patch(target=sub_target, return_value=stub().listdir): sb = stub() self.assertEqual( cpu.cache, { 'L1': sb.cores * 2, 'L2': sb.cores, 'L3': sb.cores } ) @PlatformTest('win') def test_cpu_win_logical(self): cpu = platform_import( platform='win', module_name='cpu' ) cpu = cpu.instance() self.assertEqual( cpu.logical, # https://docs.microsoft.com/en-us/previous-versions/ # windows/it-pro/windows-xp/bb490954(v=technet.10) int(environ['NUMBER_OF_PROCESSORS']) ) if __name__ == '__main__': unittest.main() plyer-2.1.0/plyer/tests/test_devicename.py000066400000000000000000000044111433372044000206530ustar00rootroot00000000000000''' TestDeviceName ============ Tested platforms: * Windows ''' import unittest from mock import patch from plyer.tests.common import PlatformTest, platform_import import socket class TestDeviceName(unittest.TestCase): ''' TestCase for plyer.devicename. ''' @PlatformTest('win') def test_devicename_win(self): ''' Test Windows API for plyer.devicename. ''' devicename = platform_import(platform='win', module_name='devicename' ) devicename_instance = devicename.instance() with patch.object(socket, 'gethostname', return_value='mocked_windows_hostname' ) as _: evaluated_device_name = devicename_instance.device_name self.assertEqual(evaluated_device_name, 'mocked_windows_hostname') @PlatformTest('linux') def test_devicename_linux(self): ''' Test Linux API for plyer.devicename. ''' devicename = platform_import(platform='linux', module_name='devicename' ) devicename_instance = devicename.instance() with patch.object(socket, 'gethostname', return_value='mocked_linux_hostname' ) as _: evaluated_device_name = devicename_instance.device_name self.assertEqual(evaluated_device_name, 'mocked_linux_hostname') @PlatformTest('macosx') def test_devicename_macosx(self): ''' Test MacOSX API for plyer.devicename. ''' devicename = platform_import(platform='macosx', module_name='devicename' ) devicename_instance = devicename.instance() with patch.object(socket, 'gethostname', return_value='mocked_macosx_hostname' ) as _: evaluated_device_name = devicename_instance.device_name self.assertEqual(evaluated_device_name, 'mocked_macosx_hostname') if __name__ == '__main__': unittest.main() plyer-2.1.0/plyer/tests/test_email.py000066400000000000000000000021451433372044000176440ustar00rootroot00000000000000''' TestEmail ========= Tested platforms: * Windows ''' import unittest from mock import Mock, patch from plyer.tests.common import PlatformTest, platform_import class TestEmail(unittest.TestCase): ''' TestCase for plyer.email. ''' @staticmethod @PlatformTest('win') def test_email_win(): ''' Test starting Windows email client for plyer.email. ''' email = platform_import( platform='win', module_name='email' ) try: test_mailto = 'mailto:recipient?subject=subject&body=text' with patch(target='os.startfile', new=Mock()) as startfile: email.instance().send( recipient='recipient', subject='subject', text='text' ) startfile.assert_called_once_with(test_mailto) except WindowsError: # if WE is raised, email client isn't found, # but the platform code works correctly print('Mail client not found!') if __name__ == '__main__': unittest.main() plyer-2.1.0/plyer/tests/test_facade.py000066400000000000000000000130151433372044000177560ustar00rootroot00000000000000''' TestFacade ========== Tested platforms: * Android * iOS * Windows * MacOS * Linux ''' import unittest import sys from types import MethodType from mock import Mock, patch import plyer def mock_platform_module(mod, platform, cls): ''' Create a stub module for a specific platform. This module contains: * class inheriting from facade implementing the desired feature * 'instance' function returning an instance of the implementing class ''' # assemble an instance returned from the instance() function # which is created from a dynamically created class # .'> e.g.: # stub_inst = Mock( __module__=mod, __class__=type( '{}{}'.format(platform.title(), cls), (object, ), { '__module__': mod } ), ) # manual 'return_value' assign to Mock, so that the instance() call # can return stub_inst's own instance instead of creating another # unnecessary Mock object stub_inst.return_value = stub_inst # bind custom function returning the class name to stub_inst instance, # so that instance().show() call requires 'self' i.e. instance parameter # for the function to access the instance's class name stub_inst.show = MethodType(lambda slf: slf, stub_inst) stub_mod = Mock(instance=stub_inst) return stub_mod # dummy pyjnius class to silence the import + config class DummyJnius: ''' Mocked PyJNIus module. ''' def __init__(self, *args, **kwargs): class JavaClass: ''' Mocked PyJNIus JavaClass object. ''' def __init__(self): self.ANDROID_VERSION = None self.SDK_INT = 1 self.mActivity = None self.autoclass = lambda *a, **kw: JavaClass() class TestFacade(unittest.TestCase): ''' TestCase for plyer.utils.Proxy and plyer.facades. ''' def test_facade_existing_platforms(self): ''' Test for returning an object for Android API implementation from Proxy object using a dynamically generated dummy objects. ''' _original = plyer.utils.platform for plat in {'android', 'ios', 'win', 'macosx', 'linux'}: plyer.utils.platform = plat if plat == 'android': # android platform automatically imports jnius sys.modules['jnius'] = DummyJnius() # create stub module with instance func and class stub_mod = mock_platform_module( mod='plyer.platforms.{}.dummy'.format(plat), platform=plyer.utils.platform, cls='Dummy' ) proxy_cls = plyer.utils.Proxy target = 'builtins.__import__' with patch(target=target, return_value=stub_mod): dummy = proxy_cls('dummy', stub_mod) self.assertEqual( str(dummy.__class__).split("'")[1], 'plyer.platforms.{}.dummy.{}Dummy'.format( plat, plat.title() ) ) self.assertEqual( str(dummy.show().__class__).split("'")[1], 'plyer.platforms.{}.dummy.{}Dummy'.format( plat, plat.title() ) ) plyer.utils.platform = _original def test_facade_unknown(self): ''' Test fallback of Proxy to facade if there is no such requested platform. ''' _original = plyer.utils.platform plyer.utils.platform = 'unknown' # no 'unknown' platform (folder), fallback to facade class MockedProxy(plyer.utils.Proxy): ''' Partially mocked Proxy class, so that we pull the error from traceback.print_exc to the test and check the calls. ''' # _ensure_obj is called only once, to either # get the platform object or fall back to facade # therefore the three self.asserts below will return # different values expected_asserts = [True, False, False] def _ensure_obj(inst): # called once, prints to stderr # mock stderr because traceback.print_exc uses it # https://github.com/python/cpython/blob/ # 16dfca4d829e45f36e71bf43f83226659ce49315/Lib/traceback.py#L99 sys.stderr = Mock() # call the original function to trigger # ImportError warnings in stderr super(MockedProxy, inst)._ensure_obj() # Traceback (most recent call last): # File "/app/plyer/utils.py", line 88, in _ensure_obj # mod = __import__(module, fromlist='.') # ImportError: No module named unknown.dummy # must not use self.assertX # (has to be checked on the go!) expected_bool = MockedProxy.expected_asserts.pop(0) call_count = sys.stderr.write.call_count assert (call_count == 6) == expected_bool, call_count # return stderr to the original state sys.stderr = sys.__stderr__ proxy_cls = MockedProxy facade = Mock() dummy = proxy_cls('dummy', facade) self.assertEqual(dummy._mock_new_parent, facade) plyer.utils.platform = _original if __name__ == '__main__': unittest.main() plyer-2.1.0/plyer/tests/test_notification.py000066400000000000000000000134251433372044000212460ustar00rootroot00000000000000''' TestNotification ================ Tested platforms: * Windows * Linux - notify-send, dbus ''' import unittest import sys from time import sleep from os.path import dirname, abspath, join from mock import Mock, patch from plyer.tests.common import PlatformTest, platform_import class MockedNotifySend: ''' Mocked object used instead of the console-like calling of notify-send binary with parameters. ''' @staticmethod def whereis_exe(binary): ''' Mock whereis_exe, so that it looks like Linux notify-send binary is present on the system. ''' return binary == 'notify-send' @staticmethod def call(args): ''' Mocked subprocess.call to check console parameters. ''' assert len(args) >= 3 assert TestNotification.data['title'] in args assert TestNotification.data['message'] in args @staticmethod def warn(msg): ''' Mocked warnings.warn, so that we check the custom ImportError message. ''' assert 'dbus package is not installed' in msg class TestNotification(unittest.TestCase): ''' TestCase for plyer.notification. ''' data = { 'title': 'title', 'message': 'My Message\nis multiline', 'app_name': 'Plyer Test', 'app_icon': join( dirname(abspath(__file__)), 'images', 'kivy32.ico' ), 'timeout': 0.7 } def show_notification(self, instance): ''' Call notify() from platform specific instance with sample data. ''' instance.notify(**self.data) @PlatformTest('win') def test_notification_windows(self): ''' Test Windows API for plyer.notification. ''' import ctypes from ctypes import ( WINFUNCTYPE, POINTER, create_unicode_buffer, c_bool, c_int ) notif = platform_import( platform='win', module_name='notification' ).instance() enum_windows = ctypes.windll.user32.EnumWindows get_class_name = ctypes.windll.user32.GetClassNameW # loop over windows and get refs to # the opened plyer notifications clsnames = [] def fetch_class(hwnd, *args): ''' EnumWindowsProc callback for EnumWindows. ''' buff = create_unicode_buffer(50) get_class_name(hwnd, buff, 50) if 'Plyer' in buff.value: clsnames.append(buff.value) # ensure it's not an empty facade self.assertIn('WindowsNotification', str(notif)) # create enum function for EnumWindows enum_windows_proc = WINFUNCTYPE( # returns c_bool, # input params: hwnd, lParam POINTER(c_int), POINTER(c_int) ) for i in range(3): self.show_notification(notif) # the balloon needs some time to became visible in WinAPI sleep(0.2) # fetch window class names enum_windows( # enum & params enum_windows_proc(fetch_class), None ) # 3 active balloons at the same time, # class_name is incremented - see WindowsBalloonTip self.assertEqual(len(clsnames), i + 1) self.assertIn('PlyerTaskbar' + str(i), clsnames) clsnames = [] @PlatformTest('linux') def test_notification_dbus(self): ''' Test mocked Linux DBus for plyer.notification. ''' notif = platform_import( platform='linux', module_name='notification' ) self.assertIn('NotifyDbus', dir(notif)) # (3) mocked Interface called from dbus interface = Mock() interface.side_effect = (interface, ) # (2) mocked SessionBus called from dbus session_bus = Mock() session_bus.side_effect = (session_bus, ) # (1) mocked dbus for import dbus = Mock(SessionBus=session_bus, Interface=interface) # inject the mocked module self.assertNotIn('dbus', sys.modules) sys.modules['dbus'] = dbus try: notif = notif.instance() self.assertIn('NotifyDbus', str(notif)) # call notify() self.show_notification(notif) # check whether Mocks were called dbus.SessionBus.assert_called_once() session_bus.get_object.assert_called_once_with( 'org.freedesktop.Notifications', '/org/freedesktop/Notifications' ) interface.Notify.assert_called_once_with( TestNotification.data['app_name'], 0, TestNotification.data['app_icon'], TestNotification.data['title'], TestNotification.data['message'], [], {}, TestNotification.data['timeout'] * 1000 ) finally: del sys.modules['dbus'] self.assertNotIn('dbus', sys.modules) @PlatformTest('linux') def test_notification_notifysend(self): ''' Test mocked Linux notify-send for plyer.notification. ''' notif = platform_import( platform='linux', module_name='notification', whereis_exe=MockedNotifySend.whereis_exe ) self.assertIn('NotifySendNotification', dir(notif)) with patch(target='warnings.warn', new=MockedNotifySend.warn): notif = notif.instance() self.assertIn('NotifySendNotification', str(notif)) with patch(target='subprocess.call', new=MockedNotifySend.call): self.assertIsNone(self.show_notification(notif)) if __name__ == '__main__': unittest.main() plyer-2.1.0/plyer/tests/test_screenshot.py000066400000000000000000000070601433372044000207330ustar00rootroot00000000000000''' TestScreenshot ============== Tested platforms: * MacOS * Linux ''' import unittest from os import mkdir, remove from os.path import join, expanduser, exists from mock import patch from plyer.tests.common import PlatformTest, platform_import class MockedScreenCapture: ''' Mocked object used instead of the console-like calling of screencapture binary with parameters. ''' @staticmethod def whereis_exe(binary): ''' Mock whereis_exe, so that it looks like MacOS screencapture binary is present on the system. ''' return binary == 'screencapture' @staticmethod def call(args): ''' Mocked subprocess.call to check console parameters. ''' assert len(args) == 2, len(args) assert args[0] == 'screencapture', args assert args[1] == join( expanduser('~'), 'Pictures', 'screenshot.png' ), args with open(args[1], 'w') as scr: scr.write('') class MockedXWD: ''' Mocked object used instead of the console-like calling of X11 xwd binary with parameters. ''' @staticmethod def whereis_exe(binary): ''' Mock whereis_exe, so that it looks like X11 xwd binary is present on the system. ''' return binary == 'xwd' @staticmethod def call(args, stdout): ''' Mocked subprocess.call to check console parameters. ''' assert len(args) == 3, args assert args[0] == 'xwd', args assert args[1:] == ['-silent', '-root'], args assert stdout.name == join( expanduser('~'), 'Pictures', 'screenshot.xwd' ), stdout.name with open(stdout.name, 'w') as scr: scr.write('') class TestScreenshot(unittest.TestCase): ''' TestCase for plyer.screenshot. ''' def setUp(self): path = join(expanduser('~'), 'Pictures') if not exists(path): mkdir(path) @PlatformTest('macosx') def test_screenshot_screencapture(self): ''' Test mocked MacOS screencapture for plyer.screenshot. ''' scr = platform_import( platform='macosx', module_name='screenshot', whereis_exe=MockedScreenCapture.whereis_exe ) # such class exists in screenshot module self.assertIn('OSXScreenshot', dir(scr)) # the required instance is created scr = scr.instance() self.assertIn('OSXScreenshot', str(scr)) # move capture from context manager to run without mock with patch(target='subprocess.call', new=MockedScreenCapture.call): self.assertIsNone(scr.capture()) self.assertTrue(exists(scr.file_path)) remove(scr.file_path) @PlatformTest('linux') def test_screenshot_xwd(self): ''' Test mocked X11 xwd for plyer.screenshot. ''' scr = platform_import( platform='linux', module_name='screenshot', whereis_exe=MockedXWD.whereis_exe ) # such class exists in screenshot module self.assertIn('LinuxScreenshot', dir(scr)) # the required instance is created scr = scr.instance() self.assertIn('LinuxScreenshot', str(scr)) # move capture from context manager to run without mock with patch(target='subprocess.call', new=MockedXWD.call): self.assertIsNone(scr.capture()) self.assertTrue(exists(scr.file_path)) remove(scr.file_path) if __name__ == '__main__': unittest.main() plyer-2.1.0/plyer/tests/test_storagepath.py000066400000000000000000000042701433372044000210770ustar00rootroot00000000000000''' TestStoragePath =============== Tested platforms: * macOS ''' import unittest from plyer.tests.common import platform_import, PlatformTest class TestStoragePath(unittest.TestCase): ''' TestCase for plyer.storagepath. ''' @PlatformTest('macosx') def test_storagepath_macosx(self): ''' Test macOS for plyer.storagepath. ''' storagepath = platform_import( platform='macosx', module_name='storagepath' ) self.assertIn('OSXStoragePath', dir(storagepath)) storagepath = storagepath.instance() self.assertIn('OSXStoragePath', str(storagepath)) path_format = 'file:///Users/' self.assertIn(path_format, storagepath.get_home_dir()) self.assertIn('/', storagepath.get_root_dir()) self.assertIn(path_format, storagepath.get_documents_dir()) self.assertIn(path_format, storagepath.get_downloads_dir()) self.assertIn(path_format, storagepath.get_videos_dir()) self.assertIn(path_format, storagepath.get_music_dir()) self.assertIn(path_format, storagepath.get_pictures_dir()) self.assertIn(path_format, storagepath.get_application_dir()) @PlatformTest('win') def test_storagepath_windows(self): ''' Test win for plyer.storagepath. ''' storagepath = platform_import( platform='win', module_name='storagepath' ) self.assertIn('WinStoragePath', dir(storagepath)) storagepath = storagepath.instance() self.assertIn('WinStoragePath', str(storagepath)) path_format = ':\\' self.assertIn(path_format, storagepath.get_home_dir()) self.assertIn(path_format, storagepath.get_root_dir()) self.assertIn(path_format, storagepath.get_documents_dir()) self.assertIn(path_format, storagepath.get_downloads_dir()) self.assertIn(path_format, storagepath.get_videos_dir()) self.assertIn(path_format, storagepath.get_music_dir()) self.assertIn(path_format, storagepath.get_pictures_dir()) self.assertIn(path_format, storagepath.get_application_dir()) if __name__ == '__main__': unittest.main() plyer-2.1.0/plyer/tests/test_uniqueid.py000066400000000000000000000046221433372044000204020ustar00rootroot00000000000000''' TestUniqueID ============ Tested platforms: * Windows ''' import unittest from mock import patch, Mock from plyer.tests.common import PlatformTest, platform_import class TestUniqueID(unittest.TestCase): ''' TestCase for plyer.uniqueid. ''' def test_uniqueid(self): ''' General all platform test for plyer.uniqueid. ''' from plyer import uniqueid self.assertTrue(len(uniqueid.id) > 0) @PlatformTest('win') def test_uniqueid_win(self): ''' Test Windows API for plyer.uniqueid. ''' try: from winreg import ( HKEY_LOCAL_MACHINE as HKLM, KEY_READ as READ, KEY_WOW64_64KEY as VIEW ) except ImportError: from _winreg import ( HKEY_LOCAL_MACHINE as HKLM, KEY_READ as READ, KEY_WOW64_64KEY as VIEW ) # mock the 'regedit' alias for winreg, # see if the import passes and get the instance regedit_mod = 'plyer.platforms.win.uniqueid.regedit' with patch(target=regedit_mod): uniqueid_ = platform_import( platform='win', module_name='uniqueid' ) uniqueid = uniqueid_.instance() self.assertIsInstance(uniqueid_.regedit, Mock) # out of mocking block, regedit should be a winreg module self.assertIsInstance(uniqueid_.regedit, type(unittest)) # OpenKey is supposed to return a handle to registry key regedit_opkey = 'plyer.platforms.win.uniqueid.regedit.OpenKey' with patch(target=regedit_opkey, return_value='unicorn') as opkey: # QueryValueEx is supposed to return 2 packed values # (key, type_id) queryval = 'plyer.platforms.win.uniqueid.regedit.QueryValueEx' retval = ('unique', None) with patch(target=queryval, return_value=retval) as query: uid = uniqueid.id opkey.assert_called_once_with( # key, subkey HKLM, r'SOFTWARE\\Microsoft\\Cryptography', # reserved integer (has to be 0 - zero), access mask 0, READ | VIEW ) query.assert_called_once_with('unicorn', 'MachineGuid') self.assertEqual(uid, retval[0]) if __name__ == '__main__': unittest.main() plyer-2.1.0/plyer/tests/test_utils.py000066400000000000000000000306601433372044000177200ustar00rootroot00000000000000''' TestUtils ========= Tested platforms: * Android * iOS * Windows * MacOS * Linux ''' import unittest from mock import patch class TestUtils(unittest.TestCase): ''' TestCase for plyer.utils. ''' def cutter(self, part, string): ''' Cut off a part of a string if it contains a substring, otherwise raise an error. ''' self.assertIn(part, string) return string[len(part):] def test_deprecated_function(self): ''' Test printed out warning with @deprecated decorator on a function without any arguments. ''' from plyer.utils import deprecated @deprecated def function(): ''' Dummy deprecated function. ''' return 1 with patch(target='warnings.warn') as stderr: self.assertEqual(function(), 1) args, _ = stderr.call_args_list[0] args = args[0] args = self.cutter('[WARNING] ', args) args = self.cutter('deprecated function function', args) args = self.cutter('test_utils.py', args) args = self.cutter('Called from', args) args = self.cutter('test_utils.py', args) args = self.cutter('by test_deprecated_function().\n', args) args, _ = stderr.call_args_list[1] self.assertEqual(args, ( ''' Dummy deprecated function. ''', )) def test_deprecated_function_arg(self): ''' Test printed out warning with @deprecated decorator on a function with arguments. ''' from plyer.utils import deprecated @deprecated def function_with_arg(arg): ''' Dummy deprecated function with arg. ''' return arg with patch(target='warnings.warn') as stderr: self.assertEqual(function_with_arg(1), 1) args, _ = stderr.call_args_list[0] args = args[0] args = self.cutter('[WARNING] ', args) args = self.cutter('deprecated function function_with_arg', args) args = self.cutter('test_utils.py', args) args = self.cutter('Called from', args) args = self.cutter('test_utils.py', args) args = self.cutter('by test_deprecated_function_arg().\n', args) args, _ = stderr.call_args_list[1] self.assertEqual(args, ( ''' Dummy deprecated function with arg. ''', )) def test_deprecated_function_kwarg(self): ''' Test printed out warning with @deprecated decorator on a function with keyword arguments. ''' from plyer.utils import deprecated @deprecated def function_with_kwarg(kwarg): ''' Dummy deprecated function with kwarg. ''' return kwarg with patch(target='warnings.warn') as stderr: self.assertEqual(function_with_kwarg(kwarg=1), 1) args, _ = stderr.call_args_list[0] args = args[0] args = self.cutter('[WARNING] ', args) args = self.cutter('deprecated function function_with_kwarg', args) args = self.cutter('test_utils.py', args) args = self.cutter('Called from', args) args = self.cutter('test_utils.py', args) args = self.cutter('by test_deprecated_function_kwarg().\n', args) args, _ = stderr.call_args_list[1] self.assertEqual(args, ( ''' Dummy deprecated function with kwarg. ''', )) def test_deprecated_class_method(self): ''' Test printed out warning with @deprecated decorator on a instance bound method. ''' from plyer.utils import deprecated class Class: ''' Dummy class with deprecated method method. ''' def __init__(self, *args, **kwargs): self.args = args self.kwargs = kwargs @deprecated def method(self): ''' Dummy deprecated method. ''' return (self.args, self.kwargs) with patch(target='warnings.warn') as stderr: args = (1, 2, 3) kwargs = dict(x=1, y=2) cls = Class(*args, **kwargs) self.assertEqual(cls.method(), (args, kwargs)) args, kwargs = stderr.call_args_list[0] args = args[0] args = self.cutter('[WARNING] ', args) args = self.cutter('deprecated function method', args) args = self.cutter('test_utils.py', args) args = self.cutter('Called from', args) args = self.cutter('test_utils.py', args) args = self.cutter('by test_deprecated_class_method().\n', args) args, kwargs = stderr.call_args_list[1] self.assertEqual(args, ( ''' Dummy deprecated method. ''', )) def test_deprecated_class_static_none(self): ''' Test printed out warning with @deprecated decorator on a static method without arguments. ''' from plyer.utils import deprecated class Class: ''' Dummy class with deprecated static method. ''' args = None kwargs = None def __init__(self, *args, **kwargs): Class.args = args Class.kwargs = kwargs @staticmethod @deprecated def static(): ''' Dummy deprecated static method. ''' return (Class.args, Class.kwargs) with patch(target='warnings.warn') as stderr: self.assertEqual(Class.static(), (None, None)) args, _ = stderr.call_args_list[0] args = args[0] args = self.cutter('[WARNING] ', args) args = self.cutter('deprecated function static', args) args = self.cutter('test_utils.py', args) args = self.cutter('Called from', args) args = self.cutter('test_utils.py', args) args = self.cutter( 'by test_deprecated_class_static_none().\n', args ) args, _ = stderr.call_args_list[1] self.assertEqual(args, ( ''' Dummy deprecated static method. ''', )) def test_deprecated_class_static_argskwargs(self): ''' Test printed out warning with @deprecated decorator on a static method with arguments and keyword argument. ''' from plyer.utils import deprecated class Class: ''' Dummy class with deprecated static method. ''' args = None kwargs = None def __init__(self, *args, **kwargs): Class.args = args Class.kwargs = kwargs @staticmethod @deprecated def static(): ''' Dummy deprecated static method. ''' return (Class.args, Class.kwargs) with patch(target='warnings.warn') as stderr: args = (1, 2, 3) kwargs = dict(x=1, y=2) cls = Class(*args, **kwargs) self.assertEqual(cls.static(), (args, kwargs)) args, kwargs = stderr.call_args_list[0] args = args[0] args = self.cutter('[WARNING] ', args) args = self.cutter('deprecated function static', args) args = self.cutter('test_utils.py', args) args = self.cutter('Called from', args) args = self.cutter('test_utils.py', args) args = self.cutter( 'by test_deprecated_class_static_argskwargs().\n', args ) args, kwargs = stderr.call_args_list[1] self.assertEqual(args, ( ''' Dummy deprecated static method. ''', )) def test_deprecated_class_clsmethod(self): ''' Test printed out warning with @deprecated decorator on a class bound method. ''' from plyer.utils import deprecated class Class: ''' Dummy class with deprecated class method. ''' args = None kwargs = None @classmethod @deprecated def clsmethod(cls): ''' Dummy deprecated class method. ''' return (cls.args, cls.kwargs) with patch(target='warnings.warn') as stderr: self.assertEqual(Class.clsmethod(), (None, None)) args, _ = stderr.call_args_list[0] args = args[0] args = self.cutter('[WARNING] ', args) args = self.cutter('deprecated function clsmethod', args) args = self.cutter('test_utils.py', args) args = self.cutter('Called from', args) args = self.cutter('test_utils.py', args) args = self.cutter('by test_deprecated_class_clsmethod().\n', args) args, _ = stderr.call_args_list[1] self.assertEqual(args, ( ''' Dummy deprecated class method. ''', )) def test_deprecated_class(self): ''' Test printed out warning with @deprecated decorator on a class. ''' from plyer.utils import deprecated @deprecated class Class: ''' Dummy deprecated class. ''' def __init__(self, *args, **kwargs): self.args = args self.kwargs = kwargs with patch(target='warnings.warn') as stderr: args = (1, 2, 3) kwargs = dict(x=1, y=2) cls = Class(*args, **kwargs) self.assertIsInstance(cls, Class) self.assertEqual(cls.args, args) self.assertEqual(cls.kwargs, kwargs) args, _ = stderr.call_args_list[0] args = args[0] args = self.cutter('[WARNING] ', args) args = self.cutter('Creating an instance', args) args = self.cutter('deprecated class Class in', args) args = self.cutter(__name__, args) args = self.cutter('Called from', args) args = self.cutter('test_utils.py', args) args = self.cutter('by test_deprecated_class().\n', args) args, kwargs = stderr.call_args_list[1] self.assertEqual(args, ( ''' Dummy deprecated class. ''', )) def test_deprecated_class_inherited(self): ''' Test printed out warning with @deprecated decorator on a class which inherits from a deprecated class. ''' from plyer.utils import deprecated @deprecated class Class: ''' Dummy deprecated class. ''' def __init__(self, *args, **kwargs): self.args = args self.kwargs = kwargs class Inherited(Class): ''' Dummy class inheriting from a dummy deprecated class. ''' def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.args = args self.kwargs = kwargs with patch(target='warnings.warn') as stderr: args = (1, 2, 3) kwargs = dict(x=1, y=2) cls = Inherited(*args, **kwargs) self.assertIsInstance(cls, Inherited) self.assertEqual(cls.args, args) self.assertEqual(cls.kwargs, kwargs) args, _ = stderr.call_args_list[0] args = args[0] args = self.cutter('[WARNING] ', args) args = self.cutter('Creating an instance', args) args = self.cutter('deprecated class Class in', args) args = self.cutter(__name__, args) args = self.cutter('Called from', args) args = self.cutter('test_utils.py', args) args = self.cutter('by test_deprecated_class_inherited().\n', args) args, kwargs = stderr.call_args_list[1] self.assertEqual(args, ( ''' Dummy deprecated class. ''', )) if __name__ == '__main__': unittest.main() plyer-2.1.0/plyer/tools/000077500000000000000000000000001433372044000151405ustar00rootroot00000000000000plyer-2.1.0/plyer/tools/pep8checker/000077500000000000000000000000001433372044000173415ustar00rootroot00000000000000plyer-2.1.0/plyer/tools/pep8checker/pep8.py000066400000000000000000002156661433372044000206070ustar00rootroot00000000000000#!/usr/bin/env python # pep8.py - Check Python source code formatting, according to PEP 8 # Copyright (C) 2006 Johann C. Rocholl # # 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. # flake8: noqa 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:\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: """ 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] == '.': msg = ('E289 Too many spaces after period. ' 'Use only one.') self.report_error(line, col, msg, check=None) elif sre.group(0)[0] == ',': msg = 'E288 Too many spaces after comma. Use only one.' self.report_error(line, col, msg, check=None) else: msg = ('E287 Too many spaces after punctuation. ' 'Use only one.') self.report_error(line, col, msg, 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: msg = 'E389 File ends in multiple blank lines' self.report_error(token[2][0], 0, msg, check=None) return self.report.get_file_results() class BaseReport: """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().__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().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().__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().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().__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: """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|]") 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() plyer-2.1.0/plyer/tools/pep8checker/pep8base.html000066400000000000000000000456701433372044000217520ustar00rootroot00000000000000 Kivy Styleguide Check

Kivy Styleguide (PEP8) Check

plyer-2.1.0/plyer/tools/pep8checker/pep8kivy.py000066400000000000000000000072321433372044000214760ustar00rootroot00000000000000import sys from os import walk from os.path import isdir, join, abspath, dirname import pep8 import time htmlmode = False pep8_ignores = ( # continuation line does not distinguish itself from next logical line 'E125', '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('{0}{1}'.format(line_number, text)) if __name__ == '__main__': def usage(): print('Usage: python pep8kivy.py [-html] *') 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( '''

Generated: %s

''' '' % (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( '' '' % complete_filename ) errors += check(complete_filename) if htmlmode: print('
%s
') 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) plyer-2.1.0/plyer/tools/pep8checker/pre-commit.githook000077500000000000000000000052361433372044000230140ustar00rootroot00000000000000#!/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) plyer-2.1.0/plyer/utils.py000066400000000000000000000225221433372044000155150ustar00rootroot00000000000000''' Utils ===== ''' __all__ = ('platform', 'reify', 'deprecated') from os import environ from os import path from sys import platform as _sys_platform import sys class Platform: ''' 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().__repr__() ) def __hash__(self): return self._get_platform().__hash__() def _get_platform(self): if self._platform_android is None: # sys.getandroidapilevel is defined as of Python 3.7 # ANDROID_ARGUMENT and ANDROID_PRIVATE are 2 environment variables # from python-for-android project self._platform_android = hasattr(sys, 'getandroidapilevel') or \ '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: ''' Based on http://code.activestate.com/recipes/496741-object-proxying version by Tomer Filiba, PSF license. ''' __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 Exception: import traceback traceback.print_exc() facade = object.__getattribute__(self, '_facade') obj = facade() object.__setattr__(self, '_obj', obj) return obj def __getattribute__(self, name): result = None if name == '__doc__': return result # run _ensure_obj func, result in _obj object.__getattribute__(self, '_ensure_obj')() # return either Proxy instance or platform-dependent implementation result = getattr(object.__getattribute__(self, '_obj'), name) return result 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 pth in environ.get('PATH', '').split(path_split): folder = path.isdir(path.join(pth, program)) available = path.exists(path.join(pth, program)) if available and not folder: return path.join(pth, program) return None class reify: ''' Put the result of a method which uses this (non-data) descriptor decorator in the instance dict after the first call, effectively replacing the decorator with an instance variable. It acts like @property, except that the function is only ever called once; after that, the value is cached as a regular attribute. This gives you lazy attribute creation on objects that are meant to be immutable. Taken from the `Pyramid project `_. To use this as a decorator:: @reify def lazy(self): ... return hard_to_compute_int first_time = self.lazy # lazy is reify obj, reify.__get__() runs second_time = self.lazy # lazy is hard_to_compute_int ''' def __init__(self, func): self.func = func self.__doc__ = func.__doc__ def __get__(self, inst, cls): if inst is None: return self retval = self.func(inst) setattr(inst, self.func.__name__, retval) return retval def deprecated(obj): ''' This is a decorator which can be used to mark functions and classes as deprecated. It will result in a warning being emitted when a deprecated function is called or a new instance of a class created. In case of classes, the warning is emitted before the __new__ method of the decorated class is called, therefore a way before the __init__ method itself. ''' import warnings from inspect import stack from functools import wraps from types import FunctionType, MethodType new_obj = None # wrap a function into a function emitting a deprecated warning if isinstance(obj, FunctionType): @wraps(obj) def new_func(*args, **kwargs): # get the previous stack frame and extract file, line and caller # stack() -> caller() call_file, call_line, caller = stack()[1][1:4] # assemble warning warning = ( 'Call to deprecated function {} in {} line {}. ' 'Called from {} line {}' ' by {}().\n'.format( obj.__name__, obj.__code__.co_filename, obj.__code__.co_firstlineno + 1, call_file, call_line, caller ) ) warnings.warn('[{}] {}'.format('WARNING', warning)) # if there is a docstring present, emit docstring too if obj.__doc__: warnings.warn(obj.__doc__) # return function wrapper return obj(*args, **kwargs) new_obj = new_func # wrap a class into a class emitting a deprecated warning # obj is class, type(obj) is metaclass, metaclasses inherit from type elif isinstance(type(obj), type): # we have an access to the metaclass instance (class) and need to print # the warning when a class instance (object) is created with __new__ # i.e. when calling Class() def obj_new(cls, child, *args, **kwargs): ''' Custom metaclass instance's __new__ method with deprecated warning. Calls the original __new__ method afterwards. ''' # get the previous stack frame and extract file, line and caller # stack() -> caller() call_file, call_line, caller = stack()[1][1:4] loc_file = obj.__module__ warnings.warn( '[{}] Creating an instance of a deprecated class {} in {}.' ' Called from {} line {} by {}().\n'.format( 'WARNING', obj.__name__, loc_file, call_file, call_line, caller ) ) # if there is a docstring present, emit docstring too if obj.__doc__: warnings.warn(obj.__doc__) # make sure nothing silly gets into the function assert obj is cls # we are creating a __new__ for a class that inherits from # a deprecated class, therefore in this particular case # MRO is (child, cls, object) > (cls, object) if len(child.__mro__) > len(cls.__mro__): assert cls is child.__mro__[1], (cls.__mro__, child.__mro__) # we are creating __new__ directly for the deprecated class # therefore MRO is the same for parent and child class elif len(child.__mro__) == len(cls.__mro__): assert cls is child # return the class back with the extended __new__ method return obj.__old_new__(child) # back up the old __new__ method and create an extended # __new__ method that emits deprecated warnings obj.__old_new__ = obj.__new__ obj.__new__ = MethodType(obj_new, obj) new_obj = obj # return a function wrapper or an extended class return new_obj plyer-2.1.0/setup.py000066400000000000000000000044131433372044000143610ustar00rootroot00000000000000#!/usr/bin/env python ''' Setup.py ======== For MacOS or iOS install additional dependency PyOBJus:: pip install https://github.com/kivy/pyobjus/zipball/master For Android install additional dependency PyJNIus:: pip install https://github.com/kivy/pyjnius/zipball/master ''' from os.path import dirname, join import plyer import io EXTRA_OPTIONS = {} try: from setuptools import setup EXTRA_OPTIONS = dict( EXTRA_OPTIONS, **{ 'extras_require': { 'ios': ['pyobjus'], 'macosx': ['pyobjus'], 'android': ['pyjnius'], 'dev': ['mock', 'flake8'] } } ) except ImportError: from distutils.core import setup CURDIR = dirname(__file__) PACKAGES = [ 'plyer', 'plyer.facades', 'plyer.platforms', 'plyer.platforms.linux', 'plyer.platforms.android', 'plyer.platforms.win', 'plyer.platforms.win.libs', 'plyer.platforms.ios', 'plyer.platforms.macosx', 'plyer.platforms.macosx.libs', ] with io.open(join(CURDIR, "README.md"), encoding="utf8") as fd: README = fd.read() with io.open(join(CURDIR, "CHANGELOG.md"), encoding="utf8") as fd: CHANGELOG = fd.read() setup( name='plyer', version=plyer.__version__, description='Platform-independent wrapper for platform-dependent APIs', long_description=README + u"\n\n" + CHANGELOG + u"\n\n", long_description_content_type='text/markdown', author='Kivy team', author_email='mat@kivy.org', url='https://plyer.readthedocs.org/en/latest/', packages=PACKAGES, package_data={'': ['LICENSE', 'README.md']}, package_dir={'plyer': 'plyer'}, include_package_data=True, license='MIT', zip_safe=False, classifiers=[ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', 'Natural Language :: English', 'License :: OSI Approved :: MIT License', 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', ], **EXTRA_OPTIONS )