mirror of
https://github.com/liberatedsystems/openCom-Companion.git
synced 2024-11-22 05:20:36 +01:00
Updated, modded and refactored plyer
This commit is contained in:
parent
16e055ba25
commit
bb86bee399
@ -13,11 +13,15 @@ __all__ = (
|
|||||||
'stt', 'temperature', 'tts', 'uniqueid', 'vibrator', 'wifi', 'devicename'
|
'stt', 'temperature', 'tts', 'uniqueid', 'vibrator', 'wifi', 'devicename'
|
||||||
)
|
)
|
||||||
|
|
||||||
__version__ = '2.1.0.dev0'
|
__version__ = '2.2.0.dev0'
|
||||||
|
|
||||||
|
|
||||||
|
import RNS
|
||||||
|
if RNS.vendor.platformutils.is_android():
|
||||||
from plyer import facades
|
from plyer import facades
|
||||||
from plyer.utils import Proxy
|
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)
|
||||||
|
@ -14,6 +14,8 @@ __all__ = ('Accelerometer', 'Audio', 'Barometer', 'Battery', 'Call', 'Camera',
|
|||||||
'Processors', 'StoragePath', 'Keystore', 'Bluetooth', 'Screenshot',
|
'Processors', 'StoragePath', 'Keystore', 'Bluetooth', 'Screenshot',
|
||||||
'STT', 'DeviceName')
|
'STT', 'DeviceName')
|
||||||
|
|
||||||
|
import RNS
|
||||||
|
if RNS.vendor.platformutils.is_android():
|
||||||
from plyer.facades.accelerometer import Accelerometer
|
from plyer.facades.accelerometer import Accelerometer
|
||||||
from plyer.facades.audio import Audio
|
from plyer.facades.audio import Audio
|
||||||
from plyer.facades.barometer import Barometer
|
from plyer.facades.barometer import Barometer
|
||||||
@ -49,3 +51,39 @@ from plyer.facades.processors import Processors
|
|||||||
from plyer.facades.cpu import CPU
|
from plyer.facades.cpu import CPU
|
||||||
from plyer.facades.screenshot import Screenshot
|
from plyer.facades.screenshot import Screenshot
|
||||||
from plyer.facades.devicename import DeviceName
|
from plyer.facades.devicename import DeviceName
|
||||||
|
else:
|
||||||
|
from sbapp.plyer.facades.accelerometer import Accelerometer
|
||||||
|
from sbapp.plyer.facades.audio import Audio
|
||||||
|
from sbapp.plyer.facades.barometer import Barometer
|
||||||
|
from sbapp.plyer.facades.battery import Battery
|
||||||
|
from sbapp.plyer.facades.call import Call
|
||||||
|
from sbapp.plyer.facades.camera import Camera
|
||||||
|
from sbapp.plyer.facades.compass import Compass
|
||||||
|
from sbapp.plyer.facades.email import Email
|
||||||
|
from sbapp.plyer.facades.filechooser import FileChooser
|
||||||
|
from sbapp.plyer.facades.flash import Flash
|
||||||
|
from sbapp.plyer.facades.gps import GPS
|
||||||
|
from sbapp.plyer.facades.gravity import Gravity
|
||||||
|
from sbapp.plyer.facades.gyroscope import Gyroscope
|
||||||
|
from sbapp.plyer.facades.irblaster import IrBlaster
|
||||||
|
from sbapp.plyer.facades.light import Light
|
||||||
|
from sbapp.plyer.facades.proximity import Proximity
|
||||||
|
from sbapp.plyer.facades.orientation import Orientation
|
||||||
|
from sbapp.plyer.facades.notification import Notification
|
||||||
|
from sbapp.plyer.facades.sms import Sms
|
||||||
|
from sbapp.plyer.facades.stt import STT
|
||||||
|
from sbapp.plyer.facades.tts import TTS
|
||||||
|
from sbapp.plyer.facades.uniqueid import UniqueID
|
||||||
|
from sbapp.plyer.facades.vibrator import Vibrator
|
||||||
|
from sbapp.plyer.facades.wifi import Wifi
|
||||||
|
from sbapp.plyer.facades.temperature import Temperature
|
||||||
|
from sbapp.plyer.facades.humidity import Humidity
|
||||||
|
from sbapp.plyer.facades.spatialorientation import SpatialOrientation
|
||||||
|
from sbapp.plyer.facades.brightness import Brightness
|
||||||
|
from sbapp.plyer.facades.keystore import Keystore
|
||||||
|
from sbapp.plyer.facades.storagepath import StoragePath
|
||||||
|
from sbapp.plyer.facades.bluetooth import Bluetooth
|
||||||
|
from sbapp.plyer.facades.processors import Processors
|
||||||
|
from sbapp.plyer.facades.cpu import CPU
|
||||||
|
from sbapp.plyer.facades.screenshot import Screenshot
|
||||||
|
from sbapp.plyer.facades.devicename import DeviceName
|
||||||
|
@ -94,6 +94,7 @@ class Audio:
|
|||||||
# private
|
# private
|
||||||
|
|
||||||
def _start(self):
|
def _start(self):
|
||||||
|
raise IOError("JUICE")
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def _stop(self):
|
def _stop(self):
|
||||||
|
88
sbapp/plyer/facades/maps.py
Normal file
88
sbapp/plyer/facades/maps.py
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
'''
|
||||||
|
Maps
|
||||||
|
=======
|
||||||
|
The :class:`Maps` creates a client for accessing the default Maps API.
|
||||||
|
|
||||||
|
Holds features such as opening a location by
|
||||||
|
address & latitude/longitude, create queries, or find directions between
|
||||||
|
two points
|
||||||
|
|
||||||
|
Simple Examples
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Perform a search::
|
||||||
|
|
||||||
|
>>> from plyer import maps
|
||||||
|
>>> maps.search('Mexican Restaurant')
|
||||||
|
>>> maps.search('Taco Bell', latitude=38.5810606, longitude=-121.493895)
|
||||||
|
|
||||||
|
Get directions to a location::
|
||||||
|
|
||||||
|
>>> from plyer import maps
|
||||||
|
>>> maps.route('Cupertino', 'San Francisco')
|
||||||
|
>>> maps.route('41.9156316,-72.6130726', '42.65228271484,-73.7577362060')
|
||||||
|
|
||||||
|
View a specific location::
|
||||||
|
|
||||||
|
>>> from plyer import maps
|
||||||
|
>>> maps.open_by_address('25 Leshin Lane, Hightstown, NJ')
|
||||||
|
>>> maps.open_by_lat_long(30.451468, -91.187149)
|
||||||
|
>>> maps.open_by_lat_long(30.451468, -91.187149, name='Home')
|
||||||
|
|
||||||
|
Supported Platforms
|
||||||
|
-------------------
|
||||||
|
macOS, iOS
|
||||||
|
---------------
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
class Maps:
|
||||||
|
'''
|
||||||
|
Maps facade.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def open_by_address(self, address, **kwargs):
|
||||||
|
'''
|
||||||
|
Open the specificed location by address in the default Maps API
|
||||||
|
'''
|
||||||
|
self._open_by_address(address, **kwargs)
|
||||||
|
|
||||||
|
def open_by_lat_long(self, latitude, longitude, **kwargs):
|
||||||
|
'''
|
||||||
|
Open the specificed location by latitude & longitude coordinates
|
||||||
|
in the default Maps API
|
||||||
|
'''
|
||||||
|
self._open_by_lat_long(latitude, longitude, **kwargs)
|
||||||
|
|
||||||
|
def search(self, query, **kwargs):
|
||||||
|
'''
|
||||||
|
The query. This parameter is treated as if its value had been typed
|
||||||
|
into the Maps search field by the user.
|
||||||
|
|
||||||
|
Note that query=* is not supported
|
||||||
|
'''
|
||||||
|
self._search(query, **kwargs)
|
||||||
|
|
||||||
|
def route(self, saddr, daddr, **kwargs):
|
||||||
|
'''
|
||||||
|
To provide navigation directions from one location to another.
|
||||||
|
|
||||||
|
:param saddr: The source address to be used as the starting
|
||||||
|
point for directions.
|
||||||
|
|
||||||
|
:param daddr: The destination address to be used as the
|
||||||
|
destination point for directions.
|
||||||
|
'''
|
||||||
|
self._route(saddr, daddr, **kwargs)
|
||||||
|
|
||||||
|
def _open_by_address(self, address, **kwargs):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def _open_by_lat_long(self, latitude, longitude, **kwargs):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def _search(self, query, **kwargs):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def _route(self, saddr, daddr, **kwargs):
|
||||||
|
raise NotImplementedError()
|
@ -45,8 +45,8 @@ class Notification:
|
|||||||
Notification facade.
|
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
|
||||||
|
@ -30,7 +30,7 @@ To set sensor::
|
|||||||
|
|
||||||
Supported Platforms
|
Supported Platforms
|
||||||
-------------------
|
-------------------
|
||||||
Android
|
Android, Linux
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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__()
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
@ -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)
|
||||||
|
@ -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__()
|
||||||
|
|
||||||
|
@ -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():
|
||||||
|
@ -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__()
|
||||||
|
|
||||||
|
@ -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'):
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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')
|
||||||
|
@ -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')
|
||||||
|
@ -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')
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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")
|
||||||
|
@ -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')
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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')
|
||||||
|
78
sbapp/plyer/platforms/ios/maps.py
Normal file
78
sbapp/plyer/platforms/ios/maps.py
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
'''
|
||||||
|
Module of iOS API for plyer.maps.
|
||||||
|
'''
|
||||||
|
|
||||||
|
import webbrowser
|
||||||
|
from sbapp.plyer.facades import Maps
|
||||||
|
from urllib.parse import quote_plus
|
||||||
|
|
||||||
|
|
||||||
|
class iOSMaps(Maps):
|
||||||
|
'''
|
||||||
|
Implementation of iOS Maps API.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def _open_by_address(self, address, **kwargs):
|
||||||
|
'''
|
||||||
|
:param address: An address string that geolocation can understand.
|
||||||
|
'''
|
||||||
|
|
||||||
|
address = quote_plus(address, safe=',')
|
||||||
|
maps_address = 'http://maps.apple.com/?address=' + address
|
||||||
|
|
||||||
|
webbrowser.open(maps_address)
|
||||||
|
|
||||||
|
def _open_by_lat_long(self, latitude, longitude, **kwargs):
|
||||||
|
'''
|
||||||
|
Open a coordinate span denoting a latitudinal delta and a
|
||||||
|
longitudinal delta (similar to MKCoordinateSpan)
|
||||||
|
|
||||||
|
:param name: (optional), will set the name of the dropped pin
|
||||||
|
'''
|
||||||
|
|
||||||
|
name = kwargs.get("name", "Selected Location")
|
||||||
|
maps_address = 'http://maps.apple.com/?ll={},{}&q={}'.format(
|
||||||
|
latitude, longitude, name)
|
||||||
|
|
||||||
|
webbrowser.open(maps_address)
|
||||||
|
|
||||||
|
def _search(self, query, **kwargs):
|
||||||
|
'''
|
||||||
|
:param query: A string that describes the search object (ex. "Pizza")
|
||||||
|
|
||||||
|
:param latitude: (optional), narrow down query within area,
|
||||||
|
MUST BE USED WITH LONGITUDE
|
||||||
|
|
||||||
|
:param longitude: (optional), narrow down query within area,
|
||||||
|
MUST BE USED WITH LATITUDE
|
||||||
|
'''
|
||||||
|
|
||||||
|
latitude = kwargs.get('latitude')
|
||||||
|
longitude = kwargs.get('longitude')
|
||||||
|
|
||||||
|
query = quote_plus(query, safe=',')
|
||||||
|
maps_address = 'http://maps.apple.com/?q=' + query
|
||||||
|
|
||||||
|
if latitude is not None and longitude is not None:
|
||||||
|
maps_address += '&sll={},{}'.format(latitude, longitude)
|
||||||
|
|
||||||
|
webbrowser.open(maps_address)
|
||||||
|
|
||||||
|
def _route(self, saddr, daddr, **kwargs):
|
||||||
|
'''
|
||||||
|
:param saddr: can be given as 'address' or 'lat,long'
|
||||||
|
:param daddr: can be given as 'address' or 'lat,long'
|
||||||
|
'''
|
||||||
|
saddr = quote_plus(saddr, safe=',')
|
||||||
|
daddr = quote_plus(daddr, safe=',')
|
||||||
|
|
||||||
|
maps_address = 'http://maps.apple.com/?saddr={}&daddr={}'.format(
|
||||||
|
saddr, daddr)
|
||||||
|
webbrowser.open(maps_address)
|
||||||
|
|
||||||
|
|
||||||
|
def instance():
|
||||||
|
'''
|
||||||
|
Instance for facade proxy.
|
||||||
|
'''
|
||||||
|
return iOSMaps()
|
@ -3,7 +3,7 @@ IOS Sms
|
|||||||
----------
|
----------
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from plyer.facades import Sms
|
from sbapp.plyer.facades import Sms
|
||||||
from pyobjus import autoclass, objc_str
|
from pyobjus import autoclass, objc_str
|
||||||
from pyobjus.dylib_manager import load_framework
|
from pyobjus.dylib_manager import load_framework
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ iOS Spatial Orientation
|
|||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from plyer.facades import SpatialOrientation
|
from sbapp.plyer.facades import SpatialOrientation
|
||||||
from pyobjus import autoclass
|
from pyobjus import autoclass
|
||||||
|
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ iOS Storage Path
|
|||||||
--------------------
|
--------------------
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from plyer.facades import StoragePath
|
from sbapp.plyer.facades import StoragePath
|
||||||
from pyobjus import autoclass
|
from pyobjus import autoclass
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
@ -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 = \
|
||||||
|
@ -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')
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
48
sbapp/plyer/platforms/linux/audio.py
Normal file
48
sbapp/plyer/platforms/linux/audio.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import time
|
||||||
|
import threading
|
||||||
|
from sbapp.plyer.facades.audio import Audio
|
||||||
|
|
||||||
|
class LinuxAudio(Audio):
|
||||||
|
|
||||||
|
def __init__(self, file_path=None):
|
||||||
|
default_path = None
|
||||||
|
super().__init__(file_path or default_path)
|
||||||
|
|
||||||
|
self._recorder = None
|
||||||
|
self._player = None
|
||||||
|
self._check_thread = None
|
||||||
|
self._finished_callback = None
|
||||||
|
|
||||||
|
def _check_playback(self):
|
||||||
|
while self.is_playing:
|
||||||
|
time.sleep(0.25)
|
||||||
|
|
||||||
|
if self._finished_callback and callable(self._finished_callback):
|
||||||
|
self._check_thread = None
|
||||||
|
self._finished_callback(self)
|
||||||
|
|
||||||
|
def _start(self):
|
||||||
|
# TODO: Implement recording
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _stop(self):
|
||||||
|
# TODO: Implement recording
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _play(self):
|
||||||
|
# TODO: Implement playback
|
||||||
|
self.is_playing = True
|
||||||
|
|
||||||
|
self._check_thread = threading.Thread(target=self._check_playback, daemon=True)
|
||||||
|
self._check_thread.start()
|
||||||
|
|
||||||
|
def fauxplay():
|
||||||
|
time.sleep(1.5)
|
||||||
|
self.is_playing = False
|
||||||
|
|
||||||
|
threading.Thread(target=fauxplay, daemon=True).start()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def instance():
|
||||||
|
return LinuxAudio()
|
@ -2,13 +2,12 @@
|
|||||||
Module of Linux API for plyer.battery.
|
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()
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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):
|
||||||
|
@ -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):
|
||||||
|
@ -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):
|
||||||
|
@ -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 += [
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
||||||
|
@ -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):
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
||||||
|
@ -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):
|
||||||
|
@ -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()
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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):
|
||||||
|
@ -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):
|
||||||
|
@ -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):
|
||||||
|
@ -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:
|
||||||
|
@ -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):
|
||||||
|
@ -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)
|
||||||
|
90
sbapp/plyer/platforms/macosx/maps.py
Normal file
90
sbapp/plyer/platforms/macosx/maps.py
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
'''
|
||||||
|
Module of macOS API for plyer.maps.
|
||||||
|
'''
|
||||||
|
|
||||||
|
from subprocess import Popen, PIPE
|
||||||
|
from sbapp.plyer.facades import Maps
|
||||||
|
from urllib.parse import quote_plus
|
||||||
|
|
||||||
|
|
||||||
|
class MacOSMaps(Maps):
|
||||||
|
'''
|
||||||
|
Implementation of MacOS Maps API.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def _open_by_address(self, address, **kwargs):
|
||||||
|
'''
|
||||||
|
:param address: An address string that geolocation can understand.
|
||||||
|
'''
|
||||||
|
|
||||||
|
address = quote_plus(address, safe=',')
|
||||||
|
maps_address = 'http://maps.apple.com/?address=' + address
|
||||||
|
|
||||||
|
process = Popen(
|
||||||
|
['open', '-a', 'Maps', maps_address],
|
||||||
|
stdout=PIPE, stderr=PIPE)
|
||||||
|
stdout, stderr = process.communicate()
|
||||||
|
|
||||||
|
def _open_by_lat_long(self, latitude, longitude, **kwargs):
|
||||||
|
'''
|
||||||
|
Open a coordinate span denoting a latitudinal delta and a
|
||||||
|
longitudinal delta (similar to MKCoordinateSpan)
|
||||||
|
|
||||||
|
:param name: (optional), will set the name of the dropped pin
|
||||||
|
'''
|
||||||
|
|
||||||
|
name = kwargs.get("name", "Selected Location")
|
||||||
|
maps_address = 'http://maps.apple.com/?ll={},{}&q={}'.format(
|
||||||
|
latitude, longitude, name)
|
||||||
|
|
||||||
|
process = Popen(
|
||||||
|
['open', '-a', 'Maps', maps_address],
|
||||||
|
stdout=PIPE, stderr=PIPE)
|
||||||
|
stdout, stderr = process.communicate()
|
||||||
|
|
||||||
|
def _search(self, query, **kwargs):
|
||||||
|
'''
|
||||||
|
:param query: A string that describes the search object (ex. "Pizza")
|
||||||
|
|
||||||
|
:param latitude: (optional), narrow down query within area,
|
||||||
|
MUST BE USED WITH LONGITUDE
|
||||||
|
|
||||||
|
:param longitude: (optional), narrow down query within area,
|
||||||
|
MUST BE USED WITH LATITUDE
|
||||||
|
'''
|
||||||
|
|
||||||
|
latitude = kwargs.get('latitude')
|
||||||
|
longitude = kwargs.get('longitude')
|
||||||
|
|
||||||
|
query = quote_plus(query, safe=',')
|
||||||
|
maps_address = 'http://maps.apple.com/?q=' + query
|
||||||
|
|
||||||
|
if latitude is not None and longitude is not None:
|
||||||
|
maps_address += '&sll={},{}'.format(latitude, longitude)
|
||||||
|
|
||||||
|
process = Popen(
|
||||||
|
['open', '-a', 'Maps', maps_address],
|
||||||
|
stdout=PIPE, stderr=PIPE)
|
||||||
|
stdout, stderr = process.communicate()
|
||||||
|
|
||||||
|
def _route(self, saddr, daddr, **kwargs):
|
||||||
|
'''
|
||||||
|
:param saddr: can be given as 'address' or 'lat,long'
|
||||||
|
:param daddr: can be given as 'address' or 'lat,long'
|
||||||
|
'''
|
||||||
|
saddr = quote_plus(saddr, safe=',')
|
||||||
|
daddr = quote_plus(daddr, safe=',')
|
||||||
|
|
||||||
|
maps_address = 'http://maps.apple.com/?saddr={}&daddr={}'.format(
|
||||||
|
saddr, daddr)
|
||||||
|
process = Popen(
|
||||||
|
['open', '-a', 'Maps', maps_address],
|
||||||
|
stdout=PIPE, stderr=PIPE)
|
||||||
|
stdout, stderr = process.communicate()
|
||||||
|
|
||||||
|
|
||||||
|
def instance():
|
||||||
|
'''
|
||||||
|
Instance for facade proxy.
|
||||||
|
'''
|
||||||
|
return MacOSMaps()
|
@ -2,7 +2,7 @@
|
|||||||
Module of MacOS API for plyer.notification.
|
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
|
||||||
|
@ -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):
|
||||||
|
42
sbapp/plyer/platforms/macosx/sms.py
Normal file
42
sbapp/plyer/platforms/macosx/sms.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
from subprocess import Popen, PIPE
|
||||||
|
from sbapp.plyer.facades import Sms as SMS
|
||||||
|
from sbapp.plyer.utils import whereis_exe
|
||||||
|
|
||||||
|
|
||||||
|
class MacOSSMS(SMS):
|
||||||
|
'''
|
||||||
|
Implementation of macOS' Messages API
|
||||||
|
'''
|
||||||
|
|
||||||
|
def _send(self, **kwargs):
|
||||||
|
'''
|
||||||
|
Will send `message` to `recipient` via Messages app
|
||||||
|
|
||||||
|
By default, if `mode` is not explicitly set, `iMessage` is used.
|
||||||
|
In order to use `SMS` mode, a valid carrier-activated device must
|
||||||
|
be connected and configured.
|
||||||
|
'''
|
||||||
|
|
||||||
|
recipient = kwargs.get('recipient')
|
||||||
|
message = kwargs.get('message')
|
||||||
|
mode = kwargs.get('mode') # Supported modes: iMessage (default), SMS
|
||||||
|
if not mode:
|
||||||
|
mode = 'iMessage'
|
||||||
|
|
||||||
|
APPLESCRIPT = f"""tell application "Messages"
|
||||||
|
set targetService to 1st account whose service type = {mode}
|
||||||
|
set targetBuddy to participant "{recipient}" of targetService
|
||||||
|
send "{message}" to targetBuddy
|
||||||
|
end tell"""
|
||||||
|
|
||||||
|
osascript_process = Popen(
|
||||||
|
['osascript', '-e', APPLESCRIPT], stdout=PIPE, stderr=PIPE)
|
||||||
|
stdout, stderr = osascript_process.communicate()
|
||||||
|
|
||||||
|
|
||||||
|
def instance():
|
||||||
|
import sys
|
||||||
|
if whereis_exe('osascript'):
|
||||||
|
return MacOSSMS()
|
||||||
|
sys.stderr.write('osascript not found.')
|
||||||
|
return SMS()
|
@ -3,7 +3,7 @@ MacOS X Storage Path
|
|||||||
--------------------
|
--------------------
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from plyer.facades import StoragePath
|
from sbapp.plyer.facades import StoragePath
|
||||||
from pyobjus import autoclass
|
from pyobjus import autoclass
|
||||||
|
|
||||||
NSFileManager = autoclass('NSFileManager')
|
NSFileManager = autoclass('NSFileManager')
|
||||||
|
@ -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):
|
||||||
|
@ -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):
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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):
|
||||||
|
@ -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):
|
||||||
|
@ -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"
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
||||||
|
@ -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():
|
||||||
|
@ -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):
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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):
|
||||||
|
@ -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):
|
||||||
|
@ -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):
|
||||||
|
@ -1,76 +0,0 @@
|
|||||||
'''
|
|
||||||
Common objects for testing
|
|
||||||
==========================
|
|
||||||
|
|
||||||
* :class:`PlatformTest` - used as a decorator, allows running a test function
|
|
||||||
only on a specific platform (see `plyer.utils.platform`).
|
|
||||||
* :func:`platform_import` - manual import of a platform specific class instead
|
|
||||||
of using `plyer.facades.*` proxies.
|
|
||||||
'''
|
|
||||||
|
|
||||||
import traceback
|
|
||||||
from os import sep
|
|
||||||
from os.path import normpath, splitdrive
|
|
||||||
from plyer.utils import platform as plyer_platform
|
|
||||||
|
|
||||||
|
|
||||||
class PlatformTest:
|
|
||||||
'''
|
|
||||||
Class for the @PlatformTest decorator to prevent running tests
|
|
||||||
calling platform dependent API on different platforms.
|
|
||||||
'''
|
|
||||||
|
|
||||||
def __init__(self, platform):
|
|
||||||
self.platform = platform
|
|
||||||
|
|
||||||
def __call__(self, func):
|
|
||||||
platform = self.platform
|
|
||||||
|
|
||||||
if platform != plyer_platform:
|
|
||||||
print("Skipping test '{}' - not on '{}'".format(
|
|
||||||
func.__name__, platform
|
|
||||||
))
|
|
||||||
func = self.eat
|
|
||||||
return func
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def eat(*args, **kwargs):
|
|
||||||
'''
|
|
||||||
Simply eat all positional and keyword arguments
|
|
||||||
and return None as an empty function.
|
|
||||||
'''
|
|
||||||
|
|
||||||
|
|
||||||
def platform_import(platform, module_name, whereis_exe=None):
|
|
||||||
'''
|
|
||||||
Import platform API directly instead of through Proxy.
|
|
||||||
'''
|
|
||||||
|
|
||||||
try:
|
|
||||||
module = 'plyer.platforms.{}.{}'.format(
|
|
||||||
platform, module_name
|
|
||||||
)
|
|
||||||
mod = __import__(module, fromlist='.')
|
|
||||||
|
|
||||||
except ImportError as exc:
|
|
||||||
print(vars(exc))
|
|
||||||
traceback.print_exc()
|
|
||||||
|
|
||||||
if whereis_exe:
|
|
||||||
mod.whereis_exe = whereis_exe
|
|
||||||
return mod
|
|
||||||
|
|
||||||
|
|
||||||
def splitpath(path):
|
|
||||||
'''
|
|
||||||
Split string path into a list of folders (+ file if available).
|
|
||||||
'''
|
|
||||||
if path[0] == sep and path[1] != sep:
|
|
||||||
path = path[1:]
|
|
||||||
path = normpath(path).split(sep)
|
|
||||||
else:
|
|
||||||
drive, path = splitdrive(path)
|
|
||||||
if path[0] == sep and path[1] != sep:
|
|
||||||
path = path[1:]
|
|
||||||
path = [drive, ] + normpath(path).split(sep)
|
|
||||||
return path
|
|
Binary file not shown.
Before Width: | Height: | Size: 4.2 KiB |
@ -1,110 +0,0 @@
|
|||||||
'''
|
|
||||||
TestAudio
|
|
||||||
=========
|
|
||||||
|
|
||||||
Tested platforms:
|
|
||||||
|
|
||||||
* macOS
|
|
||||||
* Windows
|
|
||||||
|
|
||||||
.. versionadded:: 1.4.0
|
|
||||||
'''
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
import time
|
|
||||||
|
|
||||||
from os import mkdir, remove, environ
|
|
||||||
from os.path import join, expanduser, exists
|
|
||||||
from plyer.tests.common import platform_import, PlatformTest
|
|
||||||
|
|
||||||
|
|
||||||
class TestAudio(unittest.TestCase):
|
|
||||||
'''
|
|
||||||
TestCase for plyer.audio.
|
|
||||||
|
|
||||||
.. versionadded:: 1.4.0
|
|
||||||
'''
|
|
||||||
|
|
||||||
@PlatformTest('macosx')
|
|
||||||
def test_audio_macosx(self):
|
|
||||||
'''
|
|
||||||
Test macOS audio start, stop and play
|
|
||||||
|
|
||||||
.. versionadded:: 1.4.0
|
|
||||||
'''
|
|
||||||
|
|
||||||
path = join(expanduser('~'), 'Music')
|
|
||||||
if not exists(path):
|
|
||||||
mkdir(path)
|
|
||||||
|
|
||||||
audio = platform_import(
|
|
||||||
platform='macosx',
|
|
||||||
module_name='audio',
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertIn('OSXAudio', dir(audio))
|
|
||||||
audio = audio.instance()
|
|
||||||
self.assertIn('OSXAudio', str(audio))
|
|
||||||
|
|
||||||
self.assertFalse(exists(audio.file_path))
|
|
||||||
self.assertIsNone(audio.start())
|
|
||||||
time.sleep(0.5)
|
|
||||||
self.assertIsNone(audio.stop())
|
|
||||||
self.assertIsNone(audio.play())
|
|
||||||
time.sleep(0.5)
|
|
||||||
self.assertIsNone(audio.stop())
|
|
||||||
|
|
||||||
audio.file_path = audio.file_path.replace(
|
|
||||||
'file://', ''
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertTrue(exists(audio.file_path))
|
|
||||||
|
|
||||||
remove(audio.file_path)
|
|
||||||
|
|
||||||
@PlatformTest('win')
|
|
||||||
def test_audio_win(self):
|
|
||||||
'''
|
|
||||||
Test Windows audio start, stop and play
|
|
||||||
|
|
||||||
.. versionadded:: 1.4.0
|
|
||||||
'''
|
|
||||||
|
|
||||||
if environ.get('APPVEYOR'):
|
|
||||||
# Appveyor has no recording device installed
|
|
||||||
# therefore the test will 100% fail
|
|
||||||
#
|
|
||||||
# error_code: 328
|
|
||||||
# message:
|
|
||||||
# 'No wave device is installed that can record files in the current
|
|
||||||
# format. To install a wave device, go to Control Panel, click P')
|
|
||||||
return
|
|
||||||
|
|
||||||
path = join(environ['USERPROFILE'], 'Music')
|
|
||||||
if not exists(path):
|
|
||||||
mkdir(path)
|
|
||||||
|
|
||||||
audio = platform_import(
|
|
||||||
platform='win',
|
|
||||||
module_name='audio',
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertIn('WinAudio', dir(audio))
|
|
||||||
audio = audio.instance()
|
|
||||||
self.assertIn('WinAudio', str(audio))
|
|
||||||
|
|
||||||
self.assertFalse(exists(audio.file_path))
|
|
||||||
self.assertIsNone(audio.start())
|
|
||||||
time.sleep(0.5)
|
|
||||||
self.assertIsNone(audio.stop())
|
|
||||||
self.assertIsNone(audio.play())
|
|
||||||
time.sleep(0.5)
|
|
||||||
self.assertIsNone(audio.stop())
|
|
||||||
|
|
||||||
self.assertTrue(exists(audio.file_path))
|
|
||||||
|
|
||||||
remove(audio.file_path)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
unittest.main()
|
|
@ -1,413 +0,0 @@
|
|||||||
'''
|
|
||||||
TestBattery
|
|
||||||
===========
|
|
||||||
|
|
||||||
Tested platforms:
|
|
||||||
|
|
||||||
* Windows
|
|
||||||
* Linux - upower, kernel sysclass
|
|
||||||
* macOS - ioreg
|
|
||||||
'''
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
from io import BytesIO
|
|
||||||
from os.path import join
|
|
||||||
from textwrap import dedent
|
|
||||||
from mock import patch, Mock
|
|
||||||
|
|
||||||
from plyer.tests.common import PlatformTest, platform_import
|
|
||||||
|
|
||||||
|
|
||||||
class MockedKernelSysclass:
|
|
||||||
'''
|
|
||||||
Mocked object used instead of Linux's sysclass for power_supply
|
|
||||||
battery uevent.
|
|
||||||
'''
|
|
||||||
|
|
||||||
@property
|
|
||||||
def path(self):
|
|
||||||
'''
|
|
||||||
Mocked path to Linux kernel sysclass.
|
|
||||||
'''
|
|
||||||
return join('/sys', 'class', 'power_supply', 'BAT0')
|
|
||||||
|
|
||||||
@property
|
|
||||||
def charging(self):
|
|
||||||
'''
|
|
||||||
Mocked battery charging status.
|
|
||||||
'''
|
|
||||||
return u'Discharging'
|
|
||||||
|
|
||||||
@property
|
|
||||||
def percentage(self):
|
|
||||||
'''
|
|
||||||
Mocked battery charge percentage.
|
|
||||||
'''
|
|
||||||
return 89.0
|
|
||||||
|
|
||||||
@property
|
|
||||||
def full(self):
|
|
||||||
'''
|
|
||||||
Mocked full battery charge.
|
|
||||||
'''
|
|
||||||
return 4764000
|
|
||||||
|
|
||||||
@property
|
|
||||||
def now(self):
|
|
||||||
'''
|
|
||||||
Calculated current mocked battery charge.
|
|
||||||
'''
|
|
||||||
return self.percentage * self.full / 100.0
|
|
||||||
|
|
||||||
@property
|
|
||||||
def uevent(self):
|
|
||||||
'''
|
|
||||||
Mocked /sys/class/power_supply/BAT0 file.
|
|
||||||
'''
|
|
||||||
return BytesIO(dedent(b'''\
|
|
||||||
POWER_SUPPLY_NAME=BAT0
|
|
||||||
POWER_SUPPLY_STATUS={}
|
|
||||||
POWER_SUPPLY_PRESENT=1
|
|
||||||
POWER_SUPPLY_TECHNOLOGY=Li-ion
|
|
||||||
POWER_SUPPLY_CYCLE_COUNT=0
|
|
||||||
POWER_SUPPLY_VOLTAGE_MIN_DESIGN=10800000
|
|
||||||
POWER_SUPPLY_VOLTAGE_NOW=12074000
|
|
||||||
POWER_SUPPLY_CURRENT_NOW=1584000
|
|
||||||
POWER_SUPPLY_CHARGE_FULL_DESIGN=5800000
|
|
||||||
POWER_SUPPLY_CHARGE_FULL={}
|
|
||||||
POWER_SUPPLY_CHARGE_NOW={}
|
|
||||||
POWER_SUPPLY_CAPACITY={}
|
|
||||||
POWER_SUPPLY_CAPACITY_LEVEL=Normal
|
|
||||||
POWER_SUPPLY_MODEL_NAME=1005HA
|
|
||||||
POWER_SUPPLY_MANUFACTURER=ASUS
|
|
||||||
POWER_SUPPLY_SERIAL_NUMBER=0
|
|
||||||
'''.decode('utf-8').format(
|
|
||||||
self.charging, self.full,
|
|
||||||
self.now, int(self.percentage)
|
|
||||||
)).encode('utf-8'))
|
|
||||||
|
|
||||||
|
|
||||||
class MockedUPower:
|
|
||||||
'''
|
|
||||||
Mocked object used instead of 'upower' binary in the Linux specific API
|
|
||||||
plyer.platforms.linux.battery. The same output structure is tested for
|
|
||||||
the range of <min_version, max_version>.
|
|
||||||
|
|
||||||
.. note:: Extend the object with another data sample if it does not match.
|
|
||||||
'''
|
|
||||||
|
|
||||||
min_version = '0.99.4'
|
|
||||||
max_version = '0.99.4'
|
|
||||||
|
|
||||||
values = {
|
|
||||||
u'Device': u'/org/freedesktop/UPower/devices/battery_BAT0',
|
|
||||||
u'native-path': u'BAT0',
|
|
||||||
u'vendor': u'ASUS',
|
|
||||||
u'model': u'1005HA',
|
|
||||||
u'power supply': u'yes',
|
|
||||||
u'updated': u'Thu 05 Jul 2018 23:15:01 PM CEST',
|
|
||||||
u'has history': u'yes',
|
|
||||||
u'has statistics': u'yes',
|
|
||||||
u'battery': {
|
|
||||||
u'present': u'yes',
|
|
||||||
u'rechargeable': u'yes',
|
|
||||||
u'state': u'discharging',
|
|
||||||
u'warning-level': u'none',
|
|
||||||
u'energy': u'48,708 Wh',
|
|
||||||
u'energy-empty': u'0 Wh',
|
|
||||||
u'energy-full': u'54,216 Wh',
|
|
||||||
u'energy-full-design': u'62,64 Wh',
|
|
||||||
u'energy-rate': u'7,722 W',
|
|
||||||
u'voltage': u'11,916 V',
|
|
||||||
u'time to empty': u'6,3 hours',
|
|
||||||
u'percentage': u'89%',
|
|
||||||
u'capacity': u'86,5517%',
|
|
||||||
u'technology': u'lithium-ion',
|
|
||||||
u'icon-name': u"'battery-full-symbolic"
|
|
||||||
},
|
|
||||||
u'History (charge)': u'1530959637 89,000 discharging',
|
|
||||||
u'History (rate)': u'1530958556 7,474 discharging'
|
|
||||||
}
|
|
||||||
|
|
||||||
data = str(
|
|
||||||
' native-path: {native-path}\n'
|
|
||||||
' vendor: {vendor}\n'
|
|
||||||
' model: {model}\n'
|
|
||||||
' power supply: {power supply}\n'
|
|
||||||
' updated: {updated}\n'
|
|
||||||
' has history: {has history}\n'
|
|
||||||
' has statistics: {has statistics}\n'
|
|
||||||
' battery\n'
|
|
||||||
' present: {battery[present]}\n'
|
|
||||||
' rechargeable: {battery[rechargeable]}\n'
|
|
||||||
' state: {battery[state]}\n'
|
|
||||||
' warning-level: {battery[warning-level]}\n'
|
|
||||||
' energy: {battery[energy]}\n'
|
|
||||||
' energy-empty: {battery[energy-empty]}\n'
|
|
||||||
' energy-full: {battery[energy-full]}\n'
|
|
||||||
' energy-full-design: {battery[energy-full-design]}\n'
|
|
||||||
' energy-rate: {battery[energy-rate]}\n'
|
|
||||||
' voltage: {battery[voltage]}\n'
|
|
||||||
' time to empty: {battery[time to empty]}\n'
|
|
||||||
' percentage: {battery[percentage]}\n'
|
|
||||||
' capacity: {battery[capacity]}\n'
|
|
||||||
' technology: {battery[technology]}\n'
|
|
||||||
' icon-name: {battery[icon-name]}\n'
|
|
||||||
' History (charge):\n'
|
|
||||||
' {History (charge)}\n'
|
|
||||||
' History (rate):\n'
|
|
||||||
' {History (rate)}\n'
|
|
||||||
).format(**values).encode('utf-8')
|
|
||||||
# LinuxBattery calls decode()
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
# only to ignore all args, kwargs
|
|
||||||
pass
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def communicate():
|
|
||||||
'''
|
|
||||||
Mock Popen.communicate, so that 'upower' isn't used.
|
|
||||||
'''
|
|
||||||
return (MockedUPower.data, )
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def whereis_exe(binary):
|
|
||||||
'''
|
|
||||||
Mock whereis_exe, so that it looks like
|
|
||||||
Linux UPower binary is present on the system.
|
|
||||||
'''
|
|
||||||
return binary == 'upower'
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def charging():
|
|
||||||
'''
|
|
||||||
Return charging bool from mocked data.
|
|
||||||
'''
|
|
||||||
return MockedUPower.values['battery']['state'] == 'charging'
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def percentage():
|
|
||||||
'''
|
|
||||||
Return percentage from mocked data.
|
|
||||||
'''
|
|
||||||
percentage = MockedUPower.values['battery']['percentage'][:-1]
|
|
||||||
return float(percentage.replace(',', '.'))
|
|
||||||
|
|
||||||
|
|
||||||
class MockedIOReg:
|
|
||||||
'''
|
|
||||||
Mocked object used instead of Apple's ioreg.
|
|
||||||
'''
|
|
||||||
values = {
|
|
||||||
"MaxCapacity": "5023",
|
|
||||||
"CurrentCapacity": "4222",
|
|
||||||
"IsCharging": "No"
|
|
||||||
}
|
|
||||||
|
|
||||||
output = dedent(
|
|
||||||
"""+-o AppleSmartBattery <class AppleSmartBattery,\
|
|
||||||
id 0x1000002c9, registered, matched, active, busy 0 (0 ms), retain 6>
|
|
||||||
{{
|
|
||||||
"TimeRemaining" = 585
|
|
||||||
"AvgTimeToEmpty" = 585
|
|
||||||
"InstantTimeToEmpty" = 761
|
|
||||||
"ExternalChargeCapable" = Yes
|
|
||||||
"FullPathUpdated" = 1541845134
|
|
||||||
"CellVoltage" = (4109,4118,4099,0)
|
|
||||||
"PermanentFailureStatus" = 0
|
|
||||||
"BatteryInvalidWakeSeconds" = 30
|
|
||||||
"AdapterInfo" = 0
|
|
||||||
"MaxCapacity" = {MaxCapacity}
|
|
||||||
"Voltage" = 12326
|
|
||||||
"DesignCycleCount70" = 13
|
|
||||||
"Manufacturer" = "SWD"
|
|
||||||
"Location" = 0
|
|
||||||
"CurrentCapacity" = {CurrentCapacity}
|
|
||||||
"LegacyBatteryInfo" = {{"Amperage"=18446744073709551183,"Flags"=4,\
|
|
||||||
"Capacity"=5023,"Current"=4222,"Voltage"=12326,"Cycle Count"=40}}
|
|
||||||
"FirmwareSerialNumber" = 1
|
|
||||||
"BatteryInstalled" = Yes
|
|
||||||
"PackReserve" = 117
|
|
||||||
"CycleCount" = 40
|
|
||||||
"DesignCapacity" = 5088
|
|
||||||
"OperationStatus" = 58435
|
|
||||||
"ManufactureDate" = 19700
|
|
||||||
"AvgTimeToFull" = 65535
|
|
||||||
"BatterySerialNumber" = "1234567890ABCDEFGH"
|
|
||||||
"BootPathUpdated" = 1541839734
|
|
||||||
"PostDischargeWaitSeconds" = 120
|
|
||||||
"Temperature" = 3038
|
|
||||||
"UserVisiblePathUpdated" = 1541845194
|
|
||||||
"InstantAmperage" = 18446744073709551249
|
|
||||||
"ManufacturerData" = <000000000>
|
|
||||||
"FullyCharged" = No
|
|
||||||
"MaxErr" = 1
|
|
||||||
"DeviceName" = "bq20z451"
|
|
||||||
"IOGeneralInterest" = "IOCommand is not serializable"
|
|
||||||
"Amperage" = 18446744073709551183
|
|
||||||
"IsCharging" = {IsCharging}
|
|
||||||
"DesignCycleCount9C" = 1000
|
|
||||||
"PostChargeWaitSeconds" = 120
|
|
||||||
"ExternalConnected" = No
|
|
||||||
}}"""
|
|
||||||
).format(**values).encode('utf-8')
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
# only to ignore all args, kwargs
|
|
||||||
pass
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def communicate():
|
|
||||||
'''
|
|
||||||
Mock Popen.communicate, so that 'ioreg' isn't used.
|
|
||||||
'''
|
|
||||||
return (MockedIOReg.output, )
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def whereis_exe(binary):
|
|
||||||
'''
|
|
||||||
Mock whereis_exe, so that it looks like
|
|
||||||
macOS ioreg binary is present on the system.
|
|
||||||
'''
|
|
||||||
return binary == 'ioreg'
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def charging():
|
|
||||||
'''
|
|
||||||
Return charging bool from mocked data.
|
|
||||||
'''
|
|
||||||
return MockedIOReg.values['IsCharging'] == 'Yes'
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def percentage():
|
|
||||||
'''
|
|
||||||
Return percentage from mocked data.
|
|
||||||
'''
|
|
||||||
current_capacity = int(MockedIOReg.values['CurrentCapacity'])
|
|
||||||
max_capacity = int(MockedIOReg.values['MaxCapacity'])
|
|
||||||
percentage = 100.0 * current_capacity / max_capacity
|
|
||||||
|
|
||||||
return percentage
|
|
||||||
|
|
||||||
|
|
||||||
class TestBattery(unittest.TestCase):
|
|
||||||
'''
|
|
||||||
TestCase for plyer.battery.
|
|
||||||
'''
|
|
||||||
|
|
||||||
def test_battery_linux_upower(self):
|
|
||||||
'''
|
|
||||||
Test mocked Linux UPower for plyer.battery.
|
|
||||||
'''
|
|
||||||
battery = platform_import(
|
|
||||||
platform='linux',
|
|
||||||
module_name='battery',
|
|
||||||
whereis_exe=MockedUPower.whereis_exe
|
|
||||||
)
|
|
||||||
battery.Popen = MockedUPower
|
|
||||||
battery = battery.instance()
|
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
battery.status, {
|
|
||||||
'isCharging': MockedUPower.charging(),
|
|
||||||
'percentage': MockedUPower.percentage()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_battery_linux_kernel(self):
|
|
||||||
'''
|
|
||||||
Test mocked Linux kernel sysclass for plyer.battery.
|
|
||||||
'''
|
|
||||||
|
|
||||||
def false(*args, **kwargs):
|
|
||||||
return False
|
|
||||||
|
|
||||||
sysclass = MockedKernelSysclass()
|
|
||||||
|
|
||||||
with patch(target='os.path.exists') as bat_path:
|
|
||||||
# first call to trigger exists() call
|
|
||||||
platform_import(
|
|
||||||
platform='linux',
|
|
||||||
module_name='battery',
|
|
||||||
whereis_exe=false
|
|
||||||
).instance()
|
|
||||||
bat_path.assert_called_once_with(sysclass.path)
|
|
||||||
|
|
||||||
# exists() checked with sysclass path
|
|
||||||
# set mock to proceed with this branch
|
|
||||||
bat_path.return_value = True
|
|
||||||
|
|
||||||
battery = platform_import(
|
|
||||||
platform='linux',
|
|
||||||
module_name='battery',
|
|
||||||
whereis_exe=false
|
|
||||||
).instance()
|
|
||||||
|
|
||||||
stub = Mock(return_value=sysclass.uevent)
|
|
||||||
target = 'builtins.open'
|
|
||||||
|
|
||||||
with patch(target=target, new=stub):
|
|
||||||
self.assertEqual(
|
|
||||||
battery.status, {
|
|
||||||
'isCharging': sysclass.charging == 'Charging',
|
|
||||||
'percentage': sysclass.percentage
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
@PlatformTest('win')
|
|
||||||
def test_battery_win(self):
|
|
||||||
'''
|
|
||||||
Test Windows API for plyer.battery.
|
|
||||||
'''
|
|
||||||
battery = platform_import(
|
|
||||||
platform='win',
|
|
||||||
module_name='battery'
|
|
||||||
).instance()
|
|
||||||
for key in ('isCharging', 'percentage'):
|
|
||||||
self.assertIn(key, battery.status)
|
|
||||||
self.assertIsNotNone(battery.status[key])
|
|
||||||
|
|
||||||
def test_battery_macosx(self):
|
|
||||||
'''
|
|
||||||
Test macOS IOReg for plyer.battery.
|
|
||||||
'''
|
|
||||||
battery = platform_import(
|
|
||||||
platform='macosx',
|
|
||||||
module_name='battery',
|
|
||||||
whereis_exe=MockedIOReg.whereis_exe
|
|
||||||
)
|
|
||||||
|
|
||||||
battery.Popen = MockedIOReg
|
|
||||||
self.assertIn('OSXBattery', dir(battery))
|
|
||||||
battery = battery.instance()
|
|
||||||
self.assertIn('OSXBattery', str(battery))
|
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
battery.status, {
|
|
||||||
'isCharging': MockedIOReg.charging(),
|
|
||||||
'percentage': MockedIOReg.percentage()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_battery_macosx_instance(self):
|
|
||||||
'''
|
|
||||||
Test macOS instance for plyer.battery
|
|
||||||
'''
|
|
||||||
|
|
||||||
def no_exe(*args, **kwargs):
|
|
||||||
return
|
|
||||||
|
|
||||||
battery = platform_import(
|
|
||||||
platform='macosx',
|
|
||||||
module_name='battery',
|
|
||||||
whereis_exe=no_exe
|
|
||||||
)
|
|
||||||
|
|
||||||
battery = battery.instance()
|
|
||||||
self.assertNotIn('OSXBattery', str(battery))
|
|
||||||
self.assertIn('Battery', str(battery))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
unittest.main()
|
|
@ -1,144 +0,0 @@
|
|||||||
'''
|
|
||||||
TestBluetooth
|
|
||||||
=============
|
|
||||||
|
|
||||||
Tested platforms:
|
|
||||||
|
|
||||||
* macOS - system_profiler
|
|
||||||
'''
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
from plyer.tests.common import platform_import
|
|
||||||
from textwrap import dedent
|
|
||||||
|
|
||||||
|
|
||||||
class MockedSystemProfiler:
|
|
||||||
'''
|
|
||||||
Mocked object used instead of Apple's system_profiler
|
|
||||||
'''
|
|
||||||
value = "On"
|
|
||||||
output = dedent(
|
|
||||||
"""Bluetooth:
|
|
||||||
|
|
||||||
Apple Bluetooth Software Version: 6.0.7f11
|
|
||||||
Hardware, Features, and Settings:
|
|
||||||
Address: AA-00-BB-11-CC-22
|
|
||||||
Bluetooth Low Energy Supported: Yes
|
|
||||||
Handoff Supported: Yes
|
|
||||||
Instant Hot Spot Supported: Yes
|
|
||||||
Manufacturer: Broadcom
|
|
||||||
Transport: UART
|
|
||||||
Chipset: 1234
|
|
||||||
Firmware Version: v00 c0000
|
|
||||||
Bluetooth Power: {}
|
|
||||||
Auto Seek Pointing: On
|
|
||||||
Remote wake: On
|
|
||||||
Vendor ID: 0x0000
|
|
||||||
Product ID: 0x0000
|
|
||||||
HCI Version: (0x0)
|
|
||||||
HCI Revision: 0x0000
|
|
||||||
LMP Version: (0x0)
|
|
||||||
LMP Subversion: 0x0000
|
|
||||||
Auto Seek Keyboard: On
|
|
||||||
Devices (Paired, Configured, etc.):
|
|
||||||
iPhone:
|
|
||||||
Address: AA-00-BB-11-CC-22
|
|
||||||
Major Type: Miscellaneous
|
|
||||||
Minor Type: Unknown
|
|
||||||
Services:
|
|
||||||
Paired: No
|
|
||||||
Configured: Yes
|
|
||||||
Connected: No
|
|
||||||
Class of Device: 0x00 0x00 0x0000
|
|
||||||
Services:
|
|
||||||
Bluetooth File Transfer:
|
|
||||||
Folder other devices can browse: ~/Public
|
|
||||||
When receiving items: Accept all without warning
|
|
||||||
State: Disabled
|
|
||||||
Bluetooth File Exchange:
|
|
||||||
Folder for accepted items: ~/Downloads
|
|
||||||
When other items are accepted: Save to location
|
|
||||||
When receiving items: Accept all without warning
|
|
||||||
State: Disabled
|
|
||||||
Bluetooth Internet Sharing:
|
|
||||||
State: Disabled
|
|
||||||
Incoming Serial Ports:
|
|
||||||
Bluetooth-Incoming-Port:
|
|
||||||
RFCOMM Channel: 3
|
|
||||||
Requires Authentication: No"""
|
|
||||||
).format(value).encode('utf-8')
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
# only to ignore all args, kwargs
|
|
||||||
pass
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def communicate():
|
|
||||||
'''
|
|
||||||
Mock Popen.communicate, so that 'system_profiler'
|
|
||||||
isn't used.
|
|
||||||
'''
|
|
||||||
return (MockedSystemProfiler.output, )
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def whereis_exe(binary):
|
|
||||||
'''
|
|
||||||
Mock whereis_exe, so that it looks like
|
|
||||||
macOS system_profiler binary is present on the system.
|
|
||||||
'''
|
|
||||||
return binary == 'system_profiler'
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_info():
|
|
||||||
'''
|
|
||||||
Return current bluetooth status from mocked output.
|
|
||||||
'''
|
|
||||||
return MockedSystemProfiler.value
|
|
||||||
|
|
||||||
|
|
||||||
class TestBluetooth(unittest.TestCase):
|
|
||||||
'''
|
|
||||||
TestCase for plyer.bluetooth.
|
|
||||||
'''
|
|
||||||
|
|
||||||
def test_bluetooth_macosx(self):
|
|
||||||
'''
|
|
||||||
Test macOS system_profiler for plyer.bluetooth.
|
|
||||||
'''
|
|
||||||
bluetooth = platform_import(
|
|
||||||
platform='macosx',
|
|
||||||
module_name='bluetooth',
|
|
||||||
whereis_exe=MockedSystemProfiler.whereis_exe
|
|
||||||
)
|
|
||||||
|
|
||||||
bluetooth.Popen = MockedSystemProfiler
|
|
||||||
self.assertIn('OSXBluetooth', dir(bluetooth))
|
|
||||||
bluetooth = bluetooth.instance()
|
|
||||||
self.assertIn('OSXBluetooth', str(bluetooth))
|
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
bluetooth.info, MockedSystemProfiler.get_info()
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_bluetooth_macosx_instance(self):
|
|
||||||
'''
|
|
||||||
Test macOS instance for plyer.bluetooth.
|
|
||||||
'''
|
|
||||||
|
|
||||||
def no_exe(*args, **kwargs):
|
|
||||||
return
|
|
||||||
|
|
||||||
bluetooth = platform_import(
|
|
||||||
platform='macosx',
|
|
||||||
module_name='bluetooth',
|
|
||||||
whereis_exe=no_exe
|
|
||||||
)
|
|
||||||
|
|
||||||
bluetooth = bluetooth.instance()
|
|
||||||
self.assertNotIn('OSXBluetooth', str(bluetooth))
|
|
||||||
self.assertIn('Bluetooth', str(bluetooth))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
unittest.main()
|
|
@ -1,262 +0,0 @@
|
|||||||
'''
|
|
||||||
TestCPU
|
|
||||||
=======
|
|
||||||
|
|
||||||
Tested platforms:
|
|
||||||
|
|
||||||
* Windows
|
|
||||||
* Linux - nproc
|
|
||||||
'''
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
from os import environ
|
|
||||||
from os.path import join
|
|
||||||
from mock import patch, Mock
|
|
||||||
from textwrap import dedent
|
|
||||||
|
|
||||||
from plyer.tests.common import PlatformTest, platform_import, splitpath
|
|
||||||
|
|
||||||
|
|
||||||
class MockedKernelCPU:
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
self.fname = args[0] if args else ''
|
|
||||||
self.cpu_path = join('/sys', 'devices', 'system', 'cpu')
|
|
||||||
self.cores = 16
|
|
||||||
self.indicies = 4
|
|
||||||
|
|
||||||
def __enter__(self, *args):
|
|
||||||
file_value = None
|
|
||||||
cpu_path = self.cpu_path
|
|
||||||
spath = splitpath(self.fname)
|
|
||||||
|
|
||||||
if self.fname == join(cpu_path, 'present'):
|
|
||||||
file_value = Mock()
|
|
||||||
file_value.read.return_value = self.present
|
|
||||||
elif spath[5] == 'cache' and spath[7] == 'level':
|
|
||||||
file_value = Mock()
|
|
||||||
# force bytes, because reading files as bytes
|
|
||||||
file_value.read.return_value = str(
|
|
||||||
self.index_types[spath[4]][spath[6]][spath[7]]
|
|
||||||
).encode('utf-8')
|
|
||||||
return file_value
|
|
||||||
|
|
||||||
def __exit__(self, *args):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@property
|
|
||||||
def present(self):
|
|
||||||
rng = list(range(self.cores))
|
|
||||||
start = str(rng[0])
|
|
||||||
end = str(rng[-1])
|
|
||||||
if start == end: # cores == 1 --> b'0'
|
|
||||||
value = str(start)
|
|
||||||
else: # cores > 1 --> b'0-n'
|
|
||||||
value = str('-'.join([start, end]))
|
|
||||||
return value.encode('utf-8')
|
|
||||||
|
|
||||||
@property
|
|
||||||
def listdir(self):
|
|
||||||
return ['index{}'.format(i) for i in range(self.indicies)]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def index_types(self):
|
|
||||||
# assign L1 to index0-1, L2 to 2, L3 to 3
|
|
||||||
types = {0: 1, 1: 1, 2: 2, 3: 3}
|
|
||||||
|
|
||||||
return {
|
|
||||||
'cpu{}'.format(c): {
|
|
||||||
'index{}'.format(i): {
|
|
||||||
'level': types[i]
|
|
||||||
}
|
|
||||||
for i in range(self.indicies)
|
|
||||||
}
|
|
||||||
for c in range(self.cores)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class MockedNProc:
|
|
||||||
'''
|
|
||||||
Mocked object used instead of 'nproc' binary in the Linux specific API
|
|
||||||
plyer.platforms.linux.cpu. The same output structure is tested for
|
|
||||||
the range of <min_version, max_version>.
|
|
||||||
|
|
||||||
.. note:: Extend the object with another data sample if it does not match.
|
|
||||||
'''
|
|
||||||
|
|
||||||
min_version = '8.21'
|
|
||||||
max_version = '8.21'
|
|
||||||
logical_cores = 99
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
# only to ignore all args, kwargs
|
|
||||||
pass
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def communicate():
|
|
||||||
'''
|
|
||||||
Mock Popen.communicate, so that 'nproc' isn't used.
|
|
||||||
'''
|
|
||||||
return (str(MockedNProc.logical_cores).encode('utf-8'), )
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def whereis_exe(binary):
|
|
||||||
'''
|
|
||||||
Mock whereis_exe, so that it looks like
|
|
||||||
Linux NProc binary is present on the system.
|
|
||||||
'''
|
|
||||||
return binary == 'nproc'
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def logical():
|
|
||||||
'''
|
|
||||||
Return percentage from mocked data.
|
|
||||||
'''
|
|
||||||
return int(MockedNProc.logical_cores)
|
|
||||||
|
|
||||||
|
|
||||||
class MockedProcinfo:
|
|
||||||
# docs:
|
|
||||||
# https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
|
|
||||||
# /tree/arch/x86/kernel/cpu/proc.c
|
|
||||||
sockets = 1 # physical id
|
|
||||||
physical = 2 # core id
|
|
||||||
threads_per_core = 2 # Intel specs document for i7-4500U
|
|
||||||
logical = physical * threads_per_core # processor
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
self.fname = args[0] if args else ''
|
|
||||||
|
|
||||||
self.output = []
|
|
||||||
__step = 0 # 0,1,0,1 -> 0,0,1,1
|
|
||||||
for soc in range(self.sockets):
|
|
||||||
for log in range(self.logical):
|
|
||||||
if log != 0 and not log % self.physical:
|
|
||||||
__step += 1
|
|
||||||
self.output.append((dedent(
|
|
||||||
'''\
|
|
||||||
processor\t: {logical}
|
|
||||||
vendor_id\t: GenuineIntel
|
|
||||||
cpu family\t: 6
|
|
||||||
model\t\t: 69
|
|
||||||
model name\t: Intel(R) Core(TM) i7-4500U CPU @ 1.80GHz
|
|
||||||
stepping\t: 1
|
|
||||||
microcode\t: 0x17
|
|
||||||
cpu MHz\t\t: 774.000
|
|
||||||
cache size\t: 4096 KB
|
|
||||||
physical id\t: {socket}
|
|
||||||
siblings\t: 4
|
|
||||||
core id\t\t: {physical}
|
|
||||||
cpu cores\t: {threads_per_core}
|
|
||||||
apicid\t\t: {logical}
|
|
||||||
initial apicid\t: 0
|
|
||||||
fpu\t\t: yes
|
|
||||||
fpu_exception\t: yes
|
|
||||||
cpuid level\t: 13
|
|
||||||
wp\t\t: yes
|
|
||||||
flags\t\t: fpu vme de pse tsc msr pae mce cx8 ...
|
|
||||||
bogomips\t: 3591.40
|
|
||||||
clflush size\t: 64
|
|
||||||
cache_alignment\t: 64
|
|
||||||
address sizes\t: 39 bits physical, 48 bits virtual
|
|
||||||
power management:
|
|
||||||
\n'''
|
|
||||||
)).format(**{
|
|
||||||
'socket': soc,
|
|
||||||
'physical': __step,
|
|
||||||
'logical': log,
|
|
||||||
'threads_per_core': self.threads_per_core
|
|
||||||
}))
|
|
||||||
self.output = ''.join(self.output).encode('utf-8')
|
|
||||||
|
|
||||||
def __enter__(self, *args):
|
|
||||||
file_value = None
|
|
||||||
|
|
||||||
if self.fname == '/proc/cpuinfo':
|
|
||||||
file_value = Mock()
|
|
||||||
file_value.readlines.return_value = self.output.split(
|
|
||||||
'\n'.encode('utf-8')
|
|
||||||
)
|
|
||||||
return file_value
|
|
||||||
|
|
||||||
def __exit__(self, *args):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class TestCPU(unittest.TestCase):
|
|
||||||
'''
|
|
||||||
TestCase for plyer.cpu.
|
|
||||||
'''
|
|
||||||
|
|
||||||
def test_cpu_linux_physical(self):
|
|
||||||
cpu = platform_import(
|
|
||||||
platform='linux',
|
|
||||||
module_name='cpu',
|
|
||||||
whereis_exe=lambda b: b == 'nproc'
|
|
||||||
).instance()
|
|
||||||
|
|
||||||
stub = MockedProcinfo
|
|
||||||
target = 'builtins.open'
|
|
||||||
|
|
||||||
with patch(target=target, new=stub):
|
|
||||||
sb = stub()
|
|
||||||
self.assertEqual(
|
|
||||||
cpu.physical, sb.physical
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_cpu_linux_logical(self):
|
|
||||||
'''
|
|
||||||
Test mocked Linux NProc for plyer.cpu.
|
|
||||||
'''
|
|
||||||
cpu = platform_import(
|
|
||||||
platform='linux',
|
|
||||||
module_name='cpu',
|
|
||||||
whereis_exe=MockedNProc.whereis_exe
|
|
||||||
)
|
|
||||||
cpu.Popen = MockedNProc
|
|
||||||
cpu = cpu.instance()
|
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
cpu.logical, MockedNProc.logical()
|
|
||||||
)
|
|
||||||
|
|
||||||
@PlatformTest('linux')
|
|
||||||
def test_cpu_linux_cache(self):
|
|
||||||
cpu = platform_import(
|
|
||||||
platform='linux',
|
|
||||||
module_name='cpu',
|
|
||||||
whereis_exe=lambda b: b == 'nproc'
|
|
||||||
).instance()
|
|
||||||
|
|
||||||
stub = MockedKernelCPU
|
|
||||||
target = 'builtins.open'
|
|
||||||
sub_target = 'plyer.platforms.linux.cpu.listdir'
|
|
||||||
|
|
||||||
with patch(target=target, new=stub):
|
|
||||||
with patch(target=sub_target, return_value=stub().listdir):
|
|
||||||
sb = stub()
|
|
||||||
self.assertEqual(
|
|
||||||
cpu.cache, {
|
|
||||||
'L1': sb.cores * 2,
|
|
||||||
'L2': sb.cores,
|
|
||||||
'L3': sb.cores
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
@PlatformTest('win')
|
|
||||||
def test_cpu_win_logical(self):
|
|
||||||
cpu = platform_import(
|
|
||||||
platform='win',
|
|
||||||
module_name='cpu'
|
|
||||||
)
|
|
||||||
|
|
||||||
cpu = cpu.instance()
|
|
||||||
self.assertEqual(
|
|
||||||
cpu.logical,
|
|
||||||
# https://docs.microsoft.com/en-us/previous-versions/
|
|
||||||
# windows/it-pro/windows-xp/bb490954(v=technet.10)
|
|
||||||
int(environ['NUMBER_OF_PROCESSORS'])
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
unittest.main()
|
|
@ -1,77 +0,0 @@
|
|||||||
'''
|
|
||||||
TestDeviceName
|
|
||||||
============
|
|
||||||
|
|
||||||
Tested platforms:
|
|
||||||
|
|
||||||
* Windows
|
|
||||||
'''
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
from mock import patch
|
|
||||||
from plyer.tests.common import PlatformTest, platform_import
|
|
||||||
import socket
|
|
||||||
|
|
||||||
|
|
||||||
class TestDeviceName(unittest.TestCase):
|
|
||||||
'''
|
|
||||||
TestCase for plyer.devicename.
|
|
||||||
'''
|
|
||||||
|
|
||||||
@PlatformTest('win')
|
|
||||||
def test_devicename_win(self):
|
|
||||||
'''
|
|
||||||
Test Windows API for plyer.devicename.
|
|
||||||
'''
|
|
||||||
devicename = platform_import(platform='win',
|
|
||||||
module_name='devicename'
|
|
||||||
)
|
|
||||||
devicename_instance = devicename.instance()
|
|
||||||
|
|
||||||
with patch.object(socket,
|
|
||||||
'gethostname',
|
|
||||||
return_value='mocked_windows_hostname'
|
|
||||||
) as _:
|
|
||||||
|
|
||||||
evaluated_device_name = devicename_instance.device_name
|
|
||||||
self.assertEqual(evaluated_device_name, 'mocked_windows_hostname')
|
|
||||||
|
|
||||||
@PlatformTest('linux')
|
|
||||||
def test_devicename_linux(self):
|
|
||||||
'''
|
|
||||||
Test Linux API for plyer.devicename.
|
|
||||||
'''
|
|
||||||
devicename = platform_import(platform='linux',
|
|
||||||
module_name='devicename'
|
|
||||||
)
|
|
||||||
devicename_instance = devicename.instance()
|
|
||||||
|
|
||||||
with patch.object(socket,
|
|
||||||
'gethostname',
|
|
||||||
return_value='mocked_linux_hostname'
|
|
||||||
) as _:
|
|
||||||
|
|
||||||
evaluated_device_name = devicename_instance.device_name
|
|
||||||
self.assertEqual(evaluated_device_name, 'mocked_linux_hostname')
|
|
||||||
|
|
||||||
@PlatformTest('macosx')
|
|
||||||
def test_devicename_macosx(self):
|
|
||||||
'''
|
|
||||||
Test MacOSX API for plyer.devicename.
|
|
||||||
'''
|
|
||||||
devicename = platform_import(platform='macosx',
|
|
||||||
module_name='devicename'
|
|
||||||
)
|
|
||||||
devicename_instance = devicename.instance()
|
|
||||||
|
|
||||||
with patch.object(socket,
|
|
||||||
'gethostname',
|
|
||||||
return_value='mocked_macosx_hostname'
|
|
||||||
) as _:
|
|
||||||
|
|
||||||
evaluated_device_name = devicename_instance.device_name
|
|
||||||
self.assertEqual(evaluated_device_name, 'mocked_macosx_hostname')
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
unittest.main()
|
|
@ -1,48 +0,0 @@
|
|||||||
'''
|
|
||||||
TestEmail
|
|
||||||
=========
|
|
||||||
|
|
||||||
Tested platforms:
|
|
||||||
|
|
||||||
* Windows
|
|
||||||
'''
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
from mock import Mock, patch
|
|
||||||
from plyer.tests.common import PlatformTest, platform_import
|
|
||||||
|
|
||||||
|
|
||||||
class TestEmail(unittest.TestCase):
|
|
||||||
'''
|
|
||||||
TestCase for plyer.email.
|
|
||||||
'''
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
@PlatformTest('win')
|
|
||||||
def test_email_win():
|
|
||||||
'''
|
|
||||||
Test starting Windows email client for plyer.email.
|
|
||||||
'''
|
|
||||||
email = platform_import(
|
|
||||||
platform='win',
|
|
||||||
module_name='email'
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
test_mailto = 'mailto:recipient?subject=subject&body=text'
|
|
||||||
with patch(target='os.startfile', new=Mock()) as startfile:
|
|
||||||
email.instance().send(
|
|
||||||
recipient='recipient',
|
|
||||||
subject='subject',
|
|
||||||
text='text'
|
|
||||||
)
|
|
||||||
startfile.assert_called_once_with(test_mailto)
|
|
||||||
except WindowsError:
|
|
||||||
# if WE is raised, email client isn't found,
|
|
||||||
# but the platform code works correctly
|
|
||||||
print('Mail client not found!')
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
unittest.main()
|
|
@ -1,183 +0,0 @@
|
|||||||
'''
|
|
||||||
TestFacade
|
|
||||||
==========
|
|
||||||
|
|
||||||
Tested platforms:
|
|
||||||
|
|
||||||
* Android
|
|
||||||
* iOS
|
|
||||||
* Windows
|
|
||||||
* MacOS
|
|
||||||
* Linux
|
|
||||||
'''
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
import sys
|
|
||||||
from types import MethodType
|
|
||||||
|
|
||||||
from mock import Mock, patch
|
|
||||||
|
|
||||||
import plyer
|
|
||||||
|
|
||||||
|
|
||||||
def mock_platform_module(mod, platform, cls):
|
|
||||||
'''
|
|
||||||
Create a stub module for a specific platform. This module contains:
|
|
||||||
|
|
||||||
* class inheriting from facade implementing the desired feature
|
|
||||||
* 'instance' function returning an instance of the implementing class
|
|
||||||
'''
|
|
||||||
|
|
||||||
# assemble an instance returned from the instance() function
|
|
||||||
# which is created from a dynamically created class
|
|
||||||
# <class '<mod>.<platform><cls>'> e.g.:
|
|
||||||
# <class 'plyer.platforms.win.dummy . WinDummy'>
|
|
||||||
stub_inst = Mock(
|
|
||||||
__module__=mod,
|
|
||||||
__class__=type(
|
|
||||||
'{}{}'.format(platform.title(), cls), (object, ), {
|
|
||||||
'__module__': mod
|
|
||||||
}
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
# manual 'return_value' assign to Mock, so that the instance() call
|
|
||||||
# can return stub_inst's own instance instead of creating another
|
|
||||||
# unnecessary Mock object
|
|
||||||
stub_inst.return_value = stub_inst
|
|
||||||
|
|
||||||
# bind custom function returning the class name to stub_inst instance,
|
|
||||||
# so that instance().show() call requires 'self' i.e. instance parameter
|
|
||||||
# for the function to access the instance's class name
|
|
||||||
stub_inst.show = MethodType(lambda slf: slf, stub_inst)
|
|
||||||
|
|
||||||
stub_mod = Mock(instance=stub_inst)
|
|
||||||
return stub_mod
|
|
||||||
|
|
||||||
|
|
||||||
# dummy pyjnius class to silence the import + config
|
|
||||||
class DummyJnius:
|
|
||||||
'''
|
|
||||||
Mocked PyJNIus module.
|
|
||||||
'''
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
class JavaClass:
|
|
||||||
'''
|
|
||||||
Mocked PyJNIus JavaClass object.
|
|
||||||
'''
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.ANDROID_VERSION = None
|
|
||||||
self.SDK_INT = 1
|
|
||||||
self.mActivity = None
|
|
||||||
|
|
||||||
self.autoclass = lambda *a, **kw: JavaClass()
|
|
||||||
|
|
||||||
|
|
||||||
class TestFacade(unittest.TestCase):
|
|
||||||
'''
|
|
||||||
TestCase for plyer.utils.Proxy and plyer.facades.
|
|
||||||
'''
|
|
||||||
|
|
||||||
def test_facade_existing_platforms(self):
|
|
||||||
'''
|
|
||||||
Test for returning an object for Android API implementation
|
|
||||||
from Proxy object using a dynamically generated dummy objects.
|
|
||||||
'''
|
|
||||||
_original = plyer.utils.platform
|
|
||||||
|
|
||||||
for plat in {'android', 'ios', 'win', 'macosx', 'linux'}:
|
|
||||||
plyer.utils.platform = plat
|
|
||||||
|
|
||||||
if plat == 'android':
|
|
||||||
# android platform automatically imports jnius
|
|
||||||
sys.modules['jnius'] = DummyJnius()
|
|
||||||
|
|
||||||
# create stub module with instance func and class
|
|
||||||
stub_mod = mock_platform_module(
|
|
||||||
mod='plyer.platforms.{}.dummy'.format(plat),
|
|
||||||
platform=plyer.utils.platform,
|
|
||||||
cls='Dummy'
|
|
||||||
)
|
|
||||||
|
|
||||||
proxy_cls = plyer.utils.Proxy
|
|
||||||
target = 'builtins.__import__'
|
|
||||||
|
|
||||||
with patch(target=target, return_value=stub_mod):
|
|
||||||
dummy = proxy_cls('dummy', stub_mod)
|
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
str(dummy.__class__).split("'")[1],
|
|
||||||
'plyer.platforms.{}.dummy.{}Dummy'.format(
|
|
||||||
plat, plat.title()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
self.assertEqual(
|
|
||||||
str(dummy.show().__class__).split("'")[1],
|
|
||||||
'plyer.platforms.{}.dummy.{}Dummy'.format(
|
|
||||||
plat, plat.title()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
plyer.utils.platform = _original
|
|
||||||
|
|
||||||
def test_facade_unknown(self):
|
|
||||||
'''
|
|
||||||
Test fallback of Proxy to facade if there
|
|
||||||
is no such requested platform.
|
|
||||||
'''
|
|
||||||
|
|
||||||
_original = plyer.utils.platform
|
|
||||||
plyer.utils.platform = 'unknown'
|
|
||||||
|
|
||||||
# no 'unknown' platform (folder), fallback to facade
|
|
||||||
class MockedProxy(plyer.utils.Proxy):
|
|
||||||
'''
|
|
||||||
Partially mocked Proxy class, so that we pull the error
|
|
||||||
from traceback.print_exc to the test and check the calls.
|
|
||||||
'''
|
|
||||||
|
|
||||||
# _ensure_obj is called only once, to either
|
|
||||||
# get the platform object or fall back to facade
|
|
||||||
# therefore the three self.asserts below will return
|
|
||||||
# different values
|
|
||||||
expected_asserts = [True, False, False]
|
|
||||||
|
|
||||||
def _ensure_obj(inst):
|
|
||||||
# called once, prints to stderr
|
|
||||||
|
|
||||||
# mock stderr because traceback.print_exc uses it
|
|
||||||
# https://github.com/python/cpython/blob/
|
|
||||||
# 16dfca4d829e45f36e71bf43f83226659ce49315/Lib/traceback.py#L99
|
|
||||||
sys.stderr = Mock()
|
|
||||||
|
|
||||||
# call the original function to trigger
|
|
||||||
# ImportError warnings in stderr
|
|
||||||
super(MockedProxy, inst)._ensure_obj()
|
|
||||||
|
|
||||||
# Traceback (most recent call last):
|
|
||||||
# File "/app/plyer/utils.py", line 88, in _ensure_obj
|
|
||||||
# mod = __import__(module, fromlist='.')
|
|
||||||
# ImportError: No module named unknown.dummy
|
|
||||||
|
|
||||||
# must not use self.assertX
|
|
||||||
# (has to be checked on the go!)
|
|
||||||
expected_bool = MockedProxy.expected_asserts.pop(0)
|
|
||||||
call_count = sys.stderr.write.call_count
|
|
||||||
assert (call_count == 6) == expected_bool, call_count
|
|
||||||
|
|
||||||
# return stderr to the original state
|
|
||||||
sys.stderr = sys.__stderr__
|
|
||||||
|
|
||||||
proxy_cls = MockedProxy
|
|
||||||
facade = Mock()
|
|
||||||
dummy = proxy_cls('dummy', facade)
|
|
||||||
|
|
||||||
self.assertEqual(dummy._mock_new_parent, facade)
|
|
||||||
plyer.utils.platform = _original
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
unittest.main()
|
|
@ -1,209 +0,0 @@
|
|||||||
'''
|
|
||||||
TestNotification
|
|
||||||
================
|
|
||||||
|
|
||||||
Tested platforms:
|
|
||||||
|
|
||||||
* Windows
|
|
||||||
* Linux - notify-send, dbus
|
|
||||||
'''
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from time import sleep
|
|
||||||
from os.path import dirname, abspath, join
|
|
||||||
|
|
||||||
from mock import Mock, patch
|
|
||||||
from plyer.tests.common import PlatformTest, platform_import
|
|
||||||
|
|
||||||
|
|
||||||
class MockedNotifySend:
|
|
||||||
'''
|
|
||||||
Mocked object used instead of the console-like calling
|
|
||||||
of notify-send binary with parameters.
|
|
||||||
'''
|
|
||||||
@staticmethod
|
|
||||||
def whereis_exe(binary):
|
|
||||||
'''
|
|
||||||
Mock whereis_exe, so that it looks like
|
|
||||||
Linux notify-send binary is present on the system.
|
|
||||||
'''
|
|
||||||
return binary == 'notify-send'
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def call(args):
|
|
||||||
'''
|
|
||||||
Mocked subprocess.call to check console parameters.
|
|
||||||
'''
|
|
||||||
assert len(args) >= 3
|
|
||||||
assert TestNotification.data['title'] in args
|
|
||||||
assert TestNotification.data['message'] in args
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def warn(msg):
|
|
||||||
'''
|
|
||||||
Mocked warnings.warn, so that we check the custom ImportError message.
|
|
||||||
'''
|
|
||||||
assert 'dbus package is not installed' in msg
|
|
||||||
|
|
||||||
|
|
||||||
class TestNotification(unittest.TestCase):
|
|
||||||
'''
|
|
||||||
TestCase for plyer.notification.
|
|
||||||
'''
|
|
||||||
|
|
||||||
data = {
|
|
||||||
'title': 'title',
|
|
||||||
'message': 'My Message\nis multiline',
|
|
||||||
'app_name': 'Plyer Test',
|
|
||||||
'app_icon': join(
|
|
||||||
dirname(abspath(__file__)),
|
|
||||||
'images', 'kivy32.ico'
|
|
||||||
),
|
|
||||||
'timeout': 0.7
|
|
||||||
}
|
|
||||||
|
|
||||||
def show_notification(self, instance):
|
|
||||||
'''
|
|
||||||
Call notify() from platform specific instance with sample data.
|
|
||||||
'''
|
|
||||||
instance.notify(**self.data)
|
|
||||||
|
|
||||||
@PlatformTest('win')
|
|
||||||
def test_notification_windows(self):
|
|
||||||
'''
|
|
||||||
Test Windows API for plyer.notification.
|
|
||||||
'''
|
|
||||||
import ctypes
|
|
||||||
from ctypes import (
|
|
||||||
WINFUNCTYPE, POINTER,
|
|
||||||
create_unicode_buffer,
|
|
||||||
c_bool, c_int
|
|
||||||
)
|
|
||||||
notif = platform_import(
|
|
||||||
platform='win',
|
|
||||||
module_name='notification'
|
|
||||||
).instance()
|
|
||||||
enum_windows = ctypes.windll.user32.EnumWindows
|
|
||||||
get_class_name = ctypes.windll.user32.GetClassNameW
|
|
||||||
|
|
||||||
# loop over windows and get refs to
|
|
||||||
# the opened plyer notifications
|
|
||||||
clsnames = []
|
|
||||||
|
|
||||||
def fetch_class(hwnd, *args):
|
|
||||||
'''
|
|
||||||
EnumWindowsProc callback for EnumWindows.
|
|
||||||
'''
|
|
||||||
buff = create_unicode_buffer(50)
|
|
||||||
get_class_name(hwnd, buff, 50)
|
|
||||||
|
|
||||||
if 'Plyer' in buff.value:
|
|
||||||
clsnames.append(buff.value)
|
|
||||||
|
|
||||||
# ensure it's not an empty facade
|
|
||||||
self.assertIn('WindowsNotification', str(notif))
|
|
||||||
|
|
||||||
# create enum function for EnumWindows
|
|
||||||
enum_windows_proc = WINFUNCTYPE(
|
|
||||||
# returns
|
|
||||||
c_bool,
|
|
||||||
|
|
||||||
# input params: hwnd, lParam
|
|
||||||
POINTER(c_int), POINTER(c_int)
|
|
||||||
)
|
|
||||||
|
|
||||||
for i in range(3):
|
|
||||||
self.show_notification(notif)
|
|
||||||
|
|
||||||
# the balloon needs some time to became visible in WinAPI
|
|
||||||
sleep(0.2)
|
|
||||||
|
|
||||||
# fetch window class names
|
|
||||||
enum_windows(
|
|
||||||
# enum & params
|
|
||||||
enum_windows_proc(fetch_class), None
|
|
||||||
)
|
|
||||||
|
|
||||||
# 3 active balloons at the same time,
|
|
||||||
# class_name is incremented - see WindowsBalloonTip
|
|
||||||
self.assertEqual(len(clsnames), i + 1)
|
|
||||||
self.assertIn('PlyerTaskbar' + str(i), clsnames)
|
|
||||||
clsnames = []
|
|
||||||
|
|
||||||
@PlatformTest('linux')
|
|
||||||
def test_notification_dbus(self):
|
|
||||||
'''
|
|
||||||
Test mocked Linux DBus for plyer.notification.
|
|
||||||
'''
|
|
||||||
notif = platform_import(
|
|
||||||
platform='linux',
|
|
||||||
module_name='notification'
|
|
||||||
)
|
|
||||||
self.assertIn('NotifyDbus', dir(notif))
|
|
||||||
|
|
||||||
# (3) mocked Interface called from dbus
|
|
||||||
interface = Mock()
|
|
||||||
interface.side_effect = (interface, )
|
|
||||||
|
|
||||||
# (2) mocked SessionBus called from dbus
|
|
||||||
session_bus = Mock()
|
|
||||||
session_bus.side_effect = (session_bus, )
|
|
||||||
|
|
||||||
# (1) mocked dbus for import
|
|
||||||
dbus = Mock(SessionBus=session_bus, Interface=interface)
|
|
||||||
|
|
||||||
# inject the mocked module
|
|
||||||
self.assertNotIn('dbus', sys.modules)
|
|
||||||
sys.modules['dbus'] = dbus
|
|
||||||
|
|
||||||
try:
|
|
||||||
notif = notif.instance()
|
|
||||||
self.assertIn('NotifyDbus', str(notif))
|
|
||||||
|
|
||||||
# call notify()
|
|
||||||
self.show_notification(notif)
|
|
||||||
|
|
||||||
# check whether Mocks were called
|
|
||||||
dbus.SessionBus.assert_called_once()
|
|
||||||
|
|
||||||
session_bus.get_object.assert_called_once_with(
|
|
||||||
'org.freedesktop.Notifications',
|
|
||||||
'/org/freedesktop/Notifications'
|
|
||||||
)
|
|
||||||
|
|
||||||
interface.Notify.assert_called_once_with(
|
|
||||||
TestNotification.data['app_name'],
|
|
||||||
0,
|
|
||||||
TestNotification.data['app_icon'],
|
|
||||||
TestNotification.data['title'],
|
|
||||||
TestNotification.data['message'],
|
|
||||||
[], {},
|
|
||||||
TestNotification.data['timeout'] * 1000
|
|
||||||
)
|
|
||||||
finally:
|
|
||||||
del sys.modules['dbus']
|
|
||||||
self.assertNotIn('dbus', sys.modules)
|
|
||||||
|
|
||||||
@PlatformTest('linux')
|
|
||||||
def test_notification_notifysend(self):
|
|
||||||
'''
|
|
||||||
Test mocked Linux notify-send for plyer.notification.
|
|
||||||
'''
|
|
||||||
notif = platform_import(
|
|
||||||
platform='linux',
|
|
||||||
module_name='notification',
|
|
||||||
whereis_exe=MockedNotifySend.whereis_exe
|
|
||||||
)
|
|
||||||
self.assertIn('NotifySendNotification', dir(notif))
|
|
||||||
with patch(target='warnings.warn', new=MockedNotifySend.warn):
|
|
||||||
notif = notif.instance()
|
|
||||||
self.assertIn('NotifySendNotification', str(notif))
|
|
||||||
|
|
||||||
with patch(target='subprocess.call', new=MockedNotifySend.call):
|
|
||||||
self.assertIsNone(self.show_notification(notif))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
unittest.main()
|
|
@ -1,137 +0,0 @@
|
|||||||
'''
|
|
||||||
TestScreenshot
|
|
||||||
==============
|
|
||||||
|
|
||||||
Tested platforms:
|
|
||||||
|
|
||||||
* MacOS
|
|
||||||
* Linux
|
|
||||||
'''
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
from os import mkdir, remove
|
|
||||||
from os.path import join, expanduser, exists
|
|
||||||
|
|
||||||
from mock import patch
|
|
||||||
from plyer.tests.common import PlatformTest, platform_import
|
|
||||||
|
|
||||||
|
|
||||||
class MockedScreenCapture:
|
|
||||||
'''
|
|
||||||
Mocked object used instead of the console-like calling
|
|
||||||
of screencapture binary with parameters.
|
|
||||||
'''
|
|
||||||
@staticmethod
|
|
||||||
def whereis_exe(binary):
|
|
||||||
'''
|
|
||||||
Mock whereis_exe, so that it looks like
|
|
||||||
MacOS screencapture binary is present on the system.
|
|
||||||
'''
|
|
||||||
return binary == 'screencapture'
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def call(args):
|
|
||||||
'''
|
|
||||||
Mocked subprocess.call to check console parameters.
|
|
||||||
'''
|
|
||||||
assert len(args) == 2, len(args)
|
|
||||||
assert args[0] == 'screencapture', args
|
|
||||||
assert args[1] == join(
|
|
||||||
expanduser('~'), 'Pictures', 'screenshot.png'
|
|
||||||
), args
|
|
||||||
with open(args[1], 'w') as scr:
|
|
||||||
scr.write('')
|
|
||||||
|
|
||||||
|
|
||||||
class MockedXWD:
|
|
||||||
'''
|
|
||||||
Mocked object used instead of the console-like calling
|
|
||||||
of X11 xwd binary with parameters.
|
|
||||||
'''
|
|
||||||
@staticmethod
|
|
||||||
def whereis_exe(binary):
|
|
||||||
'''
|
|
||||||
Mock whereis_exe, so that it looks like
|
|
||||||
X11 xwd binary is present on the system.
|
|
||||||
'''
|
|
||||||
return binary == 'xwd'
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def call(args, stdout):
|
|
||||||
'''
|
|
||||||
Mocked subprocess.call to check console parameters.
|
|
||||||
'''
|
|
||||||
assert len(args) == 3, args
|
|
||||||
assert args[0] == 'xwd', args
|
|
||||||
assert args[1:] == ['-silent', '-root'], args
|
|
||||||
assert stdout.name == join(
|
|
||||||
expanduser('~'), 'Pictures', 'screenshot.xwd'
|
|
||||||
), stdout.name
|
|
||||||
with open(stdout.name, 'w') as scr:
|
|
||||||
scr.write('')
|
|
||||||
|
|
||||||
|
|
||||||
class TestScreenshot(unittest.TestCase):
|
|
||||||
'''
|
|
||||||
TestCase for plyer.screenshot.
|
|
||||||
'''
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
path = join(expanduser('~'), 'Pictures')
|
|
||||||
if not exists(path):
|
|
||||||
mkdir(path)
|
|
||||||
|
|
||||||
@PlatformTest('macosx')
|
|
||||||
def test_screenshot_screencapture(self):
|
|
||||||
'''
|
|
||||||
Test mocked MacOS screencapture for plyer.screenshot.
|
|
||||||
'''
|
|
||||||
scr = platform_import(
|
|
||||||
platform='macosx',
|
|
||||||
module_name='screenshot',
|
|
||||||
whereis_exe=MockedScreenCapture.whereis_exe
|
|
||||||
)
|
|
||||||
|
|
||||||
# such class exists in screenshot module
|
|
||||||
self.assertIn('OSXScreenshot', dir(scr))
|
|
||||||
|
|
||||||
# the required instance is created
|
|
||||||
scr = scr.instance()
|
|
||||||
self.assertIn('OSXScreenshot', str(scr))
|
|
||||||
|
|
||||||
# move capture from context manager to run without mock
|
|
||||||
with patch(target='subprocess.call', new=MockedScreenCapture.call):
|
|
||||||
self.assertIsNone(scr.capture())
|
|
||||||
|
|
||||||
self.assertTrue(exists(scr.file_path))
|
|
||||||
remove(scr.file_path)
|
|
||||||
|
|
||||||
@PlatformTest('linux')
|
|
||||||
def test_screenshot_xwd(self):
|
|
||||||
'''
|
|
||||||
Test mocked X11 xwd for plyer.screenshot.
|
|
||||||
'''
|
|
||||||
scr = platform_import(
|
|
||||||
platform='linux',
|
|
||||||
module_name='screenshot',
|
|
||||||
whereis_exe=MockedXWD.whereis_exe
|
|
||||||
)
|
|
||||||
|
|
||||||
# such class exists in screenshot module
|
|
||||||
self.assertIn('LinuxScreenshot', dir(scr))
|
|
||||||
|
|
||||||
# the required instance is created
|
|
||||||
scr = scr.instance()
|
|
||||||
self.assertIn('LinuxScreenshot', str(scr))
|
|
||||||
|
|
||||||
# move capture from context manager to run without mock
|
|
||||||
with patch(target='subprocess.call', new=MockedXWD.call):
|
|
||||||
self.assertIsNone(scr.capture())
|
|
||||||
|
|
||||||
self.assertTrue(exists(scr.file_path))
|
|
||||||
remove(scr.file_path)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
unittest.main()
|
|
@ -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()
|
|
@ -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
Loading…
Reference in New Issue
Block a user