Updated, modded and refactored plyer

This commit is contained in:
Mark Qvist 2024-06-02 18:31:58 +02:00
parent 16e055ba25
commit bb86bee399
104 changed files with 790 additions and 2472 deletions

View File

@ -13,11 +13,15 @@ __all__ = (
'stt', 'temperature', 'tts', 'uniqueid', 'vibrator', 'wifi', 'devicename' 'stt', 'temperature', 'tts', 'uniqueid', 'vibrator', 'wifi', 'devicename'
) )
__version__ = '2.1.0.dev0' __version__ = '2.2.0.dev0'
import RNS
from plyer import facades if RNS.vendor.platformutils.is_android():
from plyer.utils import Proxy 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 to :class:`plyer.facades.Accelerometer`
accelerometer = Proxy('accelerometer', facades.Accelerometer) accelerometer = Proxy('accelerometer', facades.Accelerometer)

View File

@ -14,38 +14,76 @@ __all__ = ('Accelerometer', 'Audio', 'Barometer', 'Battery', 'Call', 'Camera',
'Processors', 'StoragePath', 'Keystore', 'Bluetooth', 'Screenshot', 'Processors', 'StoragePath', 'Keystore', 'Bluetooth', 'Screenshot',
'STT', 'DeviceName') 'STT', 'DeviceName')
from plyer.facades.accelerometer import Accelerometer import RNS
from plyer.facades.audio import Audio if RNS.vendor.platformutils.is_android():
from plyer.facades.barometer import Barometer from plyer.facades.accelerometer import Accelerometer
from plyer.facades.battery import Battery from plyer.facades.audio import Audio
from plyer.facades.call import Call from plyer.facades.barometer import Barometer
from plyer.facades.camera import Camera from plyer.facades.battery import Battery
from plyer.facades.compass import Compass from plyer.facades.call import Call
from plyer.facades.email import Email from plyer.facades.camera import Camera
from plyer.facades.filechooser import FileChooser from plyer.facades.compass import Compass
from plyer.facades.flash import Flash from plyer.facades.email import Email
from plyer.facades.gps import GPS from plyer.facades.filechooser import FileChooser
from plyer.facades.gravity import Gravity from plyer.facades.flash import Flash
from plyer.facades.gyroscope import Gyroscope from plyer.facades.gps import GPS
from plyer.facades.irblaster import IrBlaster from plyer.facades.gravity import Gravity
from plyer.facades.light import Light from plyer.facades.gyroscope import Gyroscope
from plyer.facades.proximity import Proximity from plyer.facades.irblaster import IrBlaster
from plyer.facades.orientation import Orientation from plyer.facades.light import Light
from plyer.facades.notification import Notification from plyer.facades.proximity import Proximity
from plyer.facades.sms import Sms from plyer.facades.orientation import Orientation
from plyer.facades.stt import STT from plyer.facades.notification import Notification
from plyer.facades.tts import TTS from plyer.facades.sms import Sms
from plyer.facades.uniqueid import UniqueID from plyer.facades.stt import STT
from plyer.facades.vibrator import Vibrator from plyer.facades.tts import TTS
from plyer.facades.wifi import Wifi from plyer.facades.uniqueid import UniqueID
from plyer.facades.temperature import Temperature from plyer.facades.vibrator import Vibrator
from plyer.facades.humidity import Humidity from plyer.facades.wifi import Wifi
from plyer.facades.spatialorientation import SpatialOrientation from plyer.facades.temperature import Temperature
from plyer.facades.brightness import Brightness from plyer.facades.humidity import Humidity
from plyer.facades.keystore import Keystore from plyer.facades.spatialorientation import SpatialOrientation
from plyer.facades.storagepath import StoragePath from plyer.facades.brightness import Brightness
from plyer.facades.bluetooth import Bluetooth from plyer.facades.keystore import Keystore
from plyer.facades.processors import Processors from plyer.facades.storagepath import StoragePath
from plyer.facades.cpu import CPU from plyer.facades.bluetooth import Bluetooth
from plyer.facades.screenshot import Screenshot from plyer.facades.processors import Processors
from plyer.facades.devicename import DeviceName 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

View File

@ -94,6 +94,7 @@ class Audio:
# private # private
def _start(self): def _start(self):
raise IOError("JUICE")
raise NotImplementedError() raise NotImplementedError()
def _stop(self): def _stop(self):

View 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()

View File

@ -45,8 +45,8 @@ class Notification:
Notification facade. Notification facade.
''' '''
def notify(self, title='', message='', app_name='', app_icon='', notification_icon=None, def notify(self, title='', message='', app_name='', app_icon='',
timeout=10, ticker='', toast=False, hints={}, context_override=None): timeout=10, ticker='', toast=False, hints={}):
''' '''
Send a notification. Send a notification.
@ -83,8 +83,8 @@ class Notification:
self._notify( self._notify(
title=title, message=message, title=title, message=message,
app_icon=app_icon, app_name=app_name, notification_icon=notification_icon, app_icon=app_icon, app_name=app_name,
timeout=timeout, ticker=ticker, toast=toast, hints=hints, context_override=context_override timeout=timeout, ticker=ticker, toast=toast, hints=hints
) )
# private # private

View File

@ -30,7 +30,7 @@ To set sensor::
Supported Platforms Supported Platforms
------------------- -------------------
Android Android, Linux
''' '''

View File

@ -23,7 +23,7 @@ To send sms::
Supported Platforms Supported Platforms
------------------- -------------------
Android, iOS Android, iOS, macOS
''' '''
@ -33,17 +33,23 @@ class Sms:
Sms facade. Sms facade.
''' '''
def send(self, recipient, message): def send(self, recipient, message, mode=None, **kwargs):
''' '''
Send SMS or open SMS interface. 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 recipient: The receiver
:param message: the message :param message: the message
:param mode: (optional, macOS only), can be set to 'iMessage'
(default) or 'SMS'
:type recipient: number :type recipient: number
:type message: str :type message: str
:type mode: str
''' '''
self._send(recipient=recipient, message=message) self._send(recipient=recipient, message=message, mode=mode, **kwargs)
# private # private

View File

@ -70,7 +70,7 @@ class AndroidAccelerometer(Accelerometer):
return (None, None, None) return (None, None, None)
def __del__(self): def __del__(self):
if(self.bState): if self.bState:
self._disable() self._disable()
super().__del__() super().__del__()

View File

@ -1,3 +1,5 @@
import time
import threading
from jnius import autoclass from jnius import autoclass
from plyer.facades.audio import Audio from plyer.facades.audio import Audio
@ -20,17 +22,31 @@ class AndroidAudio(Audio):
''' '''
def __init__(self, file_path=None): def __init__(self, file_path=None):
default_path = '/sdcard/testrecorder.3gp' default_path = None
super().__init__(file_path or default_path) super().__init__(file_path or default_path)
self._recorder = None self._recorder = None
self._player = 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): def _start(self):
self._recorder = MediaRecorder() self._recorder = MediaRecorder()
self._recorder.setAudioSource(AudioSource.DEFAULT) self._recorder.setAudioSource(AudioSource.DEFAULT)
self._recorder.setOutputFormat(OutputFormat.DEFAULT) self._recorder.setAudioSamplingRate(44100)
self._recorder.setAudioEncoder(AudioEncoder.DEFAULT) 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.setOutputFile(self.file_path)
self._recorder.prepare() self._recorder.prepare()
@ -53,6 +69,10 @@ class AndroidAudio(Audio):
self._player.prepare() self._player.prepare()
self._player.start() self._player.start()
self._check_thread = threading.Thread(target=self._check_playback, daemon=True)
self._check_thread.start()
def instance(): def instance():
return AndroidAudio() return AndroidAudio()

View File

@ -14,7 +14,7 @@ Uri = autoclass('android.net.Uri')
class AndroidCamera(Camera): class AndroidCamera(Camera):
def _take_picture(self, on_complete, filename=None): 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.on_complete = on_complete
self.filename = filename self.filename = filename
android.activity.unbind(on_activity_result=self._on_activity_result) android.activity.unbind(on_activity_result=self._on_activity_result)
@ -26,7 +26,7 @@ class AndroidCamera(Camera):
activity.startActivityForResult(intent, 0x123) activity.startActivityForResult(intent, 0x123)
def _take_video(self, on_complete, filename=None): 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.on_complete = on_complete
self.filename = filename self.filename = filename
android.activity.unbind(on_activity_result=self._on_activity_result) android.activity.unbind(on_activity_result=self._on_activity_result)

View File

@ -110,7 +110,7 @@ class AndroidCompass(Compass):
return (None, None, None, None, None, None) return (None, None, None, None, None, None)
def __del__(self): def __del__(self):
if(self.bState): if self.bState:
self._disable() self._disable()
super().__del__() super().__del__()

View File

@ -43,7 +43,7 @@ using that result will use an incorrect one i.e. the default value of
.. versionadded:: 1.4.0 .. versionadded:: 1.4.0
''' '''
from os.path import join, basename from os.path import join
from random import randint from random import randint
from android import activity, mActivity from android import activity, mActivity
@ -62,6 +62,8 @@ Long = autoclass('java.lang.Long')
IMedia = autoclass('android.provider.MediaStore$Images$Media') IMedia = autoclass('android.provider.MediaStore$Images$Media')
VMedia = autoclass('android.provider.MediaStore$Video$Media') VMedia = autoclass('android.provider.MediaStore$Video$Media')
AMedia = autoclass('android.provider.MediaStore$Audio$Media') AMedia = autoclass('android.provider.MediaStore$Audio$Media')
Files = autoclass('android.provider.MediaStore$Files')
FileOutputStream = autoclass('java.io.FileOutputStream')
class AndroidFileChooser(FileChooser): class AndroidFileChooser(FileChooser):
@ -74,6 +76,7 @@ class AndroidFileChooser(FileChooser):
# filechooser activity <-> result pair identification # filechooser activity <-> result pair identification
select_code = None select_code = None
save_code = None
# default selection value # default selection value
selection = None selection = None
@ -105,6 +108,7 @@ class AndroidFileChooser(FileChooser):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.select_code = randint(123456, 654321) self.select_code = randint(123456, 654321)
self.save_code = randint(123456, 654321)
self.selection = None self.selection = None
# bind a function for a response from filechooser activity # bind a function for a response from filechooser activity
@ -139,9 +143,11 @@ class AndroidFileChooser(FileChooser):
# create Intent for opening # create Intent for opening
file_intent = Intent(Intent.ACTION_GET_CONTENT) file_intent = Intent(Intent.ACTION_GET_CONTENT)
if not self.selected_mime_type or \ if (
type(self.selected_mime_type) != str or \ not self.selected_mime_type
self.selected_mime_type not in self.mime_type: or not isinstance(self.selected_mime_type, str)
or self.selected_mime_type not in self.mime_type
):
file_intent.setType("*/*") file_intent.setType("*/*")
else: else:
file_intent.setType(self.mime_type[self.selected_mime_type]) file_intent.setType(self.mime_type[self.selected_mime_type])
@ -163,6 +169,38 @@ class AndroidFileChooser(FileChooser):
self.select_code 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): def _on_activity_result(self, request_code, result_code, data):
''' '''
Listener for ``android.app.Activity.onActivityResult()`` assigned Listener for ``android.app.Activity.onActivityResult()`` assigned
@ -171,14 +209,15 @@ class AndroidFileChooser(FileChooser):
.. versionadded:: 1.4.0 .. versionadded:: 1.4.0
''' '''
# not our response # bad data
if request_code != self.select_code: if data is None:
return return
if result_code != Activity.RESULT_OK: if result_code != Activity.RESULT_OK:
# The action had been cancelled. # The action had been cancelled.
return return
if request_code == self.select_code:
selection = [] selection = []
# Process multiple URI if multiple files selected # Process multiple URI if multiple files selected
try: try:
@ -194,6 +233,18 @@ class AndroidFileChooser(FileChooser):
# return value via callback # return value via callback
self._handle_selection(selection) 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 @staticmethod
def _handle_external_documents(uri): def _handle_external_documents(uri):
''' '''
@ -206,28 +257,19 @@ class AndroidFileChooser(FileChooser):
file_id = DocumentsContract.getDocumentId(uri) file_id = DocumentsContract.getDocumentId(uri)
file_type, file_name = file_id.split(':') file_type, file_name = file_id.split(':')
# internal SD card mostly mounted as a files storage in phone primary_storage = storagepath.get_external_storage_dir()
internal = storagepath.get_external_storage_dir() sdcard_storage = storagepath.get_sdcard_dir()
# external (removable) SD card i.e. microSD directory = primary_storage
external = storagepath.get_sdcard_dir()
try:
external_base = basename(external)
except TypeError:
external_base = basename(internal)
# resolve sdcard path if file_type == "primary":
sd_card = internal directory = primary_storage
# because external might have /storage/.../1 or other suffix
# and file_type might be only a part of the real folder in /storage
if file_type in external_base or external_base in file_type:
sd_card = external
elif file_type == "home": elif file_type == "home":
sd_card = join(Environment.getExternalStorageDirectory( directory = join(primary_storage, Environment.DIRECTORY_DOCUMENTS)
).getAbsolutePath(), 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 @staticmethod
def _handle_media_documents(uri): def _handle_media_documents(uri):
@ -248,6 +290,11 @@ class AndroidFileChooser(FileChooser):
uri = VMedia.EXTERNAL_CONTENT_URI uri = VMedia.EXTERNAL_CONTENT_URI
elif file_type == 'audio': elif file_type == 'audio':
uri = AMedia.EXTERNAL_CONTENT_URI 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 return file_name, selection, uri
@staticmethod @staticmethod
@ -279,6 +326,23 @@ class AndroidFileChooser(FileChooser):
.. versionadded:: 1.4.0 .. 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 # known locations, differ between machines
downloads = [ downloads = [
'content://downloads/public_downloads', 'content://downloads/public_downloads',
@ -441,6 +505,8 @@ class AndroidFileChooser(FileChooser):
mode = kwargs.pop('mode', None) mode = kwargs.pop('mode', None)
if mode == 'open': if mode == 'open':
self._open_file(**kwargs) self._open_file(**kwargs)
elif mode == 'save':
self._save_file(**kwargs)
def instance(): def instance():

View File

@ -110,7 +110,7 @@ class AndroidGyroscope(Gyroscope):
return (None, None, None, None, None, None) return (None, None, None, None, None, None)
def __del__(self): def __del__(self):
if(self.bState): if self.bState:
self._disable() self._disable()
super().__del__() super().__del__()

View File

@ -154,10 +154,15 @@ class AndroidNotification(Notification):
notification_intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) notification_intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
notification_intent.setAction(Intent.ACTION_MAIN) notification_intent.setAction(Intent.ACTION_MAIN)
notification_intent.addCategory(Intent.CATEGORY_LAUNCHER) 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 # get our application Activity
pending_intent = PendingIntent.getActivity( pending_intent = PendingIntent.getActivity(
app_context, 0, notification_intent, 0 app_context, 0, notification_intent, pending_flags
) )
notification.setContentIntent(pending_intent) notification.setContentIntent(pending_intent)
@ -179,8 +184,6 @@ class AndroidNotification(Notification):
kwargs.get('title', '').encode('utf-8') kwargs.get('title', '').encode('utf-8')
) )
icon = kwargs.get('app_icon') icon = kwargs.get('app_icon')
notification_icon = kwargs.get('notification_icon')
context_override = kwargs.get('context_override')
# decide whether toast only or proper notification # decide whether toast only or proper notification
if kwargs.get('toast'): if kwargs.get('toast'):

View File

@ -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 plyer.facades import StoragePath
from jnius import autoclass from plyer.platforms.android import SDK_INT
from jnius import autoclass, cast
from android import mActivity from android import mActivity
Environment = autoclass('android.os.Environment') Environment = autoclass("android.os.Environment")
Context = autoclass('android.content.Context') Context = autoclass("android.content.Context")
class AndroidStoragePath(StoragePath): class AndroidStoragePath(StoragePath):
@ -25,17 +24,29 @@ class AndroidStoragePath(StoragePath):
''' '''
.. versionadded:: 1.4.0 .. versionadded:: 1.4.0
''' '''
# folder in /storage/ that is readable
# and is not internal SD card
path = None path = None
for folder in listdir('/storage'): context = mActivity.getApplicationContext()
folder = join('/storage', folder) storage_manager = cast(
if folder in self._get_external_storage_dir(): "android.os.storage.StorageManager",
continue context.getSystemService(Context.STORAGE_SERVICE),
if not access(folder, R_OK): )
continue
path = folder if storage_manager is not None:
break 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 return path
def _get_root_dir(self): def _get_root_dir(self):

View File

@ -6,7 +6,7 @@ Taken from: http://pyobjus.readthedocs.org/en/latest/pyobjus_ios.html \
#accessing-accelerometer #accessing-accelerometer
''' '''
from plyer.facades import Accelerometer from sbapp.plyer.facades import Accelerometer
from pyobjus import autoclass from pyobjus import autoclass

View File

@ -3,7 +3,7 @@ iOS Barometer
------------- -------------
''' '''
from plyer.facades import Barometer from sbapp.plyer.facades import Barometer
from pyobjus import autoclass from pyobjus import autoclass

View File

@ -4,7 +4,7 @@ Module of iOS API for plyer.battery.
from pyobjus import autoclass from pyobjus import autoclass
from pyobjus.dylib_manager import load_framework 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') load_framework('/System/Library/Frameworks/UIKit.framework')
UIDevice = autoclass('UIDevice') UIDevice = autoclass('UIDevice')

View File

@ -4,7 +4,7 @@ iOS Brightness
''' '''
from pyobjus import autoclass from pyobjus import autoclass
from plyer.facades import Brightness from sbapp.plyer.facades import Brightness
from pyobjus.dylib_manager import load_framework from pyobjus.dylib_manager import load_framework
load_framework('/System/Library/Frameworks/UIKit.framework') load_framework('/System/Library/Frameworks/UIKit.framework')

View File

@ -3,7 +3,7 @@ IOS Call
---------- ----------
''' '''
from plyer.facades import Call from sbapp.plyer.facades import Call
from pyobjus import autoclass, objc_str from pyobjus import autoclass, objc_str
NSURL = autoclass('NSURL') NSURL = autoclass('NSURL')

View File

@ -1,7 +1,7 @@
from os import remove 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): class iOSCamera(Camera):
@ -14,7 +14,7 @@ class iOSCamera(Camera):
return PhotosLibrary() return PhotosLibrary()
def _take_picture(self, on_complete, filename=None): 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.on_complete = on_complete
self.filename = filename self.filename = filename
photos = self.photos photos = self.photos
@ -38,7 +38,7 @@ class iOSCamera(Camera):
self._remove(self.filename) self._remove(self.filename)
def _take_video(self, on_complete, filename=None): def _take_video(self, on_complete, filename=None):
assert(on_complete is not None) assert on_complete is not None
raise NotImplementedError raise NotImplementedError
def _remove(self, fn): def _remove(self, fn):

View File

@ -3,7 +3,7 @@ iOS Compass
----------- -----------
''' '''
from plyer.facades import Compass from sbapp.plyer.facades import Compass
from pyobjus import autoclass from pyobjus import autoclass

View File

@ -7,7 +7,7 @@ try:
except ImportError: except ImportError:
from urllib import quote from urllib import quote
from plyer.facades import Email from sbapp.plyer.facades import Email
from pyobjus import autoclass, objc_str from pyobjus import autoclass, objc_str
from pyobjus.dylib_manager import load_framework from pyobjus.dylib_manager import load_framework

View File

@ -7,7 +7,7 @@ This module houses the iOS implementation of the plyer FileChooser.
.. versionadded:: 1.4.4 .. versionadded:: 1.4.4
''' '''
from plyer.facades import FileChooser from sbapp.plyer.facades import FileChooser
from pyobjus import autoclass, protocol from pyobjus import autoclass, protocol
from pyobjus.dylib_manager import load_framework from pyobjus.dylib_manager import load_framework

View File

@ -3,7 +3,7 @@
Flash Flash
----- -----
""" """
from plyer.facades import Flash from sbapp.plyer.facades import Flash
from pyobjus import autoclass from pyobjus import autoclass
NSString = autoclass("NSString") NSString = autoclass("NSString")

View File

@ -5,7 +5,7 @@ iOS GPS
from pyobjus import autoclass, protocol from pyobjus import autoclass, protocol
from pyobjus.dylib_manager import load_framework 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') load_framework('/System/Library/Frameworks/CoreLocation.framework')
CLLocationManager = autoclass('CLLocationManager') CLLocationManager = autoclass('CLLocationManager')

View File

@ -4,7 +4,7 @@ iOS Gravity
''' '''
from plyer.facades import Gravity from sbapp.plyer.facades import Gravity
from pyobjus import autoclass from pyobjus import autoclass

View File

@ -3,7 +3,7 @@ iOS Gyroscope
--------------------- ---------------------
''' '''
from plyer.facades import Gyroscope from sbapp.plyer.facades import Gyroscope
from pyobjus import autoclass from pyobjus import autoclass
from pyobjus.dylib_manager import load_framework from pyobjus.dylib_manager import load_framework

View File

@ -1,4 +1,4 @@
from plyer.facades import Keystore from sbapp.plyer.facades import Keystore
from pyobjus import autoclass, objc_str from pyobjus import autoclass, objc_str
NSUserDefaults = autoclass('NSUserDefaults') NSUserDefaults = autoclass('NSUserDefaults')

View 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()

View File

@ -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 import autoclass, objc_str
from pyobjus.dylib_manager import load_framework from pyobjus.dylib_manager import load_framework

View File

@ -4,7 +4,7 @@ iOS Spatial Orientation
''' '''
from plyer.facades import SpatialOrientation from sbapp.plyer.facades import SpatialOrientation
from pyobjus import autoclass from pyobjus import autoclass

View File

@ -3,7 +3,7 @@ iOS Storage Path
-------------------- --------------------
''' '''
from plyer.facades import StoragePath from sbapp.plyer.facades import StoragePath
from pyobjus import autoclass from pyobjus import autoclass
import os import os

View File

@ -1,7 +1,7 @@
from pyobjus import autoclass, objc_str from pyobjus import autoclass, objc_str
from pyobjus.dylib_manager import load_framework 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') load_framework('/System/Library/Frameworks/AVFoundation.framework')
AVSpeechUtterance = autoclass('AVSpeechUtterance') AVSpeechUtterance = autoclass('AVSpeechUtterance')
@ -23,7 +23,7 @@ class iOSTextToSpeech(TTS):
def _speak(self, **kwargs): def _speak(self, **kwargs):
message = kwargs.get('message') message = kwargs.get('message')
if(not self.voice): if not self.voice:
self._set_locale() self._set_locale()
utterance = \ utterance = \

View File

@ -4,7 +4,7 @@ Module of iOS API for plyer.uniqueid.
from pyobjus import autoclass from pyobjus import autoclass
from pyobjus.dylib_manager import load_framework 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') load_framework('/System/Library/Frameworks/UIKit.framework')
UIDevice = autoclass('UIDevice') UIDevice = autoclass('UIDevice')

View File

@ -4,7 +4,7 @@ Install: Add AudioToolbox framework to your application.
''' '''
import ctypes import ctypes
from plyer.facades import Vibrator from sbapp.plyer.facades import Vibrator
class IosVibrator(Vibrator): class IosVibrator(Vibrator):

View File

@ -3,7 +3,7 @@ Linux accelerometer
--------------------- ---------------------
''' '''
from plyer.facades import Accelerometer from sbapp.plyer.facades import Accelerometer
import glob import glob
import re import re

View 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()

View File

@ -2,13 +2,12 @@
Module of Linux API for plyer.battery. Module of Linux API for plyer.battery.
''' '''
import os
from math import floor from math import floor
from os import environ from os import environ
from os.path import exists, join from os.path import exists, join
from subprocess import Popen, PIPE from subprocess import Popen, PIPE
from plyer.facades import Battery from sbapp.plyer.facades import Battery
from plyer.utils import whereis_exe from sbapp.plyer.utils import whereis_exe
class LinuxBattery(Battery): class LinuxBattery(Battery):
@ -20,10 +19,10 @@ class LinuxBattery(Battery):
def _get_state(self): def _get_state(self):
status = {"isCharging": None, "percentage": None} 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') uevent = join(kernel_bat_path, 'uevent')
with open(uevent, "rb") as fle: with open(uevent) as fle:
lines = [ lines = [
line.decode('utf-8').strip() line.decode('utf-8').strip()
for line in fle.readlines() for line in fle.readlines()
@ -34,34 +33,70 @@ class LinuxBattery(Battery):
} }
is_charging = output['POWER_SUPPLY_STATUS'] == 'Charging' 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 status['isCharging'] = is_charging
return status 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(): def instance():
''' '''
Instance for facade proxy. Instance for facade proxy.
''' '''
import sys import sys
# if whereis_exe('upower'): if whereis_exe('upower'):
# return UPowerBattery() return UPowerBattery()
# sys.stderr.write("upower not found.") 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 exists(join('/sys', 'class', 'power_supply', 'BAT0')):
return LinuxBattery()
return Battery() return Battery()

View File

@ -4,7 +4,7 @@ Linux Brightness
''' '''
from plyer.facades import Brightness from sbapp.plyer.facades import Brightness
import subprocess import subprocess
import os import os

View File

@ -5,8 +5,8 @@ Module of Linux API for plyer.cpu.
from os.path import join from os.path import join
from os import environ, listdir from os import environ, listdir
from subprocess import Popen, PIPE from subprocess import Popen, PIPE
from plyer.facades import CPU from sbapp.plyer.facades import CPU
from plyer.utils import whereis_exe from sbapp.plyer.utils import whereis_exe
class LinuxCPU(CPU): class LinuxCPU(CPU):

View File

@ -3,7 +3,7 @@ Module of Linux API for plyer.devicename.
''' '''
import socket import socket
from plyer.facades import DeviceName from sbapp.plyer.facades import DeviceName
class LinuxDeviceName(DeviceName): class LinuxDeviceName(DeviceName):

View File

@ -7,8 +7,8 @@ try:
from urllib.parse import quote from urllib.parse import quote
except ImportError: except ImportError:
from urllib import quote from urllib import quote
from plyer.facades import Email from sbapp.plyer.facades import Email
from plyer.utils import whereis_exe from sbapp.plyer.utils import whereis_exe
class LinuxEmail(Email): class LinuxEmail(Email):

View File

@ -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 from distutils.spawn import find_executable as which
import os import os
import subprocess as sp import subprocess as sp
@ -122,7 +122,7 @@ class ZenityFileChooser(SubprocessFileChooser):
if self.icon: if self.icon:
cmdline += ["--window-icon", self.icon] cmdline += ["--window-icon", self.icon]
for f in self.filters: for f in self.filters:
if type(f) == str: if isinstance(f, str):
cmdline += ["--file-filter", f] cmdline += ["--file-filter", f]
else: else:
cmdline += [ cmdline += [
@ -150,7 +150,7 @@ class KDialogFileChooser(SubprocessFileChooser):
filt = [] filt = []
for f in self.filters: for f in self.filters:
if type(f) == str: if isinstance(f, str):
filt += [f] filt += [f]
else: else:
filt += list(f[1:]) filt += list(f[1:])
@ -195,7 +195,7 @@ class YADFileChooser(SubprocessFileChooser):
def _gen_cmdline(self): def _gen_cmdline(self):
cmdline = [ cmdline = [
which(self.executable), which(self.executable),
"--file-selection", "--file",
"--confirm-overwrite", "--confirm-overwrite",
"--geometry", "--geometry",
"800x600+150+150" "800x600+150+150"
@ -215,7 +215,7 @@ class YADFileChooser(SubprocessFileChooser):
if self.icon: if self.icon:
cmdline += ["--window-icon", self.icon] cmdline += ["--window-icon", self.icon]
for f in self.filters: for f in self.filters:
if type(f) == str: if isinstance(f, str):
cmdline += ["--file-filter", f] cmdline += ["--file-filter", f]
else: else:
cmdline += [ cmdline += [

View File

@ -3,7 +3,7 @@ try:
except ImportError: except ImportError:
raise NotImplementedError() raise NotImplementedError()
from plyer.facades import Keystore from sbapp.plyer.facades import Keystore
class LinuxKeystore(Keystore): class LinuxKeystore(Keystore):

View File

@ -4,8 +4,8 @@ Module of Linux API for plyer.notification.
import warnings import warnings
import subprocess import subprocess
from plyer.facades import Notification from sbapp.plyer.facades import Notification
from plyer.utils import whereis_exe from sbapp.plyer.utils import whereis_exe
import os import os

View File

@ -1,5 +1,5 @@
import subprocess as sb import subprocess as sb
from plyer.facades import Orientation from sbapp.plyer.facades import Orientation
class LinuxOrientation(Orientation): class LinuxOrientation(Orientation):

View File

@ -1,6 +1,6 @@
from subprocess import Popen, PIPE from subprocess import Popen, PIPE
from plyer.facades import Processors from sbapp.plyer.facades import Processors
from plyer.utils import whereis_exe from sbapp.plyer.utils import whereis_exe
from os import environ from os import environ

View File

@ -1,8 +1,8 @@
import subprocess import subprocess
from os.path import join from os.path import join
from plyer.facades import Screenshot from sbapp.plyer.facades import Screenshot
from plyer.utils import whereis_exe from sbapp.plyer.utils import whereis_exe
from plyer.platforms.linux.storagepath import LinuxStoragePath from sbapp.plyer.platforms.linux.storagepath import LinuxStoragePath
class LinuxScreenshot(Screenshot): class LinuxScreenshot(Screenshot):

View File

@ -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 from os.path import expanduser, dirname, abspath, join, exists
# Default paths for each name # Default paths for each name

View File

@ -1,6 +1,6 @@
import subprocess import subprocess
from plyer.facades import TTS from sbapp.plyer.facades import TTS
from plyer.utils import whereis_exe from sbapp.plyer.utils import whereis_exe
class EspeakTextToSpeech(TTS): class EspeakTextToSpeech(TTS):

View File

@ -4,8 +4,8 @@ Module of Linux API for plyer.uniqueid.
from os import environ from os import environ
from subprocess import Popen, PIPE from subprocess import Popen, PIPE
from plyer.facades import UniqueID from sbapp.plyer.facades import UniqueID
from plyer.utils import whereis_exe from sbapp.plyer.utils import whereis_exe
class LinuxUniqueID(UniqueID): class LinuxUniqueID(UniqueID):

View File

@ -6,8 +6,8 @@
''' '''
from subprocess import Popen, PIPE, call from subprocess import Popen, PIPE, call
from plyer.facades import Wifi from sbapp.plyer.facades import Wifi
from plyer.utils import whereis_exe, deprecated from sbapp.plyer.utils import whereis_exe, deprecated
try: try:
import wifi import wifi

View File

@ -3,8 +3,8 @@ MacOSX accelerometer
--------------------- ---------------------
''' '''
from plyer.facades import Accelerometer from sbapp.plyer.facades import Accelerometer
from plyer.platforms.macosx.libs import osx_motion_sensor from sbapp.plyer.platforms.macosx.libs import osx_motion_sensor
class OSXAccelerometer(Accelerometer): class OSXAccelerometer(Accelerometer):

View File

@ -3,8 +3,8 @@ from os.path import join
from pyobjus import autoclass from pyobjus import autoclass
from pyobjus.dylib_manager import INCLUDE, load_framework from pyobjus.dylib_manager import INCLUDE, load_framework
from plyer.facades import Audio from sbapp.plyer.facades import Audio
from plyer.platforms.macosx.storagepath import OSXStoragePath from sbapp.plyer.platforms.macosx.storagepath import OSXStoragePath
load_framework(INCLUDE.Foundation) load_framework(INCLUDE.Foundation)
load_framework(INCLUDE.AVFoundation) load_framework(INCLUDE.AVFoundation)
@ -19,16 +19,24 @@ NSError = autoclass('NSError').alloc()
class OSXAudio(Audio): class OSXAudio(Audio):
def __init__(self, file_path=None): def __init__(self, file_path=None):
default_path = join( default_path = None
OSXStoragePath().get_music_dir(),
'audio.wav'
)
super().__init__(file_path or default_path) super().__init__(file_path or default_path)
self._recorder = None self._recorder = None
self._player = None self._player = None
self._current_file = 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): def _start(self):
# Conversion of Python file path string to Objective-C NSString # Conversion of Python file path string to Objective-C NSString
file_path_NSString = NSString.alloc() file_path_NSString = NSString.alloc()
@ -44,7 +52,7 @@ class OSXAudio(Audio):
# Internal audio file format specification # Internal audio file format specification
af = AVAudioFormat.alloc() af = AVAudioFormat.alloc()
af = af.initWithCommonFormat_sampleRate_channels_interleaved_( af = af.initWithCommonFormat_sampleRate_channels_interleaved_(
1, 44100.0, 2, True 1, 44100.0, 1, True
) )
# Audio recorder instance initialization with specified file NSURL # Audio recorder instance initialization with specified file NSURL
@ -85,6 +93,9 @@ class OSXAudio(Audio):
self._player.play() self._player.play()
self._check_thread = threading.Thread(target=self._check_playback, daemon=True)
self._check_thread.start()
def instance(): def instance():
return OSXAudio() return OSXAudio()

View File

@ -4,8 +4,8 @@ Module of MacOS API for plyer.battery.
from os import environ from os import environ
from subprocess import Popen, PIPE from subprocess import Popen, PIPE
from plyer.facades import Battery from sbapp.plyer.facades import Battery
from plyer.utils import whereis_exe from sbapp.plyer.utils import whereis_exe
class OSXBattery(Battery): class OSXBattery(Battery):

View File

@ -3,8 +3,8 @@ Module of MacOS API for plyer.bluetooth.
''' '''
from subprocess import Popen, PIPE from subprocess import Popen, PIPE
from plyer.facades import Bluetooth from sbapp.plyer.facades import Bluetooth
from plyer.utils import whereis_exe from sbapp.plyer.utils import whereis_exe
from os import environ from os import environ

View File

@ -3,8 +3,8 @@ Module of MacOS API for plyer.cpu.
''' '''
from subprocess import Popen, PIPE from subprocess import Popen, PIPE
from plyer.facades import CPU from sbapp.plyer.facades import CPU
from plyer.utils import whereis_exe from sbapp.plyer.utils import whereis_exe
class OSXCPU(CPU): class OSXCPU(CPU):

View File

@ -3,7 +3,7 @@ Module of MacOSX API for plyer.devicename.
''' '''
import socket import socket
from plyer.facades import DeviceName from sbapp.plyer.facades import DeviceName
class OSXDeviceName(DeviceName): class OSXDeviceName(DeviceName):

View File

@ -9,8 +9,8 @@ try:
except ImportError: except ImportError:
from urllib import quote from urllib import quote
from plyer.facades import Email from sbapp.plyer.facades import Email
from plyer.utils import whereis_exe from sbapp.plyer.utils import whereis_exe
class MacOSXEmail(Email): class MacOSXEmail(Email):

View File

@ -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 import autoclass, objc_arr, objc_str
from pyobjus.dylib_manager import load_framework, INCLUDE from pyobjus.dylib_manager import load_framework, INCLUDE
@ -80,7 +80,7 @@ class MacFileChooser:
if self.filters: if self.filters:
filthies = [] filthies = []
for f in self.filters: for f in self.filters:
if type(f) == str: if isinstance(f, str):
f = (None, f) f = (None, f)
for s in f[1:]: for s in f[1:]:
if not self.use_extensions: if not self.use_extensions:

View File

@ -3,7 +3,7 @@ try:
except ImportError: except ImportError:
raise NotImplementedError() raise NotImplementedError()
from plyer.facades import Keystore from sbapp.plyer.facades import Keystore
class OSXKeystore(Keystore): class OSXKeystore(Keystore):

View File

@ -86,7 +86,7 @@ def read_sms():
inStructure = data_structure() inStructure = data_structure()
outStructure = 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)) structureInSize = IOItemCount(sizeof(data_structure))
structureOutSize = c_size_t(sizeof(data_structure)) structureOutSize = c_size_t(sizeof(data_structure))
@ -120,7 +120,7 @@ def get_coord():
ret, data = read_sms() ret, data = read_sms()
if (ret > 0): if (ret > 0):
if(data.x): if data.x:
return (data.x, data.y, data.z) return (data.x, data.y, data.z)
else: else:
return (None, None, None) return (None, None, None)

View 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()

View File

@ -2,7 +2,7 @@
Module of MacOS API for plyer.notification. Module of MacOS API for plyer.notification.
''' '''
from plyer.facades import Notification from sbapp.plyer.facades import Notification
from pyobjus import ( from pyobjus import (
autoclass, protocol, objc_str, ObjcBOOL autoclass, protocol, objc_str, ObjcBOOL

View File

@ -1,8 +1,8 @@
import subprocess import subprocess
from os.path import join from os.path import join
from plyer.facades import Screenshot from sbapp.plyer.facades import Screenshot
from plyer.utils import whereis_exe from sbapp.plyer.utils import whereis_exe
from plyer.platforms.macosx.storagepath import OSXStoragePath from sbapp.plyer.platforms.macosx.storagepath import OSXStoragePath
class OSXScreenshot(Screenshot): class OSXScreenshot(Screenshot):

View 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()

View File

@ -3,7 +3,7 @@ MacOS X Storage Path
-------------------- --------------------
''' '''
from plyer.facades import StoragePath from sbapp.plyer.facades import StoragePath
from pyobjus import autoclass from pyobjus import autoclass
NSFileManager = autoclass('NSFileManager') NSFileManager = autoclass('NSFileManager')

View File

@ -1,6 +1,6 @@
import subprocess import subprocess
from plyer.facades import TTS from sbapp.plyer.facades import TTS
from plyer.utils import whereis_exe from sbapp.plyer.utils import whereis_exe
class NativeSayTextToSpeech(TTS): class NativeSayTextToSpeech(TTS):

View File

@ -4,8 +4,8 @@ Module of MacOS API for plyer.uniqueid.
from os import environ from os import environ
from subprocess import Popen, PIPE from subprocess import Popen, PIPE
from plyer.facades import UniqueID from sbapp.plyer.facades import UniqueID
from plyer.utils import whereis_exe from sbapp.plyer.utils import whereis_exe
class OSXUniqueID(UniqueID): class OSXUniqueID(UniqueID):

View File

@ -1,7 +1,7 @@
from pyobjus import autoclass from pyobjus import autoclass
from pyobjus.dylib_manager import load_framework, INCLUDE 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.Foundation)
load_framework(INCLUDE.CoreWLAN) load_framework(INCLUDE.CoreWLAN)

View File

@ -14,8 +14,8 @@ from ctypes import (
) )
from ctypes.wintypes import DWORD, UINT from ctypes.wintypes import DWORD, UINT
from plyer.facades import Audio from sbapp.plyer.facades import Audio
from plyer.platforms.win.storagepath import WinStoragePath from sbapp.plyer.platforms.win.storagepath import WinStoragePath
# DWORD_PTR i.e. ULONG_PTR, 32/64bit # DWORD_PTR i.e. ULONG_PTR, 32/64bit
ULONG_PTR = c_ulonglong if sizeof(c_void_p) == 8 else c_ulong ULONG_PTR = c_ulonglong if sizeof(c_void_p) == 8 else c_ulong

View File

@ -2,8 +2,8 @@
Module of Windows API for plyer.battery. Module of Windows API for plyer.battery.
''' '''
from plyer.platforms.win.libs.batterystatus import battery_status from sbapp.plyer.platforms.win.libs.batterystatus import battery_status
from plyer.facades import Battery from sbapp.plyer.facades import Battery
from ctypes.wintypes import BYTE from ctypes.wintypes import BYTE

View File

@ -11,7 +11,7 @@ from ctypes.wintypes import (
BYTE, DWORD, WORD BYTE, DWORD, WORD
) )
from plyer.facades import CPU from sbapp.plyer.facades import CPU
KERNEL = windll.kernel32 KERNEL = windll.kernel32

View File

@ -3,7 +3,7 @@ Module of Win API for plyer.devicename.
''' '''
import socket import socket
from plyer.facades import DeviceName from sbapp.plyer.facades import DeviceName
class WinDeviceName(DeviceName): class WinDeviceName(DeviceName):

View File

@ -7,7 +7,7 @@ try:
from urllib.parse import quote from urllib.parse import quote
except ImportError: except ImportError:
from urllib import quote from urllib import quote
from plyer.facades import Email from sbapp.plyer.facades import Email
class WindowsEmail(Email): class WindowsEmail(Email):

View File

@ -3,7 +3,7 @@ Windows file chooser
-------------------- --------------------
''' '''
from plyer.facades import FileChooser from sbapp.plyer.facades import FileChooser
from win32com.shell.shell import ( from win32com.shell.shell import (
SHBrowseForFolder as browse, SHBrowseForFolder as browse,
SHGetPathFromIDList as get_path SHGetPathFromIDList as get_path
@ -84,7 +84,7 @@ class Win32FileChooser:
# e.g. open_file(filters=['*.txt', '*.py']) # e.g. open_file(filters=['*.txt', '*.py'])
filters = "" filters = ""
for f in self.filters: for f in self.filters:
if type(f) == str: if isinstance(f, str):
filters += (f + "\x00") * 2 filters += (f + "\x00") * 2
else: else:
filters += f[0] + "\x00" + ";".join(f[1:]) + "\x00" filters += f[0] + "\x00" + ";".join(f[1:]) + "\x00"

View File

@ -3,7 +3,7 @@ try:
except Exception: except Exception:
raise NotImplementedError() raise NotImplementedError()
from plyer.facades import Keystore from sbapp.plyer.facades import Keystore
class WinKeystore(Keystore): class WinKeystore(Keystore):

View File

@ -12,7 +12,7 @@ import ctypes
import atexit import atexit
from threading import RLock 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 WS_OVERLAPPED = 0x00000000

View File

@ -6,7 +6,7 @@ __all__ = ('battery_status')
import ctypes import ctypes
from plyer.platforms.win.libs import win_api_defs from sbapp.plyer.platforms.win.libs import win_api_defs
def battery_status(): def battery_status():

View File

@ -4,8 +4,8 @@ Module of Windows API for plyer.notification.
from threading import Thread as thread from threading import Thread as thread
from plyer.facades import Notification from sbapp.plyer.facades import Notification
from plyer.platforms.win.libs.balloontip import balloon_tip from sbapp.plyer.platforms.win.libs.balloontip import balloon_tip
class WindowsNotification(Notification): class WindowsNotification(Notification):

View File

@ -18,8 +18,8 @@ from win32con import (
SRCCOPY SRCCOPY
) )
from plyer.facades import Screenshot from sbapp.plyer.facades import Screenshot
from plyer.platforms.win.storagepath import WinStoragePath from sbapp.plyer.platforms.win.storagepath import WinStoragePath
class WinScreenshot(Screenshot): class WinScreenshot(Screenshot):

View File

@ -3,9 +3,9 @@ Windows Storage Path
-------------------- --------------------
''' '''
from plyer.facades import StoragePath from sbapp.plyer.facades import StoragePath
from os.path import expanduser 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 from uuid import UUID

View File

@ -1,6 +1,6 @@
import subprocess import subprocess
from plyer.facades import TTS from sbapp.plyer.facades import TTS
from plyer.utils import whereis_exe from sbapp.plyer.utils import whereis_exe
class EspeakTextToSpeech(TTS): class EspeakTextToSpeech(TTS):

View File

@ -10,7 +10,7 @@ except ImportError:
except ImportError: except ImportError:
raise NotImplementedError() raise NotImplementedError()
from plyer.facades import UniqueID from sbapp.plyer.facades import UniqueID
class WinUniqueID(UniqueID): class WinUniqueID(UniqueID):

View File

@ -1,5 +1,5 @@
import plyer.platforms.win.libs.wifi_defs as wifi_lib import sbapp.plyer.platforms.win.libs.wifi_defs as wifi_lib
from plyer.facades import Wifi from sbapp.plyer.facades import Wifi
class WindowWifi(Wifi): class WindowWifi(Wifi):

View File

@ -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

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -1,72 +0,0 @@
'''
TestStoragePath
===============
Tested platforms:
* macOS
'''
import unittest
from plyer.tests.common import platform_import, PlatformTest
class TestStoragePath(unittest.TestCase):
'''
TestCase for plyer.storagepath.
'''
@PlatformTest('macosx')
def test_storagepath_macosx(self):
'''
Test macOS for plyer.storagepath.
'''
storagepath = platform_import(
platform='macosx',
module_name='storagepath'
)
self.assertIn('OSXStoragePath', dir(storagepath))
storagepath = storagepath.instance()
self.assertIn('OSXStoragePath', str(storagepath))
path_format = 'file:///Users/'
self.assertIn(path_format, storagepath.get_home_dir())
self.assertIn('/', storagepath.get_root_dir())
self.assertIn(path_format, storagepath.get_documents_dir())
self.assertIn(path_format, storagepath.get_downloads_dir())
self.assertIn(path_format, storagepath.get_videos_dir())
self.assertIn(path_format, storagepath.get_music_dir())
self.assertIn(path_format, storagepath.get_pictures_dir())
self.assertIn(path_format, storagepath.get_application_dir())
@PlatformTest('win')
def test_storagepath_windows(self):
'''
Test win for plyer.storagepath.
'''
storagepath = platform_import(
platform='win',
module_name='storagepath'
)
self.assertIn('WinStoragePath', dir(storagepath))
storagepath = storagepath.instance()
self.assertIn('WinStoragePath', str(storagepath))
path_format = ':\\'
self.assertIn(path_format, storagepath.get_home_dir())
self.assertIn(path_format, storagepath.get_root_dir())
self.assertIn(path_format, storagepath.get_documents_dir())
self.assertIn(path_format, storagepath.get_downloads_dir())
self.assertIn(path_format, storagepath.get_videos_dir())
self.assertIn(path_format, storagepath.get_music_dir())
self.assertIn(path_format, storagepath.get_pictures_dir())
self.assertIn(path_format, storagepath.get_application_dir())
if __name__ == '__main__':
unittest.main()

View File

@ -1,78 +0,0 @@
'''
TestUniqueID
============
Tested platforms:
* Windows
'''
import unittest
from mock import patch, Mock
from plyer.tests.common import PlatformTest, platform_import
class TestUniqueID(unittest.TestCase):
'''
TestCase for plyer.uniqueid.
'''
def test_uniqueid(self):
'''
General all platform test for plyer.uniqueid.
'''
from plyer import uniqueid
self.assertTrue(len(uniqueid.id) > 0)
@PlatformTest('win')
def test_uniqueid_win(self):
'''
Test Windows API for plyer.uniqueid.
'''
try:
from winreg import (
HKEY_LOCAL_MACHINE as HKLM,
KEY_READ as READ, KEY_WOW64_64KEY as VIEW
)
except ImportError:
from _winreg import (
HKEY_LOCAL_MACHINE as HKLM,
KEY_READ as READ, KEY_WOW64_64KEY as VIEW
)
# mock the 'regedit' alias for winreg,
# see if the import passes and get the instance
regedit_mod = 'plyer.platforms.win.uniqueid.regedit'
with patch(target=regedit_mod):
uniqueid_ = platform_import(
platform='win',
module_name='uniqueid'
)
uniqueid = uniqueid_.instance()
self.assertIsInstance(uniqueid_.regedit, Mock)
# out of mocking block, regedit should be a winreg module
self.assertIsInstance(uniqueid_.regedit, type(unittest))
# OpenKey is supposed to return a handle to registry key
regedit_opkey = 'plyer.platforms.win.uniqueid.regedit.OpenKey'
with patch(target=regedit_opkey, return_value='unicorn') as opkey:
# QueryValueEx is supposed to return 2 packed values
# (key, type_id)
queryval = 'plyer.platforms.win.uniqueid.regedit.QueryValueEx'
retval = ('unique', None)
with patch(target=queryval, return_value=retval) as query:
uid = uniqueid.id
opkey.assert_called_once_with(
# key, subkey
HKLM, r'SOFTWARE\\Microsoft\\Cryptography',
# reserved integer (has to be 0 - zero), access mask
0, READ | VIEW
)
query.assert_called_once_with('unicorn', 'MachineGuid')
self.assertEqual(uid, retval[0])
if __name__ == '__main__':
unittest.main()

Some files were not shown because too many files have changed in this diff Show More