mirror of
				https://github.com/liberatedsystems/openCom-Companion.git
				synced 2025-07-08 05:07:21 +02:00 
			
		
		
		
	Updated, modded and refactored plyer
This commit is contained in:
		
							parent
							
								
									16e055ba25
								
							
						
					
					
						commit
						bb86bee399
					
				| @ -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) | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -94,6 +94,7 @@ class Audio: | ||||
|     # private | ||||
| 
 | ||||
|     def _start(self): | ||||
|         raise IOError("JUICE") | ||||
|         raise NotImplementedError() | ||||
| 
 | ||||
|     def _stop(self): | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
							
								
								
									
										88
									
								
								sbapp/plyer/facades/maps.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								sbapp/plyer/facades/maps.py
									
									
									
									
									
										Normal file
									
								
							| @ -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() | ||||
| @ -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 | ||||
|  | ||||
| @ -30,7 +30,7 @@ To set sensor:: | ||||
| 
 | ||||
| Supported Platforms | ||||
| ------------------- | ||||
| Android | ||||
| Android, Linux | ||||
| 
 | ||||
| ''' | ||||
| 
 | ||||
|  | ||||
| @ -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 | ||||
| 
 | ||||
|  | ||||
| @ -70,7 +70,7 @@ class AndroidAccelerometer(Accelerometer): | ||||
|             return (None, None, None) | ||||
| 
 | ||||
|     def __del__(self): | ||||
|         if(self.bState): | ||||
|         if self.bState: | ||||
|             self._disable() | ||||
|         super().__del__() | ||||
| 
 | ||||
|  | ||||
| @ -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() | ||||
|  | ||||
| @ -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) | ||||
|  | ||||
| @ -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__() | ||||
| 
 | ||||
|  | ||||
| @ -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(): | ||||
|  | ||||
| @ -86,4 +86,4 @@ class AndroidGPS(GPS): | ||||
| 
 | ||||
| 
 | ||||
| def instance(): | ||||
|     return AndroidGPS() | ||||
|     return AndroidGPS() | ||||
|  | ||||
| @ -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__() | ||||
| 
 | ||||
|  | ||||
| @ -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'): | ||||
|  | ||||
| @ -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): | ||||
|  | ||||
| @ -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 | ||||
| 
 | ||||
| 
 | ||||
|  | ||||
| @ -3,7 +3,7 @@ iOS Barometer | ||||
| ------------- | ||||
| ''' | ||||
| 
 | ||||
| from plyer.facades import Barometer | ||||
| from sbapp.plyer.facades import Barometer | ||||
| from pyobjus import autoclass | ||||
| 
 | ||||
| 
 | ||||
|  | ||||
| @ -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') | ||||
|  | ||||
| @ -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') | ||||
|  | ||||
| @ -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') | ||||
|  | ||||
| @ -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): | ||||
|  | ||||
| @ -3,7 +3,7 @@ iOS Compass | ||||
| ----------- | ||||
| ''' | ||||
| 
 | ||||
| from plyer.facades import Compass | ||||
| from sbapp.plyer.facades import Compass | ||||
| from pyobjus import autoclass | ||||
| 
 | ||||
| 
 | ||||
|  | ||||
| @ -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 | ||||
| 
 | ||||
|  | ||||
| @ -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 | ||||
| 
 | ||||
|  | ||||
| @ -3,7 +3,7 @@ | ||||
| Flash | ||||
| ----- | ||||
| """ | ||||
| from plyer.facades import Flash | ||||
| from sbapp.plyer.facades import Flash | ||||
| from pyobjus import autoclass | ||||
| 
 | ||||
| NSString = autoclass("NSString") | ||||
|  | ||||
| @ -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') | ||||
|  | ||||
| @ -4,7 +4,7 @@ iOS Gravity | ||||
| 
 | ||||
| ''' | ||||
| 
 | ||||
| from plyer.facades import Gravity | ||||
| from sbapp.plyer.facades import Gravity | ||||
| from pyobjus import autoclass | ||||
| 
 | ||||
| 
 | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| from plyer.facades import Keystore | ||||
| from sbapp.plyer.facades import Keystore | ||||
| from pyobjus import autoclass, objc_str | ||||
| 
 | ||||
| NSUserDefaults = autoclass('NSUserDefaults') | ||||
|  | ||||
							
								
								
									
										78
									
								
								sbapp/plyer/platforms/ios/maps.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								sbapp/plyer/platforms/ios/maps.py
									
									
									
									
									
										Normal file
									
								
							| @ -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() | ||||
| @ -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 | ||||
| 
 | ||||
|  | ||||
| @ -4,7 +4,7 @@ iOS Spatial Orientation | ||||
| 
 | ||||
| ''' | ||||
| 
 | ||||
| from plyer.facades import SpatialOrientation | ||||
| from sbapp.plyer.facades import SpatialOrientation | ||||
| from pyobjus import autoclass | ||||
| 
 | ||||
| 
 | ||||
|  | ||||
| @ -3,7 +3,7 @@ iOS Storage Path | ||||
| -------------------- | ||||
| ''' | ||||
| 
 | ||||
| from plyer.facades import StoragePath | ||||
| from sbapp.plyer.facades import StoragePath | ||||
| from pyobjus import autoclass | ||||
| import os | ||||
| 
 | ||||
|  | ||||
| @ -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 = \ | ||||
|  | ||||
| @ -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') | ||||
|  | ||||
| @ -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): | ||||
|  | ||||
| @ -3,7 +3,7 @@ Linux accelerometer | ||||
| --------------------- | ||||
| ''' | ||||
| 
 | ||||
| from plyer.facades import Accelerometer | ||||
| from sbapp.plyer.facades import Accelerometer | ||||
| import glob | ||||
| import re | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										48
									
								
								sbapp/plyer/platforms/linux/audio.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								sbapp/plyer/platforms/linux/audio.py
									
									
									
									
									
										Normal file
									
								
							| @ -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() | ||||
| @ -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() | ||||
|  | ||||
| @ -4,7 +4,7 @@ Linux Brightness | ||||
| 
 | ||||
| ''' | ||||
| 
 | ||||
| from plyer.facades import Brightness | ||||
| from sbapp.plyer.facades import Brightness | ||||
| import subprocess | ||||
| import os | ||||
| 
 | ||||
|  | ||||
| @ -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): | ||||
|  | ||||
| @ -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): | ||||
|  | ||||
| @ -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): | ||||
|  | ||||
| @ -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 += [ | ||||
|  | ||||
| @ -3,7 +3,7 @@ try: | ||||
| except ImportError: | ||||
|     raise NotImplementedError() | ||||
| 
 | ||||
| from plyer.facades import Keystore | ||||
| from sbapp.plyer.facades import Keystore | ||||
| 
 | ||||
| 
 | ||||
| class LinuxKeystore(Keystore): | ||||
|  | ||||
| @ -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 | ||||
| 
 | ||||
| 
 | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| import subprocess as sb | ||||
| from plyer.facades import Orientation | ||||
| from sbapp.plyer.facades import Orientation | ||||
| 
 | ||||
| 
 | ||||
| class LinuxOrientation(Orientation): | ||||
|  | ||||
| @ -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 | ||||
| 
 | ||||
|  | ||||
| @ -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): | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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): | ||||
|  | ||||
| @ -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): | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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): | ||||
|  | ||||
| @ -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() | ||||
|  | ||||
| @ -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): | ||||
|  | ||||
| @ -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 | ||||
| 
 | ||||
|  | ||||
| @ -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): | ||||
|  | ||||
| @ -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): | ||||
|  | ||||
| @ -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): | ||||
|  | ||||
| @ -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: | ||||
|  | ||||
| @ -3,7 +3,7 @@ try: | ||||
| except ImportError: | ||||
|     raise NotImplementedError() | ||||
| 
 | ||||
| from plyer.facades import Keystore | ||||
| from sbapp.plyer.facades import Keystore | ||||
| 
 | ||||
| 
 | ||||
| class OSXKeystore(Keystore): | ||||
|  | ||||
| @ -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) | ||||
|  | ||||
							
								
								
									
										90
									
								
								sbapp/plyer/platforms/macosx/maps.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								sbapp/plyer/platforms/macosx/maps.py
									
									
									
									
									
										Normal file
									
								
							| @ -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() | ||||
| @ -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 | ||||
|  | ||||
| @ -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): | ||||
|  | ||||
							
								
								
									
										42
									
								
								sbapp/plyer/platforms/macosx/sms.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								sbapp/plyer/platforms/macosx/sms.py
									
									
									
									
									
										Normal file
									
								
							| @ -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() | ||||
| @ -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') | ||||
|  | ||||
| @ -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): | ||||
|  | ||||
| @ -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): | ||||
|  | ||||
| @ -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) | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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 | ||||
| 
 | ||||
| 
 | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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): | ||||
|  | ||||
| @ -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): | ||||
|  | ||||
| @ -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" | ||||
|  | ||||
| @ -3,7 +3,7 @@ try: | ||||
| except Exception: | ||||
|     raise NotImplementedError() | ||||
| 
 | ||||
| from plyer.facades import Keystore | ||||
| from sbapp.plyer.facades import Keystore | ||||
| 
 | ||||
| 
 | ||||
| class WinKeystore(Keystore): | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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(): | ||||
|  | ||||
| @ -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): | ||||
|  | ||||
| @ -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): | ||||
|  | ||||
| @ -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 | ||||
| 
 | ||||
| 
 | ||||
|  | ||||
| @ -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): | ||||
|  | ||||
| @ -10,7 +10,7 @@ except ImportError: | ||||
|     except ImportError: | ||||
|         raise NotImplementedError() | ||||
| 
 | ||||
| from plyer.facades import UniqueID | ||||
| from sbapp.plyer.facades import UniqueID | ||||
| 
 | ||||
| 
 | ||||
| class WinUniqueID(UniqueID): | ||||
|  | ||||
| @ -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): | ||||
|  | ||||
| @ -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 | ||||
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 4.2 KiB | 
| @ -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() | ||||
| @ -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 <min_version, max_version>. | ||||
| 
 | ||||
|     .. 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  <class AppleSmartBattery,\ | ||||
|     id 0x1000002c9, registered, matched, active, busy 0 (0 ms), retain 6> | ||||
|     {{ | ||||
|       "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() | ||||
| @ -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() | ||||
| @ -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 <min_version, max_version>. | ||||
| 
 | ||||
|     .. 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() | ||||
| @ -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() | ||||
| @ -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() | ||||
| @ -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 | ||||
|     # <class '<mod>.<platform><cls>'> e.g.: | ||||
|     # <class 'plyer.platforms.win.dummy . WinDummy'> | ||||
|     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() | ||||
| @ -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() | ||||
| @ -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() | ||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user