From bb86bee3994834129cbb3c668c94920bf9daeeb0 Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Sun, 2 Jun 2024 18:31:58 +0200 Subject: [PATCH] Updated, modded and refactored plyer --- sbapp/plyer/__init__.py | 12 +- sbapp/plyer/facades/__init__.py | 108 +++-- sbapp/plyer/facades/audio.py | 1 + sbapp/plyer/facades/humidity.py | 2 +- sbapp/plyer/facades/maps.py | 88 ++++ sbapp/plyer/facades/notification.py | 8 +- sbapp/plyer/facades/orientation.py | 2 +- sbapp/plyer/facades/sms.py | 12 +- .../plyer/platforms/android/accelerometer.py | 2 +- sbapp/plyer/platforms/android/audio.py | 26 +- sbapp/plyer/platforms/android/camera.py | 4 +- sbapp/plyer/platforms/android/compass.py | 2 +- sbapp/plyer/platforms/android/filechooser.py | 140 ++++-- sbapp/plyer/platforms/android/gps.py | 2 +- sbapp/plyer/platforms/android/gyroscope.py | 2 +- sbapp/plyer/platforms/android/notification.py | 9 +- sbapp/plyer/platforms/android/storagepath.py | 41 +- sbapp/plyer/platforms/ios/accelerometer.py | 2 +- sbapp/plyer/platforms/ios/barometer.py | 2 +- sbapp/plyer/platforms/ios/battery.py | 2 +- sbapp/plyer/platforms/ios/brightness.py | 2 +- sbapp/plyer/platforms/ios/call.py | 2 +- sbapp/plyer/platforms/ios/camera.py | 8 +- sbapp/plyer/platforms/ios/compass.py | 2 +- sbapp/plyer/platforms/ios/email.py | 2 +- sbapp/plyer/platforms/ios/filechooser.py | 2 +- sbapp/plyer/platforms/ios/flash.py | 2 +- sbapp/plyer/platforms/ios/gps.py | 2 +- sbapp/plyer/platforms/ios/gravity.py | 2 +- sbapp/plyer/platforms/ios/gyroscope.py | 2 +- sbapp/plyer/platforms/ios/keystore.py | 2 +- sbapp/plyer/platforms/ios/maps.py | 78 ++++ sbapp/plyer/platforms/ios/sms.py | 2 +- .../plyer/platforms/ios/spatialorientation.py | 2 +- sbapp/plyer/platforms/ios/storagepath.py | 2 +- sbapp/plyer/platforms/ios/tts.py | 4 +- sbapp/plyer/platforms/ios/uniqueid.py | 2 +- sbapp/plyer/platforms/ios/vibrator.py | 2 +- sbapp/plyer/platforms/linux/accelerometer.py | 2 +- sbapp/plyer/platforms/linux/audio.py | 48 ++ sbapp/plyer/platforms/linux/battery.py | 83 +++- sbapp/plyer/platforms/linux/brightness.py | 2 +- sbapp/plyer/platforms/linux/cpu.py | 4 +- sbapp/plyer/platforms/linux/devicename.py | 2 +- sbapp/plyer/platforms/linux/email.py | 4 +- sbapp/plyer/platforms/linux/filechooser.py | 10 +- sbapp/plyer/platforms/linux/keystore.py | 2 +- sbapp/plyer/platforms/linux/notification.py | 4 +- sbapp/plyer/platforms/linux/orientation.py | 2 +- sbapp/plyer/platforms/linux/processors.py | 4 +- sbapp/plyer/platforms/linux/screenshot.py | 6 +- sbapp/plyer/platforms/linux/storagepath.py | 2 +- sbapp/plyer/platforms/linux/tts.py | 4 +- sbapp/plyer/platforms/linux/uniqueid.py | 4 +- sbapp/plyer/platforms/linux/wifi.py | 4 +- sbapp/plyer/platforms/macosx/accelerometer.py | 4 +- sbapp/plyer/platforms/macosx/audio.py | 25 +- sbapp/plyer/platforms/macosx/battery.py | 4 +- sbapp/plyer/platforms/macosx/bluetooth.py | 4 +- sbapp/plyer/platforms/macosx/cpu.py | 4 +- sbapp/plyer/platforms/macosx/devicename.py | 2 +- sbapp/plyer/platforms/macosx/email.py | 4 +- sbapp/plyer/platforms/macosx/filechooser.py | 4 +- sbapp/plyer/platforms/macosx/keystore.py | 2 +- .../macosx/libs/osx_motion_sensor.py | 4 +- sbapp/plyer/platforms/macosx/maps.py | 90 ++++ sbapp/plyer/platforms/macosx/notification.py | 2 +- sbapp/plyer/platforms/macosx/screenshot.py | 6 +- sbapp/plyer/platforms/macosx/sms.py | 42 ++ sbapp/plyer/platforms/macosx/storagepath.py | 2 +- sbapp/plyer/platforms/macosx/tts.py | 4 +- sbapp/plyer/platforms/macosx/uniqueid.py | 4 +- sbapp/plyer/platforms/macosx/wifi.py | 2 +- sbapp/plyer/platforms/win/audio.py | 4 +- sbapp/plyer/platforms/win/battery.py | 4 +- sbapp/plyer/platforms/win/cpu.py | 2 +- sbapp/plyer/platforms/win/devicename.py | 2 +- sbapp/plyer/platforms/win/email.py | 2 +- sbapp/plyer/platforms/win/filechooser.py | 4 +- sbapp/plyer/platforms/win/keystore.py | 2 +- sbapp/plyer/platforms/win/libs/balloontip.py | 2 +- .../plyer/platforms/win/libs/batterystatus.py | 2 +- sbapp/plyer/platforms/win/notification.py | 4 +- sbapp/plyer/platforms/win/screenshot.py | 4 +- sbapp/plyer/platforms/win/storagepath.py | 4 +- sbapp/plyer/platforms/win/tts.py | 4 +- sbapp/plyer/platforms/win/uniqueid.py | 2 +- sbapp/plyer/platforms/win/wifi.py | 4 +- sbapp/plyer/tests/__init__.py | 0 sbapp/plyer/tests/common.py | 76 ---- sbapp/plyer/tests/images/kivy32.ico | Bin 4286 -> 0 bytes sbapp/plyer/tests/test_audio.py | 110 ----- sbapp/plyer/tests/test_battery.py | 413 ----------------- sbapp/plyer/tests/test_bluetooth.py | 144 ------ sbapp/plyer/tests/test_cpu.py | 262 ----------- sbapp/plyer/tests/test_devicename.py | 77 ---- sbapp/plyer/tests/test_email.py | 48 -- sbapp/plyer/tests/test_facade.py | 183 -------- sbapp/plyer/tests/test_notification.py | 209 --------- sbapp/plyer/tests/test_screenshot.py | 137 ------ sbapp/plyer/tests/test_storagepath.py | 72 --- sbapp/plyer/tests/test_uniqueid.py | 78 ---- sbapp/plyer/tests/test_utils.py | 417 ------------------ sbapp/plyer/utils.py | 7 +- 104 files changed, 790 insertions(+), 2472 deletions(-) create mode 100644 sbapp/plyer/facades/maps.py create mode 100644 sbapp/plyer/platforms/ios/maps.py create mode 100644 sbapp/plyer/platforms/linux/audio.py create mode 100644 sbapp/plyer/platforms/macosx/maps.py create mode 100644 sbapp/plyer/platforms/macosx/sms.py delete mode 100644 sbapp/plyer/tests/__init__.py delete mode 100644 sbapp/plyer/tests/common.py delete mode 100644 sbapp/plyer/tests/images/kivy32.ico delete mode 100644 sbapp/plyer/tests/test_audio.py delete mode 100644 sbapp/plyer/tests/test_battery.py delete mode 100644 sbapp/plyer/tests/test_bluetooth.py delete mode 100644 sbapp/plyer/tests/test_cpu.py delete mode 100644 sbapp/plyer/tests/test_devicename.py delete mode 100644 sbapp/plyer/tests/test_email.py delete mode 100644 sbapp/plyer/tests/test_facade.py delete mode 100644 sbapp/plyer/tests/test_notification.py delete mode 100644 sbapp/plyer/tests/test_screenshot.py delete mode 100644 sbapp/plyer/tests/test_storagepath.py delete mode 100644 sbapp/plyer/tests/test_uniqueid.py delete mode 100644 sbapp/plyer/tests/test_utils.py diff --git a/sbapp/plyer/__init__.py b/sbapp/plyer/__init__.py index ab87901..9bfb190 100644 --- a/sbapp/plyer/__init__.py +++ b/sbapp/plyer/__init__.py @@ -13,11 +13,15 @@ __all__ = ( 'stt', 'temperature', 'tts', 'uniqueid', 'vibrator', 'wifi', 'devicename' ) -__version__ = '2.1.0.dev0' +__version__ = '2.2.0.dev0' - -from plyer import facades -from plyer.utils import Proxy +import RNS +if RNS.vendor.platformutils.is_android(): + from plyer import facades + from plyer.utils import Proxy +else: + from sbapp.plyer import facades + from sbapp.plyer.utils import Proxy #: Accelerometer proxy to :class:`plyer.facades.Accelerometer` accelerometer = Proxy('accelerometer', facades.Accelerometer) diff --git a/sbapp/plyer/facades/__init__.py b/sbapp/plyer/facades/__init__.py index c1e2560..81a0f63 100644 --- a/sbapp/plyer/facades/__init__.py +++ b/sbapp/plyer/facades/__init__.py @@ -14,38 +14,76 @@ __all__ = ('Accelerometer', 'Audio', 'Barometer', 'Battery', 'Call', 'Camera', '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 +import RNS +if RNS.vendor.platformutils.is_android(): + 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 +else: + from sbapp.plyer.facades.accelerometer import Accelerometer + from sbapp.plyer.facades.audio import Audio + from sbapp.plyer.facades.barometer import Barometer + from sbapp.plyer.facades.battery import Battery + from sbapp.plyer.facades.call import Call + from sbapp.plyer.facades.camera import Camera + from sbapp.plyer.facades.compass import Compass + from sbapp.plyer.facades.email import Email + from sbapp.plyer.facades.filechooser import FileChooser + from sbapp.plyer.facades.flash import Flash + from sbapp.plyer.facades.gps import GPS + from sbapp.plyer.facades.gravity import Gravity + from sbapp.plyer.facades.gyroscope import Gyroscope + from sbapp.plyer.facades.irblaster import IrBlaster + from sbapp.plyer.facades.light import Light + from sbapp.plyer.facades.proximity import Proximity + from sbapp.plyer.facades.orientation import Orientation + from sbapp.plyer.facades.notification import Notification + from sbapp.plyer.facades.sms import Sms + from sbapp.plyer.facades.stt import STT + from sbapp.plyer.facades.tts import TTS + from sbapp.plyer.facades.uniqueid import UniqueID + from sbapp.plyer.facades.vibrator import Vibrator + from sbapp.plyer.facades.wifi import Wifi + from sbapp.plyer.facades.temperature import Temperature + from sbapp.plyer.facades.humidity import Humidity + from sbapp.plyer.facades.spatialorientation import SpatialOrientation + from sbapp.plyer.facades.brightness import Brightness + from sbapp.plyer.facades.keystore import Keystore + from sbapp.plyer.facades.storagepath import StoragePath + from sbapp.plyer.facades.bluetooth import Bluetooth + from sbapp.plyer.facades.processors import Processors + from sbapp.plyer.facades.cpu import CPU + from sbapp.plyer.facades.screenshot import Screenshot + from sbapp.plyer.facades.devicename import DeviceName diff --git a/sbapp/plyer/facades/audio.py b/sbapp/plyer/facades/audio.py index c5e1db5..0394037 100644 --- a/sbapp/plyer/facades/audio.py +++ b/sbapp/plyer/facades/audio.py @@ -94,6 +94,7 @@ class Audio: # private def _start(self): + raise IOError("JUICE") raise NotImplementedError() def _stop(self): diff --git a/sbapp/plyer/facades/humidity.py b/sbapp/plyer/facades/humidity.py index ac63333..8d72239 100644 --- a/sbapp/plyer/facades/humidity.py +++ b/sbapp/plyer/facades/humidity.py @@ -4,7 +4,7 @@ class 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 diff --git a/sbapp/plyer/facades/maps.py b/sbapp/plyer/facades/maps.py new file mode 100644 index 0000000..ba8d5fe --- /dev/null +++ b/sbapp/plyer/facades/maps.py @@ -0,0 +1,88 @@ +''' +Maps +======= +The :class:`Maps` creates a client for accessing the default Maps API. + +Holds features such as opening a location by +address & latitude/longitude, create queries, or find directions between +two points + +Simple Examples +--------------- + +Perform a search:: + + >>> from plyer import maps + >>> maps.search('Mexican Restaurant') + >>> maps.search('Taco Bell', latitude=38.5810606, longitude=-121.493895) + +Get directions to a location:: + + >>> from plyer import maps + >>> maps.route('Cupertino', 'San Francisco') + >>> maps.route('41.9156316,-72.6130726', '42.65228271484,-73.7577362060') + +View a specific location:: + + >>> from plyer import maps + >>> maps.open_by_address('25 Leshin Lane, Hightstown, NJ') + >>> maps.open_by_lat_long(30.451468, -91.187149) + >>> maps.open_by_lat_long(30.451468, -91.187149, name='Home') + +Supported Platforms +------------------- +macOS, iOS +--------------- +''' + + +class Maps: + ''' + Maps facade. + ''' + + def open_by_address(self, address, **kwargs): + ''' + Open the specificed location by address in the default Maps API + ''' + self._open_by_address(address, **kwargs) + + def open_by_lat_long(self, latitude, longitude, **kwargs): + ''' + Open the specificed location by latitude & longitude coordinates + in the default Maps API + ''' + self._open_by_lat_long(latitude, longitude, **kwargs) + + def search(self, query, **kwargs): + ''' + The query. This parameter is treated as if its value had been typed + into the Maps search field by the user. + + Note that query=* is not supported + ''' + self._search(query, **kwargs) + + def route(self, saddr, daddr, **kwargs): + ''' + To provide navigation directions from one location to another. + + :param saddr: The source address to be used as the starting + point for directions. + + :param daddr: The destination address to be used as the + destination point for directions. + ''' + self._route(saddr, daddr, **kwargs) + + def _open_by_address(self, address, **kwargs): + raise NotImplementedError() + + def _open_by_lat_long(self, latitude, longitude, **kwargs): + raise NotImplementedError() + + def _search(self, query, **kwargs): + raise NotImplementedError() + + def _route(self, saddr, daddr, **kwargs): + raise NotImplementedError() diff --git a/sbapp/plyer/facades/notification.py b/sbapp/plyer/facades/notification.py index 71c7d53..db0d227 100644 --- a/sbapp/plyer/facades/notification.py +++ b/sbapp/plyer/facades/notification.py @@ -45,8 +45,8 @@ class Notification: Notification facade. ''' - def notify(self, title='', message='', app_name='', app_icon='', notification_icon=None, - timeout=10, ticker='', toast=False, hints={}, context_override=None): + def notify(self, title='', message='', app_name='', app_icon='', + timeout=10, ticker='', toast=False, hints={}): ''' Send a notification. @@ -83,8 +83,8 @@ class Notification: self._notify( title=title, message=message, - app_icon=app_icon, app_name=app_name, notification_icon=notification_icon, - timeout=timeout, ticker=ticker, toast=toast, hints=hints, context_override=context_override + app_icon=app_icon, app_name=app_name, + timeout=timeout, ticker=ticker, toast=toast, hints=hints ) # private diff --git a/sbapp/plyer/facades/orientation.py b/sbapp/plyer/facades/orientation.py index 56ea0ba..077de55 100644 --- a/sbapp/plyer/facades/orientation.py +++ b/sbapp/plyer/facades/orientation.py @@ -30,7 +30,7 @@ To set sensor:: Supported Platforms ------------------- -Android +Android, Linux ''' diff --git a/sbapp/plyer/facades/sms.py b/sbapp/plyer/facades/sms.py index 0e58f44..5940822 100644 --- a/sbapp/plyer/facades/sms.py +++ b/sbapp/plyer/facades/sms.py @@ -23,7 +23,7 @@ To send sms:: Supported Platforms ------------------- -Android, iOS +Android, iOS, macOS ''' @@ -33,17 +33,23 @@ class Sms: Sms facade. ''' - def send(self, recipient, message): + def send(self, recipient, message, mode=None, **kwargs): ''' Send SMS or open SMS interface. + Includes optional `mode` parameter for macOS that can be set to + `'SMS'` if carrier-activated device is correctly paired and + configured to macOS. :param recipient: The receiver :param message: the message + :param mode: (optional, macOS only), can be set to 'iMessage' + (default) or 'SMS' :type recipient: number :type message: str + :type mode: str ''' - self._send(recipient=recipient, message=message) + self._send(recipient=recipient, message=message, mode=mode, **kwargs) # private diff --git a/sbapp/plyer/platforms/android/accelerometer.py b/sbapp/plyer/platforms/android/accelerometer.py index 00c73cf..7754579 100644 --- a/sbapp/plyer/platforms/android/accelerometer.py +++ b/sbapp/plyer/platforms/android/accelerometer.py @@ -70,7 +70,7 @@ class AndroidAccelerometer(Accelerometer): return (None, None, None) def __del__(self): - if(self.bState): + if self.bState: self._disable() super().__del__() diff --git a/sbapp/plyer/platforms/android/audio.py b/sbapp/plyer/platforms/android/audio.py index 9f000ff..0b229c4 100644 --- a/sbapp/plyer/platforms/android/audio.py +++ b/sbapp/plyer/platforms/android/audio.py @@ -1,3 +1,5 @@ +import time +import threading from jnius import autoclass from plyer.facades.audio import Audio @@ -20,17 +22,31 @@ class AndroidAudio(Audio): ''' def __init__(self, file_path=None): - default_path = '/sdcard/testrecorder.3gp' + default_path = None super().__init__(file_path or default_path) self._recorder = None self._player = None + self._check_thread = None + self._finished_callback = None + + def _check_playback(self): + while self._player and self._player.isPlaying(): + time.sleep(0.25) + + if self._finished_callback and callable(self._finished_callback): + self._check_thread = None + self._finished_callback(self) + def _start(self): self._recorder = MediaRecorder() self._recorder.setAudioSource(AudioSource.DEFAULT) - self._recorder.setOutputFormat(OutputFormat.DEFAULT) - self._recorder.setAudioEncoder(AudioEncoder.DEFAULT) + self._recorder.setAudioSamplingRate(44100) + self._recorder.setAudioEncodingBitRate(128000) + self._recorder.setAudioChannels(1) + self._recorder.setOutputFormat(OutputFormat.MPEG_4) + self._recorder.setAudioEncoder(AudioEncoder.AAC) self._recorder.setOutputFile(self.file_path) self._recorder.prepare() @@ -53,6 +69,10 @@ class AndroidAudio(Audio): self._player.prepare() self._player.start() + self._check_thread = threading.Thread(target=self._check_playback, daemon=True) + self._check_thread.start() + + def instance(): return AndroidAudio() diff --git a/sbapp/plyer/platforms/android/camera.py b/sbapp/plyer/platforms/android/camera.py index 19707bd..3384a66 100644 --- a/sbapp/plyer/platforms/android/camera.py +++ b/sbapp/plyer/platforms/android/camera.py @@ -14,7 +14,7 @@ Uri = autoclass('android.net.Uri') class AndroidCamera(Camera): def _take_picture(self, on_complete, filename=None): - assert(on_complete is not 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) @@ -26,7 +26,7 @@ class AndroidCamera(Camera): activity.startActivityForResult(intent, 0x123) def _take_video(self, on_complete, filename=None): - assert(on_complete is not 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) diff --git a/sbapp/plyer/platforms/android/compass.py b/sbapp/plyer/platforms/android/compass.py index fcf0483..5bb16c6 100644 --- a/sbapp/plyer/platforms/android/compass.py +++ b/sbapp/plyer/platforms/android/compass.py @@ -110,7 +110,7 @@ class AndroidCompass(Compass): return (None, None, None, None, None, None) def __del__(self): - if(self.bState): + if self.bState: self._disable() super().__del__() diff --git a/sbapp/plyer/platforms/android/filechooser.py b/sbapp/plyer/platforms/android/filechooser.py index b809ccb..b8c943c 100644 --- a/sbapp/plyer/platforms/android/filechooser.py +++ b/sbapp/plyer/platforms/android/filechooser.py @@ -43,7 +43,7 @@ using that result will use an incorrect one i.e. the default value of .. versionadded:: 1.4.0 ''' -from os.path import join, basename +from os.path import join from random import randint from android import activity, mActivity @@ -62,6 +62,8 @@ 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') +Files = autoclass('android.provider.MediaStore$Files') +FileOutputStream = autoclass('java.io.FileOutputStream') class AndroidFileChooser(FileChooser): @@ -74,6 +76,7 @@ class AndroidFileChooser(FileChooser): # filechooser activity <-> result pair identification select_code = None + save_code = None # default selection value selection = None @@ -105,6 +108,7 @@ class AndroidFileChooser(FileChooser): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.select_code = randint(123456, 654321) + self.save_code = randint(123456, 654321) self.selection = None # bind a function for a response from filechooser activity @@ -139,9 +143,11 @@ class AndroidFileChooser(FileChooser): # 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: + if ( + not self.selected_mime_type + or not isinstance(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]) @@ -163,6 +169,38 @@ class AndroidFileChooser(FileChooser): self.select_code ) + def _save_file(self, **kwargs): + self._save_callback = kwargs.pop("callback") + + title = kwargs.pop("title", None) + + self.selected_mime_type = \ + kwargs.pop("filters")[0] if "filters" in kwargs else "" + + file_intent = Intent(Intent.ACTION_CREATE_DOCUMENT) + if ( + not self.selected_mime_type + or not isinstance(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 + ) + + if title: + file_intent.putExtra(Intent.EXTRA_TITLE, title) + + mActivity.startActivityForResult( + Intent.createChooser(file_intent, cast( + 'java.lang.CharSequence', + String("FileChooser") + )), + self.save_code + ) + def _on_activity_result(self, request_code, result_code, data): ''' Listener for ``android.app.Activity.onActivityResult()`` assigned @@ -171,28 +209,41 @@ class AndroidFileChooser(FileChooser): .. versionadded:: 1.4.0 ''' - # not our response - if request_code != self.select_code: + # bad data + if data is None: 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()), ] + if request_code == self.select_code: + 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) + # return value to object + self.selection = selection + # return value via callback + self._handle_selection(selection) + + elif request_code == self.save_code: + uri = data.getData() + + with mActivity.getContentResolver().openFileDescriptor( + uri, "w" + ) as pfd: + with FileOutputStream( + pfd.getFileDescriptor() + ) as fileOutputStream: + # return value via callback + self._save_callback(fileOutputStream) @staticmethod def _handle_external_documents(uri): @@ -206,28 +257,19 @@ class AndroidFileChooser(FileChooser): 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() + primary_storage = storagepath.get_external_storage_dir() + sdcard_storage = storagepath.get_sdcard_dir() - # external (removable) SD card i.e. microSD - external = storagepath.get_sdcard_dir() - try: - external_base = basename(external) - except TypeError: - external_base = basename(internal) + directory = primary_storage - # 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 + if file_type == "primary": + directory = primary_storage elif file_type == "home": - sd_card = join(Environment.getExternalStorageDirectory( - ).getAbsolutePath(), Environment.DIRECTORY_DOCUMENTS) + directory = join(primary_storage, Environment.DIRECTORY_DOCUMENTS) + elif sdcard_storage and file_type in sdcard_storage: + directory = sdcard_storage - return join(sd_card, file_name) + return join(directory, file_name) @staticmethod def _handle_media_documents(uri): @@ -248,6 +290,11 @@ class AndroidFileChooser(FileChooser): uri = VMedia.EXTERNAL_CONTENT_URI elif file_type == 'audio': uri = AMedia.EXTERNAL_CONTENT_URI + + # Other file type was selected (probably in the Documents folder) + else: + uri = Files.getContentUri("external") + return file_name, selection, uri @staticmethod @@ -279,6 +326,23 @@ class AndroidFileChooser(FileChooser): .. versionadded:: 1.4.0 ''' + try: + download_dir = Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_DOWNLOADS + ).getPath() + path = AndroidFileChooser._parse_content( + uri=uri, + projection=["_display_name"], + selection=None, + selection_args=None, + sort_order=None, + ) + return join(download_dir, path) + + except Exception: + import traceback + traceback.print_exc() + # known locations, differ between machines downloads = [ 'content://downloads/public_downloads', @@ -441,6 +505,8 @@ class AndroidFileChooser(FileChooser): mode = kwargs.pop('mode', None) if mode == 'open': self._open_file(**kwargs) + elif mode == 'save': + self._save_file(**kwargs) def instance(): diff --git a/sbapp/plyer/platforms/android/gps.py b/sbapp/plyer/platforms/android/gps.py index b173876..740b636 100644 --- a/sbapp/plyer/platforms/android/gps.py +++ b/sbapp/plyer/platforms/android/gps.py @@ -86,4 +86,4 @@ class AndroidGPS(GPS): def instance(): - return AndroidGPS() \ No newline at end of file + return AndroidGPS() diff --git a/sbapp/plyer/platforms/android/gyroscope.py b/sbapp/plyer/platforms/android/gyroscope.py index d6382b1..99b224e 100644 --- a/sbapp/plyer/platforms/android/gyroscope.py +++ b/sbapp/plyer/platforms/android/gyroscope.py @@ -110,7 +110,7 @@ class AndroidGyroscope(Gyroscope): return (None, None, None, None, None, None) def __del__(self): - if(self.bState): + if self.bState: self._disable() super().__del__() diff --git a/sbapp/plyer/platforms/android/notification.py b/sbapp/plyer/platforms/android/notification.py index ac9f76b..ee0de2d 100644 --- a/sbapp/plyer/platforms/android/notification.py +++ b/sbapp/plyer/platforms/android/notification.py @@ -154,10 +154,15 @@ class AndroidNotification(Notification): notification_intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) notification_intent.setAction(Intent.ACTION_MAIN) notification_intent.addCategory(Intent.CATEGORY_LAUNCHER) + if SDK_INT >= 23: + # FLAG_IMMUTABLE added in SDK 23, required since SDK 31: + pending_flags = PendingIntent.FLAG_IMMUTABLE + else: + pending_flags = 0 # get our application Activity pending_intent = PendingIntent.getActivity( - app_context, 0, notification_intent, 0 + app_context, 0, notification_intent, pending_flags ) notification.setContentIntent(pending_intent) @@ -179,8 +184,6 @@ class AndroidNotification(Notification): kwargs.get('title', '').encode('utf-8') ) icon = kwargs.get('app_icon') - notification_icon = kwargs.get('notification_icon') - context_override = kwargs.get('context_override') # decide whether toast only or proper notification if kwargs.get('toast'): diff --git a/sbapp/plyer/platforms/android/storagepath.py b/sbapp/plyer/platforms/android/storagepath.py index 788e3fc..d18ea61 100755 --- a/sbapp/plyer/platforms/android/storagepath.py +++ b/sbapp/plyer/platforms/android/storagepath.py @@ -3,14 +3,13 @@ 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 plyer.platforms.android import SDK_INT +from jnius import autoclass, cast from android import mActivity -Environment = autoclass('android.os.Environment') -Context = autoclass('android.content.Context') +Environment = autoclass("android.os.Environment") +Context = autoclass("android.content.Context") class AndroidStoragePath(StoragePath): @@ -25,17 +24,29 @@ class AndroidStoragePath(StoragePath): ''' .. 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 + context = mActivity.getApplicationContext() + storage_manager = cast( + "android.os.storage.StorageManager", + context.getSystemService(Context.STORAGE_SERVICE), + ) + + if storage_manager is not None: + if SDK_INT >= 24: + storage_volumes = storage_manager.getStorageVolumes() + for storage_volume in storage_volumes: + if storage_volume.isRemovable(): + try: + directory = storage_volume.getDirectory() + except AttributeError: + directory = storage_volume.getPathFile() + path = directory.getAbsolutePath() + else: + storage_volumes = storage_manager.getVolumeList() + for storage_volume in storage_volumes: + if storage_volume.isRemovable(): + path = storage_volume.getPath() + return path def _get_root_dir(self): diff --git a/sbapp/plyer/platforms/ios/accelerometer.py b/sbapp/plyer/platforms/ios/accelerometer.py index 7250384..0f61f2b 100644 --- a/sbapp/plyer/platforms/ios/accelerometer.py +++ b/sbapp/plyer/platforms/ios/accelerometer.py @@ -6,7 +6,7 @@ Taken from: http://pyobjus.readthedocs.org/en/latest/pyobjus_ios.html \ #accessing-accelerometer ''' -from plyer.facades import Accelerometer +from sbapp.plyer.facades import Accelerometer from pyobjus import autoclass diff --git a/sbapp/plyer/platforms/ios/barometer.py b/sbapp/plyer/platforms/ios/barometer.py index a5c5dc3..9ec8d6e 100644 --- a/sbapp/plyer/platforms/ios/barometer.py +++ b/sbapp/plyer/platforms/ios/barometer.py @@ -3,7 +3,7 @@ iOS Barometer ------------- ''' -from plyer.facades import Barometer +from sbapp.plyer.facades import Barometer from pyobjus import autoclass diff --git a/sbapp/plyer/platforms/ios/battery.py b/sbapp/plyer/platforms/ios/battery.py index 818f0be..d7fa5d8 100644 --- a/sbapp/plyer/platforms/ios/battery.py +++ b/sbapp/plyer/platforms/ios/battery.py @@ -4,7 +4,7 @@ Module of iOS API for plyer.battery. from pyobjus import autoclass from pyobjus.dylib_manager import load_framework -from plyer.facades import Battery +from sbapp.plyer.facades import Battery load_framework('/System/Library/Frameworks/UIKit.framework') UIDevice = autoclass('UIDevice') diff --git a/sbapp/plyer/platforms/ios/brightness.py b/sbapp/plyer/platforms/ios/brightness.py index 065869a..f4a70f4 100644 --- a/sbapp/plyer/platforms/ios/brightness.py +++ b/sbapp/plyer/platforms/ios/brightness.py @@ -4,7 +4,7 @@ iOS Brightness ''' from pyobjus import autoclass -from plyer.facades import Brightness +from sbapp.plyer.facades import Brightness from pyobjus.dylib_manager import load_framework load_framework('/System/Library/Frameworks/UIKit.framework') diff --git a/sbapp/plyer/platforms/ios/call.py b/sbapp/plyer/platforms/ios/call.py index c751399..9999d6a 100644 --- a/sbapp/plyer/platforms/ios/call.py +++ b/sbapp/plyer/platforms/ios/call.py @@ -3,7 +3,7 @@ IOS Call ---------- ''' -from plyer.facades import Call +from sbapp.plyer.facades import Call from pyobjus import autoclass, objc_str NSURL = autoclass('NSURL') diff --git a/sbapp/plyer/platforms/ios/camera.py b/sbapp/plyer/platforms/ios/camera.py index dfe5265..a61bd96 100644 --- a/sbapp/plyer/platforms/ios/camera.py +++ b/sbapp/plyer/platforms/ios/camera.py @@ -1,7 +1,7 @@ from os import remove -from plyer.facades import Camera +from sbapp.plyer.facades import Camera -from plyer.utils import reify +from sbapp.plyer.utils import reify class iOSCamera(Camera): @@ -14,7 +14,7 @@ class iOSCamera(Camera): return PhotosLibrary() def _take_picture(self, on_complete, filename=None): - assert(on_complete is not None) + assert on_complete is not None self.on_complete = on_complete self.filename = filename photos = self.photos @@ -38,7 +38,7 @@ class iOSCamera(Camera): self._remove(self.filename) def _take_video(self, on_complete, filename=None): - assert(on_complete is not None) + assert on_complete is not None raise NotImplementedError def _remove(self, fn): diff --git a/sbapp/plyer/platforms/ios/compass.py b/sbapp/plyer/platforms/ios/compass.py index 855484b..a5c865f 100644 --- a/sbapp/plyer/platforms/ios/compass.py +++ b/sbapp/plyer/platforms/ios/compass.py @@ -3,7 +3,7 @@ iOS Compass ----------- ''' -from plyer.facades import Compass +from sbapp.plyer.facades import Compass from pyobjus import autoclass diff --git a/sbapp/plyer/platforms/ios/email.py b/sbapp/plyer/platforms/ios/email.py index e1cc7cb..ec54860 100644 --- a/sbapp/plyer/platforms/ios/email.py +++ b/sbapp/plyer/platforms/ios/email.py @@ -7,7 +7,7 @@ try: except ImportError: from urllib import quote -from plyer.facades import Email +from sbapp.plyer.facades import Email from pyobjus import autoclass, objc_str from pyobjus.dylib_manager import load_framework diff --git a/sbapp/plyer/platforms/ios/filechooser.py b/sbapp/plyer/platforms/ios/filechooser.py index ba49ba9..0320d6c 100644 --- a/sbapp/plyer/platforms/ios/filechooser.py +++ b/sbapp/plyer/platforms/ios/filechooser.py @@ -7,7 +7,7 @@ This module houses the iOS implementation of the plyer FileChooser. .. versionadded:: 1.4.4 ''' -from plyer.facades import FileChooser +from sbapp.plyer.facades import FileChooser from pyobjus import autoclass, protocol from pyobjus.dylib_manager import load_framework diff --git a/sbapp/plyer/platforms/ios/flash.py b/sbapp/plyer/platforms/ios/flash.py index e4bedac..17f59f1 100644 --- a/sbapp/plyer/platforms/ios/flash.py +++ b/sbapp/plyer/platforms/ios/flash.py @@ -3,7 +3,7 @@ Flash ----- """ -from plyer.facades import Flash +from sbapp.plyer.facades import Flash from pyobjus import autoclass NSString = autoclass("NSString") diff --git a/sbapp/plyer/platforms/ios/gps.py b/sbapp/plyer/platforms/ios/gps.py index 4c52738..d7ee0a7 100644 --- a/sbapp/plyer/platforms/ios/gps.py +++ b/sbapp/plyer/platforms/ios/gps.py @@ -5,7 +5,7 @@ iOS GPS from pyobjus import autoclass, protocol from pyobjus.dylib_manager import load_framework -from plyer.facades import GPS +from sbapp.plyer.facades import GPS load_framework('/System/Library/Frameworks/CoreLocation.framework') CLLocationManager = autoclass('CLLocationManager') diff --git a/sbapp/plyer/platforms/ios/gravity.py b/sbapp/plyer/platforms/ios/gravity.py index a2b1be0..9452bda 100644 --- a/sbapp/plyer/platforms/ios/gravity.py +++ b/sbapp/plyer/platforms/ios/gravity.py @@ -4,7 +4,7 @@ iOS Gravity ''' -from plyer.facades import Gravity +from sbapp.plyer.facades import Gravity from pyobjus import autoclass diff --git a/sbapp/plyer/platforms/ios/gyroscope.py b/sbapp/plyer/platforms/ios/gyroscope.py index 340653c..367e1bf 100644 --- a/sbapp/plyer/platforms/ios/gyroscope.py +++ b/sbapp/plyer/platforms/ios/gyroscope.py @@ -3,7 +3,7 @@ iOS Gyroscope --------------------- ''' -from plyer.facades import Gyroscope +from sbapp.plyer.facades import Gyroscope from pyobjus import autoclass from pyobjus.dylib_manager import load_framework diff --git a/sbapp/plyer/platforms/ios/keystore.py b/sbapp/plyer/platforms/ios/keystore.py index 289a5c0..a18cde2 100644 --- a/sbapp/plyer/platforms/ios/keystore.py +++ b/sbapp/plyer/platforms/ios/keystore.py @@ -1,4 +1,4 @@ -from plyer.facades import Keystore +from sbapp.plyer.facades import Keystore from pyobjus import autoclass, objc_str NSUserDefaults = autoclass('NSUserDefaults') diff --git a/sbapp/plyer/platforms/ios/maps.py b/sbapp/plyer/platforms/ios/maps.py new file mode 100644 index 0000000..dac01d5 --- /dev/null +++ b/sbapp/plyer/platforms/ios/maps.py @@ -0,0 +1,78 @@ +''' +Module of iOS API for plyer.maps. +''' + +import webbrowser +from sbapp.plyer.facades import Maps +from urllib.parse import quote_plus + + +class iOSMaps(Maps): + ''' + Implementation of iOS Maps API. + ''' + + def _open_by_address(self, address, **kwargs): + ''' + :param address: An address string that geolocation can understand. + ''' + + address = quote_plus(address, safe=',') + maps_address = 'http://maps.apple.com/?address=' + address + + webbrowser.open(maps_address) + + def _open_by_lat_long(self, latitude, longitude, **kwargs): + ''' + Open a coordinate span denoting a latitudinal delta and a + longitudinal delta (similar to MKCoordinateSpan) + + :param name: (optional), will set the name of the dropped pin + ''' + + name = kwargs.get("name", "Selected Location") + maps_address = 'http://maps.apple.com/?ll={},{}&q={}'.format( + latitude, longitude, name) + + webbrowser.open(maps_address) + + def _search(self, query, **kwargs): + ''' + :param query: A string that describes the search object (ex. "Pizza") + + :param latitude: (optional), narrow down query within area, + MUST BE USED WITH LONGITUDE + + :param longitude: (optional), narrow down query within area, + MUST BE USED WITH LATITUDE + ''' + + latitude = kwargs.get('latitude') + longitude = kwargs.get('longitude') + + query = quote_plus(query, safe=',') + maps_address = 'http://maps.apple.com/?q=' + query + + if latitude is not None and longitude is not None: + maps_address += '&sll={},{}'.format(latitude, longitude) + + webbrowser.open(maps_address) + + def _route(self, saddr, daddr, **kwargs): + ''' + :param saddr: can be given as 'address' or 'lat,long' + :param daddr: can be given as 'address' or 'lat,long' + ''' + saddr = quote_plus(saddr, safe=',') + daddr = quote_plus(daddr, safe=',') + + maps_address = 'http://maps.apple.com/?saddr={}&daddr={}'.format( + saddr, daddr) + webbrowser.open(maps_address) + + +def instance(): + ''' + Instance for facade proxy. + ''' + return iOSMaps() diff --git a/sbapp/plyer/platforms/ios/sms.py b/sbapp/plyer/platforms/ios/sms.py index ef3acaa..fc6b1b4 100644 --- a/sbapp/plyer/platforms/ios/sms.py +++ b/sbapp/plyer/platforms/ios/sms.py @@ -3,7 +3,7 @@ IOS Sms ---------- ''' -from plyer.facades import Sms +from sbapp.plyer.facades import Sms from pyobjus import autoclass, objc_str from pyobjus.dylib_manager import load_framework diff --git a/sbapp/plyer/platforms/ios/spatialorientation.py b/sbapp/plyer/platforms/ios/spatialorientation.py index d42d0fa..7c6d9cc 100644 --- a/sbapp/plyer/platforms/ios/spatialorientation.py +++ b/sbapp/plyer/platforms/ios/spatialorientation.py @@ -4,7 +4,7 @@ iOS Spatial Orientation ''' -from plyer.facades import SpatialOrientation +from sbapp.plyer.facades import SpatialOrientation from pyobjus import autoclass diff --git a/sbapp/plyer/platforms/ios/storagepath.py b/sbapp/plyer/platforms/ios/storagepath.py index cd8bbc3..c69a083 100644 --- a/sbapp/plyer/platforms/ios/storagepath.py +++ b/sbapp/plyer/platforms/ios/storagepath.py @@ -3,7 +3,7 @@ iOS Storage Path -------------------- ''' -from plyer.facades import StoragePath +from sbapp.plyer.facades import StoragePath from pyobjus import autoclass import os diff --git a/sbapp/plyer/platforms/ios/tts.py b/sbapp/plyer/platforms/ios/tts.py index 046769f..510399d 100644 --- a/sbapp/plyer/platforms/ios/tts.py +++ b/sbapp/plyer/platforms/ios/tts.py @@ -1,7 +1,7 @@ from pyobjus import autoclass, objc_str from pyobjus.dylib_manager import load_framework -from plyer.facades import TTS +from sbapp.plyer.facades import TTS load_framework('/System/Library/Frameworks/AVFoundation.framework') AVSpeechUtterance = autoclass('AVSpeechUtterance') @@ -23,7 +23,7 @@ class iOSTextToSpeech(TTS): def _speak(self, **kwargs): message = kwargs.get('message') - if(not self.voice): + if not self.voice: self._set_locale() utterance = \ diff --git a/sbapp/plyer/platforms/ios/uniqueid.py b/sbapp/plyer/platforms/ios/uniqueid.py index 4d30249..e9fa815 100644 --- a/sbapp/plyer/platforms/ios/uniqueid.py +++ b/sbapp/plyer/platforms/ios/uniqueid.py @@ -4,7 +4,7 @@ Module of iOS API for plyer.uniqueid. from pyobjus import autoclass from pyobjus.dylib_manager import load_framework -from plyer.facades import UniqueID +from sbapp.plyer.facades import UniqueID load_framework('/System/Library/Frameworks/UIKit.framework') UIDevice = autoclass('UIDevice') diff --git a/sbapp/plyer/platforms/ios/vibrator.py b/sbapp/plyer/platforms/ios/vibrator.py index 922182c..05c12c5 100644 --- a/sbapp/plyer/platforms/ios/vibrator.py +++ b/sbapp/plyer/platforms/ios/vibrator.py @@ -4,7 +4,7 @@ Install: Add AudioToolbox framework to your application. ''' import ctypes -from plyer.facades import Vibrator +from sbapp.plyer.facades import Vibrator class IosVibrator(Vibrator): diff --git a/sbapp/plyer/platforms/linux/accelerometer.py b/sbapp/plyer/platforms/linux/accelerometer.py index 7af283b..889de15 100644 --- a/sbapp/plyer/platforms/linux/accelerometer.py +++ b/sbapp/plyer/platforms/linux/accelerometer.py @@ -3,7 +3,7 @@ Linux accelerometer --------------------- ''' -from plyer.facades import Accelerometer +from sbapp.plyer.facades import Accelerometer import glob import re diff --git a/sbapp/plyer/platforms/linux/audio.py b/sbapp/plyer/platforms/linux/audio.py new file mode 100644 index 0000000..55cdb7c --- /dev/null +++ b/sbapp/plyer/platforms/linux/audio.py @@ -0,0 +1,48 @@ +import time +import threading +from sbapp.plyer.facades.audio import Audio + +class LinuxAudio(Audio): + + def __init__(self, file_path=None): + default_path = None + super().__init__(file_path or default_path) + + self._recorder = None + self._player = None + self._check_thread = None + self._finished_callback = None + + def _check_playback(self): + while self.is_playing: + time.sleep(0.25) + + if self._finished_callback and callable(self._finished_callback): + self._check_thread = None + self._finished_callback(self) + + def _start(self): + # TODO: Implement recording + pass + + def _stop(self): + # TODO: Implement recording + pass + + def _play(self): + # TODO: Implement playback + self.is_playing = True + + self._check_thread = threading.Thread(target=self._check_playback, daemon=True) + self._check_thread.start() + + def fauxplay(): + time.sleep(1.5) + self.is_playing = False + + threading.Thread(target=fauxplay, daemon=True).start() + + + +def instance(): + return LinuxAudio() diff --git a/sbapp/plyer/platforms/linux/battery.py b/sbapp/plyer/platforms/linux/battery.py index b0c4691..9fc9f9f 100644 --- a/sbapp/plyer/platforms/linux/battery.py +++ b/sbapp/plyer/platforms/linux/battery.py @@ -2,13 +2,12 @@ Module of Linux API for plyer.battery. ''' -import os 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 +from sbapp.plyer.facades import Battery +from sbapp.plyer.utils import whereis_exe class LinuxBattery(Battery): @@ -20,10 +19,10 @@ class LinuxBattery(Battery): def _get_state(self): status = {"isCharging": None, "percentage": None} - kernel_bat_path = join('/sys', 'class', 'power_supply', self.node_name) + kernel_bat_path = join('/sys', 'class', 'power_supply', 'BAT0') uevent = join(kernel_bat_path, 'uevent') - with open(uevent, "rb") as fle: + with open(uevent) as fle: lines = [ line.decode('utf-8').strip() for line in fle.readlines() @@ -34,34 +33,70 @@ class LinuxBattery(Battery): } is_charging = output['POWER_SUPPLY_STATUS'] == 'Charging' - charge_percent = float(output['POWER_SUPPLY_CAPACITY']) + total = float(output['POWER_SUPPLY_CHARGE_FULL']) + now = float(output['POWER_SUPPLY_CHARGE_NOW']) - status['percentage'] = charge_percent + 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.") - - node_exists = False - bn = 0 - node_name = None - for bi in range(0,10): - path = join('/sys', 'class', 'power_supply', 'BAT'+str(bi)) - if os.path.isdir(path): - node_name = "BAT"+str(bi) - break - - if node_name: - b = LinuxBattery() - b.node_name = node_name - return b + if whereis_exe('upower'): + return UPowerBattery() + sys.stderr.write("upower not found.") + if exists(join('/sys', 'class', 'power_supply', 'BAT0')): + return LinuxBattery() return Battery() diff --git a/sbapp/plyer/platforms/linux/brightness.py b/sbapp/plyer/platforms/linux/brightness.py index 0de1698..eb30536 100755 --- a/sbapp/plyer/platforms/linux/brightness.py +++ b/sbapp/plyer/platforms/linux/brightness.py @@ -4,7 +4,7 @@ Linux Brightness ''' -from plyer.facades import Brightness +from sbapp.plyer.facades import Brightness import subprocess import os diff --git a/sbapp/plyer/platforms/linux/cpu.py b/sbapp/plyer/platforms/linux/cpu.py index 049a2d4..66a532f 100644 --- a/sbapp/plyer/platforms/linux/cpu.py +++ b/sbapp/plyer/platforms/linux/cpu.py @@ -5,8 +5,8 @@ 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 +from sbapp.plyer.facades import CPU +from sbapp.plyer.utils import whereis_exe class LinuxCPU(CPU): diff --git a/sbapp/plyer/platforms/linux/devicename.py b/sbapp/plyer/platforms/linux/devicename.py index 8941379..cbd9008 100644 --- a/sbapp/plyer/platforms/linux/devicename.py +++ b/sbapp/plyer/platforms/linux/devicename.py @@ -3,7 +3,7 @@ Module of Linux API for plyer.devicename. ''' import socket -from plyer.facades import DeviceName +from sbapp.plyer.facades import DeviceName class LinuxDeviceName(DeviceName): diff --git a/sbapp/plyer/platforms/linux/email.py b/sbapp/plyer/platforms/linux/email.py index 64cbb6d..90efce9 100644 --- a/sbapp/plyer/platforms/linux/email.py +++ b/sbapp/plyer/platforms/linux/email.py @@ -7,8 +7,8 @@ try: from urllib.parse import quote except ImportError: from urllib import quote -from plyer.facades import Email -from plyer.utils import whereis_exe +from sbapp.plyer.facades import Email +from sbapp.plyer.utils import whereis_exe class LinuxEmail(Email): diff --git a/sbapp/plyer/platforms/linux/filechooser.py b/sbapp/plyer/platforms/linux/filechooser.py index cfdb1ae..a8e2ef2 100644 --- a/sbapp/plyer/platforms/linux/filechooser.py +++ b/sbapp/plyer/platforms/linux/filechooser.py @@ -3,7 +3,7 @@ Linux file chooser ------------------ ''' -from plyer.facades import FileChooser +from sbapp.plyer.facades import FileChooser from distutils.spawn import find_executable as which import os import subprocess as sp @@ -122,7 +122,7 @@ class ZenityFileChooser(SubprocessFileChooser): if self.icon: cmdline += ["--window-icon", self.icon] for f in self.filters: - if type(f) == str: + if isinstance(f, str): cmdline += ["--file-filter", f] else: cmdline += [ @@ -150,7 +150,7 @@ class KDialogFileChooser(SubprocessFileChooser): filt = [] for f in self.filters: - if type(f) == str: + if isinstance(f, str): filt += [f] else: filt += list(f[1:]) @@ -195,7 +195,7 @@ class YADFileChooser(SubprocessFileChooser): def _gen_cmdline(self): cmdline = [ which(self.executable), - "--file-selection", + "--file", "--confirm-overwrite", "--geometry", "800x600+150+150" @@ -215,7 +215,7 @@ class YADFileChooser(SubprocessFileChooser): if self.icon: cmdline += ["--window-icon", self.icon] for f in self.filters: - if type(f) == str: + if isinstance(f, str): cmdline += ["--file-filter", f] else: cmdline += [ diff --git a/sbapp/plyer/platforms/linux/keystore.py b/sbapp/plyer/platforms/linux/keystore.py index 105ebd3..b43be70 100644 --- a/sbapp/plyer/platforms/linux/keystore.py +++ b/sbapp/plyer/platforms/linux/keystore.py @@ -3,7 +3,7 @@ try: except ImportError: raise NotImplementedError() -from plyer.facades import Keystore +from sbapp.plyer.facades import Keystore class LinuxKeystore(Keystore): diff --git a/sbapp/plyer/platforms/linux/notification.py b/sbapp/plyer/platforms/linux/notification.py index 95a6472..65f5131 100644 --- a/sbapp/plyer/platforms/linux/notification.py +++ b/sbapp/plyer/platforms/linux/notification.py @@ -4,8 +4,8 @@ Module of Linux API for plyer.notification. import warnings import subprocess -from plyer.facades import Notification -from plyer.utils import whereis_exe +from sbapp.plyer.facades import Notification +from sbapp.plyer.utils import whereis_exe import os diff --git a/sbapp/plyer/platforms/linux/orientation.py b/sbapp/plyer/platforms/linux/orientation.py index e60fa42..c190128 100644 --- a/sbapp/plyer/platforms/linux/orientation.py +++ b/sbapp/plyer/platforms/linux/orientation.py @@ -1,5 +1,5 @@ import subprocess as sb -from plyer.facades import Orientation +from sbapp.plyer.facades import Orientation class LinuxOrientation(Orientation): diff --git a/sbapp/plyer/platforms/linux/processors.py b/sbapp/plyer/platforms/linux/processors.py index 74bb73a..332ec83 100644 --- a/sbapp/plyer/platforms/linux/processors.py +++ b/sbapp/plyer/platforms/linux/processors.py @@ -1,6 +1,6 @@ from subprocess import Popen, PIPE -from plyer.facades import Processors -from plyer.utils import whereis_exe +from sbapp.plyer.facades import Processors +from sbapp.plyer.utils import whereis_exe from os import environ diff --git a/sbapp/plyer/platforms/linux/screenshot.py b/sbapp/plyer/platforms/linux/screenshot.py index 00eeb5c..5dc2ccf 100644 --- a/sbapp/plyer/platforms/linux/screenshot.py +++ b/sbapp/plyer/platforms/linux/screenshot.py @@ -1,8 +1,8 @@ import subprocess from os.path import join -from plyer.facades import Screenshot -from plyer.utils import whereis_exe -from plyer.platforms.linux.storagepath import LinuxStoragePath +from sbapp.plyer.facades import Screenshot +from sbapp.plyer.utils import whereis_exe +from sbapp.plyer.platforms.linux.storagepath import LinuxStoragePath class LinuxScreenshot(Screenshot): diff --git a/sbapp/plyer/platforms/linux/storagepath.py b/sbapp/plyer/platforms/linux/storagepath.py index 736674a..b32a5fe 100755 --- a/sbapp/plyer/platforms/linux/storagepath.py +++ b/sbapp/plyer/platforms/linux/storagepath.py @@ -3,7 +3,7 @@ Linux Storage Path -------------------- ''' -from plyer.facades import StoragePath +from sbapp.plyer.facades import StoragePath from os.path import expanduser, dirname, abspath, join, exists # Default paths for each name diff --git a/sbapp/plyer/platforms/linux/tts.py b/sbapp/plyer/platforms/linux/tts.py index 3932772..3b8b634 100644 --- a/sbapp/plyer/platforms/linux/tts.py +++ b/sbapp/plyer/platforms/linux/tts.py @@ -1,6 +1,6 @@ import subprocess -from plyer.facades import TTS -from plyer.utils import whereis_exe +from sbapp.plyer.facades import TTS +from sbapp.plyer.utils import whereis_exe class EspeakTextToSpeech(TTS): diff --git a/sbapp/plyer/platforms/linux/uniqueid.py b/sbapp/plyer/platforms/linux/uniqueid.py index 44926b1..30c1112 100644 --- a/sbapp/plyer/platforms/linux/uniqueid.py +++ b/sbapp/plyer/platforms/linux/uniqueid.py @@ -4,8 +4,8 @@ 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 +from sbapp.plyer.facades import UniqueID +from sbapp.plyer.utils import whereis_exe class LinuxUniqueID(UniqueID): diff --git a/sbapp/plyer/platforms/linux/wifi.py b/sbapp/plyer/platforms/linux/wifi.py index 73f09b6..144f58a 100644 --- a/sbapp/plyer/platforms/linux/wifi.py +++ b/sbapp/plyer/platforms/linux/wifi.py @@ -6,8 +6,8 @@ ''' from subprocess import Popen, PIPE, call -from plyer.facades import Wifi -from plyer.utils import whereis_exe, deprecated +from sbapp.plyer.facades import Wifi +from sbapp.plyer.utils import whereis_exe, deprecated try: import wifi diff --git a/sbapp/plyer/platforms/macosx/accelerometer.py b/sbapp/plyer/platforms/macosx/accelerometer.py index 91e72c3..5b52578 100644 --- a/sbapp/plyer/platforms/macosx/accelerometer.py +++ b/sbapp/plyer/platforms/macosx/accelerometer.py @@ -3,8 +3,8 @@ MacOSX accelerometer --------------------- ''' -from plyer.facades import Accelerometer -from plyer.platforms.macosx.libs import osx_motion_sensor +from sbapp.plyer.facades import Accelerometer +from sbapp.plyer.platforms.macosx.libs import osx_motion_sensor class OSXAccelerometer(Accelerometer): diff --git a/sbapp/plyer/platforms/macosx/audio.py b/sbapp/plyer/platforms/macosx/audio.py index 3ab9ce1..a9f4237 100644 --- a/sbapp/plyer/platforms/macosx/audio.py +++ b/sbapp/plyer/platforms/macosx/audio.py @@ -3,8 +3,8 @@ from 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 +from sbapp.plyer.facades import Audio +from sbapp.plyer.platforms.macosx.storagepath import OSXStoragePath load_framework(INCLUDE.Foundation) load_framework(INCLUDE.AVFoundation) @@ -19,16 +19,24 @@ NSError = autoclass('NSError').alloc() class OSXAudio(Audio): def __init__(self, file_path=None): - default_path = join( - OSXStoragePath().get_music_dir(), - 'audio.wav' - ) + default_path = None super().__init__(file_path or default_path) self._recorder = None self._player = None self._current_file = None + self._check_thread = None + self._finished_callback = None + + def _check_playback(self): + while self._player and self._player.isPlaying: + time.sleep(0.25) + + if self._finished_callback and callable(self._finished_callback): + self._check_thread = None + self._finished_callback(self) + def _start(self): # Conversion of Python file path string to Objective-C NSString file_path_NSString = NSString.alloc() @@ -44,7 +52,7 @@ class OSXAudio(Audio): # Internal audio file format specification af = AVAudioFormat.alloc() af = af.initWithCommonFormat_sampleRate_channels_interleaved_( - 1, 44100.0, 2, True + 1, 44100.0, 1, True ) # Audio recorder instance initialization with specified file NSURL @@ -85,6 +93,9 @@ class OSXAudio(Audio): self._player.play() + self._check_thread = threading.Thread(target=self._check_playback, daemon=True) + self._check_thread.start() + def instance(): return OSXAudio() diff --git a/sbapp/plyer/platforms/macosx/battery.py b/sbapp/plyer/platforms/macosx/battery.py index 57965d3..ec42486 100644 --- a/sbapp/plyer/platforms/macosx/battery.py +++ b/sbapp/plyer/platforms/macosx/battery.py @@ -4,8 +4,8 @@ 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 +from sbapp.plyer.facades import Battery +from sbapp.plyer.utils import whereis_exe class OSXBattery(Battery): diff --git a/sbapp/plyer/platforms/macosx/bluetooth.py b/sbapp/plyer/platforms/macosx/bluetooth.py index 2575817..7c2ec22 100644 --- a/sbapp/plyer/platforms/macosx/bluetooth.py +++ b/sbapp/plyer/platforms/macosx/bluetooth.py @@ -3,8 +3,8 @@ Module of MacOS API for plyer.bluetooth. ''' from subprocess import Popen, PIPE -from plyer.facades import Bluetooth -from plyer.utils import whereis_exe +from sbapp.plyer.facades import Bluetooth +from sbapp.plyer.utils import whereis_exe from os import environ diff --git a/sbapp/plyer/platforms/macosx/cpu.py b/sbapp/plyer/platforms/macosx/cpu.py index 7b7da7f..26eeebb 100644 --- a/sbapp/plyer/platforms/macosx/cpu.py +++ b/sbapp/plyer/platforms/macosx/cpu.py @@ -3,8 +3,8 @@ Module of MacOS API for plyer.cpu. ''' from subprocess import Popen, PIPE -from plyer.facades import CPU -from plyer.utils import whereis_exe +from sbapp.plyer.facades import CPU +from sbapp.plyer.utils import whereis_exe class OSXCPU(CPU): diff --git a/sbapp/plyer/platforms/macosx/devicename.py b/sbapp/plyer/platforms/macosx/devicename.py index 6652425..e100246 100644 --- a/sbapp/plyer/platforms/macosx/devicename.py +++ b/sbapp/plyer/platforms/macosx/devicename.py @@ -3,7 +3,7 @@ Module of MacOSX API for plyer.devicename. ''' import socket -from plyer.facades import DeviceName +from sbapp.plyer.facades import DeviceName class OSXDeviceName(DeviceName): diff --git a/sbapp/plyer/platforms/macosx/email.py b/sbapp/plyer/platforms/macosx/email.py index 49c5d4d..42295ad 100644 --- a/sbapp/plyer/platforms/macosx/email.py +++ b/sbapp/plyer/platforms/macosx/email.py @@ -9,8 +9,8 @@ try: except ImportError: from urllib import quote -from plyer.facades import Email -from plyer.utils import whereis_exe +from sbapp.plyer.facades import Email +from sbapp.plyer.utils import whereis_exe class MacOSXEmail(Email): diff --git a/sbapp/plyer/platforms/macosx/filechooser.py b/sbapp/plyer/platforms/macosx/filechooser.py index 85fb91c..1d48062 100644 --- a/sbapp/plyer/platforms/macosx/filechooser.py +++ b/sbapp/plyer/platforms/macosx/filechooser.py @@ -3,7 +3,7 @@ Mac OS X file chooser --------------------- ''' -from plyer.facades import FileChooser +from sbapp.plyer.facades import FileChooser from pyobjus import autoclass, objc_arr, objc_str from pyobjus.dylib_manager import load_framework, INCLUDE @@ -80,7 +80,7 @@ class MacFileChooser: if self.filters: filthies = [] for f in self.filters: - if type(f) == str: + if isinstance(f, str): f = (None, f) for s in f[1:]: if not self.use_extensions: diff --git a/sbapp/plyer/platforms/macosx/keystore.py b/sbapp/plyer/platforms/macosx/keystore.py index 11e60e8..ec00cff 100644 --- a/sbapp/plyer/platforms/macosx/keystore.py +++ b/sbapp/plyer/platforms/macosx/keystore.py @@ -3,7 +3,7 @@ try: except ImportError: raise NotImplementedError() -from plyer.facades import Keystore +from sbapp.plyer.facades import Keystore class OSXKeystore(Keystore): diff --git a/sbapp/plyer/platforms/macosx/libs/osx_motion_sensor.py b/sbapp/plyer/platforms/macosx/libs/osx_motion_sensor.py index d49df43..a5dc76a 100644 --- a/sbapp/plyer/platforms/macosx/libs/osx_motion_sensor.py +++ b/sbapp/plyer/platforms/macosx/libs/osx_motion_sensor.py @@ -86,7 +86,7 @@ def read_sms(): inStructure = data_structure() outStructure = data_structure() - if(is_os_64bit() or hasattr(IOKit, 'IOConnectCallStructMethod')): + if is_os_64bit() or hasattr(IOKit, 'IOConnectCallStructMethod'): structureInSize = IOItemCount(sizeof(data_structure)) structureOutSize = c_size_t(sizeof(data_structure)) @@ -120,7 +120,7 @@ def get_coord(): ret, data = read_sms() if (ret > 0): - if(data.x): + if data.x: return (data.x, data.y, data.z) else: return (None, None, None) diff --git a/sbapp/plyer/platforms/macosx/maps.py b/sbapp/plyer/platforms/macosx/maps.py new file mode 100644 index 0000000..7a6d998 --- /dev/null +++ b/sbapp/plyer/platforms/macosx/maps.py @@ -0,0 +1,90 @@ +''' +Module of macOS API for plyer.maps. +''' + +from subprocess import Popen, PIPE +from sbapp.plyer.facades import Maps +from urllib.parse import quote_plus + + +class MacOSMaps(Maps): + ''' + Implementation of MacOS Maps API. + ''' + + def _open_by_address(self, address, **kwargs): + ''' + :param address: An address string that geolocation can understand. + ''' + + address = quote_plus(address, safe=',') + maps_address = 'http://maps.apple.com/?address=' + address + + process = Popen( + ['open', '-a', 'Maps', maps_address], + stdout=PIPE, stderr=PIPE) + stdout, stderr = process.communicate() + + def _open_by_lat_long(self, latitude, longitude, **kwargs): + ''' + Open a coordinate span denoting a latitudinal delta and a + longitudinal delta (similar to MKCoordinateSpan) + + :param name: (optional), will set the name of the dropped pin + ''' + + name = kwargs.get("name", "Selected Location") + maps_address = 'http://maps.apple.com/?ll={},{}&q={}'.format( + latitude, longitude, name) + + process = Popen( + ['open', '-a', 'Maps', maps_address], + stdout=PIPE, stderr=PIPE) + stdout, stderr = process.communicate() + + def _search(self, query, **kwargs): + ''' + :param query: A string that describes the search object (ex. "Pizza") + + :param latitude: (optional), narrow down query within area, + MUST BE USED WITH LONGITUDE + + :param longitude: (optional), narrow down query within area, + MUST BE USED WITH LATITUDE + ''' + + latitude = kwargs.get('latitude') + longitude = kwargs.get('longitude') + + query = quote_plus(query, safe=',') + maps_address = 'http://maps.apple.com/?q=' + query + + if latitude is not None and longitude is not None: + maps_address += '&sll={},{}'.format(latitude, longitude) + + process = Popen( + ['open', '-a', 'Maps', maps_address], + stdout=PIPE, stderr=PIPE) + stdout, stderr = process.communicate() + + def _route(self, saddr, daddr, **kwargs): + ''' + :param saddr: can be given as 'address' or 'lat,long' + :param daddr: can be given as 'address' or 'lat,long' + ''' + saddr = quote_plus(saddr, safe=',') + daddr = quote_plus(daddr, safe=',') + + maps_address = 'http://maps.apple.com/?saddr={}&daddr={}'.format( + saddr, daddr) + process = Popen( + ['open', '-a', 'Maps', maps_address], + stdout=PIPE, stderr=PIPE) + stdout, stderr = process.communicate() + + +def instance(): + ''' + Instance for facade proxy. + ''' + return MacOSMaps() diff --git a/sbapp/plyer/platforms/macosx/notification.py b/sbapp/plyer/platforms/macosx/notification.py index ed78ab1..35ce67a 100644 --- a/sbapp/plyer/platforms/macosx/notification.py +++ b/sbapp/plyer/platforms/macosx/notification.py @@ -2,7 +2,7 @@ Module of MacOS API for plyer.notification. ''' -from plyer.facades import Notification +from sbapp.plyer.facades import Notification from pyobjus import ( autoclass, protocol, objc_str, ObjcBOOL diff --git a/sbapp/plyer/platforms/macosx/screenshot.py b/sbapp/plyer/platforms/macosx/screenshot.py index c76766a..16a35c4 100644 --- a/sbapp/plyer/platforms/macosx/screenshot.py +++ b/sbapp/plyer/platforms/macosx/screenshot.py @@ -1,8 +1,8 @@ import subprocess from os.path import join -from plyer.facades import Screenshot -from plyer.utils import whereis_exe -from plyer.platforms.macosx.storagepath import OSXStoragePath +from sbapp.plyer.facades import Screenshot +from sbapp.plyer.utils import whereis_exe +from sbapp.plyer.platforms.macosx.storagepath import OSXStoragePath class OSXScreenshot(Screenshot): diff --git a/sbapp/plyer/platforms/macosx/sms.py b/sbapp/plyer/platforms/macosx/sms.py new file mode 100644 index 0000000..8634443 --- /dev/null +++ b/sbapp/plyer/platforms/macosx/sms.py @@ -0,0 +1,42 @@ +from subprocess import Popen, PIPE +from sbapp.plyer.facades import Sms as SMS +from sbapp.plyer.utils import whereis_exe + + +class MacOSSMS(SMS): + ''' + Implementation of macOS' Messages API + ''' + + def _send(self, **kwargs): + ''' + Will send `message` to `recipient` via Messages app + + By default, if `mode` is not explicitly set, `iMessage` is used. + In order to use `SMS` mode, a valid carrier-activated device must + be connected and configured. + ''' + + recipient = kwargs.get('recipient') + message = kwargs.get('message') + mode = kwargs.get('mode') # Supported modes: iMessage (default), SMS + if not mode: + mode = 'iMessage' + + APPLESCRIPT = f"""tell application "Messages" + set targetService to 1st account whose service type = {mode} + set targetBuddy to participant "{recipient}" of targetService + send "{message}" to targetBuddy +end tell""" + + osascript_process = Popen( + ['osascript', '-e', APPLESCRIPT], stdout=PIPE, stderr=PIPE) + stdout, stderr = osascript_process.communicate() + + +def instance(): + import sys + if whereis_exe('osascript'): + return MacOSSMS() + sys.stderr.write('osascript not found.') + return SMS() diff --git a/sbapp/plyer/platforms/macosx/storagepath.py b/sbapp/plyer/platforms/macosx/storagepath.py index 027b04e..79973a4 100644 --- a/sbapp/plyer/platforms/macosx/storagepath.py +++ b/sbapp/plyer/platforms/macosx/storagepath.py @@ -3,7 +3,7 @@ MacOS X Storage Path -------------------- ''' -from plyer.facades import StoragePath +from sbapp.plyer.facades import StoragePath from pyobjus import autoclass NSFileManager = autoclass('NSFileManager') diff --git a/sbapp/plyer/platforms/macosx/tts.py b/sbapp/plyer/platforms/macosx/tts.py index 755e820..464de56 100644 --- a/sbapp/plyer/platforms/macosx/tts.py +++ b/sbapp/plyer/platforms/macosx/tts.py @@ -1,6 +1,6 @@ import subprocess -from plyer.facades import TTS -from plyer.utils import whereis_exe +from sbapp.plyer.facades import TTS +from sbapp.plyer.utils import whereis_exe class NativeSayTextToSpeech(TTS): diff --git a/sbapp/plyer/platforms/macosx/uniqueid.py b/sbapp/plyer/platforms/macosx/uniqueid.py index dc84153..464e723 100644 --- a/sbapp/plyer/platforms/macosx/uniqueid.py +++ b/sbapp/plyer/platforms/macosx/uniqueid.py @@ -4,8 +4,8 @@ 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 +from sbapp.plyer.facades import UniqueID +from sbapp.plyer.utils import whereis_exe class OSXUniqueID(UniqueID): diff --git a/sbapp/plyer/platforms/macosx/wifi.py b/sbapp/plyer/platforms/macosx/wifi.py index 32c02ab..61da385 100644 --- a/sbapp/plyer/platforms/macosx/wifi.py +++ b/sbapp/plyer/platforms/macosx/wifi.py @@ -1,7 +1,7 @@ from pyobjus import autoclass from pyobjus.dylib_manager import load_framework, INCLUDE -from plyer.facades import Wifi +from sbapp.plyer.facades import Wifi load_framework(INCLUDE.Foundation) load_framework(INCLUDE.CoreWLAN) diff --git a/sbapp/plyer/platforms/win/audio.py b/sbapp/plyer/platforms/win/audio.py index c9d8f35..dfe9edf 100644 --- a/sbapp/plyer/platforms/win/audio.py +++ b/sbapp/plyer/platforms/win/audio.py @@ -14,8 +14,8 @@ from ctypes import ( ) from ctypes.wintypes import DWORD, UINT -from plyer.facades import Audio -from plyer.platforms.win.storagepath import WinStoragePath +from sbapp.plyer.facades import Audio +from sbapp.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 diff --git a/sbapp/plyer/platforms/win/battery.py b/sbapp/plyer/platforms/win/battery.py index 861d904..af899ca 100644 --- a/sbapp/plyer/platforms/win/battery.py +++ b/sbapp/plyer/platforms/win/battery.py @@ -2,8 +2,8 @@ Module of Windows API for plyer.battery. ''' -from plyer.platforms.win.libs.batterystatus import battery_status -from plyer.facades import Battery +from sbapp.plyer.platforms.win.libs.batterystatus import battery_status +from sbapp.plyer.facades import Battery from ctypes.wintypes import BYTE diff --git a/sbapp/plyer/platforms/win/cpu.py b/sbapp/plyer/platforms/win/cpu.py index d1262dc..9b65549 100644 --- a/sbapp/plyer/platforms/win/cpu.py +++ b/sbapp/plyer/platforms/win/cpu.py @@ -11,7 +11,7 @@ from ctypes.wintypes import ( BYTE, DWORD, WORD ) -from plyer.facades import CPU +from sbapp.plyer.facades import CPU KERNEL = windll.kernel32 diff --git a/sbapp/plyer/platforms/win/devicename.py b/sbapp/plyer/platforms/win/devicename.py index d35d76b..d631ac4 100644 --- a/sbapp/plyer/platforms/win/devicename.py +++ b/sbapp/plyer/platforms/win/devicename.py @@ -3,7 +3,7 @@ Module of Win API for plyer.devicename. ''' import socket -from plyer.facades import DeviceName +from sbapp.plyer.facades import DeviceName class WinDeviceName(DeviceName): diff --git a/sbapp/plyer/platforms/win/email.py b/sbapp/plyer/platforms/win/email.py index 4c0f9b7..3c43cde 100644 --- a/sbapp/plyer/platforms/win/email.py +++ b/sbapp/plyer/platforms/win/email.py @@ -7,7 +7,7 @@ try: from urllib.parse import quote except ImportError: from urllib import quote -from plyer.facades import Email +from sbapp.plyer.facades import Email class WindowsEmail(Email): diff --git a/sbapp/plyer/platforms/win/filechooser.py b/sbapp/plyer/platforms/win/filechooser.py index d61fdc1..9932d9b 100644 --- a/sbapp/plyer/platforms/win/filechooser.py +++ b/sbapp/plyer/platforms/win/filechooser.py @@ -3,7 +3,7 @@ Windows file chooser -------------------- ''' -from plyer.facades import FileChooser +from sbapp.plyer.facades import FileChooser from win32com.shell.shell import ( SHBrowseForFolder as browse, SHGetPathFromIDList as get_path @@ -84,7 +84,7 @@ class Win32FileChooser: # e.g. open_file(filters=['*.txt', '*.py']) filters = "" for f in self.filters: - if type(f) == str: + if isinstance(f, str): filters += (f + "\x00") * 2 else: filters += f[0] + "\x00" + ";".join(f[1:]) + "\x00" diff --git a/sbapp/plyer/platforms/win/keystore.py b/sbapp/plyer/platforms/win/keystore.py index 0065a6a..59e48d1 100644 --- a/sbapp/plyer/platforms/win/keystore.py +++ b/sbapp/plyer/platforms/win/keystore.py @@ -3,7 +3,7 @@ try: except Exception: raise NotImplementedError() -from plyer.facades import Keystore +from sbapp.plyer.facades import Keystore class WinKeystore(Keystore): diff --git a/sbapp/plyer/platforms/win/libs/balloontip.py b/sbapp/plyer/platforms/win/libs/balloontip.py index 5494f4e..1334a99 100644 --- a/sbapp/plyer/platforms/win/libs/balloontip.py +++ b/sbapp/plyer/platforms/win/libs/balloontip.py @@ -12,7 +12,7 @@ import ctypes import atexit from threading import RLock -from plyer.platforms.win.libs import win_api_defs +from sbapp.plyer.platforms.win.libs import win_api_defs WS_OVERLAPPED = 0x00000000 diff --git a/sbapp/plyer/platforms/win/libs/batterystatus.py b/sbapp/plyer/platforms/win/libs/batterystatus.py index 1d8202d..0c0c635 100644 --- a/sbapp/plyer/platforms/win/libs/batterystatus.py +++ b/sbapp/plyer/platforms/win/libs/batterystatus.py @@ -6,7 +6,7 @@ __all__ = ('battery_status') import ctypes -from plyer.platforms.win.libs import win_api_defs +from sbapp.plyer.platforms.win.libs import win_api_defs def battery_status(): diff --git a/sbapp/plyer/platforms/win/notification.py b/sbapp/plyer/platforms/win/notification.py index bac381b..b83838e 100644 --- a/sbapp/plyer/platforms/win/notification.py +++ b/sbapp/plyer/platforms/win/notification.py @@ -4,8 +4,8 @@ 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 +from sbapp.plyer.facades import Notification +from sbapp.plyer.platforms.win.libs.balloontip import balloon_tip class WindowsNotification(Notification): diff --git a/sbapp/plyer/platforms/win/screenshot.py b/sbapp/plyer/platforms/win/screenshot.py index b04eab5..1291fe9 100644 --- a/sbapp/plyer/platforms/win/screenshot.py +++ b/sbapp/plyer/platforms/win/screenshot.py @@ -18,8 +18,8 @@ from win32con import ( SRCCOPY ) -from plyer.facades import Screenshot -from plyer.platforms.win.storagepath import WinStoragePath +from sbapp.plyer.facades import Screenshot +from sbapp.plyer.platforms.win.storagepath import WinStoragePath class WinScreenshot(Screenshot): diff --git a/sbapp/plyer/platforms/win/storagepath.py b/sbapp/plyer/platforms/win/storagepath.py index fd905d8..1528483 100755 --- a/sbapp/plyer/platforms/win/storagepath.py +++ b/sbapp/plyer/platforms/win/storagepath.py @@ -3,9 +3,9 @@ Windows Storage Path -------------------- ''' -from plyer.facades import StoragePath +from sbapp.plyer.facades import StoragePath from os.path import expanduser -from plyer.platforms.win.libs.win_api_defs import get_PATH +from sbapp.plyer.platforms.win.libs.win_api_defs import get_PATH from uuid import UUID diff --git a/sbapp/plyer/platforms/win/tts.py b/sbapp/plyer/platforms/win/tts.py index e2539c3..2b9f8f9 100644 --- a/sbapp/plyer/platforms/win/tts.py +++ b/sbapp/plyer/platforms/win/tts.py @@ -1,6 +1,6 @@ import subprocess -from plyer.facades import TTS -from plyer.utils import whereis_exe +from sbapp.plyer.facades import TTS +from sbapp.plyer.utils import whereis_exe class EspeakTextToSpeech(TTS): diff --git a/sbapp/plyer/platforms/win/uniqueid.py b/sbapp/plyer/platforms/win/uniqueid.py index 6d42391..42853b7 100644 --- a/sbapp/plyer/platforms/win/uniqueid.py +++ b/sbapp/plyer/platforms/win/uniqueid.py @@ -10,7 +10,7 @@ except ImportError: except ImportError: raise NotImplementedError() -from plyer.facades import UniqueID +from sbapp.plyer.facades import UniqueID class WinUniqueID(UniqueID): diff --git a/sbapp/plyer/platforms/win/wifi.py b/sbapp/plyer/platforms/win/wifi.py index 68efb2f..152d4c0 100644 --- a/sbapp/plyer/platforms/win/wifi.py +++ b/sbapp/plyer/platforms/win/wifi.py @@ -1,5 +1,5 @@ -import plyer.platforms.win.libs.wifi_defs as wifi_lib -from plyer.facades import Wifi +import sbapp.plyer.platforms.win.libs.wifi_defs as wifi_lib +from sbapp.plyer.facades import Wifi class WindowWifi(Wifi): diff --git a/sbapp/plyer/tests/__init__.py b/sbapp/plyer/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/sbapp/plyer/tests/common.py b/sbapp/plyer/tests/common.py deleted file mode 100644 index 7436db8..0000000 --- a/sbapp/plyer/tests/common.py +++ /dev/null @@ -1,76 +0,0 @@ -''' -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 diff --git a/sbapp/plyer/tests/images/kivy32.ico b/sbapp/plyer/tests/images/kivy32.ico deleted file mode 100644 index 2010bf3c335b84fbd7d1eb58cc4fa2fe6e626cdf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4286 zcmchaO-NKx6vyvtk`@I@L93$C>TZ^no=6>Z8|h2l!Aj0l&Frbbc_gw!HK z`Tz-9M$*b$v>DvyvcZi(IWyG=BS^RZdA<9%b9wL0o95Y*<9qMiJLmU5=iGZ88Po6+ z3^Mx7<{KE>&lo!ngNr3$XXCNR{a7qAo7Cf<$YRmR zEPT^logL>eXAg|#`gPT{w47+u6S4VJGV!xFrSnRbh)3UahC9wfKwS+YwC2N22a(s? zN<61===jjs7z{!~wf)iBT2H2s?+>TgUj0n-W%w@%9oZF`S^Ic%6B ze>1Ea4Pd^Z%H>N&zjL{D zOM_gGV)2(PpYkUSmDU6JGlGx$PpN!bYbz_C%QQTneP(H)dgSxDG7W+)df}!4d;O!t zpS0r>L)M`3H#UkEUu9R)AoHmgPJCH|Tz~SDQGcA+ZW_RREb@nzn{IsKQ0~+d!4>zZ z$q9aKc!)2&e`lOU@|hs`bXR);=9t7!x$!AS+IK3S>Y;v4y!Z88G<1!Q-sVrA%orxd z=#Kn*1s`_}zJfe1Sb|Ms!biEgvE?`p&jm2cz7JsDoIICUGMmN(zeYCIY);DEctk&f zGN04Fsi5Rpu zDOMilc;=a7G-r-_@6QOmd^QNfOE$97Grveu|qhV o>tVaHe=V?x@9Quv?a|Enfvg#W;!#|R|C_NpJgsqFsONP30oI2A_y7O^ diff --git a/sbapp/plyer/tests/test_audio.py b/sbapp/plyer/tests/test_audio.py deleted file mode 100644 index e811891..0000000 --- a/sbapp/plyer/tests/test_audio.py +++ /dev/null @@ -1,110 +0,0 @@ -''' -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() diff --git a/sbapp/plyer/tests/test_battery.py b/sbapp/plyer/tests/test_battery.py deleted file mode 100644 index b44d504..0000000 --- a/sbapp/plyer/tests/test_battery.py +++ /dev/null @@ -1,413 +0,0 @@ -''' -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() diff --git a/sbapp/plyer/tests/test_bluetooth.py b/sbapp/plyer/tests/test_bluetooth.py deleted file mode 100644 index 8997c4d..0000000 --- a/sbapp/plyer/tests/test_bluetooth.py +++ /dev/null @@ -1,144 +0,0 @@ -''' -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() diff --git a/sbapp/plyer/tests/test_cpu.py b/sbapp/plyer/tests/test_cpu.py deleted file mode 100644 index 8882522..0000000 --- a/sbapp/plyer/tests/test_cpu.py +++ /dev/null @@ -1,262 +0,0 @@ -''' -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() diff --git a/sbapp/plyer/tests/test_devicename.py b/sbapp/plyer/tests/test_devicename.py deleted file mode 100644 index cbb0fc4..0000000 --- a/sbapp/plyer/tests/test_devicename.py +++ /dev/null @@ -1,77 +0,0 @@ -''' -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() diff --git a/sbapp/plyer/tests/test_email.py b/sbapp/plyer/tests/test_email.py deleted file mode 100644 index 60aa86f..0000000 --- a/sbapp/plyer/tests/test_email.py +++ /dev/null @@ -1,48 +0,0 @@ -''' -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() diff --git a/sbapp/plyer/tests/test_facade.py b/sbapp/plyer/tests/test_facade.py deleted file mode 100644 index b49b15f..0000000 --- a/sbapp/plyer/tests/test_facade.py +++ /dev/null @@ -1,183 +0,0 @@ -''' -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() diff --git a/sbapp/plyer/tests/test_notification.py b/sbapp/plyer/tests/test_notification.py deleted file mode 100644 index 7177ad7..0000000 --- a/sbapp/plyer/tests/test_notification.py +++ /dev/null @@ -1,209 +0,0 @@ -''' -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() diff --git a/sbapp/plyer/tests/test_screenshot.py b/sbapp/plyer/tests/test_screenshot.py deleted file mode 100644 index 931fac2..0000000 --- a/sbapp/plyer/tests/test_screenshot.py +++ /dev/null @@ -1,137 +0,0 @@ -''' -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() diff --git a/sbapp/plyer/tests/test_storagepath.py b/sbapp/plyer/tests/test_storagepath.py deleted file mode 100644 index 35b8139..0000000 --- a/sbapp/plyer/tests/test_storagepath.py +++ /dev/null @@ -1,72 +0,0 @@ -''' -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() diff --git a/sbapp/plyer/tests/test_uniqueid.py b/sbapp/plyer/tests/test_uniqueid.py deleted file mode 100644 index 629c840..0000000 --- a/sbapp/plyer/tests/test_uniqueid.py +++ /dev/null @@ -1,78 +0,0 @@ -''' -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() diff --git a/sbapp/plyer/tests/test_utils.py b/sbapp/plyer/tests/test_utils.py deleted file mode 100644 index 3532b76..0000000 --- a/sbapp/plyer/tests/test_utils.py +++ /dev/null @@ -1,417 +0,0 @@ -''' -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() diff --git a/sbapp/plyer/utils.py b/sbapp/plyer/utils.py index 101763e..573075d 100644 --- a/sbapp/plyer/utils.py +++ b/sbapp/plyer/utils.py @@ -9,6 +9,7 @@ from os import environ from os import path from sys import platform as _sys_platform import sys +import RNS class Platform: @@ -91,8 +92,10 @@ class Proxy: # do the import try: name = object.__getattribute__(self, '_name') - module = 'plyer.platforms.{}.{}'.format( - platform, name) + if RNS.vendor.platformutils.is_android(): + module = 'plyer.platforms.{}.{}'.format(platform, name) + else: + module = 'sbapp.plyer.platforms.{}.{}'.format(platform, name) mod = __import__(module, fromlist='.') obj = mod.instance() except Exception: