Included local plyer

This commit is contained in:
Mark Qvist 2022-09-16 18:07:57 +02:00
parent 25f0d52260
commit f23855fb68
166 changed files with 15862 additions and 0 deletions

View file

View file

@ -0,0 +1,18 @@
from os import environ
from jnius import autoclass
ANDROID_VERSION = autoclass('android.os.Build$VERSION')
SDK_INT = ANDROID_VERSION.SDK_INT
try:
from android import config
ns = config.JAVA_NAMESPACE
except (ImportError, AttributeError):
ns = 'org.renpy.android'
if 'PYTHON_SERVICE_ARGUMENT' in environ:
PythonService = autoclass(ns + '.PythonService')
activity = PythonService.mService
else:
PythonActivity = autoclass(ns + '.PythonActivity')
activity = PythonActivity.mActivity

View file

@ -0,0 +1,79 @@
'''
Android accelerometer
---------------------
'''
from plyer.facades import Accelerometer
from jnius import PythonJavaClass, java_method, autoclass, cast
from plyer.platforms.android import activity
Context = autoclass('android.content.Context')
Sensor = autoclass('android.hardware.Sensor')
SensorManager = autoclass('android.hardware.SensorManager')
class AccelerometerSensorListener(PythonJavaClass):
__javainterfaces__ = ['android/hardware/SensorEventListener']
def __init__(self):
super().__init__()
self.SensorManager = cast(
'android.hardware.SensorManager',
activity.getSystemService(Context.SENSOR_SERVICE)
)
self.sensor = self.SensorManager.getDefaultSensor(
Sensor.TYPE_ACCELEROMETER
)
self.values = [None, None, None]
def enable(self):
self.SensorManager.registerListener(
self, self.sensor,
SensorManager.SENSOR_DELAY_NORMAL
)
def disable(self):
self.SensorManager.unregisterListener(self, self.sensor)
@java_method('(Landroid/hardware/SensorEvent;)V')
def onSensorChanged(self, event):
self.values = event.values[:3]
@java_method('(Landroid/hardware/Sensor;I)V')
def onAccuracyChanged(self, sensor, accuracy):
# Maybe, do something in future?
pass
class AndroidAccelerometer(Accelerometer):
def __init__(self):
super().__init__()
self.bState = False
def _enable(self):
if (not self.bState):
self.listener = AccelerometerSensorListener()
self.listener.enable()
self.bState = True
def _disable(self):
if (self.bState):
self.bState = False
self.listener.disable()
del self.listener
def _get_acceleration(self):
if (self.bState):
return tuple(self.listener.values)
else:
return (None, None, None)
def __del__(self):
if(self.bState):
self._disable()
super().__del__()
def instance():
return AndroidAccelerometer()

View file

@ -0,0 +1,58 @@
from jnius import autoclass
from plyer.facades.audio import Audio
# Recorder Classes
MediaRecorder = autoclass('android.media.MediaRecorder')
AudioSource = autoclass('android.media.MediaRecorder$AudioSource')
OutputFormat = autoclass('android.media.MediaRecorder$OutputFormat')
AudioEncoder = autoclass('android.media.MediaRecorder$AudioEncoder')
# Player Classes
MediaPlayer = autoclass('android.media.MediaPlayer')
class AndroidAudio(Audio):
'''Audio for android.
For recording audio we use MediaRecorder Android class.
For playing audio we use MediaPlayer Android class.
'''
def __init__(self, file_path=None):
default_path = '/sdcard/testrecorder.3gp'
super().__init__(file_path or default_path)
self._recorder = None
self._player = None
def _start(self):
self._recorder = MediaRecorder()
self._recorder.setAudioSource(AudioSource.DEFAULT)
self._recorder.setOutputFormat(OutputFormat.DEFAULT)
self._recorder.setAudioEncoder(AudioEncoder.DEFAULT)
self._recorder.setOutputFile(self.file_path)
self._recorder.prepare()
self._recorder.start()
def _stop(self):
if self._recorder:
self._recorder.stop()
self._recorder.release()
self._recorder = None
if self._player:
self._player.stop()
self._player.release()
self._player = None
def _play(self):
self._player = MediaPlayer()
self._player.setDataSource(self.file_path)
self._player.prepare()
self._player.start()
def instance():
return AndroidAudio()

View file

@ -0,0 +1,65 @@
from jnius import autoclass
from jnius import cast
from jnius import java_method
from jnius import PythonJavaClass
from plyer.facades import Barometer
from plyer.platforms.android import activity
ActivityInfo = autoclass('android.content.pm.ActivityInfo')
Context = autoclass('android.content.Context')
Sensor = autoclass('android.hardware.Sensor')
SensorManager = autoclass('android.hardware.SensorManager')
class BarometerSensorListener(PythonJavaClass):
__javainterfaces__ = ['android/hardware/SensorEventListener']
def __init__(self):
super().__init__()
service = activity.getSystemService(Context.SENSOR_SERVICE)
self.SensorManager = cast('android.hardware.SensorManager', service)
self.sensor = self.SensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE)
self.value = None
def enable(self):
self.SensorManager.registerListener(
self, self.sensor,
SensorManager.SENSOR_DELAY_NORMAL
)
def disable(self):
self.SensorManager.unregisterListener(self, self.sensor)
@java_method('(Landroid/hardware/SensorEvent;)V')
def onSensorChanged(self, event):
self.value = event.values[0]
@java_method('(Landroid/hardware/Sensor;I)V')
def onAccuracyChanged(self, sensor, accuracy):
pass
class AndroidBarometer(Barometer):
listener = None
def _get_pressure(self):
if self.listener and self.listener.value:
pressure = self.listener.value
return pressure
def _enable(self):
if not self.listener:
self.listener = BarometerSensorListener()
self.listener.enable()
def _disable(self):
if self.listener:
self.listener.disable()
delattr(self, 'listener')
def instance():
return AndroidBarometer()

View file

@ -0,0 +1,47 @@
'''
Module of Android API for plyer.battery.
'''
from jnius import autoclass, cast
from plyer.platforms.android import activity
from plyer.facades import Battery
Intent = autoclass('android.content.Intent')
BatteryManager = autoclass('android.os.BatteryManager')
IntentFilter = autoclass('android.content.IntentFilter')
class AndroidBattery(Battery):
'''
Implementation of Android battery API.
'''
def _get_state(self):
status = {"isCharging": None, "percentage": None}
ifilter = IntentFilter(Intent.ACTION_BATTERY_CHANGED)
battery_status = cast(
'android.content.Intent',
activity.registerReceiver(None, ifilter)
)
query = battery_status.getIntExtra(BatteryManager.EXTRA_STATUS, -1)
is_charging = query == BatteryManager.BATTERY_STATUS_CHARGING
is_full = query == BatteryManager.BATTERY_STATUS_FULL
level = battery_status.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)
scale = battery_status.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
percentage = (level / float(scale)) * 100
status['isCharging'] = is_charging or is_full
status['percentage'] = percentage
return status
def instance():
'''
Instance for facade proxy.
'''
return AndroidBattery()

View file

@ -0,0 +1,32 @@
'''
Module of Android API for plyer.bluetooth.
'''
from jnius import autoclass
from plyer.platforms.android import activity
from plyer.facades import Bluetooth
Global = autoclass('android.provider.Settings$Global')
class AndroidBluetooth(Bluetooth):
'''
Implementation of Android Bluetooth API.
'''
def _get_info(self):
bluetooth_enabled = Global.getString(
activity.getContentResolver(),
Global.BLUETOOTH_ON
)
status = 'off'
if bluetooth_enabled:
status = 'on'
return status
def instance():
'''
Instance for facade proxy.
'''
return AndroidBluetooth()

View file

@ -0,0 +1,34 @@
'''
Android Brightness
------------------
'''
from jnius import autoclass
from plyer.facades import Brightness
from android import mActivity
System = autoclass('android.provider.Settings$System')
class AndroidBrightness(Brightness):
def _current_level(self):
System.putInt(
mActivity.getContentResolver(),
System.SCREEN_BRIGHTNESS_MODE,
System.SCREEN_BRIGHTNESS_MODE_MANUAL)
cr_level = System.getInt(
mActivity.getContentResolver(),
System.SCREEN_BRIGHTNESS)
return (cr_level / 255.) * 100
def _set_level(self, level):
System.putInt(
mActivity.getContentResolver(),
System.SCREEN_BRIGHTNESS,
(level / 100.) * 255)
def instance():
return AndroidBrightness()

View file

@ -0,0 +1,29 @@
'''
Android Call
-----------
'''
from jnius import autoclass
from plyer.facades import Call
from plyer.platforms.android import activity
Intent = autoclass('android.content.Intent')
uri = autoclass('android.net.Uri')
class AndroidCall(Call):
def _makecall(self, **kwargs):
intent = Intent(Intent.ACTION_CALL)
tel = kwargs.get('tel')
intent.setData(uri.parse("tel:{}".format(tel)))
activity.startActivity(intent)
def _dialcall(self, **kwargs):
intent_ = Intent(Intent.ACTION_DIAL)
activity.startActivity(intent_)
def instance():
return AndroidCall()

View file

@ -0,0 +1,59 @@
import android
import android.activity
from os import remove
from jnius import autoclass, cast
from plyer.facades import Camera
from plyer.platforms.android import activity
Intent = autoclass('android.content.Intent')
PythonActivity = autoclass('org.kivy.android.PythonActivity')
MediaStore = autoclass('android.provider.MediaStore')
Uri = autoclass('android.net.Uri')
class AndroidCamera(Camera):
def _take_picture(self, on_complete, filename=None):
assert(on_complete is not None)
self.on_complete = on_complete
self.filename = filename
android.activity.unbind(on_activity_result=self._on_activity_result)
android.activity.bind(on_activity_result=self._on_activity_result)
intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
uri = Uri.parse('file://' + filename)
parcelable = cast('android.os.Parcelable', uri)
intent.putExtra(MediaStore.EXTRA_OUTPUT, parcelable)
activity.startActivityForResult(intent, 0x123)
def _take_video(self, on_complete, filename=None):
assert(on_complete is not None)
self.on_complete = on_complete
self.filename = filename
android.activity.unbind(on_activity_result=self._on_activity_result)
android.activity.bind(on_activity_result=self._on_activity_result)
intent = Intent(MediaStore.ACTION_VIDEO_CAPTURE)
uri = Uri.parse('file://' + filename)
parcelable = cast('android.os.Parcelable', uri)
intent.putExtra(MediaStore.EXTRA_OUTPUT, parcelable)
# 0 = low quality, suitable for MMS messages,
# 1 = high quality
intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1)
activity.startActivityForResult(intent, 0x123)
def _on_activity_result(self, requestCode, resultCode, intent):
if requestCode != 0x123:
return
android.activity.unbind(on_activity_result=self._on_activity_result)
if self.on_complete(self.filename):
self._remove(self.filename)
def _remove(self, fn):
try:
remove(fn)
except OSError:
pass
def instance():
return AndroidCamera()

View file

@ -0,0 +1,119 @@
'''
Android Compass
---------------------
'''
from plyer.facades import Compass
from jnius import PythonJavaClass, java_method, autoclass, cast
from plyer.platforms.android import activity
Context = autoclass('android.content.Context')
Sensor = autoclass('android.hardware.Sensor')
SensorManager = autoclass('android.hardware.SensorManager')
class MFUSensorListener(PythonJavaClass):
__javainterfaces__ = ['android/hardware/SensorEventListener']
def __init__(self):
super().__init__()
service = activity.getSystemService(Context.SENSOR_SERVICE)
self.SensorManager = cast('android.hardware.SensorManager', service)
self.sensor = self.SensorManager.getDefaultSensor(
Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED)
self.values = [None, None, None, None, None, None]
def enable(self):
self.SensorManager.registerListener(
self, self.sensor,
SensorManager.SENSOR_DELAY_NORMAL
)
def disable(self):
self.SensorManager.unregisterListener(self, self.sensor)
@java_method('(Landroid/hardware/SensorEvent;)V')
def onSensorChanged(self, event):
self.values = event.values[:6]
@java_method('(Landroid/hardware/Sensor;I)V')
def onAccuracyChanged(self, sensor, accuracy):
pass
class MagneticFieldSensorListener(PythonJavaClass):
__javainterfaces__ = ['android/hardware/SensorEventListener']
def __init__(self):
super().__init__()
self.SensorManager = cast(
'android.hardware.SensorManager',
activity.getSystemService(Context.SENSOR_SERVICE)
)
self.sensor = self.SensorManager.getDefaultSensor(
Sensor.TYPE_MAGNETIC_FIELD
)
self.values = [None, None, None]
def enable(self):
self.SensorManager.registerListener(
self, self.sensor,
SensorManager.SENSOR_DELAY_NORMAL
)
def disable(self):
self.SensorManager.unregisterListener(self, self.sensor)
@java_method('(Landroid/hardware/SensorEvent;)V')
def onSensorChanged(self, event):
self.values = event.values[:3]
@java_method('(Landroid/hardware/Sensor;I)V')
def onAccuracyChanged(self, sensor, accuracy):
# Maybe, do something in future?
pass
class AndroidCompass(Compass):
def __init__(self):
super().__init__()
self.bState = False
def _enable(self):
if (not self.bState):
self.listenerm = MagneticFieldSensorListener()
self.listenermu = MFUSensorListener()
self.listenerm.enable()
self.listenermu.enable()
self.bState = True
def _disable(self):
if (self.bState):
self.bState = False
self.listenerm.disable()
self.listenermu.disable()
del self.listenerm
del self.listenermu
def _get_orientation(self):
if (self.bState):
return tuple(self.listenerm.values)
else:
return (None, None, None)
def _get_field_uncalib(self):
if (self.bState):
return tuple(self.listenermu.values)
else:
return (None, None, None, None, None, None)
def __del__(self):
if(self.bState):
self._disable()
super().__del__()
def instance():
return AndroidCompass()

View file

@ -0,0 +1,33 @@
'''
Module of Android API for plyer.devicename.
'''
from jnius import autoclass
from plyer.facades import DeviceName
Build = autoclass('android.os.Build')
class AndroidDeviceName(DeviceName):
'''
Implementation of Android devicename API.
'''
def _get_device_name(self):
"""
Method to get the device name aka model in an android environment.
Changed the implementation from 'android.provider.Settings.Global' to
'android.os.Build' because 'android.provider.Settings.Global' was
introduced in API 17 whereas 'android.os.Build' is present since API 1
Thereby making this method more backward compatible.
"""
return Build.MODEL
def instance():
'''
Instance for facade proxy.
'''
return AndroidDeviceName()

View file

@ -0,0 +1,58 @@
'''
Module of Android API for plyer.email.
'''
from jnius import autoclass, cast
from plyer.facades import Email
from plyer.platforms.android import activity
Intent = autoclass('android.content.Intent')
AndroidString = autoclass('java.lang.String')
class AndroidEmail(Email):
'''
Implementation of Android email API.
'''
def _send(self, **kwargs):
intent = Intent(Intent.ACTION_SEND)
intent.setType('text/plain')
recipient = kwargs.get('recipient')
subject = kwargs.get('subject')
text = kwargs.get('text')
create_chooser = kwargs.get('create_chooser')
if recipient:
intent.putExtra(Intent.EXTRA_EMAIL, [recipient])
if subject:
android_subject = cast(
'java.lang.CharSequence',
AndroidString(subject)
)
intent.putExtra(Intent.EXTRA_SUBJECT, android_subject)
if text:
android_text = cast(
'java.lang.CharSequence',
AndroidString(text)
)
intent.putExtra(Intent.EXTRA_TEXT, android_text)
if create_chooser:
chooser_title = cast(
'java.lang.CharSequence',
AndroidString('Send message with:')
)
activity.startActivity(
Intent.createChooser(intent, chooser_title)
)
else:
activity.startActivity(intent)
def instance():
'''
Instance for facade proxy.
'''
return AndroidEmail()

View file

@ -0,0 +1,447 @@
'''
Android file chooser
--------------------
Android runs ``Activity`` asynchronously via pausing our ``PythonActivity``
and starting a new one in the foreground. This means
``AndroidFileChooser._open_file()`` will always return the default value of
``AndroidFileChooser.selection`` i.e. ``None``.
After the ``Activity`` (for us it's the file chooser ``Intent``) is completed,
Android moves it to the background (or destroys or whatever is implemented)
and pushes ``PythonActivity`` to the foreground.
We have a custom listener for ``android.app.Activity.onActivityResult()``
via `android` package from `python-for-android` recipe,
``AndroidFileChooser._on_activity_result()`` which is called independently of
any our action (we may call anything from our application in Python and this
handler will be called nevertheless on each ``android.app.Activity`` result
in the system).
In the handler we check if the ``request_code`` matches the code passed to the
``Context.startActivityForResult()`` i.e. if the result from
``android.app.Activity`` is indeed meant for our ``PythonActivity`` and then we
proceed.
Since the ``android.app.Activity.onActivityResult()`` is the only way for us
to intercept the result and we have a handler bound via ``android`` package,
we need to get the path/file/... selection to the user the same way.
Threading + ``Thread.join()`` or ``time.sleep()`` or any other kind of waiting
for the result is not an option because:
1) ``android.app.Activity.onActivityResult()`` might remain unexecuted if
the launched file chooser activity does not return the result (``Activity``
dies/freezes/etc).
2) Thread will be still waiting for the result e.g. an update of a value or
to actually finish, however the result from the call of
``AndroidFileChooser._open_file()`` will be returned nevertheless and anything
using that result will use an incorrect one i.e. the default value of
``AndroidFilechooser.selection`` (``None``).
.. versionadded:: 1.4.0
'''
from os.path import join, basename
from random import randint
from android import activity, mActivity
from jnius import autoclass, cast, JavaException
from plyer.facades import FileChooser
from plyer import storagepath
Environment = autoclass("android.os.Environment")
String = autoclass('java.lang.String')
Intent = autoclass('android.content.Intent')
Activity = autoclass('android.app.Activity')
DocumentsContract = autoclass('android.provider.DocumentsContract')
ContentUris = autoclass('android.content.ContentUris')
Uri = autoclass('android.net.Uri')
Long = autoclass('java.lang.Long')
IMedia = autoclass('android.provider.MediaStore$Images$Media')
VMedia = autoclass('android.provider.MediaStore$Video$Media')
AMedia = autoclass('android.provider.MediaStore$Audio$Media')
class AndroidFileChooser(FileChooser):
'''
FileChooser implementation for Android using
the built-in file browser via Intent.
.. versionadded:: 1.4.0
'''
# filechooser activity <-> result pair identification
select_code = None
# default selection value
selection = None
# select multiple files
multiple = False
# mime types
mime_type = {
"doc": "application/msword",
"docx": "application/vnd.openxmlformats-officedocument." +
"wordprocessingml.document",
"ppt": "application/vnd.ms-powerpoint",
"pptx": "application/vnd.openxmlformats-officedocument." +
"presentationml.presentation",
"xls": "application/vnd.ms-excel",
"xlsx": "application/vnd.openxmlformats-officedocument." +
"spreadsheetml.sheet",
"text": "text/*",
"pdf": "application/pdf",
"zip": "application/zip",
"image": "image/*",
"video": "video/*",
"audio": "audio/*",
"application": "application/*"}
selected_mime_type = None
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.select_code = randint(123456, 654321)
self.selection = None
# bind a function for a response from filechooser activity
activity.bind(on_activity_result=self._on_activity_result)
@staticmethod
def _handle_selection(selection):
'''
Dummy placeholder for returning selection from
``android.app.Activity.onActivityResult()``.
.. versionadded:: 1.4.0
'''
return selection
def _open_file(self, **kwargs):
'''
Running Android Activity is non-blocking and the only call
that blocks is onActivityResult running in GUI thread
.. versionadded:: 1.4.0
'''
# set up selection handler
# startActivityForResult is async
# onActivityResult is sync
self._handle_selection = kwargs.pop(
'on_selection', self._handle_selection
)
self.selected_mime_type = \
kwargs.pop("filters")[0] if "filters" in kwargs else ""
# create Intent for opening
file_intent = Intent(Intent.ACTION_GET_CONTENT)
if not self.selected_mime_type or \
type(self.selected_mime_type) != str or \
self.selected_mime_type not in self.mime_type:
file_intent.setType("*/*")
else:
file_intent.setType(self.mime_type[self.selected_mime_type])
file_intent.addCategory(
Intent.CATEGORY_OPENABLE
)
# use putExtra to allow multiple file selection
if kwargs.get('multiple', self.multiple):
file_intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, True)
# start a new activity from PythonActivity
# which creates a filechooser via intent
mActivity.startActivityForResult(
Intent.createChooser(file_intent, cast(
'java.lang.CharSequence',
String("FileChooser")
)),
self.select_code
)
def _on_activity_result(self, request_code, result_code, data):
'''
Listener for ``android.app.Activity.onActivityResult()`` assigned
via ``android.activity.bind()``.
.. versionadded:: 1.4.0
'''
# not our response
if request_code != self.select_code:
return
if result_code != Activity.RESULT_OK:
# The action had been cancelled.
return
selection = []
# Process multiple URI if multiple files selected
try:
for count in range(data.getClipData().getItemCount()):
ele = self._resolve_uri(
data.getClipData().getItemAt(count).getUri()) or []
selection.append(ele)
except Exception:
selection = [self._resolve_uri(data.getData()), ]
# return value to object
self.selection = selection
# return value via callback
self._handle_selection(selection)
@staticmethod
def _handle_external_documents(uri):
'''
Selection from the system filechooser when using ``Phone``
or ``Internal storage`` or ``SD card`` option from menu.
.. versionadded:: 1.4.0
'''
file_id = DocumentsContract.getDocumentId(uri)
file_type, file_name = file_id.split(':')
# internal SD card mostly mounted as a files storage in phone
internal = storagepath.get_external_storage_dir()
# external (removable) SD card i.e. microSD
external = storagepath.get_sdcard_dir()
try:
external_base = basename(external)
except TypeError:
external_base = basename(internal)
# resolve sdcard path
sd_card = internal
# because external might have /storage/.../1 or other suffix
# and file_type might be only a part of the real folder in /storage
if file_type in external_base or external_base in file_type:
sd_card = external
elif file_type == "home":
sd_card = join(Environment.getExternalStorageDirectory(
).getAbsolutePath(), Environment.DIRECTORY_DOCUMENTS)
return join(sd_card, file_name)
@staticmethod
def _handle_media_documents(uri):
'''
Selection from the system filechooser when using ``Images``
or ``Videos`` or ``Audio`` option from menu.
.. versionadded:: 1.4.0
'''
file_id = DocumentsContract.getDocumentId(uri)
file_type, file_name = file_id.split(':')
selection = '_id=?'
if file_type == 'image':
uri = IMedia.EXTERNAL_CONTENT_URI
elif file_type == 'video':
uri = VMedia.EXTERNAL_CONTENT_URI
elif file_type == 'audio':
uri = AMedia.EXTERNAL_CONTENT_URI
return file_name, selection, uri
@staticmethod
def _handle_downloads_documents(uri):
'''
Selection from the system filechooser when using ``Downloads``
option from menu. Might not work all the time due to:
1) invalid URI:
jnius.jnius.JavaException:
JVM exception occurred: Unknown URI:
content://downloads/public_downloads/1034
2) missing URI / android permissions
jnius.jnius.JavaException:
JVM exception occurred:
Permission Denial: reading
com.android.providers.downloads.DownloadProvider uri
content://downloads/all_downloads/1034 from pid=2532, uid=10455
requires android.permission.ACCESS_ALL_DOWNLOADS,
or grantUriPermission()
Workaround:
Selecting path from ``Phone`` -> ``Download`` -> ``<file>``
(or ``Internal storage``) manually.
.. versionadded:: 1.4.0
'''
# known locations, differ between machines
downloads = [
'content://downloads/public_downloads',
'content://downloads/my_downloads',
# all_downloads requires separate permission
# android.permission.ACCESS_ALL_DOWNLOADS
'content://downloads/all_downloads'
]
file_id = DocumentsContract.getDocumentId(uri)
try_uris = [
ContentUris.withAppendedId(
Uri.parse(down), Long.valueOf(file_id)
)
for down in downloads
]
# try all known Download folder uris
# and handle JavaExceptions due to different locations
# for content:// downloads or missing permission
path = None
for down in try_uris:
try:
path = AndroidFileChooser._parse_content(
uri=down, projection=['_data'],
selection=None,
selection_args=None,
sort_order=None
)
except JavaException:
import traceback
traceback.print_exc()
# we got a path, ignore the rest
if path:
break
# alternative approach to Downloads by joining
# all data items from Activity result
if not path:
for down in try_uris:
try:
path = AndroidFileChooser._parse_content(
uri=down, projection=None,
selection=None,
selection_args=None,
sort_order=None,
index_all=True
)
except JavaException:
import traceback
traceback.print_exc()
# we got a path, ignore the rest
if path:
break
return path
def _resolve_uri(self, uri):
'''
Resolve URI input from ``android.app.Activity.onActivityResult()``.
.. versionadded:: 1.4.0
'''
uri_authority = uri.getAuthority()
uri_scheme = uri.getScheme().lower()
path = None
file_name = None
selection = None
downloads = None
# This does not allow file selected from google photos or gallery
# or even any other file explorer to work
# not a document URI, nothing to convert from
# if not DocumentsContract.isDocumentUri(mActivity, uri):
# return path
if uri_authority == 'com.android.externalstorage.documents':
return self._handle_external_documents(uri)
# in case a user selects a file from 'Downloads' section
# note: this won't be triggered if a user selects a path directly
# e.g.: Phone -> Download -> <some file>
elif uri_authority == 'com.android.providers.downloads.documents':
path = downloads = self._handle_downloads_documents(uri)
elif uri_authority == 'com.android.providers.media.documents':
file_name, selection, uri = self._handle_media_documents(uri)
# parse content:// scheme to path
if uri_scheme == 'content' and not downloads:
try:
path = self._parse_content(
uri=uri, projection=['_data'], selection=selection,
selection_args=file_name, sort_order=None
)
except JavaException: # handles array error for selection_args
path = self._parse_content(
uri=uri, projection=['_data'], selection=selection,
selection_args=[file_name], sort_order=None
)
# nothing to parse, file:// will return a proper path
elif uri_scheme == 'file':
path = uri.getPath()
return path
@staticmethod
def _parse_content(
uri, projection, selection, selection_args, sort_order,
index_all=False
):
'''
Parser for ``content://`` URI returned by some Android resources.
.. versionadded:: 1.4.0
'''
result = None
resolver = mActivity.getContentResolver()
read = Intent.FLAG_GRANT_READ_URI_PERMISSION
write = Intent.FLAG_GRANT_READ_URI_PERMISSION
persist = Intent.FLAG_GRANT_READ_URI_PERMISSION
# grant permission for our activity
mActivity.grantUriPermission(
mActivity.getPackageName(),
uri,
read | write | persist
)
if not index_all:
cursor = resolver.query(
uri, projection, selection,
selection_args, sort_order
)
idx = cursor.getColumnIndex(projection[0])
if idx != -1 and cursor.moveToFirst():
result = cursor.getString(idx)
else:
result = []
cursor = resolver.query(
uri, projection, selection,
selection_args, sort_order
)
while cursor.moveToNext():
for idx in range(cursor.getColumnCount()):
result.append(cursor.getString(idx))
result = '/'.join(result)
return result
def _file_selection_dialog(self, **kwargs):
mode = kwargs.pop('mode', None)
if mode == 'open':
self._open_file(**kwargs)
def instance():
return AndroidFileChooser()

View file

@ -0,0 +1,55 @@
# coding=utf-8
"""
Flash
-----
"""
from plyer.facades import Flash
from jnius import autoclass
from plyer.platforms.android import activity
Camera = autoclass("android.hardware.Camera")
CameraParameters = autoclass("android.hardware.Camera$Parameters")
SurfaceTexture = autoclass("android.graphics.SurfaceTexture")
PackageManager = autoclass('android.content.pm.PackageManager')
pm = activity.getPackageManager()
flash_available = pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH)
class AndroidFlash(Flash):
_camera = None
def _on(self):
if self._camera is None:
self._camera_open()
if not self._camera:
return
self._camera.setParameters(self._f_on)
def _off(self):
if not self._camera:
return
self._camera.setParameters(self._f_off)
def _release(self):
if not self._camera:
return
self._camera.stopPreview()
self._camera.release()
self._camera = None
def _camera_open(self):
if not flash_available:
return
self._camera = Camera.open()
self._f_on = Camera.getParameters()
self._f_off = Camera.getParameters()
self._f_on.setFlashMode(CameraParameters.FLASH_MODE_TORCH)
self._f_off.setFlashMode(CameraParameters.FLASH_MODE_OFF)
self._camera.startPreview()
# Need this for Nexus 5
self._camera.setPreviewTexture(SurfaceTexture(0))
def instance():
return AndroidFlash()

View file

@ -0,0 +1,82 @@
'''
Android GPS
-----------
'''
from plyer.facades import GPS
from plyer.platforms.android import activity
from jnius import autoclass, java_method, PythonJavaClass
Looper = autoclass('android.os.Looper')
LocationManager = autoclass('android.location.LocationManager')
Context = autoclass('android.content.Context')
class _LocationListener(PythonJavaClass):
__javainterfaces__ = ['android/location/LocationListener']
def __init__(self, root):
self.root = root
super().__init__()
@java_method('(Landroid/location/Location;)V')
def onLocationChanged(self, location):
self.root.on_location(
lat=location.getLatitude(),
lon=location.getLongitude(),
speed=location.getSpeed(),
bearing=location.getBearing(),
altitude=location.getAltitude(),
accuracy=location.getAccuracy())
@java_method('(Ljava/lang/String;)V')
def onProviderEnabled(self, status):
if self.root.on_status:
self.root.on_status('provider-enabled', status)
@java_method('(Ljava/lang/String;)V')
def onProviderDisabled(self, status):
if self.root.on_status:
self.root.on_status('provider-disabled', status)
@java_method('(Ljava/lang/String;ILandroid/os/Bundle;)V')
def onStatusChanged(self, provider, status, extras):
if self.root.on_status:
s_status = 'unknown'
if status == 0x00:
s_status = 'out-of-service'
elif status == 0x01:
s_status = 'temporarily-unavailable'
elif status == 0x02:
s_status = 'available'
self.root.on_status('provider-status', '{}: {}'.format(
provider, s_status))
class AndroidGPS(GPS):
def _configure(self):
if not hasattr(self, '_location_manager'):
self._location_manager = activity.getSystemService(
Context.LOCATION_SERVICE
)
self._location_listener = _LocationListener(self)
def _start(self, **kwargs):
min_time = kwargs.get('minTime')
min_distance = kwargs.get('minDistance')
providers = self._location_manager.getProviders(False).toArray()
for provider in providers:
self._location_manager.requestLocationUpdates(
provider,
min_time, # minTime, in milliseconds
min_distance, # minDistance, in meters
self._location_listener,
Looper.getMainLooper())
def _stop(self):
self._location_manager.removeUpdates(self._location_listener)
def instance():
return AndroidGPS()

View file

@ -0,0 +1,84 @@
'''
Android gravity
---------------------
'''
from jnius import autoclass
from jnius import cast
from jnius import java_method
from jnius import PythonJavaClass
from plyer.facades import Gravity
from plyer.platforms.android import activity
Context = autoclass('android.content.Context')
Sensor = autoclass('android.hardware.Sensor')
SensorManager = autoclass('android.hardware.SensorManager')
class GravitySensorListener(PythonJavaClass):
__javainterfaces__ = ['android/hardware/SensorEventListener']
def __init__(self):
super().__init__()
service = activity.getSystemService(Context.SENSOR_SERVICE)
self.SensorManager = cast('android.hardware.SensorManager', service)
self.sensor = self.SensorManager.getDefaultSensor(
Sensor.TYPE_GRAVITY
)
self.values = [None, None, None]
def enable(self):
self.SensorManager.registerListener(
self,
self.sensor,
SensorManager.SENSOR_DELAY_NORMAL
)
def disable(self):
self.SensorManager.unregisterListener(self, self.sensor)
@java_method('(Landroid/hardware/SensorEvent;)V')
def onSensorChanged(self, event):
self.values = event.values[:3]
@java_method('(Landroid/hardware/Sensor;I)V')
def onAccuracyChanged(self, sensor, accuracy):
pass
class AndroidGravity(Gravity):
def __init__(self):
super().__init__()
self.state = False
def _enable(self):
if not self.state:
self.listener = GravitySensorListener()
self.listener.enable()
self.state = True
def _disable(self):
if self.state:
self.state = False
self.listener.disable()
del self.listener
def _get_gravity(self):
if self.state:
return tuple(self.listener.values)
else:
return (None, None, None)
def __del__(self):
if self.state:
self._disable()
super().__del__()
def instance():
return AndroidGravity()

View file

@ -0,0 +1,119 @@
'''
Android Gyroscope
-----------------
'''
from plyer.facades import Gyroscope
from jnius import PythonJavaClass, java_method, autoclass, cast
from plyer.platforms.android import activity
Context = autoclass('android.content.Context')
Sensor = autoclass('android.hardware.Sensor')
SensorManager = autoclass('android.hardware.SensorManager')
class GyroscopeSensorListener(PythonJavaClass):
__javainterfaces__ = ['android/hardware/SensorEventListener']
def __init__(self):
super().__init__()
self.SensorManager = cast(
'android.hardware.SensorManager',
activity.getSystemService(Context.SENSOR_SERVICE)
)
self.sensor = self.SensorManager.getDefaultSensor(
Sensor.TYPE_GYROSCOPE
)
self.values = [None, None, None]
def enable(self):
self.SensorManager.registerListener(
self, self.sensor,
SensorManager.SENSOR_DELAY_NORMAL
)
def disable(self):
self.SensorManager.unregisterListener(self, self.sensor)
@java_method('(Landroid/hardware/SensorEvent;)V')
def onSensorChanged(self, event):
self.values = event.values[:3]
@java_method('(Landroid/hardware/Sensor;I)V')
def onAccuracyChanged(self, sensor, accuracy):
# Maybe, do something in future?
pass
class GyroUncalibratedSensorListener(PythonJavaClass):
__javainterfaces__ = ['android/hardware/SensorEventListener']
def __init__(self):
super().__init__()
service = activity.getSystemService(Context.SENSOR_SERVICE)
self.SensorManager = cast('android.hardware.SensorManager', service)
self.sensor = self.SensorManager.getDefaultSensor(
Sensor.TYPE_GYROSCOPE_UNCALIBRATED)
self.values = [None, None, None, None, None, None]
def enable(self):
self.SensorManager.registerListener(
self, self.sensor,
SensorManager.SENSOR_DELAY_NORMAL
)
def disable(self):
self.SensorManager.unregisterListener(self, self.sensor)
@java_method('(Landroid/hardware/SensorEvent;)V')
def onSensorChanged(self, event):
self.values = event.values[:6]
@java_method('(Landroid/hardware/Sensor;I)V')
def onAccuracyChanged(self, sensor, accuracy):
pass
class AndroidGyroscope(Gyroscope):
def __init__(self):
super().__init__()
self.bState = False
def _enable(self):
if (not self.bState):
self.listenerg = GyroscopeSensorListener()
self.listenergu = GyroUncalibratedSensorListener()
self.listenerg.enable()
self.listenergu.enable()
self.bState = True
def _disable(self):
if (self.bState):
self.bState = False
self.listenerg.disable()
self.listenergu.disable()
del self.listenerg
del self.listenergu
def _get_orientation(self):
if (self.bState):
return tuple(self.listenerg.values)
else:
return (None, None, None)
def _get_rotation_uncalib(self):
if (self.bState):
return tuple(self.listenergu.values)
else:
return (None, None, None, None, None, None)
def __del__(self):
if(self.bState):
self._disable()
super().__del__()
def instance():
return AndroidGyroscope()

View file

@ -0,0 +1,107 @@
from jnius import autoclass
from jnius import cast
from jnius import java_method
from jnius import PythonJavaClass
from math import exp
from plyer.facades import Humidity
from plyer.platforms.android import activity
ActivityInfo = autoclass('android.content.pm.ActivityInfo')
Context = autoclass('android.content.Context')
Sensor = autoclass('android.hardware.Sensor')
SensorManager = autoclass('android.hardware.SensorManager')
class RelativeHumiditySensorListener(PythonJavaClass):
__javainterfaces__ = ['android/hardware/SensorEventListener']
def __init__(self):
super().__init__()
service = activity.getSystemService(Context.SENSOR_SERVICE)
self.SensorManager = cast('android.hardware.SensorManager', service)
self.sensor = self.SensorManager.getDefaultSensor(
Sensor.TYPE_RELATIVE_HUMIDITY)
self.value = None
def enable(self):
self.SensorManager.registerListener(self, self.sensor,
SensorManager.SENSOR_DELAY_NORMAL)
def disable(self):
self.SensorManager.unregisterListener(self, self.sensor)
@java_method('(Landroid/hardware/SensorEvent;)V')
def onSensorChanged(self, event):
self.value = event.values[0]
@java_method('(Landroid/hardware/Sensor;I)V')
def onAccuracyChanged(self, sensor, accuracy):
pass
class AmbientTemperatureSensorListener(PythonJavaClass):
__javainterfaces__ = ['android/hardware/SensorEventListener']
def __init__(self):
super().__init__()
service = activity.getSystemService(Context.SENSOR_SERVICE)
self.SensorManager = cast('android.hardware.SensorManager', service)
self.sensor = self.SensorManager.getDefaultSensor(
Sensor.TYPE_AMBIENT_TEMPERATURE)
self.value = None
def enable(self):
self.SensorManager.registerListener(self, self.sensor,
SensorManager.SENSOR_DELAY_NORMAL)
def disable(self):
self.SensorManager.unregisterListener(self, self.sensor)
@java_method('(Landroid/hardware/SensorEvent;)V')
def onSensorChanged(self, event):
self.value = event.values[0]
@java_method('(Landroid/hardware/Sensor;I)V')
def onAccuracyChanged(self, sensor, accuracy):
pass
class AndroidHumidity(Humidity):
def __init__(self):
self.state = False
def _get_humidity(self):
if self.state:
m = 17.62
Tn = 243.12
Ta = 216.7
Rh = self.listener_r.value
Tc = self.listener_a.value
A = 6.112
K = 273.15
humidity = (Ta * (Rh / 100) * A * exp(m * Tc / (Tn + Tc))
/ (K + Tc))
return humidity
def _enable(self):
if not self.state:
self.listener_r = RelativeHumiditySensorListener()
self.listener_a = AmbientTemperatureSensorListener()
self.listener_r.enable()
self.listener_a.enable()
self.state = True
def _disable(self):
if self.state:
self.listener_r.disable()
self.listener_a.disable()
self.state = False
delattr(self, 'listener_r')
delattr(self, 'listener_a')
def instance():
return AndroidHumidity()

View file

@ -0,0 +1,55 @@
from jnius import autoclass
from plyer.facades import IrBlaster
from plyer.platforms.android import activity, SDK_INT, ANDROID_VERSION
if SDK_INT >= 19:
Context = autoclass('android.content.Context')
ir_manager = activity.getSystemService(Context.CONSUMER_IR_SERVICE)
else:
ir_manager = None
class AndroidIrBlaster(IrBlaster):
def _exists(self):
if ir_manager and ir_manager.hasIrEmitter():
return True
return False
@property
def multiply_pulse(self):
'''Android 4.4.3+ uses microseconds instead of period counts
'''
return not (SDK_INT == 19
and int(str(ANDROID_VERSION.RELEASE).rsplit('.', 1)[-1])
< 3)
def _get_frequencies(self):
if not ir_manager:
return None
if hasattr(self, '_frequencies'):
return self._frequencies
ir_frequencies = ir_manager.getCarrierFrequencies()
if not ir_frequencies:
return []
frequencies = []
for freqrange in ir_frequencies:
freq = (freqrange.getMinFrequency(), freqrange.getMaxFrequency())
frequencies.append(freq)
self._frequencies = frequencies
return frequencies
def _transmit(self, frequency, pattern, mode):
if self.multiply_pulse and mode == 'period':
pattern = self.periods_to_microseconds(frequency, pattern)
elif not self.multiply_pulse and mode == 'microseconds':
pattern = self.microseconds_to_periods(frequency, pattern)
ir_manager.transmit(frequency, pattern)
def instance():
return AndroidIrBlaster()

View file

@ -0,0 +1,25 @@
from plyer.facades import Keystore
from plyer.platforms.android import activity
class AndroidKeystore(Keystore):
def _set_key(self, servicename, key, value, **kwargs):
mode = kwargs.get("mode", 0)
settings = activity.getSharedPreferences(servicename, mode)
editor = settings.edit()
editor.putString(key, value)
editor.commit()
def _get_key(self, servicename, key, **kwargs):
mode = kwargs.get("mode", 0)
default = kwargs.get("default", "__None")
settings = activity.getSharedPreferences(servicename, mode)
ret = settings.getString(key, default)
if ret == "__None":
ret = None
return ret
def instance():
return AndroidKeystore()

View file

@ -0,0 +1,63 @@
from jnius import autoclass
from jnius import cast
from jnius import java_method
from jnius import PythonJavaClass
from plyer.facades import Light
from plyer.platforms.android import activity
Context = autoclass('android.content.Context')
Sensor = autoclass('android.hardware.Sensor')
SensorManager = autoclass('android.hardware.SensorManager')
class LightSensorListener(PythonJavaClass):
__javainterfaces__ = ['android/hardware/SensorEventListener']
def __init__(self):
super().__init__()
service = activity.getSystemService(Context.SENSOR_SERVICE)
self.SensorManager = cast('android.hardware.SensorManager', service)
self.sensor = self.SensorManager.getDefaultSensor(Sensor.TYPE_LIGHT)
self.value = None
def enable(self):
self.SensorManager.registerListener(
self, self.sensor,
SensorManager.SENSOR_DELAY_NORMAL
)
def disable(self):
self.SensorManager.unregisterListener(self, self.sensor)
@java_method('(Landroid/hardware/SensorEvent;)V')
def onSensorChanged(self, event):
self.value = event.values[0]
@java_method('(Landroid/hardware/Sensor;I)V')
def onAccuracyChanged(self, sensor, accuracy):
pass
class AndroidLight(Light):
listener = None
def _get_illumination(self):
if self.listener and self.listener.value:
light = self.listener.value
return light
def _enable(self):
if not self.listener:
self.listener = LightSensorListener()
self.listener.enable()
def _disable(self):
if self.listener:
self.listener.disable()
delattr(self, 'listener')
def instance():
return AndroidLight()

View file

@ -0,0 +1,208 @@
'''
Module of Android API for plyer.notification.
.. versionadded:: 1.0.0
.. versionchanged:: 1.4.0
Fixed notifications not displaying due to missing NotificationChannel
required by Android Oreo 8.0+ (API 26+).
.. versionchanged:: 1.4.0
Added simple toaster notification.
.. versionchanged:: 1.4.0
Fixed notifications not displaying big icons properly.
Added option for custom big icon via `icon`.
'''
from android import python_act
from android.runnable import run_on_ui_thread
from jnius import autoclass, cast
from plyer.facades import Notification
from plyer.platforms.android import activity, SDK_INT
AndroidString = autoclass('java.lang.String')
Context = autoclass('android.content.Context')
NotificationBuilder = autoclass('android.app.Notification$Builder')
NotificationManager = autoclass('android.app.NotificationManager')
PendingIntent = autoclass('android.app.PendingIntent')
Intent = autoclass('android.content.Intent')
Toast = autoclass('android.widget.Toast')
BitmapFactory = autoclass('android.graphics.BitmapFactory')
Icon = autoclass("android.graphics.drawable.Icon")
class AndroidNotification(Notification):
'''
Implementation of Android notification API.
.. versionadded:: 1.0.0
'''
def __init__(self):
package_name = activity.getPackageName()
self._ns = None
self._channel_id = package_name
pm = activity.getPackageManager()
info = pm.getActivityInfo(activity.getComponentName(), 0)
if info.icon == 0:
# Take the application icon instead.
info = pm.getApplicationInfo(package_name, 0)
self._app_icon = info.icon
def _get_notification_service(self):
if not self._ns:
self._ns = cast(NotificationManager, activity.getSystemService(
Context.NOTIFICATION_SERVICE
))
return self._ns
def _build_notification_channel(self, name):
'''
Create a NotificationChannel using channel id of the application
package name (com.xyz, org.xyz, ...) and channel name same as the
provided notification title if the API is high enough, otherwise
do nothing.
.. versionadded:: 1.4.0
'''
if SDK_INT < 26:
return
channel = autoclass('android.app.NotificationChannel')
app_channel = channel(
self._channel_id, name, NotificationManager.IMPORTANCE_DEFAULT
)
self._get_notification_service().createNotificationChannel(
app_channel
)
return app_channel
@run_on_ui_thread
def _toast(self, message):
'''
Display a popup-like small notification at the bottom of the screen.
.. versionadded:: 1.4.0
'''
Toast.makeText(
activity,
cast('java.lang.CharSequence', AndroidString(message)),
Toast.LENGTH_LONG
).show()
def _set_icons(self, notification, icon=None, notification_icon=None):
'''
Set the small application icon displayed at the top panel together with
WiFi, battery percentage and time and the big optional icon (preferably
PNG format with transparent parts) displayed directly in the
notification body.
.. versionadded:: 1.4.0
'''
if notification_icon == None:
app_icon = self._app_icon
else:
notification_icon_bitmap = BitmapFactory.decodeFile(notification_icon)
app_icon = Icon.createWithBitmap(notification_icon_bitmap)
notification.setSmallIcon(app_icon)
bitmap_icon = app_icon
if icon is not None:
bitmap_icon = BitmapFactory.decodeFile(icon)
notification.setLargeIcon(bitmap_icon)
elif icon == '':
# we don't want the big icon set,
# only the small one in the top panel
pass
else:
bitmap_icon = BitmapFactory.decodeResource(
python_act.getResources(), app_icon
)
notification.setLargeIcon(bitmap_icon)
def _build_notification(self, title):
'''
.. versionadded:: 1.4.0
'''
if SDK_INT < 26:
noti = NotificationBuilder(activity)
else:
self._channel = self._build_notification_channel(title)
noti = NotificationBuilder(activity, self._channel_id)
return noti
@staticmethod
def _set_open_behavior(notification):
'''
Open the source application when user opens the notification.
.. versionadded:: 1.4.0
'''
# create Intent that navigates back to the application
app_context = activity.getApplication().getApplicationContext()
notification_intent = Intent(app_context, python_act)
# set flags to run our application Activity
notification_intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
notification_intent.setAction(Intent.ACTION_MAIN)
notification_intent.addCategory(Intent.CATEGORY_LAUNCHER)
# get our application Activity
pending_intent = PendingIntent.getActivity(
app_context, 0, notification_intent, 0
)
notification.setContentIntent(pending_intent)
notification.setAutoCancel(True)
def _open_notification(self, notification):
if SDK_INT >= 16:
notification = notification.build()
else:
notification = notification.getNotification()
self._get_notification_service().notify(0, notification)
def _notify(self, **kwargs):
noti = None
message = kwargs.get('message').encode('utf-8')
ticker = kwargs.get('ticker').encode('utf-8')
title = AndroidString(
kwargs.get('title', '').encode('utf-8')
)
icon = kwargs.get('app_icon')
notification_icon = kwargs.get('notification_icon')
# decide whether toast only or proper notification
if kwargs.get('toast'):
self._toast(message)
return
else:
noti = self._build_notification(title)
# set basic properties for notification
noti.setContentTitle(title)
noti.setContentText(AndroidString(message))
noti.setTicker(AndroidString(ticker))
# set additional flags for notification
self._set_icons(noti, icon=icon, notification_icon=notification_icon)
self._set_open_behavior(noti)
# launch
self._open_notification(noti)
def instance():
'''
Instance for facade proxy.
'''
return AndroidNotification()

View file

@ -0,0 +1,43 @@
from jnius import autoclass
from plyer.platforms.android import activity
from plyer.facades import Orientation
ActivityInfo = autoclass('android.content.pm.ActivityInfo')
class AndroidOrientation(Orientation):
def _set_landscape(self, **kwargs):
reverse = kwargs.get('reverse')
if reverse:
activity.setRequestedOrientation(
ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE)
else:
activity.setRequestedOrientation(
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
def _set_portrait(self, **kwargs):
reverse = kwargs.get('reverse')
if reverse:
activity.setRequestedOrientation(
ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT)
else:
activity.setRequestedOrientation(
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
def _set_sensor(self, **kwargs):
mode = kwargs.get('mode')
if mode == 'any':
activity.setRequestedOrientation(
ActivityInfo.SCREEN_ORIENTATION_SENSOR)
elif mode == 'landscape':
activity.setRequestedOrientation(
ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE)
elif mode == 'portrait':
activity.setRequestedOrientation(
ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT)
def instance():
return AndroidOrientation()

View file

@ -0,0 +1,69 @@
from jnius import autoclass
from jnius import cast
from jnius import java_method
from jnius import PythonJavaClass
from plyer.platforms.android import activity
from plyer.facades import Proximity
ActivityInfo = autoclass('android.content.pm.ActivityInfo')
Context = autoclass('android.content.Context')
Sensor = autoclass('android.hardware.Sensor')
SensorManager = autoclass('android.hardware.SensorManager')
class ProximitySensorListener(PythonJavaClass):
__javainterfaces__ = ['android/hardware/SensorEventListener']
def __init__(self):
super().__init__()
service = activity.getSystemService(Context.SENSOR_SERVICE)
self.SensorManager = cast('android.hardware.SensorManager', service)
self.sensor = self.SensorManager.getDefaultSensor(
Sensor.TYPE_PROXIMITY)
self.value = None
def enable(self):
self.SensorManager.registerListener(
self, self.sensor,
SensorManager.SENSOR_DELAY_NORMAL
)
def disable(self):
self.SensorManager.unregisterListener(self, self.sensor)
@java_method('(Landroid/hardware/SensorEvent;)V')
def onSensorChanged(self, event):
self.value = event.values[0]
@java_method('(Landroid/hardware/Sensor;I)V')
def onAccuracyChanged(self, sensor, accuracy):
pass
class AndroidProximity(Proximity):
listener = None
def _enable(self, **kwargs):
if not self.listener:
self.listener = ProximitySensorListener()
self.listener.enable()
def _disable(self, **kwargs):
if self.listener:
self.listener.disable()
delattr(self, 'listener')
def _get_proximity(self):
if self.listener:
value = self.listener.value
# value is 0.0 when proxime sensor is covered. In other case
# value is 5.0 because in smartphone, optical proximity sensors
# are used.
return value < 5.0
def instance():
return AndroidProximity()

View file

@ -0,0 +1,25 @@
'''
Android SMS
-----------
'''
from jnius import autoclass
from plyer.facades import Sms
SmsManager = autoclass('android.telephony.SmsManager')
class AndroidSms(Sms):
def _send(self, **kwargs):
sms = SmsManager.getDefault()
recipient = kwargs.get('recipient')
message = kwargs.get('message')
if sms:
sms.sendTextMessage(recipient, None, message, None, None)
def instance():
return AndroidSms()

View file

@ -0,0 +1,118 @@
from jnius import autoclass
from jnius import cast
from jnius import java_method
from jnius import PythonJavaClass
from plyer.platforms.android import activity
from plyer.facades import SpatialOrientation
Context = autoclass('android.content.Context')
Sensor = autoclass('android.hardware.Sensor')
SensorManager = autoclass('android.hardware.SensorManager')
class AccelerometerSensorListener(PythonJavaClass):
__javainterfaces__ = ['android/hardware/SensorEventListener']
def __init__(self):
super().__init__()
self.SensorManager = cast(
'android.hardware.SensorManager',
activity.getSystemService(Context.SENSOR_SERVICE)
)
self.sensor = self.SensorManager.getDefaultSensor(
Sensor.TYPE_ACCELEROMETER
)
self.values = [None, None, None]
def enable(self):
self.SensorManager.registerListener(
self, self.sensor,
SensorManager.SENSOR_DELAY_NORMAL
)
def disable(self):
self.SensorManager.unregisterListener(self, self.sensor)
@java_method('(Landroid/hardware/SensorEvent;)V')
def onSensorChanged(self, event):
self.values = event.values[:3]
@java_method('(Landroid/hardware/Sensor;I)V')
def onAccuracyChanged(self, sensor, accuracy):
pass
class MagnetometerSensorListener(PythonJavaClass):
__javainterfaces__ = ['android/hardware/SensorEventListener']
def __init__(self):
super().__init__()
service = activity.getSystemService(Context.SENSOR_SERVICE)
self.SensorManager = cast('android.hardware.SensorManager', service)
self.sensor = self.SensorManager.getDefaultSensor(
Sensor.TYPE_MAGNETIC_FIELD)
self.values = [None, None, None]
def enable(self):
self.SensorManager.registerListener(
self, self.sensor,
SensorManager.SENSOR_DELAY_NORMAL
)
def disable(self):
self.SensorManager.unregisterListener(self, self.sensor)
@java_method('(Landroid/hardware/SensorEvent;)V')
def onSensorChanged(self, event):
self.values = event.values[:3]
@java_method('(Landroid/hardware/Sensor;I)V')
def onAccuracyChanged(self, sensor, accuracy):
pass
class AndroidSpOrientation(SpatialOrientation):
def __init__(self):
self.state = False
def _get_orientation(self):
if self.state:
rotation = [0] * 9
inclination = [0] * 9
gravity = []
geomagnetic = []
gravity = self.listener_a.values
geomagnetic = self.listener_m.values
if gravity[0] is not None and geomagnetic[0] is not None:
ff_state = SensorManager.getRotationMatrix(
rotation, inclination,
gravity, geomagnetic
)
if ff_state:
values = [0, 0, 0]
values = SensorManager.getOrientation(
rotation, values
)
return values
def _enable_listener(self, **kwargs):
if not self.state:
self.listener_a = AccelerometerSensorListener()
self.listener_m = MagnetometerSensorListener()
self.listener_a.enable()
self.listener_m.enable()
self.state = True
def _disable_listener(self, **kwargs):
if self.state:
self.listener_a.disable()
self.listener_m.disable()
self.state = False
delattr(self, 'listener_a')
delattr(self, 'listener_m')
def instance():
return AndroidSpOrientation()

View file

@ -0,0 +1,69 @@
'''
Android Storage Path
--------------------
'''
from os import listdir, access, R_OK
from os.path import join
from plyer.facades import StoragePath
from jnius import autoclass
from android import mActivity
Environment = autoclass('android.os.Environment')
Context = autoclass('android.content.Context')
class AndroidStoragePath(StoragePath):
def _get_home_dir(self):
return Environment.getDataDirectory().getAbsolutePath()
def _get_external_storage_dir(self):
return Environment.getExternalStorageDirectory().getAbsolutePath()
def _get_sdcard_dir(self):
'''
.. versionadded:: 1.4.0
'''
# folder in /storage/ that is readable
# and is not internal SD card
path = None
for folder in listdir('/storage'):
folder = join('/storage', folder)
if folder in self._get_external_storage_dir():
continue
if not access(folder, R_OK):
continue
path = folder
break
return path
def _get_root_dir(self):
return Environment.getRootDirectory().getAbsolutePath()
def _get_documents_dir(self):
return Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOCUMENTS).getAbsolutePath()
def _get_downloads_dir(self):
return Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOWNLOADS).getAbsolutePath()
def _get_videos_dir(self):
return Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_MOVIES).getAbsolutePath()
def _get_music_dir(self):
return Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_MUSIC).getAbsolutePath()
def _get_pictures_dir(self):
return Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES).getAbsolutePath()
def _get_application_dir(self):
return mActivity.getFilesDir().getParentFile().getParent()
def instance():
return AndroidStoragePath()

View file

@ -0,0 +1,252 @@
from android.runnable import run_on_ui_thread
from jnius import autoclass
from jnius import java_method
from jnius import PythonJavaClass
from plyer.facades import STT
from plyer.platforms.android import activity
ArrayList = autoclass('java.util.ArrayList')
Bundle = autoclass('android.os.Bundle')
Context = autoclass('android.content.Context')
Intent = autoclass('android.content.Intent')
RecognizerIntent = autoclass('android.speech.RecognizerIntent')
RecognitionListener = autoclass('android.speech.RecognitionListener')
SpeechRecognizer = autoclass('android.speech.SpeechRecognizer')
SpeechResults = SpeechRecognizer.RESULTS_RECOGNITION
class SpeechListener(PythonJavaClass):
__javainterfaces__ = ['android/speech/RecognitionListener']
# class variables because PythonJavaClass class failed
# to see them later in getters and setters
_error_callback = None
_result_callback = None
_partial_result_callback = None
_volume_callback = None
def __init__(self):
super().__init__()
# overwrite class variables in the object
self._error_callback = None
self._result_callback = None
self._partial_result_callback = None
self._volume_callback = None
# error handling
@property
def error_callback(self):
return self._error_callback
@error_callback.setter
def error_callback(self, callback):
'''
Set error callback. It is called when error occurs.
:param callback: function with one parameter for error message
'''
self._error_callback = callback
# result handling
@property
def result_callback(self):
return self._result_callback
@result_callback.setter
def result_callback(self, callback):
'''
Set result callback. It is called when results are received.
:param callback: function with one parameter for lists of strings
'''
self._result_callback = callback
@property
def partial_result_callback(self):
return self._partial_result_callback
@partial_result_callback.setter
def partial_result_callback(self, callback):
'''
Set partial result callback. It is called when partial results are
received while the listener is still in listening mode.
:param callback: function with one parameter for lists of strings
'''
self._partial_result_callback = callback
# voice changes handling
@property
def volume_callback(self):
return self._volume_callback
@volume_callback.setter
def volume_callback(self, callback):
'''
Set volume voice callback.
It is called when loudness of the voice changes.
:param callback: function with one parameter for volume RMS dB (float).
'''
self._volume_callback = callback
# Implementation Java Interfaces
@java_method('()V')
def onBeginningOfSpeech(self):
pass
@java_method('([B)V')
def onBufferReceived(self, buffer):
pass
@java_method('()V')
def onEndOfSpeech(self):
pass
@java_method('(I)V')
def onError(self, error):
msg = ''
if error == SpeechRecognizer.ERROR_AUDIO:
msg = 'audio'
if error == SpeechRecognizer.ERROR_CLIENT:
msg = 'client'
if error == SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS:
msg = 'insufficient_permissions'
if error == SpeechRecognizer.ERROR_NETWORK:
msg = 'network'
if error == SpeechRecognizer.ERROR_NETWORK_TIMEOUT:
msg = 'network_timeout'
if error == SpeechRecognizer.ERROR_NO_MATCH:
msg = 'no_match'
if error == SpeechRecognizer.ERROR_RECOGNIZER_BUSY:
msg = 'recognizer_busy'
if error == SpeechRecognizer.ERROR_SERVER:
msg = 'server'
if error == SpeechRecognizer.ERROR_SPEECH_TIMEOUT:
msg = 'speech_timeout'
if msg and self.error_callback:
self.error_callback('error:' + msg)
@java_method('(ILandroid/os/Bundle;)V')
def onEvent(self, event_type, params):
pass
@java_method('(Landroid/os/Bundle;)V')
def onPartialResults(self, results):
texts = []
matches = results.getStringArrayList(SpeechResults)
for match in matches.toArray():
if isinstance(match, bytes):
match = match.decode('utf-8')
texts.append(match)
if texts and self.partial_result_callback:
self.partial_result_callback(texts)
@java_method('(Landroid/os/Bundle;)V')
def onReadyForSpeech(self, params):
pass
@java_method('(Landroid/os/Bundle;)V')
def onResults(self, results):
texts = []
matches = results.getStringArrayList(SpeechResults)
for match in matches.toArray():
if isinstance(match, bytes):
match = match.decode('utf-8')
texts.append(match)
if texts and self.result_callback:
self.result_callback(texts)
@java_method('(F)V')
def onRmsChanged(self, rmsdB):
if self.volume_callback:
self.volume_callback(rmsdB)
class AndroidSpeech(STT):
'''
Android Speech Implementation.
Android class `SpeechRecognizer`'s listening deactivates automatically.
Class methods `_on_error()`, `_on_result()` listeners. You can find
documentation here:
https://developer.android.com/reference/android/speech/RecognitionListener
'''
def _on_error(self, msg):
self.errors.append(msg)
self.stop()
def _on_result(self, messages):
self.results.extend(messages)
self.stop()
def _on_partial(self, messages):
self.partial_results.extend(messages)
@run_on_ui_thread
def _start(self):
intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH)
intent.putExtra(
RecognizerIntent.EXTRA_CALLING_PACKAGE,
activity.getPackageName()
)
# language preferences
intent.putExtra(
RecognizerIntent.EXTRA_LANGUAGE_PREFERENCE, self.language
)
intent.putExtra(
RecognizerIntent.EXTRA_LANGUAGE_MODEL,
RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH
)
# results settings
intent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 1000)
intent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, True)
if self.prefer_offline:
intent.putExtra(RecognizerIntent.EXTRA_PREFER_OFFLINE, True)
# listener and callbacks
listener = SpeechListener()
listener.error_callback = self._on_error
listener.result_callback = self._on_result
listener.partial_result_callback = self._on_partial
# create recognizer and start
self.speech = SpeechRecognizer.createSpeechRecognizer(activity)
self.speech.setRecognitionListener(listener)
self.speech.startListening(intent)
@run_on_ui_thread
def _stop(self):
if not self.speech:
return
# stop listening
self.speech.stopListening()
# free object
self.speech.destroy()
self.speech = None
def _exist(self):
return bool(
SpeechRecognizer.isRecognitionAvailable(activity)
)
def instance():
return AndroidSpeech()

View file

@ -0,0 +1,66 @@
from jnius import autoclass
from jnius import cast
from jnius import java_method
from jnius import PythonJavaClass
from plyer.facades import Temperature
from plyer.platforms.android import activity
ActivityInfo = autoclass('android.content.pm.ActivityInfo')
Context = autoclass('android.content.Context')
Sensor = autoclass('android.hardware.Sensor')
SensorManager = autoclass('android.hardware.SensorManager')
class TemperatureSensorListener(PythonJavaClass):
__javainterfaces__ = ['android/hardware/SensorEventListener']
def __init__(self):
super().__init__()
service = activity.getSystemService(Context.SENSOR_SERVICE)
self.SensorManager = cast('android.hardware.SensorManager', service)
self.sensor = self.SensorManager.getDefaultSensor(
Sensor.TYPE_AMBIENT_TEMPERATURE)
self.value = None
def enable(self):
self.SensorManager.registerListener(
self, self.sensor,
SensorManager.SENSOR_DELAY_NORMAL
)
def disable(self):
self.SensorManager.unregisterListener(self, self.sensor)
@java_method('(Landroid/hardware/SensorEvent;)V')
def onSensorChanged(self, event):
self.value = event.values[0]
@java_method('(Landroid/hardware/Sensor;I)V')
def onAccuracyChanged(self, sensor, accuracy):
pass
class AndroidTemperature(Temperature):
listener = None
def _get_temperature(self):
if self.listener and self.listener.value:
temperature = self.listener.value
return temperature
def _enable(self):
if not self.listener:
self.listener = TemperatureSensorListener()
self.listener.enable()
def _disable(self):
if self.listener:
self.listener.disable()
delattr(self, 'listener')
def instance():
return AndroidTemperature()

View file

@ -0,0 +1,34 @@
from time import sleep
from jnius import autoclass
from plyer.facades import TTS
from plyer.platforms.android import activity
Locale = autoclass('java.util.Locale')
TextToSpeech = autoclass('android.speech.tts.TextToSpeech')
class AndroidTextToSpeech(TTS):
def _speak(self, **kwargs):
tts = TextToSpeech(activity, None)
tts.setLanguage(Locale.US)
retries = 0 # First try rarely succeeds due to some timing issue
message = kwargs.get('message')
# first try for while loop
speak_status = tts.speak(
message, TextToSpeech.QUEUE_FLUSH, None
)
# -1 indicates error. Let's wait and then try again
while retries < 100 and speak_status == -1:
sleep(0.1)
retries += 1
speak_status = tts.speak(
message, TextToSpeech.QUEUE_FLUSH, None
)
def instance():
return AndroidTextToSpeech()

View file

@ -0,0 +1,28 @@
'''
Module of Android API for plyer.uniqueid.
'''
from jnius import autoclass
from plyer.platforms.android import activity
from plyer.facades import UniqueID
Secure = autoclass('android.provider.Settings$Secure')
class AndroidUniqueID(UniqueID):
'''
Implementation of Android uniqueid API.
'''
def _get_uid(self):
return Secure.getString(
activity.getContentResolver(),
Secure.ANDROID_ID
)
def instance():
'''
Instance for facade proxy.
'''
return AndroidUniqueID()

View file

@ -0,0 +1,63 @@
"""Implementation Vibrator for Android."""
from jnius import autoclass, cast
from plyer.facades import Vibrator
from plyer.platforms.android import activity
from plyer.platforms.android import SDK_INT
Context = autoclass("android.content.Context")
vibrator_service = activity.getSystemService(Context.VIBRATOR_SERVICE)
vibrator = cast("android.os.Vibrator", vibrator_service)
if SDK_INT >= 26:
VibrationEffect = autoclass("android.os.VibrationEffect")
class AndroidVibrator(Vibrator):
"""Android Vibrator class.
Supported features:
* vibrate for some period of time.
* vibrate from given pattern.
* cancel vibration.
* check whether Vibrator exists.
"""
def _vibrate(self, time=None, **kwargs):
if vibrator:
if SDK_INT >= 26:
vibrator.vibrate(
VibrationEffect.createOneShot(
int(1000 * time), VibrationEffect.DEFAULT_AMPLITUDE
)
)
else:
vibrator.vibrate(int(1000 * time))
def _pattern(self, pattern=None, repeat=None, **kwargs):
pattern = [int(1000 * time) for time in pattern]
if vibrator:
if SDK_INT >= 26:
vibrator.vibrate(
VibrationEffect.createWaveform(pattern, repeat)
)
else:
vibrator.vibrate(pattern, repeat)
def _exists(self, **kwargs):
if SDK_INT >= 11:
return vibrator.hasVibrator()
elif vibrator_service is None:
raise NotImplementedError()
return True
def _cancel(self, **kwargs):
vibrator.cancel()
def instance():
"""Returns Vibrator with android features.
:return: instance of class AndroidVibrator
"""
return AndroidVibrator()

View file

View file

@ -0,0 +1,34 @@
'''
iOS accelerometer
-----------------
Taken from: http://pyobjus.readthedocs.org/en/latest/pyobjus_ios.html \
#accessing-accelerometer
'''
from plyer.facades import Accelerometer
from pyobjus import autoclass
class IosAccelerometer(Accelerometer):
def __init__(self):
super().__init__()
self.bridge = autoclass('bridge').alloc().init()
self.bridge.motionManager.setAccelerometerUpdateInterval_(0.1)
def _enable(self):
self.bridge.startAccelerometer()
def _disable(self):
self.bridge.stopAccelerometer()
def _get_acceleration(self):
return (
self.bridge.ac_x,
self.bridge.ac_y,
self.bridge.ac_z)
def instance():
return IosAccelerometer()

View file

@ -0,0 +1,31 @@
'''
iOS Barometer
-------------
'''
from plyer.facades import Barometer
from pyobjus import autoclass
class iOSBarometer(Barometer):
def __init__(self):
super().__init__()
self.bridge = autoclass('bridge').alloc().init()
def _enable(self):
self.bridge.startRelativeAltitude()
def _disable(self):
self.bridge.stopRelativeAltitude()
def _get_pressure(self):
'''
1 kPa = 10 hPa
'''
return (
self.bridge.pressure * 10)
def instance():
return iOSBarometer()

View file

@ -0,0 +1,47 @@
'''
Module of iOS API for plyer.battery.
'''
from pyobjus import autoclass
from pyobjus.dylib_manager import load_framework
from plyer.facades import Battery
load_framework('/System/Library/Frameworks/UIKit.framework')
UIDevice = autoclass('UIDevice')
class IOSBattery(Battery):
'''
Implementation of iOS battery API.
'''
def __init__(self):
super().__init__()
self.device = UIDevice.currentDevice()
def _get_state(self):
status = {"isCharging": None, "percentage": None}
if not self.device.batteryMonitoringEnabled:
self.device.setBatteryMonitoringEnabled_(True)
if self.device.batteryState == 0:
is_charging = None
elif self.device.batteryState == 2:
is_charging = True
else:
is_charging = False
percentage = self.device.batteryLevel * 100.
status['isCharging'] = is_charging
status['percentage'] = percentage
return status
def instance():
'''
Instance for facade proxy.
'''
return IOSBattery()

View file

@ -0,0 +1,27 @@
'''
iOS Brightness
--------------
'''
from pyobjus import autoclass
from plyer.facades import Brightness
from pyobjus.dylib_manager import load_framework
load_framework('/System/Library/Frameworks/UIKit.framework')
UIScreen = autoclass('UIScreen')
class iOSBrightness(Brightness):
def __init__(self):
self.screen = UIScreen.mainScreen()
def _current_level(self):
return self.screen.brightness * 100
def set_level(self, level):
self.screen.brightness = level / 100
def instance():
return iOSBrightness()

View file

@ -0,0 +1,29 @@
'''
IOS Call
----------
'''
from plyer.facades import Call
from pyobjus import autoclass, objc_str
NSURL = autoclass('NSURL')
NSString = autoclass('NSString')
UIApplication = autoclass('UIApplication')
class IOSCall(Call):
def _makecall(self, **kwargs):
tel = kwargs.get('tel')
url = "tel://" + tel
nsurl = NSURL.alloc().initWithString_(objc_str(url))
UIApplication.sharedApplication().openURL_(nsurl)
def _dialcall(self, **kwargs):
pass
# Not possible, Access not provided by iPhone SDK
def instance():
return IOSCall()

View file

@ -0,0 +1,52 @@
from os import remove
from plyer.facades import Camera
from plyer.utils import reify
class iOSCamera(Camera):
@reify
def photos(self):
# pyPhotoLibrary is a ios recipe/module that
# interacts with the gallery and the camera on ios.
from photolibrary import PhotosLibrary
return PhotosLibrary()
def _take_picture(self, on_complete, filename=None):
assert(on_complete is not None)
self.on_complete = on_complete
self.filename = filename
photos = self.photos
if not photos.isCameraAvailable():
# no camera hardware
return False
photos.bind(on_image_captured=self.capture_callback)
self._capture_filename = filename
photos.capture_image(filename)
return True
def capture_callback(self, photolibrary):
# Image was chosen
# unbind
self.photos.unbind(on_image_captured=self.capture_callback)
if self.on_complete(self.filename):
self._remove(self.filename)
def _take_video(self, on_complete, filename=None):
assert(on_complete is not None)
raise NotImplementedError
def _remove(self, fn):
try:
remove(fn)
except OSError:
print('Could not remove photo!')
def instance():
return iOSCamera()

View file

@ -0,0 +1,43 @@
'''
iOS Compass
-----------
'''
from plyer.facades import Compass
from pyobjus import autoclass
class IosCompass(Compass):
def __init__(self):
super().__init__()
self.bridge = autoclass('bridge').alloc().init()
self.bridge.motionManager.setMagnetometerUpdateInterval_(0.1)
self.bridge.motionManager.setDeviceMotionUpdateInterval_(0.1)
def _enable(self):
self.bridge.startMagnetometer()
self.bridge.startDeviceMotionWithReferenceFrame()
def _disable(self):
self.bridge.stopMagnetometer()
self.bridge.stopDeviceMotion()
def _get_orientation(self):
return (
self.bridge.mf_x,
self.bridge.mf_y,
self.bridge.mf_z)
def _get_field_uncalib(self):
return (
self.bridge.mg_x,
self.bridge.mg_y,
self.bridge.mg_z,
self.bridge.mg_x - self.bridge.mf_x,
self.bridge.mg_y - self.bridge.mf_y,
self.bridge.mg_z - self.bridge.mf_z)
def instance():
return IosCompass()

View file

@ -0,0 +1,52 @@
'''
Module of iOS API for plyer.email.
'''
try:
from urllib.parse import quote
except ImportError:
from urllib import quote
from plyer.facades import Email
from pyobjus import autoclass, objc_str
from pyobjus.dylib_manager import load_framework
load_framework('/System/Library/Frameworks/UIKit.framework')
NSURL = autoclass('NSURL')
NSString = autoclass('NSString')
UIApplication = autoclass('UIApplication')
class IOSEmail(Email):
'''
Implementation of iOS battery API.
'''
def _send(self, **kwargs):
recipient = kwargs.get('recipient')
subject = kwargs.get('subject')
text = kwargs.get('text')
uri = "mailto:"
if recipient:
uri += str(recipient)
if subject:
uri += "?" if "?" not in uri else "&"
uri += "subject="
uri += quote(str(subject))
if text:
uri += "?" if "?" not in uri else "&"
uri += "body="
uri += quote(str(text))
nsurl = NSURL.alloc().initWithString_(objc_str(uri))
UIApplication.sharedApplication().openURL_(nsurl)
def instance():
'''
Instance for facade proxy.
'''
return IOSEmail()

View file

@ -0,0 +1,81 @@
'''
IOS file chooser
--------------------
This module houses the iOS implementation of the plyer FileChooser.
.. versionadded:: 1.4.4
'''
from plyer.facades import FileChooser
from pyobjus import autoclass, protocol
from pyobjus.dylib_manager import load_framework
load_framework('/System/Library/Frameworks/Photos.framework')
class IOSFileChooser(FileChooser):
'''
FileChooser implementation for IOS using
the built-in file browser via UIImagePickerController.
.. versionadded:: 1.4.0
'''
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._on_selection = None
def _file_selection_dialog(self, *args, **kwargs):
"""
Function called when action is required, A "mode" parameter specifies
which and is one of "open", "save" or "dir".
"""
self._on_selection = kwargs["on_selection"]
if kwargs["mode"] == "open":
self._open()
else:
raise NotImplementedError()
def _get_picker(self):
"""
Return an instantiated and configured UIImagePickerController.
"""
picker = autoclass("UIImagePickerController")
po = picker.alloc().init()
po.sourceType = 0
po.delegate = self
return po
def _open(self):
"""
Launch the native iOS file browser. Upon selection, the
`imagePickerController_didFinishPickingMediaWithInfo_` delegate is
called where we close the file browser and handle the result.
"""
picker = self._get_picker()
UIApplication = autoclass('UIApplication')
vc = UIApplication.sharedApplication().keyWindow.rootViewController()
vc.presentViewController_animated_completion_(picker, True, None)
@protocol('UIImagePickerControllerDelegate')
def imagePickerController_didFinishPickingMediaWithInfo_(
self, image_picker, frozen_dict):
"""
Delegate which handles the result of the image selection process.
"""
image_picker.dismissViewControllerAnimated_completion_(True, None)
# Note: We need to call this Objective C class as there is currently
# no way to call a non-class function via pyobjus. And here,
# we have to use the `UIImagePNGRepresentation` to get the png
# representation. For this, please ensure you are using an
# appropriate version of kivy-ios.
native_image_picker = autoclass("NativeImagePicker").alloc().init()
path = native_image_picker.writeToPNG_(frozen_dict)
self._on_selection([path.UTF8String()])
def instance():
return IOSFileChooser()

View file

@ -0,0 +1,50 @@
# coding=utf-8
"""
Flash
-----
"""
from plyer.facades import Flash
from pyobjus import autoclass
NSString = autoclass("NSString")
AVCaptureDevice = autoclass("AVCaptureDevice")
AVMediaTypeVideo = NSString.alloc().initWithUTF8String_("vide")
AVCaptureTorchModeOff = 0
AVCaptureTorchModeOn = 1
class IosFlash(Flash):
_camera = None
def _on(self):
if self._camera is None:
self._camera_open()
if not self._camera:
return
self._camera.lockForConfiguration_(None)
try:
self._camera.setTorchMode(AVCaptureTorchModeOn)
finally:
self._camera.unlockForConfiguration()
def _off(self):
if not self._camera:
return
self._camera.lockForConfiguration_(None)
try:
self._camera.setTorchMode(AVCaptureTorchModeOff)
finally:
self._camera.unlockForConfiguration()
def _release(self):
pass
def _camera_open(self):
device = AVCaptureDevice.defaultDeviceWithMediaType_(AVMediaTypeVideo)
if not device:
return
self._camera = device
def instance():
return IosFlash()

View file

@ -0,0 +1,80 @@
'''
iOS GPS
-----------
'''
from pyobjus import autoclass, protocol
from pyobjus.dylib_manager import load_framework
from plyer.facades import GPS
load_framework('/System/Library/Frameworks/CoreLocation.framework')
CLLocationManager = autoclass('CLLocationManager')
class IosGPS(GPS):
def _configure(self):
if not hasattr(self, '_location_manager'):
self._location_manager = CLLocationManager.alloc().init()
def _start(self, **kwargs):
self._location_manager.delegate = self
self._location_manager.requestWhenInUseAuthorization()
# NSLocationWhenInUseUsageDescription key must exist in Info.plist
# file. When the authorization prompt is displayed your app goes
# into pause mode and if your app doesn't support background mode
# it will crash.
self._location_manager.startUpdatingLocation()
def _stop(self):
self._location_manager.stopUpdatingLocation()
@protocol('CLLocationManagerDelegate')
def locationManager_didChangeAuthorizationStatus_(self, manager, status):
if self.on_status:
s_status = ''
provider_status = ''
provider = 'standard-ios-provider'
if status == 0:
provider_status = 'provider-disabled'
s_status = 'notDetermined'
elif status == 1:
provider_status = 'provider-enabled'
s_status = 'restricted'
elif status == 2:
provider_status = 'provider-disabled'
s_status = 'denied'
elif status == 3:
provider_status = 'provider-enabled'
s_status = 'authorizedAlways'
elif status == 4:
provider_status = 'provider-enabled'
s_status = 'authorizedWhenInUse'
self.on_status(provider_status, '{}: {}'.format(
provider, s_status))
@protocol('CLLocationManagerDelegate')
def locationManager_didUpdateLocations_(self, manager, locations):
location = manager.location
description = location.description.UTF8String()
split_description = description.split('<')[-1].split('>')[0].split(',')
lat, lon = [float(coord) for coord in split_description]
acc = float(description.split(' +/- ')[-1].split('m ')[0])
speed = location.speed
altitude = location.altitude
course = location.course
self.on_location(
lat=lat,
lon=lon,
speed=speed,
bearing=course,
altitude=altitude,
accuracy=acc)
def instance():
return IosGPS()

View file

@ -0,0 +1,31 @@
'''
iOS Gravity
-----------
'''
from plyer.facades import Gravity
from pyobjus import autoclass
class iOSGravity(Gravity):
def __init__(self):
self.bridge = autoclass('bridge').alloc().init()
self.bridge.motionManager.setDeviceMotionUpdateInterval_(0.1)
def _enable(self):
self.bridge.startDeviceMotion()
def _disable(self):
self.bridge.stopDeviceMotion()
def _get_gravity(self):
return (
self.bridge.g_x,
self.bridge.g_y,
self.bridge.g_z)
def instance():
return iOSGravity()

View file

@ -0,0 +1,55 @@
'''
iOS Gyroscope
---------------------
'''
from plyer.facades import Gyroscope
from pyobjus import autoclass
from pyobjus.dylib_manager import load_framework
load_framework('/System/Library/Frameworks/UIKit.framework')
UIDevice = autoclass('UIDevice')
device = UIDevice.currentDevice()
class IosGyroscope(Gyroscope):
def __init__(self):
super().__init__()
self.bridge = autoclass('bridge').alloc().init()
if int(device.systemVersion.UTF8String().split('.')[0]) <= 4:
self.bridge.motionManager.setGyroscopeUpdateInterval_(0.1)
else:
self.bridge.motionManager.setGyroUpdateInterval_(0.1)
self.bridge.motionManager.setDeviceMotionUpdateInterval_(0.1)
def _enable(self):
self.bridge.startGyroscope()
self.bridge.startDeviceMotion()
def _disable(self):
self.bridge.stopGyroscope()
self.bridge.stopDeviceMotion()
def _get_orientation(self):
return (
self.bridge.rotation_rate_x,
self.bridge.rotation_rate_y,
self.bridge.rotation_rate_z)
def _get_rotation_uncalib(self):
return (
self.bridge.gy_x,
self.bridge.gy_y,
self.bridge.gy_z,
self.bridge.gy_x - self.bridge.rotation_rate_x,
self.bridge.gy_y - self.bridge.rotation_rate_y,
self.bridge.gy_z - self.bridge.rotation_rate_z)
def instance():
return IosGyroscope()

View file

@ -0,0 +1,23 @@
from plyer.facades import Keystore
from pyobjus import autoclass, objc_str
NSUserDefaults = autoclass('NSUserDefaults')
class IosKeystore(Keystore):
def _set_key(self, servicename, key, value, **kwargs):
NSUserDefaults.standardUserDefaults().setObject_forKey_(
objc_str(value), objc_str(key))
def _get_key(self, servicename, key, **kwargs):
ret = NSUserDefaults.standardUserDefaults().stringForKey_(
objc_str(key))
if ret is not None:
return ret.UTF8String()
else:
return ret
def instance():
return IosKeystore()

View file

@ -0,0 +1,43 @@
'''
IOS Sms
----------
'''
from plyer.facades import Sms
from pyobjus import autoclass, objc_str
from pyobjus.dylib_manager import load_framework
NSURL = autoclass('NSURL')
NSString = autoclass('NSString')
UIApplication = autoclass('UIApplication')
load_framework('/System/Library/Frameworks/MessageUI.framework')
class IOSSms(Sms):
def _send(self, **kwargs):
'''
This method provides sending messages to recipients.
Expects 2 parameters in kwargs:
- recipient: String type
- message: String type
Opens a message interface with recipient and message information.
'''
recipient = kwargs.get('recipient')
message = kwargs.get('message')
url = "sms:"
if recipient:
# Apple has not supported multiple recipients yet.
url += str(recipient)
if message:
# Apple has to supported it yet.
pass
nsurl = NSURL.alloc().initWithString_(objc_str(url))
UIApplication.sharedApplication().openURL_(nsurl)
def instance():
return IOSSms()

View file

@ -0,0 +1,31 @@
'''
iOS Spatial Orientation
-----------------------
'''
from plyer.facades import SpatialOrientation
from pyobjus import autoclass
class iOSSpatialOrientation(SpatialOrientation):
def __init__(self):
self.bridge = autoclass('bridge').alloc().init()
self.bridge.motionManager.setDeviceMotionUpdateInterval_(0.1)
def _enable_listener(self):
self.bridge.startDeviceMotion()
def _disable_listener(self):
self.bridge.stopDeviceMotion()
def _get_orientation(self):
return (
self.bridge.sp_yaw,
self.bridge.sp_pitch,
self.bridge.sp_roll)
def instance():
return iOSSpatialOrientation()

View file

@ -0,0 +1,62 @@
'''
iOS Storage Path
--------------------
'''
from plyer.facades import StoragePath
from pyobjus import autoclass
import os
NSFileManager = autoclass('NSFileManager')
# Directory constants (NSSearchPathDirectory enumeration)
NSApplicationDirectory = 1
NSDocumentDirectory = 9
NSDownloadsDirectory = 15
NSMoviesDirectory = 17
NSMusicDirectory = 18
NSPicturesDirectory = 19
class iOSStoragePath(StoragePath):
def __init__(self):
self.defaultManager = NSFileManager.defaultManager()
def _get_home_dir(self):
return os.path.expanduser('~/')
def _get_external_storage_dir(self):
return 'This feature is not implemented for this platform.'
def _get_root_dir(self):
return 'This feature is not implemented for this platform.'
def _get_documents_dir(self):
return self.defaultManager.URLsForDirectory_inDomains_(
NSDocumentDirectory, 1).firstObject().absoluteString.UTF8String()
def _get_downloads_dir(self):
return self.defaultManager.URLsForDirectory_inDomains_(
NSDownloadsDirectory, 1).firstObject().absoluteString.UTF8String()
def _get_videos_dir(self):
return self.defaultManager.URLsForDirectory_inDomains_(
NSMoviesDirectory, 1).firstObject().absoluteString.UTF8String()
def _get_music_dir(self):
return self.defaultManager.URLsForDirectory_inDomains_(
NSMusicDirectory, 1).firstObject().absoluteString.UTF8String()
def _get_pictures_dir(self):
return self.defaultManager.URLsForDirectory_inDomains_(
NSPicturesDirectory, 1).firstObject().absoluteString.UTF8String()
def _get_application_dir(self):
return self.defaultManager.URLsForDirectory_inDomains_(
NSApplicationDirectory, 1).firstObject().absoluteString.\
UTF8String()
def instance():
return iOSStoragePath()

View file

@ -0,0 +1,37 @@
from pyobjus import autoclass, objc_str
from pyobjus.dylib_manager import load_framework
from plyer.facades import TTS
load_framework('/System/Library/Frameworks/AVFoundation.framework')
AVSpeechUtterance = autoclass('AVSpeechUtterance')
AVSpeechSynthesizer = autoclass('AVSpeechSynthesizer')
AVSpeechSynthesisVoice = autoclass('AVSpeechSynthesisVoice')
class iOSTextToSpeech(TTS):
def __init__(self):
super().__init__()
self.synth = AVSpeechSynthesizer.alloc().init()
self.voice = None
def _set_locale(self, locale="en-US"):
self.voice = AVSpeechSynthesisVoice.voiceWithLanguage_(
objc_str(locale)
)
def _speak(self, **kwargs):
message = kwargs.get('message')
if(not self.voice):
self._set_locale()
utterance = \
AVSpeechUtterance.speechUtteranceWithString_(objc_str(message))
utterance.voice = self.voice
self.synth.speakUtterance_(utterance)
def instance():
return iOSTextToSpeech()

View file

@ -0,0 +1,27 @@
'''
Module of iOS API for plyer.uniqueid.
'''
from pyobjus import autoclass
from pyobjus.dylib_manager import load_framework
from plyer.facades import UniqueID
load_framework('/System/Library/Frameworks/UIKit.framework')
UIDevice = autoclass('UIDevice')
class IOSUniqueID(UniqueID):
'''
Implementation of iOS uniqueid API.
'''
def _get_uid(self):
uuid = UIDevice.currentDevice().identifierForVendor.UUIDString()
return uuid.UTF8String()
def instance():
'''
Instance for facade proxy.
'''
return IOSUniqueID()

View file

@ -0,0 +1,43 @@
'''Implementation Vibrator for iOS.
Install: Add AudioToolbox framework to your application.
'''
import ctypes
from plyer.facades import Vibrator
class IosVibrator(Vibrator):
'''iOS Vibrator class.
iOS doesn't support any feature.
All time, pattern, repetition are ignored.
'''
def __init__(self):
super().__init__()
try:
self._func = ctypes.CDLL(None).AudioServicesPlaySystemSound
except AttributeError:
self._func = None
def _vibrate(self, time=None, **kwargs):
# kSystemSoundID_Vibrate is 0x00000FFF
self._func(0xFFF)
def _pattern(self, pattern=None, repeat=None, **kwargs):
self._vibrate()
def _exists(self, **kwargs):
return self._func is not None
def _cancel(self, **kwargs):
pass
def instance():
'''Returns Vibrator
:return: instance of class IosVibrator
'''
return IosVibrator()

View file

View file

@ -0,0 +1,35 @@
'''
Linux accelerometer
---------------------
'''
from plyer.facades import Accelerometer
import glob
import re
class LinuxAccelerometer(Accelerometer):
def _enable(self):
pass
def _disable(self):
pass
def _get_acceleration(self):
try:
pos = glob.glob("/sys/devices/platform/*/position")[0]
except IndexError:
raise Exception('Could not enable accelerometer!')
with open(pos, "r") as p:
t = p.read()
coords = re.findall(r"[-]?\d+\.?\d*", t)
# Apparently the acceleration on sysfs goes from -1000 to 1000.
# I divide it by 100 to make it equivalent to Android.
# The negative is because the coordinates are inverted on Linux
return [float(i) / -100 for i in coords]
def instance():
return LinuxAccelerometer()

View file

@ -0,0 +1,102 @@
'''
Module of Linux API for plyer.battery.
'''
from math import floor
from os import environ
from os.path import exists, join
from subprocess import Popen, PIPE
from plyer.facades import Battery
from plyer.utils import whereis_exe
class LinuxBattery(Battery):
'''
Implementation of Linux battery API via accessing the sysclass power_supply
path from the kernel.
'''
def _get_state(self):
status = {"isCharging": None, "percentage": None}
kernel_bat_path = join('/sys', 'class', 'power_supply', 'BAT0')
uevent = join(kernel_bat_path, 'uevent')
with open(uevent) as fle:
lines = [
line.decode('utf-8').strip()
for line in fle.readlines()
]
output = {
line.split('=')[0]: line.split('=')[1]
for line in lines
}
is_charging = output['POWER_SUPPLY_STATUS'] == 'Charging'
total = float(output['POWER_SUPPLY_CHARGE_FULL'])
now = float(output['POWER_SUPPLY_CHARGE_NOW'])
capacity = floor(now / total * 100)
status['percentage'] = capacity
status['isCharging'] = is_charging
return status
class UPowerBattery(Battery):
'''
Implementation of UPower battery API.
'''
def _get_state(self):
# if no LANG specified, return empty string
old_lang = environ.get('LANG', '')
environ['LANG'] = 'C'
status = {"isCharging": None, "percentage": None}
# We are supporting only one battery now
# this will fail if there is no object with such path,
# however it's safer than 'upower -d' which provides
# multiple unrelated 'state' and 'percentage' keywords
dev = "/org/freedesktop/UPower/devices/battery_BAT0"
upower_process = Popen(
["upower", "--show-info", dev],
stdout=PIPE
)
output = upower_process.communicate()[0].decode()
environ['LANG'] = old_lang
if not output:
return status
state = percentage = None
for line in output.splitlines():
if 'state' in line:
state = line.rpartition(':')[-1].strip()
if 'percentage' in line:
percentage = line.rpartition(':')[-1].strip()[:-1]
# switching decimal comma to dot
# (different LC_NUMERIC locale)
percentage = float(
percentage.replace(',', '.')
)
if state:
status['isCharging'] = state == "charging"
status['percentage'] = percentage
return status
def instance():
'''
Instance for facade proxy.
'''
import sys
if whereis_exe('upower'):
return UPowerBattery()
sys.stderr.write("upower not found.")
if exists(join('/sys', 'class', 'power_supply', 'BAT0')):
return LinuxBattery()
return Battery()

View file

@ -0,0 +1,29 @@
'''
Linux Brightness
----------------
'''
from plyer.facades import Brightness
import subprocess
import os
class LinuxBrightness(Brightness):
def __init__(self):
if os.system("which xbacklight"):
msg = ("It looks like 'xbacklight' is not installed. Try "
"installing it with your distribution's package manager.")
raise Exception(msg)
def _current_level(self):
cr_level = subprocess.check_output(["xbacklight", "-get"])
return str(cr_level)
def _set_level(self, level):
subprocess.call(["xbacklight", "-set", str(level)])
def instance():
return LinuxBrightness()

View file

@ -0,0 +1,116 @@
'''
Module of Linux API for plyer.cpu.
'''
from os.path import join
from os import environ, listdir
from subprocess import Popen, PIPE
from plyer.facades import CPU
from plyer.utils import whereis_exe
class LinuxCPU(CPU):
'''
Implementation of Linux CPU API.
'''
def _sockets(self):
# physical CPU sockets (or slots) on motherboard
sockets = [] # list of CPU ids from kernel
# open Linux kernel data file for CPU
with open('/proc/cpuinfo', 'rb') as fle:
lines = fle.readlines()
# go through the lines and obtain physical CPU ids
for line in lines:
line = line.decode('utf-8')
if 'physical id' not in line:
continue
cpuid = line.split(':')[1].strip()
sockets.append(cpuid)
# total sockets is the length of unique CPU ids from kernel
sockets = len(set(sockets))
return sockets
def _physical(self):
# cores
physical = [] # list of CPU ids from kernel
# open Linux kernel data file for CPU
with open('/proc/cpuinfo', 'rb') as fle:
lines = fle.readlines()
# go through the lines and obtain CPU core ids
for line in lines:
line = line.decode('utf-8')
if 'core id' not in line:
continue
cpuid = line.split(':')[1].strip()
physical.append(cpuid)
# total cores (socket * core per socket)
# is the length of unique CPU core ids from kernel
physical = len(set(physical))
return physical
def _logical(self):
# cores * threads
logical = None
old_lang = environ.get('LANG', '')
environ['LANG'] = 'C'
_logical = Popen(['nproc', '--all'], stdout=PIPE)
output = _logical.communicate()[0].decode('utf-8').strip()
if output:
logical = int(output)
environ['LANG'] = old_lang
return logical
def _cache(self):
values = {key: 0 for key in ('L1', 'L2', 'L3')}
cpu_path = join('/sys', 'devices', 'system', 'cpu')
# get present cores from kernel device
with open(join(cpu_path, 'present')) as fle:
present = fle.read().decode('utf-8')
present = present.strip().split('-')
if len(present) == 2:
present = range(int(present[1]) + 1)
else:
present = [present[0]]
cores = ['cpu{}'.format(i) for i in present]
for core in cores:
indicies = [
# get 'indexN' files from 'cache' folder assuming
# the filename is in range index0 to index99
# in case a wild 'index_whatevercontent' file appears
fle for fle in listdir(join(cpu_path, core, 'cache'))
if fle.startswith('index') and len(fle) <= len('index') + 2
]
for index in indicies:
index_type = join(cpu_path, core, 'cache', index, 'level')
with open(index_type, 'rb') as fle:
cache_level = fle.read().decode('utf-8').strip()
values['L{}'.format(cache_level)] += 1
return values
@staticmethod
def _numa():
return
def instance():
'''
Instance for facade proxy.
'''
import sys
if whereis_exe('nproc'):
return LinuxCPU()
sys.stderr.write("nproc not found.")
return CPU()

View file

@ -0,0 +1,23 @@
'''
Module of Linux API for plyer.devicename.
'''
import socket
from plyer.facades import DeviceName
class LinuxDeviceName(DeviceName):
'''
Implementation of Linux DeviceName API.
'''
def _get_device_name(self):
hostname = socket.gethostname()
return hostname
def instance():
'''
Instance for facade proxy.
'''
return LinuxDeviceName()

View file

@ -0,0 +1,47 @@
'''
Module of Linux API for plyer.email.
'''
import subprocess
try:
from urllib.parse import quote
except ImportError:
from urllib import quote
from plyer.facades import Email
from plyer.utils import whereis_exe
class LinuxEmail(Email):
'''
Implementation of Linux email API.
'''
def _send(self, **kwargs):
recipient = kwargs.get('recipient')
subject = kwargs.get('subject')
text = kwargs.get('text')
uri = "mailto:"
if recipient:
uri += str(recipient)
if subject:
uri += "?" if "?" not in uri else "&"
uri += "subject="
uri += quote(str(subject))
if text:
uri += "?" if "?" not in uri else "&"
uri += "body="
uri += quote(str(text))
subprocess.Popen(["xdg-open", uri])
def instance():
'''
Instance for facade proxy.
'''
import sys
if whereis_exe('xdg-open'):
return LinuxEmail()
sys.stderr.write("xdg-open not found.")
return Email()

View file

@ -0,0 +1,269 @@
'''
Linux file chooser
------------------
'''
from plyer.facades import FileChooser
from distutils.spawn import find_executable as which
import os
import subprocess as sp
import time
class SubprocessFileChooser:
'''A file chooser implementation that allows using
subprocess back-ends.
Normally you only need to override _gen_cmdline, executable,
separator and successretcode.
'''
executable = ""
'''The name of the executable of the back-end.
'''
separator = "|"
'''The separator used by the back-end. Override this for automatic
splitting, or override _split_output.
'''
successretcode = 0
'''The return code which is returned when the user doesn't close the
dialog without choosing anything, or when the app doesn't crash.
'''
path = None
multiple = False
filters = []
preview = False
title = None
icon = None
show_hidden = False
def __init__(self, *args, **kwargs):
self._handle_selection = kwargs.pop(
'on_selection', self._handle_selection
)
# Simulate Kivy's behavior
for i in kwargs:
setattr(self, i, kwargs[i])
@staticmethod
def _handle_selection(selection):
'''
Dummy placeholder for returning selection from chooser.
'''
return selection
_process = None
def _run_command(self, cmd):
self._process = sp.Popen(cmd, stdout=sp.PIPE)
while True:
ret = self._process.poll()
if ret is not None:
if ret == self.successretcode:
out = self._process.communicate()[0].strip().decode('utf8')
return self._set_and_return_selection(
self._split_output(out))
else:
return self._set_and_return_selection(None)
time.sleep(0.1)
def _set_and_return_selection(self, value):
self.selection = value
self._handle_selection(value)
return value
def _split_output(self, out):
'''This methods receives the output of the back-end and turns
it into a list of paths.
'''
return out.split(self.separator)
def _gen_cmdline(self):
'''Returns the command line of the back-end, based on the current
properties. You need to override this.
'''
raise NotImplementedError()
def run(self):
return self._run_command(self._gen_cmdline())
class ZenityFileChooser(SubprocessFileChooser):
'''A FileChooser implementation using Zenity (on GNU/Linux).
Not implemented features:
* show_hidden
* preview
'''
executable = "zenity"
separator = "|"
successretcode = 0
def _gen_cmdline(self):
cmdline = [
which(self.executable),
"--file-selection",
"--confirm-overwrite"
]
if self.multiple:
cmdline += ["--multiple"]
if self.mode == "save":
cmdline += ["--save"]
elif self.mode == "dir":
cmdline += ["--directory"]
if self.path:
cmdline += ["--filename", self.path]
if self.title:
cmdline += ["--name", self.title]
if self.icon:
cmdline += ["--window-icon", self.icon]
for f in self.filters:
if type(f) == str:
cmdline += ["--file-filter", f]
else:
cmdline += [
"--file-filter",
"{name} | {flt}".format(name=f[0], flt=" ".join(f[1:]))
]
return cmdline
class KDialogFileChooser(SubprocessFileChooser):
'''A FileChooser implementation using KDialog (on GNU/Linux).
Not implemented features:
* show_hidden
* preview
'''
executable = "kdialog"
separator = "\n"
successretcode = 0
def _gen_cmdline(self):
cmdline = [which(self.executable)]
filt = []
for f in self.filters:
if type(f) == str:
filt += [f]
else:
filt += list(f[1:])
if self.mode == "dir":
cmdline += [
"--getexistingdirectory",
(self.path if self.path else os.path.expanduser("~"))
]
elif self.mode == "save":
cmdline += [
"--getsavefilename",
(self.path if self.path else os.path.expanduser("~")),
" ".join(filt)
]
else:
cmdline += [
"--getopenfilename",
(self.path if self.path else os.path.expanduser("~")),
" ".join(filt)
]
if self.multiple:
cmdline += ["--multiple", "--separate-output"]
if self.title:
cmdline += ["--title", self.title]
if self.icon:
cmdline += ["--icon", self.icon]
return cmdline
class YADFileChooser(SubprocessFileChooser):
'''A NativeFileChooser implementation using YAD (on GNU/Linux).
Not implemented features:
* show_hidden
'''
executable = "yad"
separator = "|?|"
successretcode = 0
def _gen_cmdline(self):
cmdline = [
which(self.executable),
"--file-selection",
"--confirm-overwrite",
"--geometry",
"800x600+150+150"
]
if self.multiple:
cmdline += ["--multiple", "--separator", self.separator]
if self.mode == "save":
cmdline += ["--save"]
elif self.mode == "dir":
cmdline += ["--directory"]
if self.preview:
cmdline += ["--add-preview"]
if self.path:
cmdline += ["--filename", self.path]
if self.title:
cmdline += ["--name", self.title]
if self.icon:
cmdline += ["--window-icon", self.icon]
for f in self.filters:
if type(f) == str:
cmdline += ["--file-filter", f]
else:
cmdline += [
"--file-filter",
"{name} | {flt}".format(name=f[0], flt=" ".join(f[1:]))
]
return cmdline
CHOOSERS = {
"gnome": ZenityFileChooser,
"kde": KDialogFileChooser,
"yad": YADFileChooser
}
class LinuxFileChooser(FileChooser):
'''FileChooser implementation for GNu/Linux. Accepts one additional
keyword argument, *desktop_override*, which, if set, overrides the
back-end that will be used. Set it to "gnome" for Zenity, to "kde"
for KDialog and to "yad" for YAD (Yet Another Dialog).
If set to None or not set, a default one will be picked based on
the running desktop environment and installed back-ends.
'''
desktop = None
if (str(os.environ.get("XDG_CURRENT_DESKTOP")).lower() == "kde"
and which("kdialog")):
desktop = "kde"
elif (str(os.environ.get("DESKTOP_SESSION")).lower() == "trinity"
and which('kdialog')):
desktop = "kde"
elif which("yad"):
desktop = "yad"
elif which("zenity"):
desktop = "gnome"
def _file_selection_dialog(self, desktop_override=desktop, **kwargs):
if not desktop_override:
desktop_override = self.desktop
# This means we couldn't find any back-end
if not desktop_override:
raise OSError("No back-end available. Please install one.")
chooser = CHOOSERS[desktop_override]
c = chooser(**kwargs)
return c.run()
def instance():
return LinuxFileChooser()

View file

@ -0,0 +1,19 @@
try:
import keyring
except ImportError:
raise NotImplementedError()
from plyer.facades import Keystore
class LinuxKeystore(Keystore):
def _set_key(self, servicename, key, value, **kwargs):
keyring.set_password(servicename, key, value)
def _get_key(self, servicename, key, **kwargs):
return keyring.get_password(servicename, key)
def instance():
return LinuxKeystore()

View file

@ -0,0 +1,108 @@
'''
Module of Linux API for plyer.notification.
'''
import warnings
import subprocess
from plyer.facades import Notification
from plyer.utils import whereis_exe
import os
class NotifyDesktopPortals(Notification):
'''
Implementation of xdg-desktop-portals API.
'''
def _notify(self, **kwargs):
title = kwargs.get("title", "title")
body = kwargs.get("message", "body")
subprocess.run([
"gdbus", "call", "--session", "--dest",
"org.freedesktop.portal.Desktop",
"--object-path", "/org/freedesktop/portal/desktop", "--method",
"org.freedesktop.portal.Notification.AddNotification", "",
"{'title': <'" + title + "'>, 'body': <'" + body + "'>}"
], stdout=subprocess.DEVNULL)
class NotifySendNotification(Notification):
'''
Implementation of Linux notification API
using notify-send binary.
'''
def _notify(self, **kwargs):
icon = kwargs.get('icon', '')
title = kwargs.get('title', 'title')
hint = kwargs.get('hint', 'string::')
message = kwargs.get('message', 'body')
category = kwargs.get('category', '')
app_name = kwargs.get('app_name', '')
urgency = kwargs.get('urgency', 'normal')
expire_time = kwargs.get('expire_time', '0')
notify_send_args = (title,
message,
"-i", icon,
"-h", hint,
"-u", urgency,
"-c", category,
"-a", app_name,
"-t", expire_time)
subprocess.call(["notify-send", *notify_send_args])
class NotifyDbus(Notification):
'''
Implementation of Linux notification API
using dbus library and dbus-python wrapper.
'''
def _notify(self, **kwargs):
summary = kwargs.get('title', "title")
body = kwargs.get('message', "body")
app_name = kwargs.get('app_name', '')
app_icon = kwargs.get('app_icon', '')
timeout = kwargs.get('timeout', 10)
actions = kwargs.get('actions', [])
hints = kwargs.get('hints', {})
replaces_id = kwargs.get('replaces_id', 0)
_bus_name = 'org.freedesktop.Notifications'
_object_path = '/org/freedesktop/Notifications'
_interface_name = _bus_name
import dbus
session_bus = dbus.SessionBus()
obj = session_bus.get_object(_bus_name, _object_path)
interface = dbus.Interface(obj, _interface_name)
interface.Notify(
app_name, replaces_id, app_icon,
summary, body, actions,
hints, timeout * 1000
)
def instance():
'''
Instance for facade proxy.
'''
if os.path.isdir("/app"):
# Flatpak
return NotifyDesktopPortals()
try:
import dbus # noqa: F401
return NotifyDbus()
except ImportError:
msg = ("The Python dbus package is not installed.\n"
"Try installing it with your distribution's package manager, "
"it is usually called python-dbus or python3-dbus, but you "
"might have to try dbus-python instead, e.g. when using pip.")
warnings.warn(msg)
if whereis_exe('notify-send'):
return NotifySendNotification()
warnings.warn("notify-send not found.")
return Notification()

View file

@ -0,0 +1,27 @@
import subprocess as sb
from plyer.facades import Orientation
class LinuxOrientation(Orientation):
def _set_landscape(self, **kwargs):
self.rotate = 'normal'
self.screen = sb.check_output(
"xrandr -q | grep ' connected' | head -n 1 | cut -d ' ' -f1",
shell=True
)
self.screen = self.screen.decode('utf-8').split('\n')[0]
sb.call(["xrandr", "--output", self.screen, "--rotate", self.rotate])
def _set_portrait(self, **kwargs):
self.rotate = 'left'
self.screen = sb.check_output(
"xrandr -q | grep ' connected' | head -n 1 | cut -d ' ' -f1",
shell=True
)
self.screen = self.screen.decode('utf-8').split('\n')[0]
sb.call(["xrandr", "--output", self.screen, "--rotate", self.rotate])
def instance():
return LinuxOrientation()

View file

@ -0,0 +1,37 @@
from subprocess import Popen, PIPE
from plyer.facades import Processors
from plyer.utils import whereis_exe
from os import environ
class LinuxProcessors(Processors):
def _get_state(self):
old_lang = environ.get('LANG')
environ['LANG'] = 'C'
status = {"Number_of_Processors": None}
dev = "--all"
nproc_process = Popen(
["nproc", dev],
stdout=PIPE
)
output = nproc_process.communicate()[0]
environ['LANG'] = old_lang
if not output:
return status
status['Number_of_Processors'] = output.rstrip()
return status
def instance():
import sys
if whereis_exe('nproc'):
return LinuxProcessors()
sys.stderr.write("nproc not found.")
return Processors()

View file

@ -0,0 +1,29 @@
import subprocess
from os.path import join
from plyer.facades import Screenshot
from plyer.utils import whereis_exe
from plyer.platforms.linux.storagepath import LinuxStoragePath
class LinuxScreenshot(Screenshot):
def __init__(self, file_path=None):
default_path = join(
LinuxStoragePath().get_pictures_dir(),
'screenshot.xwd'
)
super().__init__(file_path or default_path)
def _capture(self):
# call xwd and redirect bytes from stdout to file
with open(self.file_path, 'wb') as fle:
subprocess.call([
# quiet, full screen root window
'xwd', '-silent', '-root',
], stdout=fle)
def instance():
if whereis_exe('xwd'):
return LinuxScreenshot()
else:
return Screenshot()

View file

@ -0,0 +1,72 @@
'''
Linux Storage Path
--------------------
'''
from plyer.facades import StoragePath
from os.path import expanduser, dirname, abspath, join, exists
# Default paths for each name
USER_DIRS = "/.config/user-dirs.dirs"
PATHS = {
"DESKTOP": "Desktop",
"DOCUMENTS": "Documents",
"DOWNLOAD": "Downloads",
"MUSIC": "Music",
"PICTURES": "Pictures",
"VIDEOS": "Videos"
}
class LinuxStoragePath(StoragePath):
def _get_from_user_dirs(self, name):
home_dir = self._get_home_dir()
default = join(home_dir, PATHS[name])
user_dirs = join(home_dir, USER_DIRS)
if not exists(user_dirs):
return default
with open(user_dirs, "r") as f:
for line in f.readlines():
if line.startswith("XDG_" + name):
return line.split('"')[1]
return default
def _get_home_dir(self):
return expanduser('~')
def _get_external_storage_dir(self):
return "/media/" + self._get_home_dir().split("/")[-1]
def _get_root_dir(self):
return "/"
def _get_documents_dir(self):
directory = self._get_from_user_dirs("DOCUMENTS")
return directory.replace("$HOME", self._get_home_dir())
def _get_downloads_dir(self):
directory = self._get_from_user_dirs("DOWNLOAD")
return directory.replace("$HOME", self._get_home_dir())
def _get_videos_dir(self):
directory = self._get_from_user_dirs("VIDEOS")
return directory.replace("$HOME", self._get_home_dir())
def _get_music_dir(self):
directory = self._get_from_user_dirs("MUSIC")
return directory.replace("$HOME", self._get_home_dir())
def _get_pictures_dir(self):
directory = self._get_from_user_dirs("PICTURES")
return directory.replace("$HOME", self._get_home_dir())
def _get_application_dir(self):
return dirname(abspath(__name__))
def instance():
return LinuxStoragePath()

View file

@ -0,0 +1,25 @@
import subprocess
from plyer.facades import TTS
from plyer.utils import whereis_exe
class EspeakTextToSpeech(TTS):
''' Speaks using the espeak program
'''
def _speak(self, **kwargs):
subprocess.call(["espeak", kwargs.get('message')])
class FliteTextToSpeech(TTS):
''' Speaks using the flite program
'''
def _speak(self, **kwargs):
subprocess.call(["flite", "-t", kwargs.get('message'), "play"])
def instance():
if whereis_exe('espeak'):
return EspeakTextToSpeech()
elif whereis_exe('flite'):
return FliteTextToSpeech()
return TTS()

View file

@ -0,0 +1,47 @@
'''
Module of Linux API for plyer.uniqueid.
'''
from os import environ
from subprocess import Popen, PIPE
from plyer.facades import UniqueID
from plyer.utils import whereis_exe
class LinuxUniqueID(UniqueID):
'''
Implementation of Linux uniqueid API.
'''
def _get_uid(self):
old_lang = environ.get('LANG')
environ['LANG'] = 'C'
stdout = Popen(
["lshw", "-quiet"],
stdout=PIPE, stderr=PIPE
).communicate()[0].decode('utf-8')
output = u''
for line in stdout.splitlines():
if 'serial:' not in line:
continue
output = line
break
environ['LANG'] = old_lang or u''
result = None
if output:
result = output.split()[1]
return result
def instance():
'''
Instance for facade proxy.
'''
import sys
if whereis_exe('lshw'):
return LinuxUniqueID()
sys.stderr.write("lshw not found.")
return UniqueID()

View file

@ -0,0 +1,482 @@
'''
.. note::
This facade depends on `nmcli` (Network Manager command line tool).
It's found in most of the popular GNU/Linux distributions. Support for other
backends is not provided yet.
'''
from subprocess import Popen, PIPE, call
from plyer.facades import Wifi
from plyer.utils import whereis_exe, deprecated
try:
import wifi
except ModuleNotFoundError as err:
raise ModuleNotFoundError(
"python-wifi not installed. try:" +
"`pip install --user wifi`.") from err
class NMCLIWifi(Wifi):
'''
.. versionadded:: 1.4.0
'''
def __init__(self, *args, **kwargs):
'''
.. versionadded:: 1.4.0
'''
super().__init__(*args, **kwargs)
self.names = {}
@property
def interfaces(self):
'''
Get all the available interfaces for WiFi.
.. versionadded:: 1.4.0
Tested with nmcli 1.2.6.
'''
if not self._is_enabled():
self._enable()
# fetch the devices
proc = Popen([
'nmcli', '--terse',
'--fields', 'DEVICE,TYPE',
'device'
], stdout=PIPE)
lines = proc.communicate()[0].decode('utf-8').splitlines()
# filter devices by type
interfaces = []
for line in lines:
# bad escape from nmcli's side :<
line = line.replace('\\:', '$$')
device, dtype = line.split(':')
if dtype != 'wifi':
continue
interfaces.append(device.replace('$$', ':'))
# return wifi interfaces
return interfaces
def _is_enabled(self):
'''
Return the status of WiFi device.
.. versionadded:: 1.4.0
Tested with nmcli 1.2.6.
'''
output = Popen(
["nmcli", "radio", "wifi"],
stdout=PIPE
).communicate()[0].decode('utf-8')
if output.split()[0] == 'enabled':
return True
return False
def _is_connected(self, interface=None):
'''
Return whether a specified interface is connected to a WiFi network.
.. versionadded:: 1.4.0
Tested with nmcli 1.2.6.
'''
if not self._is_enabled():
self._enable()
if not interface:
interface = self.interfaces[0]
# fetch all devices
proc = Popen([
'nmcli', '--terse',
'--fields', 'DEVICE,TYPE,STATE',
'device'
], stdout=PIPE)
lines = proc.communicate()[0].decode('utf-8').splitlines()
# filter by wifi type and interface
connected = False
for line in lines:
line = line.replace('\\:', '$$')
device, dtype, state = line.split(':')
device = device.replace('$$', ':')
if dtype != 'wifi':
continue
if device != interface:
continue
if state == 'connected':
connected = True
return connected
def _start_scanning(self, interface=None):
'''
Start scanning for available Wi-Fi networks
for the specified interface.
.. versionadded:: 1.4.0
Tested with nmcli 1.2.6.
'''
if not self._is_enabled():
self._enable()
if not interface:
interface = self.interfaces[0]
# force rescan for fresh data
call(['nmcli', 'device', 'wifi', 'rescan', 'ifname', interface])
# get properties
fields = [
'SSID', 'BSSID', 'MODE', 'CHAN', 'FREQ',
'BARS', 'RATE', 'SIGNAL', 'SECURITY'
]
# fetch all networks for interface
output = Popen([
'nmcli', '--terse',
'--fields', ','.join(fields),
'device', 'wifi', 'list', 'ifname', interface
], stdout=PIPE).communicate()[0].decode('utf-8')
# parse output
for line in output.splitlines():
line = line.replace('\\:', '$$')
row = {
field: value
for field, value in zip(fields, line.split(':'))
}
row['BSSID'] = row['BSSID'].replace('$$', ':')
self.names[row['SSID']] = row
def _get_network_info(self, name):
'''
Get all the network information by network's name (SSID).
.. versionadded:: 1.4.0
Tested with nmcli 1.2.6.
'''
if not self.names:
self._start_scanning()
ret_list = {}
ret_list['ssid'] = self.names[name]['SSID']
ret_list['signal'] = self.names[name]['SIGNAL']
bars = len(self.names[name]['BARS'])
ret_list['quality'] = '{}/100'.format(bars / 5.0 * 100)
ret_list['frequency'] = self.names[name]['FREQ']
ret_list['bitrates'] = self.names[name]['RATE']
# wpa1, wpa2, wpa1 wpa2, wep, (none), perhaps something else
security = self.names[name]['SECURITY'].lower()
ret_list['encrypted'] = True
if 'wpa2' in security:
# wpa2, wpa2+wpa1
ret_list['encryption_type'] = 'wpa2'
elif 'wpa' in security:
ret_list['encryption_type'] = 'wpa'
elif 'wep' in security:
ret_list['encryption_type'] = 'wep'
elif 'none' in security:
ret_list['encrypted'] = False
ret_list['encryption_type'] = 'none'
else:
ret_list['encryption_type'] = security
ret_list['channel'] = int(self.names[name]['CHAN'])
ret_list['address'] = self.names[name]['BSSID']
ret_list['mode'] = self.names[name]['MODE']
return ret_list
def _get_available_wifi(self):
'''
Return the names of all found networks.
.. versionadded:: 1.4.0
Tested with nmcli 1.2.6.
'''
if not self.names:
self._start_scanning()
return list(self.names.keys())
def _connect(self, network, parameters, interface=None):
'''
Connect a specific interface to a WiFi network.
Expects 2 parameters:
- SSID of the network
- parameters: dict
- password: string or None
.. versionadded:: 1.4.0
Tested with nmcli 1.2.6.
'''
self._enable()
if not interface:
interface = self.interfaces[0]
password = parameters.get('password')
command = [
'nmcli', 'device', 'wifi', 'connect', network,
'ifname', interface
]
if password:
command += ['password', password]
call(command)
def _disconnect(self, interface=None):
'''
Disconnect a specific interface from a WiFi network.
.. versionadded:: 1.4.0
Tested with nmcli 1.2.6.
'''
if not self._is_enabled():
return
if not interface:
interface = self.interfaces[0]
if self._nmcli_version() >= (1, 2, 6):
call(['nmcli', 'device', 'disconnect', interface])
else:
call(['nmcli', 'nm', 'enable', 'false'])
def _enable(self):
'''
Turn WiFi device on.
.. versionadded:: 1.4.0
Tested with nmcli 1.2.6.
'''
call(['nmcli', 'radio', 'wifi', 'on'])
def _disable(self):
'''
Turn WiFi device off.
.. versionadded:: 1.4.0
Tested with nmcli 1.2.6.
'''
call(['nmcli', 'radio', 'wifi', 'off'])
def _nmcli_version(self):
'''
Get nmcli version to prevent executing deprecated commands.
.. versionadded:: 1.4.0
Tested with nmcli 1.2.6.
'''
version = Popen(['nmcli', '-v'], stdout=PIPE)
version = version.communicate()[0].decode('utf-8')
while version and not version[0].isdigit():
version = version[1:]
return tuple(map(int, (version.split('.'))))
@deprecated
class LinuxWifi(Wifi):
'''
.. versionadded:: 1.2.5
'''
def __init__(self, *args, **kwargs):
'''
.. versionadded:: 1.4.0
'''
super().__init__(*args, **kwargs)
self.names = {}
@property
def interfaces(self):
'''
.. versionadded:: 1.4.0
'''
proc = Popen([
'nmcli', '--terse',
'--fields', 'DEVICE,TYPE',
'device'
], stdout=PIPE)
lines = proc.communicate()[0].decode('utf-8').splitlines()
interfaces = []
for line in lines:
device, dtype = line.split(':')
if dtype != 'wifi':
continue
interfaces.append(device)
return interfaces
def _is_enabled(self):
'''
Returns `True` if wifi is enabled else `False`.
.. versionadded:: 1.2.5
.. versionchanged:: 1.3.2
nmcli output is properly decoded to unicode
'''
enbl = Popen(["nmcli", "radio", "wifi"], stdout=PIPE, stderr=PIPE)
if enbl.communicate()[0].split()[0].decode('utf-8') == "enabled":
return True
return False
def _is_connected(self, interface=None):
'''
.. versionadded:: 1.4.0
'''
if not interface:
interface = self.interfaces[0]
proc = Popen([
'nmcli', '--terse',
'--fields', 'DEVICE,TYPE,STATE',
'device'
], stdout=PIPE)
lines = proc.communicate()[0].decode('utf-8').splitlines()
connected = False
for line in lines:
device, dtype, state = line.split(':')
if dtype != 'wifi':
continue
if device != interface:
continue
if state == 'connected':
connected = True
return connected
def _start_scanning(self, interface=None):
'''
Returns all the network information.
.. versionadded:: 1.2.5
.. versionchanged:: 1.3.0
scan only if wifi is enabled
'''
if not interface:
interface = self.interfaces[0]
if self._is_enabled():
list_ = list(wifi.Cell.all(interface))
for i in range(len(list_)):
self.names[list_[i].ssid] = list_[i]
else:
raise Exception('Wifi not enabled.')
def _get_network_info(self, name):
'''
Starts scanning for available Wi-Fi networks and returns the available,
devices.
.. versionadded:: 1.2.5
'''
ret_list = {}
ret_list['ssid'] = self.names[name].ssid
ret_list['signal'] = self.names[name].signal
ret_list['quality'] = self.names[name].quality
ret_list['frequency'] = self.names[name].frequency
ret_list['bitrates'] = self.names[name].bitrates
ret_list['encrypted'] = self.names[name].encrypted
ret_list['channel'] = self.names[name].channel
ret_list['address'] = self.names[name].address
ret_list['mode'] = self.names[name].mode
if not ret_list['encrypted']:
return ret_list
else:
ret_list['encryption_type'] = self.names[name].encryption_type
return ret_list
def _get_available_wifi(self):
'''
Returns the name of available networks.
.. versionadded:: 1.2.5
.. versionchanged:: 1.4.0
return a proper list of elements instead of dict_keys
'''
return list(self.names.keys())
def _connect(self, network, parameters, interface=None):
'''
Expects 2 parameters:
- name/ssid of the network.
- parameters: dict type
- password: string or None
.. versionadded:: 1.2.5
'''
if not interface:
interface = self.interfaces[0]
result = None
try:
self._enable()
finally:
password = parameters['password']
cell = self.names[network]
result = wifi.Scheme.for_cell(
interface, network, cell, password
)
return result
def _disconnect(self, interface=None):
'''
Disconnect all the networks managed by Network manager.
.. versionadded:: 1.2.5
'''
if not interface:
interface = self.interfaces[0]
if self._nmcli_version() >= (1, 2, 6):
call(['nmcli', 'dev', 'disconnect', interface])
else:
call(['nmcli', 'nm', 'enable', 'false'])
def _enable(self):
'''
Wifi interface power state is set to "ON".
.. versionadded:: 1.3.2
'''
return call(['nmcli', 'radio', 'wifi', 'on'])
def _disable(self):
'''
Wifi interface power state is set to "OFF".
.. versionadded:: 1.3.2
'''
return call(['nmcli', 'radio', 'wifi', 'off'])
def _nmcli_version(self):
'''
.. versionadded:: 1.3.2
'''
version = Popen(['nmcli', '-v'], stdout=PIPE)
version = version.communicate()[0].decode('utf-8')
while version and not version[0].isdigit():
version = version[1:]
return tuple(map(int, (version.split('.'))))
def instance():
if whereis_exe('nmcli'):
return NMCLIWifi()
return LinuxWifi()

View file

View file

@ -0,0 +1,25 @@
'''
MacOSX accelerometer
---------------------
'''
from plyer.facades import Accelerometer
from plyer.platforms.macosx.libs import osx_motion_sensor
class OSXAccelerometer(Accelerometer):
def _enable(self):
try:
osx_motion_sensor.get_coord()
except Exception:
raise Exception('Could not enable motion sensor on this macbook!')
def _disable(self):
pass
def _get_acceleration(self):
return osx_motion_sensor.get_coord()
def instance():
return OSXAccelerometer()

View file

@ -0,0 +1,90 @@
from os.path import join
from pyobjus import autoclass
from pyobjus.dylib_manager import INCLUDE, load_framework
from plyer.facades import Audio
from plyer.platforms.macosx.storagepath import OSXStoragePath
load_framework(INCLUDE.Foundation)
load_framework(INCLUDE.AVFoundation)
AVAudioPlayer = autoclass("AVAudioPlayer")
AVAudioRecorder = autoclass("AVAudioRecorder")
AVAudioFormat = autoclass("AVAudioFormat")
NSString = autoclass('NSString')
NSURL = autoclass('NSURL')
NSError = autoclass('NSError').alloc()
class OSXAudio(Audio):
def __init__(self, file_path=None):
default_path = join(
OSXStoragePath().get_music_dir(),
'audio.wav'
)
super().__init__(file_path or default_path)
self._recorder = None
self._player = None
self._current_file = None
def _start(self):
# Conversion of Python file path string to Objective-C NSString
file_path_NSString = NSString.alloc()
file_path_NSString = file_path_NSString.initWithUTF8String_(
self._file_path
)
# Definition of Objective-C NSURL object for the output record file
# specified by NSString file path
file_NSURL = NSURL.alloc()
file_NSURL = file_NSURL.initWithString_(file_path_NSString)
# Internal audio file format specification
af = AVAudioFormat.alloc()
af = af.initWithCommonFormat_sampleRate_channels_interleaved_(
1, 44100.0, 2, True
)
# Audio recorder instance initialization with specified file NSURL
# and audio file format
self._recorder = AVAudioRecorder.alloc()
self._recorder = self._recorder.initWithURL_format_error_(
file_NSURL, af, NSError
)
if not self._recorder:
raise Exception(NSError.code, NSError.domain)
self._recorder.record()
# Setting the currently recorded file as current file
# for using it as a parameter in audio player
self._current_file = file_NSURL
def _stop(self):
if self._recorder:
self._recorder.stop()
self._recorder = None
if self._player:
self._player.stop()
self._player = None
def _play(self):
# Audio player instance initialization with the file NSURL
# of the last recorded audio file
self._player = AVAudioPlayer.alloc()
self._player = self._player.initWithContentsOfURL_error_(
self._current_file, NSError
)
if not self._player:
raise Exception(NSError.code, NSError.domain)
self._player.play()
def instance():
return OSXAudio()

View file

@ -0,0 +1,59 @@
'''
Module of MacOS API for plyer.battery.
'''
from os import environ
from subprocess import Popen, PIPE
from plyer.facades import Battery
from plyer.utils import whereis_exe
class OSXBattery(Battery):
'''
Implementation of MacOS battery API.
'''
def _get_state(self):
old_lang = environ.get('LANG', '')
environ['LANG'] = 'C'
status = {"isCharging": None, "percentage": None}
ioreg_process = Popen(
["ioreg", "-rc", "AppleSmartBattery"],
stdout=PIPE
)
output = ioreg_process.communicate()[0]
environ['LANG'] = old_lang
if not output:
return status
is_charging = max_capacity = current_capacity = None
for line in output.decode('utf-8').splitlines():
if 'IsCharging' in line:
is_charging = line.rpartition('=')[-1].strip()
if 'MaxCapacity' in line:
max_capacity = float(line.rpartition('=')[-1].strip())
if 'CurrentCapacity' in line:
current_capacity = float(line.rpartition('=')[-1].strip())
if is_charging:
status['isCharging'] = is_charging == "Yes"
if current_capacity and max_capacity:
status['percentage'] = 100.0 * current_capacity / max_capacity
return status
def instance():
'''
Instance for facade proxy.
'''
import sys
if whereis_exe('ioreg'):
return OSXBattery()
sys.stderr.write("ioreg not found.")
return Battery()

View file

@ -0,0 +1,54 @@
'''
Module of MacOS API for plyer.bluetooth.
'''
from subprocess import Popen, PIPE
from plyer.facades import Bluetooth
from plyer.utils import whereis_exe
from os import environ
class OSXBluetooth(Bluetooth):
'''
Implementation of MacOS bluetooth API.
'''
def _get_info(self):
old_lang = environ.get('LANG')
environ['LANG'] = 'C'
sys_profiler_process = Popen(
["system_profiler", "SPBluetoothDataType"],
stdout=PIPE
)
stdout = sys_profiler_process.communicate()[0].decode('utf-8')
output = stdout.splitlines()
lines = []
for line in output:
if 'Bluetooth Power' not in line:
continue
lines.append(line)
if old_lang is None:
environ.pop('LANG')
else:
environ['LANG'] = old_lang
if output and len(lines) == 1:
return lines[0].split()[2]
else:
return None
def instance():
'''
Instance for facade proxy.
'''
import sys
if whereis_exe('system_profiler'):
return OSXBluetooth()
sys.stderr.write("system_profiler not found.")
return Bluetooth()

View file

@ -0,0 +1,62 @@
'''
Module of MacOS API for plyer.cpu.
'''
from subprocess import Popen, PIPE
from plyer.facades import CPU
from plyer.utils import whereis_exe
class OSXCPU(CPU):
'''
Implementation of MacOS CPU API.
'''
@staticmethod
def _sockets():
return
def _physical(self):
# cores
physical = None
_physical = Popen(
['sysctl', '-n', 'hw.physicalcpu_max'],
stdout=PIPE
)
output = _physical.communicate()[0].decode('utf-8').strip()
if output:
physical = int(output)
return physical
def _logical(self):
# cores * threads
logical = None
_logical = Popen(
['sysctl', '-n', 'hw.logicalcpu_max'],
stdout=PIPE
)
output = _logical.communicate()[0].decode('utf-8').strip()
if output:
logical = int(output)
return logical
@staticmethod
def _cache():
return
@staticmethod
def _numa():
return
def instance():
'''
Instance for facade proxy.
'''
import sys
if whereis_exe('sysctl'):
return OSXCPU()
sys.stderr.write('sysctl not found.')
return CPU()

View file

@ -0,0 +1,23 @@
'''
Module of MacOSX API for plyer.devicename.
'''
import socket
from plyer.facades import DeviceName
class OSXDeviceName(DeviceName):
'''
Implementation of MacOSX DeviceName API.
'''
def _get_device_name(self):
hostname = socket.gethostname()
return hostname
def instance():
'''
Instance for facade proxy.
'''
return OSXDeviceName()

View file

@ -0,0 +1,49 @@
'''
Module of MacOS API for plyer.email.
'''
import subprocess
try:
from urllib.parse import quote
except ImportError:
from urllib import quote
from plyer.facades import Email
from plyer.utils import whereis_exe
class MacOSXEmail(Email):
'''
Implementation of MacOS email API.
'''
def _send(self, **kwargs):
recipient = kwargs.get('recipient')
subject = kwargs.get('subject')
text = kwargs.get('text')
uri = "mailto:"
if recipient:
uri += str(recipient)
if subject:
uri += "?" if "?" not in uri else "&"
uri += "subject="
uri += quote(str(subject))
if text:
uri += "?" if "?" not in uri else "&"
uri += "body="
uri += quote(str(text))
subprocess.Popen(["open", uri])
def instance():
'''
Instance for facade proxy.
'''
import sys
if whereis_exe('open'):
return MacOSXEmail()
sys.stderr.write("open not found.")
return Email()

View file

@ -0,0 +1,126 @@
'''
Mac OS X file chooser
---------------------
'''
from plyer.facades import FileChooser
from pyobjus import autoclass, objc_arr, objc_str
from pyobjus.dylib_manager import load_framework, INCLUDE
load_framework(INCLUDE.AppKit)
NSURL = autoclass('NSURL')
NSOpenPanel = autoclass('NSOpenPanel')
NSSavePanel = autoclass('NSSavePanel')
NSOKButton = 1
class MacFileChooser:
'''A native implementation of file chooser dialogs using Apple's API
through pyobjus.
Not implemented features:
* filters (partial, wildcards are converted to extensions if possible.
Pass the Mac-specific "use_extensions" if you can provide
Mac OS X-compatible to avoid automatic conversion)
* multiple (only for save dialog. Available in open dialog)
* icon
* preview
'''
mode = "open"
path = None
multiple = False
filters = []
preview = False
title = None
icon = None
show_hidden = False
use_extensions = False
def __init__(self, *args, **kwargs):
self._handle_selection = kwargs.pop(
'on_selection', self._handle_selection
)
# Simulate Kivy's behavior
for i in kwargs:
setattr(self, i, kwargs[i])
@staticmethod
def _handle_selection(selection):
'''
Dummy placeholder for returning selection from chooser.
'''
return selection
def run(self):
panel = None
if self.mode in ("open", "dir", "dir_and_files"):
panel = NSOpenPanel.openPanel()
panel.setCanChooseDirectories_(self.mode != "open")
panel.setCanChooseFiles_(self.mode != "dir")
if self.multiple:
panel.setAllowsMultipleSelection_(True)
elif self.mode == "save":
panel = NSSavePanel.savePanel()
else:
assert False, self.mode
panel.setCanCreateDirectories_(True)
panel.setShowsHiddenFiles_(self.show_hidden)
if self.title:
panel.setTitle_(objc_str(self.title))
# Mac OS X does not support wildcards unlike the other platforms.
# This tries to convert wildcards to "extensions" when possible,
# ans sets the panel to also allow other file types, just to be safe.
if self.filters:
filthies = []
for f in self.filters:
if type(f) == str:
f = (None, f)
for s in f[1:]:
if not self.use_extensions:
if s.strip().endswith("*"):
continue
pystr = s.strip().split("*")[-1].split(".")[-1]
filthies.append(objc_str(pystr))
ftypes_arr = objc_arr(*filthies)
# todo: switch to allowedContentTypes
panel.setAllowedFileTypes_(ftypes_arr)
panel.setAllowsOtherFileTypes_(not self.use_extensions)
if self.path:
url = NSURL.fileURLWithPath_(self.path)
panel.setDirectoryURL_(url)
selection = None
if panel.runModal():
if self.mode == "save" or not self.multiple:
selection = [panel.filename().UTF8String()]
else:
filename = panel.filenames()
selection = [
filename.objectAtIndex_(x).UTF8String()
for x in range(filename.count())]
self._handle_selection(selection)
return selection
class MacOSXFileChooser(FileChooser):
'''
FileChooser implementation for macOS using NSOpenPanel, NSSavePanel.
'''
def _file_selection_dialog(self, **kwargs):
return MacFileChooser(**kwargs).run()
def instance():
return MacOSXFileChooser()

View file

@ -0,0 +1,19 @@
try:
import keyring
except ImportError:
raise NotImplementedError()
from plyer.facades import Keystore
class OSXKeystore(Keystore):
def _set_key(self, servicename, key, value, **kwargs):
keyring.set_password(servicename, key, value)
def _get_key(self, servicename, key, **kwargs):
return keyring.get_password(servicename, key)
def instance():
return OSXKeystore()

View file

@ -0,0 +1,128 @@
import ctypes
from ctypes import (
Structure, cdll, sizeof,
c_int8, c_int16, c_size_t
)
from ctypes.util import find_library
import platform
ERROR_DICT = {
"0": "IOKit Framework not found, is this OSX?",
"-1": "No SMCMotionSensor service",
"-2": "No sms device",
"-3": "Could not open motion sensor device",
"-4": "Did not receive any coordinates"
}
IOKit = cdll.LoadLibrary(find_library('IOKit'))
class data_structure(Structure):
_fields_ = [
('x', c_int16),
('y', c_int16),
('z', c_int16),
('pad', c_int8 * 34),
]
void_p = ctypes.POINTER(ctypes.c_int)
kern_return_t = ctypes.c_int
KERN_SUCCESS = 0
KERN_FUNC = 5 # SMC Motion Sensor on MacBook Pro
mach_port_t = void_p
MACH_PORT_NULL = 0
io_object_t = ctypes.c_int
io_object_t = ctypes.c_int
io_iterator_t = void_p
io_object_t = void_p
io_connect_t = void_p
IOItemCount = ctypes.c_uint
CFMutableDictionaryRef = void_p
def is_os_64bit():
return platform.machine().endswith('64')
def read_sms():
result = kern_return_t()
masterPort = mach_port_t()
result = IOKit.IOMasterPort(MACH_PORT_NULL, ctypes.byref(masterPort))
IOKit.IOServiceMatching.restype = CFMutableDictionaryRef
matchingDictionary = IOKit.IOServiceMatching("SMCMotionSensor")
iterator = io_iterator_t()
result = IOKit.IOServiceGetMatchingServices(
masterPort, matchingDictionary,
ctypes.byref(iterator)
)
if (result != KERN_SUCCESS):
raise ("No coordinates received!")
return -1, None
IOKit.IOIteratorNext.restype = io_object_t
smsDevice = IOKit.IOIteratorNext(iterator)
if not smsDevice:
return -2, None
dataPort = io_connect_t()
result = IOKit.IOServiceOpen(
smsDevice, IOKit.mach_task_self(),
0, ctypes.byref(dataPort)
)
if (result != KERN_SUCCESS):
return -3, None
inStructure = data_structure()
outStructure = data_structure()
if(is_os_64bit() or hasattr(IOKit, 'IOConnectCallStructMethod')):
structureInSize = IOItemCount(sizeof(data_structure))
structureOutSize = c_size_t(sizeof(data_structure))
result = IOKit.IOConnectCallStructMethod(
dataPort, KERN_FUNC,
ctypes.byref(inStructure), structureInSize,
ctypes.byref(outStructure), ctypes.byref(structureOutSize)
)
else:
structureInSize = IOItemCount(sizeof(data_structure))
structureOutSize = IOItemCount(sizeof(data_structure))
result = IOKit.IOConnectMethodStructureIStructureO(
dataPort, KERN_FUNC,
structureInSize, ctypes.byref(structureOutSize),
ctypes.byref(inStructure), ctypes.byref(outStructure)
)
IOKit.IOServiceClose(dataPort)
if (result != KERN_SUCCESS):
return -4, None
return 1, outStructure
def get_coord():
if not IOKit:
raise Exception(ERROR_DICT["0"])
ret, data = read_sms()
if (ret > 0):
if(data.x):
return (data.x, data.y, data.z)
else:
return (None, None, None)
else:
raise Exception(ERROR_DICT[str(ret)])

View file

@ -0,0 +1,21 @@
import ctypes
import os
def NSIterateSearchPaths(directory):
LibraryPath = ("/System/Library/Frameworks/CoreFoundation.framework/"
"Versions/A/CoreFoundation")
CoreFound = ctypes.cdll.LoadLibrary(LibraryPath)
NSStartSearchPathEnumeration = CoreFound.NSStartSearchPathEnumeration
NSGetNextSearchPathEnumeration = CoreFound.NSGetNextSearchPathEnumeration
PATH_MAX = os.pathconf('/', os.pathconf_names['PC_PATH_MAX'])
PATH_ENCODING = 'utf8'
path_buffer = ctypes.create_string_buffer(PATH_MAX)
# paths = [] <- fixme, possible list of paths in directory
state = NSStartSearchPathEnumeration(directory, 1)
while True:
state = NSGetNextSearchPathEnumeration(state, path_buffer)
if state == 0:
break
path = os.path.expanduser(path_buffer.value.decode(PATH_ENCODING))
return path

View file

@ -0,0 +1,51 @@
'''
Module of MacOS API for plyer.notification.
'''
from plyer.facades import Notification
from pyobjus import (
autoclass, protocol, objc_str, ObjcBOOL
)
from pyobjus.dylib_manager import (
load_framework, INCLUDE
)
load_framework(INCLUDE.AppKit)
load_framework(INCLUDE.Foundation)
NSUserNotification = autoclass('NSUserNotification')
NSUserNotificationCenter = autoclass('NSUserNotificationCenter')
class OSXNotification(Notification):
'''
Implementation of MacOS notification API.
'''
def _notify(self, **kwargs):
title = kwargs.get('title', '')
message = kwargs.get('message', '')
app_name = kwargs.get('app_name', '')
# app_icon, timeout, ticker are not supported (yet)
notification = NSUserNotification.alloc().init()
notification.setTitle_(objc_str(title))
notification.setSubtitle_(objc_str(app_name))
notification.setInformativeText_(objc_str(message))
usrnotifctr = NSUserNotificationCenter.defaultUserNotificationCenter()
usrnotifctr.setDelegate_(self)
usrnotifctr.deliverNotification_(notification)
@protocol('NSUserNotificationCenterDelegate')
def userNotificationCenter_shouldPresentNotification_(
self, center, notification):
return ObjcBOOL(True)
def instance():
'''
Instance for facade proxy.
'''
return OSXNotification()

View file

@ -0,0 +1,27 @@
import subprocess
from os.path import join
from plyer.facades import Screenshot
from plyer.utils import whereis_exe
from plyer.platforms.macosx.storagepath import OSXStoragePath
class OSXScreenshot(Screenshot):
def __init__(self, file_path=None):
default_path = join(
OSXStoragePath().get_pictures_dir().replace('file://', ''),
'screenshot.png'
)
super().__init__(file_path or default_path)
def _capture(self):
subprocess.call([
'screencapture',
self.file_path
])
def instance():
if whereis_exe('screencapture'):
return OSXScreenshot()
else:
return Screenshot()

View file

@ -0,0 +1,62 @@
'''
MacOS X Storage Path
--------------------
'''
from plyer.facades import StoragePath
from pyobjus import autoclass
NSFileManager = autoclass('NSFileManager')
# Directory constants (NSSearchPathDirectory enumeration)
NSApplicationDirectory = 1
NSDocumentDirectory = 9
NSDownloadsDirectory = 15
NSMoviesDirectory = 17
NSMusicDirectory = 18
NSPicturesDirectory = 19
class OSXStoragePath(StoragePath):
def __init__(self):
self.defaultManager = NSFileManager.defaultManager()
def _get_home_dir(self):
home_dir_NSURL = self.defaultManager.homeDirectoryForCurrentUser
return home_dir_NSURL.absoluteString.UTF8String()
def _get_external_storage_dir(self):
return 'Method not implemented for current platform.'
def _get_root_dir(self):
return '/'
def _get_documents_dir(self):
return self.defaultManager.URLsForDirectory_inDomains_(
NSDocumentDirectory, 1).firstObject().absoluteString.UTF8String()
def _get_downloads_dir(self):
return self.defaultManager.URLsForDirectory_inDomains_(
NSDownloadsDirectory, 1).firstObject().absoluteString.UTF8String()
def _get_videos_dir(self):
return self.defaultManager.URLsForDirectory_inDomains_(
NSMoviesDirectory, 1).firstObject().absoluteString.UTF8String()
def _get_music_dir(self):
return self.defaultManager.URLsForDirectory_inDomains_(
NSMusicDirectory, 1).firstObject().absoluteString.UTF8String()
def _get_pictures_dir(self):
return self.defaultManager.URLsForDirectory_inDomains_(
NSPicturesDirectory, 1).firstObject().absoluteString.UTF8String()
def _get_application_dir(self):
return self.defaultManager.URLsForDirectory_inDomains_(
NSApplicationDirectory, 1
).firstObject().absoluteString.UTF8String()
def instance():
return OSXStoragePath()

View file

@ -0,0 +1,25 @@
import subprocess
from plyer.facades import TTS
from plyer.utils import whereis_exe
class NativeSayTextToSpeech(TTS):
'''Speaks using the native OSX 'say' command
'''
def _speak(self, **kwargs):
subprocess.call(["say", kwargs.get('message')])
class EspeakTextToSpeech(TTS):
'''Speaks using the espeak program
'''
def _speak(self, **kwargs):
subprocess.call(["espeak", kwargs.get('message')])
def instance():
if whereis_exe('say'):
return NativeSayTextToSpeech()
elif whereis_exe('espeak'):
return EspeakTextToSpeech()
return TTS()

View file

@ -0,0 +1,47 @@
'''
Module of MacOS API for plyer.uniqueid.
'''
from os import environ
from subprocess import Popen, PIPE
from plyer.facades import UniqueID
from plyer.utils import whereis_exe
class OSXUniqueID(UniqueID):
'''
Implementation of MacOS uniqueid API.
'''
def _get_uid(self):
old_lang = environ.get('LANG')
environ['LANG'] = 'C'
ioreg_process = Popen(["ioreg", "-l"], stdout=PIPE)
grep_process = Popen(
["grep", "IOPlatformSerialNumber"],
stdin=ioreg_process.stdout, stdout=PIPE
)
ioreg_process.stdout.close()
output = grep_process.communicate()[0]
if old_lang is None:
environ.pop('LANG')
else:
environ['LANG'] = old_lang
result = None
if output:
result = output.split()[3][1:-1]
return result
def instance():
'''
Instance for facade proxy.
'''
import sys
if whereis_exe('ioreg'):
return OSXUniqueID()
sys.stderr.write("ioreg not found.")
return UniqueID()

View file

@ -0,0 +1,147 @@
from pyobjus import autoclass
from pyobjus.dylib_manager import load_framework, INCLUDE
from plyer.facades import Wifi
load_framework(INCLUDE.Foundation)
load_framework(INCLUDE.CoreWLAN)
CWInterface = autoclass('CWInterface')
CWNetwork = autoclass('CWNetwork')
CWWiFiClient = autoclass('CWWiFiClient')
NSArray = autoclass('NSArray')
NSDictionary = autoclass('NSDictionary')
NSString = autoclass('NSString')
class OSXWifi(Wifi):
names = {}
def _is_enabled(self):
'''
Returns `True` if the Wifi is enabled else returns `False`.
'''
return CWWiFiClient.sharedWiFiClient().interface().powerOn()
def _get_network_info(self, name):
'''
Returns all the network information.
'''
accessNetworkType = self.names[name].accessNetworkType
aggregateRSSI = self.names[name].aggregateRSSI
beaconInterval = self.names[name].beaconInterval
bssid = self.names[name].bssid.UTF8String()
countryCode = self.names[name].countryCode
hasInternet = self.names[name].hasInternet
hasInterworkingIE = self.names[name].hasInterworkingIE
hessid = self.names[name].hessid
ibss = self.names[name].ibss
isAdditionalStepRequiredForAccess = \
self.names[name].isAdditionalStepRequiredForAccess
isCarPlayNetwork = self.names[name].isCarPlayNetwork
isEmergencyServicesReachable = \
self.names[name].isEmergencyServicesReachable
isPasspoint = self.names[name].isPasspoint
isPersonalHotspot = self.names[name].isPersonalHotspot
isUnauthenticatedEmergencyServiceAccessible = \
self.names[name].isUnauthenticatedEmergencyServiceAccessible
noiseMeasurement = self.names[name].noiseMeasurement
physicalLayerMode = self.names[name].physicalLayerMode
rssiValue = self.names[name].rssiValue
securityType = self.names[name].securityType
ssid = self.names[name].ssid.UTF8String()
supportsEasyConnect = self.names[name].supportsEasyConnect
supportsWPS = self.names[name].supportsWPS
venueGroup = self.names[name].venueGroup
venueType = self.names[name].venueType
return {'accessNetworkType': accessNetworkType,
'aggregateRSSI': aggregateRSSI,
'beaconInterval': beaconInterval,
'bssid': bssid,
'countryCode': countryCode,
'hasInternet': hasInternet,
'hasInterworkingIE': hasInterworkingIE,
'hessid': hessid,
'ibss': ibss,
'isAdditionalStepRequiredForAccess':
isAdditionalStepRequiredForAccess,
'isCarPlayNetwork': isCarPlayNetwork,
'isEmergencyServicesReachable': isEmergencyServicesReachable,
'isPasspoint': isPasspoint,
'isPersonalHotspot': isPersonalHotspot,
'isUnauthenticatedEmergencyServiceAccessible':
isUnauthenticatedEmergencyServiceAccessible,
'noiseMeasurement': noiseMeasurement,
'physicalLayerMode': physicalLayerMode,
'rssiValue': rssiValue,
'securityType': securityType,
'ssid': ssid,
'supportsEasyConnect': supportsEasyConnect,
'supportsWPS': supportsWPS,
'venueGroup': venueGroup,
'venueType': venueType}
def _start_scanning(self):
'''
Starts scanning for available Wi-Fi networks.
'''
if self._is_enabled():
self.names = {}
c = CWInterface.interface()
scan = c.scanForNetworksWithName_error_(None, None)
cnt = scan.allObjects().count()
for i in range(cnt):
self.names[
scan.allObjects().objectAtIndex_(i).ssid.UTF8String()
] = scan.allObjects().objectAtIndex_(i)
else:
raise Exception("Wifi not enabled.")
def _get_available_wifi(self):
'''
Returns the name of available networks.
'''
return self.names.keys()
def _connect(self, network, parameters):
'''
Expects 2 parameters:
- name/ssid of the network.
- password: dict type
'''
password = parameters['password']
network_object = self.names[network]
CWInterface.interface().associateToNetwork_password_error_(
network_object,
password,
None)
return
def _disconnect(self):
'''
Disconnect from network.
'''
CWInterface.interface().disassociate()
return
def _disable(self):
'''
Wifi interface power state is set to "OFF".
'''
interface = CWWiFiClient.sharedWiFiClient().interface()
interface.setPower_error_(False, None)
def _enable(self):
'''
Wifi interface power state is set to "ON".
'''
interface = CWWiFiClient.sharedWiFiClient().interface()
interface.setPower_error_(True, None)
def instance():
return OSXWifi()

View file

View file

@ -0,0 +1,398 @@
'''
Documentation:
http://docs.microsoft.com/en-us/windows/desktop/Multimedia
.. versionadded:: 1.4.0
'''
from os.path import join
from ctypes import windll
from ctypes import (
sizeof, c_void_p, c_ulonglong, c_ulong,
c_wchar_p, byref, Structure, create_string_buffer
)
from ctypes.wintypes import DWORD, UINT
from plyer.facades import Audio
from plyer.platforms.win.storagepath import WinStoragePath
# DWORD_PTR i.e. ULONG_PTR, 32/64bit
ULONG_PTR = c_ulonglong if sizeof(c_void_p) == 8 else c_ulong
# device specific symbols
MCI_OPEN = 0x803
MCI_OPEN_TYPE = 0x2000
MCI_OPEN_ELEMENT = 512
MCI_RECORD = 0x80F
MCI_STOP = 0x808
MCI_SAVE = 0x813
MCI_PLAY = 0x806
MCI_CLOSE = 0x804
# recorder specific symbols
MCI_FROM = 4
MCI_TO = 8
MCI_WAIT = 2
MCI_SAVE_FILE = 256
class MCI_OPEN_PARMS(Structure):
'''
Struct for MCI_OPEN message parameters.
.. versionadded:: 1.4.0
'''
_fields_ = [
('mciOpenParms', ULONG_PTR),
('wDeviceID', UINT),
('lpstrDeviceType', c_wchar_p),
('lpstrElementName', c_wchar_p),
('lpstrAlias', c_wchar_p)
]
class MCI_RECORD_PARMS(Structure):
'''
Struct for MCI_RECORD message parameters.
http://docs.microsoft.com/en-us/windows/desktop/Multimedia/mci-record-parms
.. versionadded:: 1.4.0
'''
_fields_ = [
('dwCallback', ULONG_PTR),
('dwFrom', DWORD),
('dwTo', DWORD)
]
class MCI_SAVE_PARMS(Structure):
'''
Struct for MCI_SAVE message parameters.
http://docs.microsoft.com/en-us/windows/desktop/Multimedia/mci-save-parms
.. versionadded:: 1.4.0
'''
_fields_ = [
('dwCallback', ULONG_PTR),
('lpfilename', c_wchar_p)
]
class MCI_PLAY_PARMS(Structure):
'''
Struct for MCI_PLAY message parameters.
http://docs.microsoft.com/en-us/windows/desktop/Multimedia/mci-play-parms
.. versionadded:: 1.4.0
'''
_fields_ = [
('dwCallback', ULONG_PTR),
('dwFrom', DWORD),
('dwTo', DWORD)
]
def send_command(device, msg, flags, params):
'''
Generic mciSendCommandW() wrapper with error handler.
All parameters are required as for mciSendCommandW().
In case of no `params` passed, use `None`, that value
won't be dereferenced.
.. versionadded:: 1.4.0
'''
multimedia = windll.winmm
send_command_w = multimedia.mciSendCommandW
get_error = multimedia.mciGetErrorStringW
# error text buffer
# by API specification 128 is max, however the API sometimes
# kind of does not respect the documented bounds and returns
# more characters than buffer length...?!
error_len = 128
# big enough to prevent API accidentally segfaulting
error_text = create_string_buffer(error_len * 2)
# open a recording device with a new file
error_code = send_command_w(
device, # device ID
msg,
flags,
# reference to parameters structure or original value
# in case of params=False/0/None/...
byref(params) if params else params
)
# handle error messages if any
if error_code:
# device did not open, raise an exception
get_error(error_code, byref(error_text), error_len)
error_text = error_text.raw.replace(b'\x00', b'').decode('utf-8')
# either it can close already open device or it will fail because
# the device is in non-closable state, but the end result is the same
# and it makes no sense to parse MCI_CLOSE's error in this case
send_command_w(device, MCI_CLOSE, 0, None)
raise Exception(error_code, error_text)
# return params struct because some commands write into it
# to pass some values out of the local function scope
return params
class WinRecorder:
'''
Generic wrapper for MCI_RECORD handling the filenames and device closing
in the same approach like it is used for other platforms.
.. versionadded:: 1.4.0
'''
def __init__(self, device, filename):
self._device = device
self._filename = filename
@property
def device(self):
'''
Public property returning device ID.
.. versionadded:: 1.4.0
'''
return self._device
@property
def filename(self):
'''
Public property returning filename for current recording.
.. versionadded:: 1.4.0
'''
return self._filename
def record(self):
'''
Start recording a WAV sound.
.. versionadded:: 1.4.0
'''
send_command(
device=self.device,
msg=MCI_RECORD,
flags=0,
params=None
)
def stop(self):
'''
Stop recording and save the data to a file path
self.filename. Wait until the file is written.
Close the device afterwards.
.. versionadded:: 1.4.0
'''
# stop the recording first
send_command(
device=self.device,
msg=MCI_STOP,
flags=MCI_WAIT,
params=None
)
# choose filename for the WAV file
save_params = MCI_SAVE_PARMS()
save_params.lpfilename = self.filename
# save the sound data to a file and wait
# until it ends writing to the file
send_command(
device=self.device,
msg=MCI_SAVE,
flags=MCI_SAVE_FILE | MCI_WAIT,
params=save_params
)
# close the recording device
send_command(
device=self.device,
msg=MCI_CLOSE,
flags=0,
params=None
)
class WinPlayer:
'''
Generic wrapper for MCI_PLAY handling the device closing.
.. versionadded:: 1.4.0
'''
def __init__(self, device):
self._device = device
@property
def device(self):
'''
Public property returning device ID.
.. versionadded:: 1.4.0
'''
return self._device
def play(self):
'''
Start playing a WAV sound.
.. versionadded:: 1.4.0
'''
play_params = MCI_PLAY_PARMS()
play_params.dwFrom = 0
send_command(
device=self.device,
msg=MCI_PLAY,
flags=MCI_FROM,
params=play_params
)
def stop(self):
'''
Stop playing a WAV sound and close the device.
.. versionadded:: 1.4.0
'''
send_command(
device=self.device,
msg=MCI_STOP,
flags=MCI_WAIT,
params=None
)
# close the playing device
send_command(
device=self.device,
msg=MCI_CLOSE,
flags=0,
params=None
)
class WinAudio(Audio):
'''
Windows implementation of audio recording and audio playing.
.. versionadded:: 1.4.0
'''
def __init__(self, file_path=None):
# default path unless specified otherwise
default_path = join(
WinStoragePath().get_music_dir(),
'audio.wav'
)
super().__init__(file_path or default_path)
self._recorder = None
self._player = None
self._current_file = None
def _start(self):
'''
Start recording a WAV sound in the background asynchronously.
.. versionadded:: 1.4.0
'''
# clean everything before recording in case
# there is a different device open
self._stop()
# create structure and set device parameters
open_params = MCI_OPEN_PARMS()
open_params.lpstrDeviceType = 'waveaudio'
open_params.lpstrElementName = ''
# open a new device for recording
open_params = send_command(
device=0, # device ID before opening
msg=MCI_OPEN,
# empty filename in lpstrElementName
# device type in lpstrDeviceType
flags=MCI_OPEN_ELEMENT | MCI_OPEN_TYPE,
params=open_params
)
# get recorder with device id and path for saving
self._recorder = WinRecorder(
device=open_params.wDeviceID,
filename=self._file_path
)
self._recorder.record()
# Setting the currently recorded file as current file
# for using it as a parameter in audio player
self._current_file = self._recorder.filename
def _stop(self):
'''
Stop recording or playing of a WAV sound.
.. versionadded:: 1.4.0
'''
if self._recorder:
self._recorder.stop()
self._recorder = None
if self._player:
self._player.stop()
self._player = None
def _play(self):
'''
Play a WAV sound from a file. Prioritize latest recorded file before
default file path from WinAudio.
.. versionadded:: 1.4.0
'''
# create structure and set device parameters
open_params = MCI_OPEN_PARMS()
open_params.lpstrDeviceType = 'waveaudio'
open_params.lpstrElementName = self._current_file or self._file_path
# open a new device for playing
open_params = send_command(
device=0, # device ID before opening
msg=MCI_OPEN,
# existing filename in lpstrElementName
# device type in lpstrDeviceType
flags=MCI_OPEN_ELEMENT | MCI_OPEN_TYPE,
params=open_params
)
# get recorder with device id and path for saving
self._player = WinPlayer(device=open_params.wDeviceID)
self._player.play()
def instance():
'''
Instance for facade proxy.
'''
return WinAudio()

View file

@ -0,0 +1,36 @@
'''
Module of Windows API for plyer.battery.
'''
from plyer.platforms.win.libs.batterystatus import battery_status
from plyer.facades import Battery
from ctypes.wintypes import BYTE
class WinBattery(Battery):
'''
Implementation of Windows battery API.
'''
def _get_state(self):
CHARGING = BYTE(8).value
UNKNOWN_STATUS = BYTE(255).value
status = {"isCharging": None, "percentage": None}
query = battery_status()
if not query:
return status
status["isCharging"] = (query["BatteryFlag"] != UNKNOWN_STATUS) and \
(query["BatteryFlag"] & CHARGING > 0)
status["percentage"] = query["BatteryLifePercent"]
return status
def instance():
'''
Instance for facade proxy.
'''
return WinBattery()

View file

@ -0,0 +1,252 @@
'''
Module of Windows API for plyer.cpu.
'''
from ctypes import (
c_ulonglong, c_ulong, byref,
Structure, POINTER, Union, windll, create_string_buffer,
sizeof, cast, c_void_p, c_uint32
)
from ctypes.wintypes import (
BYTE, DWORD, WORD
)
from plyer.facades import CPU
KERNEL = windll.kernel32
ERROR_INSUFFICIENT_BUFFER = 0x0000007A
class CacheType:
'''
Win API PROCESSOR_CACHE_TYPE enum.
'''
unified = 0
instruction = 1
data = 2
trace = 3
class RelationshipType:
'''
Win API LOGICAL_PROCESSOR_RELATIONSHIP enum.
'''
processor_core = 0 # logical proc sharing single core
numa_node = 1 # logical proc sharing single NUMA node
cache = 2 # logical proc sharing cache
processor_package = 3 # logical proc sharing physical package
group = 4 # logical proc sharing processor group
all = 0xffff # logical proc info for all groups
class CacheDescriptor(Structure):
'''
Win API CACHE_DESCRIPTOR struct.
'''
_fields_ = [
('Level', BYTE),
('Associativity', BYTE),
('LineSize', WORD),
('Size', DWORD),
('Type', DWORD)
]
class ProcessorCore(Structure):
'''
Win API ProcessorCore struct.
'''
_fields_ = [('Flags', BYTE)]
class NumaNode(Structure):
'''
Win API NumaNode struct.
'''
_fields_ = [('NodeNumber', DWORD)]
class SystemLPIUnion(Union):
'''
Win API SYSTEM_LOGICAL_PROCESSOR_INFORMATION union without name.
'''
_fields_ = [
('ProcessorCore', ProcessorCore),
('NumaNode', NumaNode),
('Cache', CacheDescriptor),
('Reserved', c_ulonglong)
]
class SystemLPI(Structure):
'''
Win API SYSTEM_LOGICAL_PROCESSOR_INFORMATION struct.
'''
_fields_ = [
('ProcessorMask', c_ulong),
('Relationship', c_ulong),
('LPI', SystemLPIUnion)
]
class WinCPU(CPU):
'''
Implementation of Windows CPU API.
'''
@staticmethod
def _countbits(mask):
# make sure the correct ULONG_PTR size is used on 64bit
# https://docs.microsoft.com/en-us/windows/
# desktop/WinProg/windows-data-types
# note: not a pointer per-se, != PULONG_PTR
ulong_ptr = c_ulonglong if sizeof(c_void_p) == 8 else c_ulong
# note: c_ulonglong only on 64bit, otherwise c_ulong
# DWORD == c_uint32
# https://docs.microsoft.com/en-us/windows/
# desktop/WinProg/windows-data-types
lshift = c_uint32(sizeof(ulong_ptr) * 8 - 1)
assert lshift.value in (31, 63), lshift # 32 or 64 bits - 1
lshift = lshift.value
test = 1 << lshift
assert test % 2 == 0, test
count = 0
i = 0
while i <= lshift:
i += 1
# do NOT remove!!!
# test value has to be %2 == 0,
# except the last case where the value is 1,
# so that int(test) == int(float(test))
# and the mask bit is counted correctly
assert test % 2 == 0 or float(test) == 1.0, test
# https://stackoverflow.com/a/1746642/5994041
# note: useful to print(str(bin(int(...)))[2:])
count += 1 if (mask & int(test)) else 0
test /= 2
return count
def _logprocinfo(self, relationship):
get_logical_process_info = KERNEL.GetLogicalProcessorInformation
# first call with no structure to get the real size of the required
buff_length = c_ulong(0)
result = get_logical_process_info(None, byref(buff_length))
assert not result, result
error = KERNEL.GetLastError()
assert error == ERROR_INSUFFICIENT_BUFFER, error
assert buff_length, buff_length
# create buffer from the real winapi buffer length
buff = create_string_buffer(buff_length.value)
# call again with buffer pointer + the same length as arguments
result = get_logical_process_info(buff, byref(buff_length))
assert result, (result, KERNEL.GetLastError())
# memory size of one LPI struct in the array of LPI structs
offset = sizeof(SystemLPI) # ok
values = {
key: 0 for key in (
'relationship', 'mask',
'L1', 'L2', 'L3'
)
}
for i in range(0, buff_length.value, offset):
slpi = cast(
buff[i: i + offset],
POINTER(SystemLPI)
).contents
if slpi.Relationship != relationship:
continue
values['relationship'] += 1
values['mask'] += self._countbits(slpi.ProcessorMask)
if slpi.LPI.Cache.Level == 1:
values['L1'] += 1
elif slpi.LPI.Cache.Level == 2:
values['L2'] += 1
elif slpi.LPI.Cache.Level == 3:
values['L3'] += 1
return values
def _sockets(self):
# physical CPU sockets (or slots) on motherboard
return self._logprocinfo(
RelationshipType.processor_package
)['relationship']
def _physical(self):
# cores
return self._logprocinfo(
RelationshipType.processor_core
)['relationship']
def _logical(self):
# cores * threads
# if hyperthreaded core -> more than one logical processor
return self._logprocinfo(
RelationshipType.processor_core
)['mask']
def _cache(self):
# L1, L2, L3 cache count
result = self._logprocinfo(
RelationshipType.cache
)
return {
key: result[key]
for key in result
if key in ('L1', 'L2', 'L3')
}
def _numa(self):
# numa nodes
return self._logprocinfo(
RelationshipType.numa_node
)['relationship']
def instance():
'''
Instance for facade proxy.
'''
return WinCPU()
# Resources:
# GetLogicalProcessInformation
# https://msdn.microsoft.com/en-us/library/ms683194(v=vs.85).aspx
# SYSTEM_LOGICAL_PROCESSOR_INFORMATION
# https://msdn.microsoft.com/en-us/library/ms686694(v=vs.85).aspx
# LOGICAL_PROCESSOR_RELATIONSHIP enum (0 - 4, 0xffff)
# https://msdn.microsoft.com/2ada52f0-70ec-4146-9ef7-9af3b08996f9
# CACHE_DESCRIPTOR struct
# https://msdn.microsoft.com/38cfa605-831c-45ef-a99f-55f42b2b56e9
# PROCESSOR_CACHE_TYPE
# https://msdn.microsoft.com/23044f67-e944-43c2-8c75-3d2fba87cb3c
# C example
# https://msdn.microsoft.com/en-us/904d2d35-f419-4e8f-a689-f39ed926644c

View file

@ -0,0 +1,23 @@
'''
Module of Win API for plyer.devicename.
'''
import socket
from plyer.facades import DeviceName
class WinDeviceName(DeviceName):
'''
Implementation of Linux DeviceName API.
'''
def _get_device_name(self):
hostname = socket.gethostname()
return hostname
def instance():
'''
Instance for facade proxy.
'''
return WinDeviceName()

View file

@ -0,0 +1,46 @@
'''
Module of Windows API for plyer.email.
'''
import os
try:
from urllib.parse import quote
except ImportError:
from urllib import quote
from plyer.facades import Email
class WindowsEmail(Email):
'''
Implementation of Windows email API.
'''
def _send(self, **kwargs):
recipient = kwargs.get('recipient')
subject = kwargs.get('subject')
text = kwargs.get('text')
uri = "mailto:"
if recipient:
uri += str(recipient)
if subject:
uri += "?" if "?" not in uri else "&"
uri += "subject="
uri += quote(str(subject))
if text:
uri += "?" if "?" not in uri else "&"
uri += "body="
uri += quote(str(text))
# WE + startfile are available only on Windows
try:
os.startfile(uri)
except WindowsError:
print("Warning: unable to find a program able to send emails.")
def instance():
'''
Instance for facade proxy.
'''
return WindowsEmail()

View file

@ -0,0 +1,162 @@
'''
Windows file chooser
--------------------
'''
from plyer.facades import FileChooser
from win32com.shell.shell import (
SHBrowseForFolder as browse,
SHGetPathFromIDList as get_path
)
from win32com.shell import shellcon
import win32gui
import win32con
import pywintypes
from os.path import dirname, splitext, join, isdir
class Win32FileChooser:
'''A native implementation of NativeFileChooser using the
Win32 API on Windows.
Not Implemented features (all dialogs):
* preview
* icon
Not implemented features (in directory selection only - it's limited
by Windows itself):
* preview
* window-icon
Known issues:
* non-existins folders such as: Network, Control Panel, My Computer, Trash,
Library and likes will raise a COM error. The path does not exist, nor
a user can open from or save to such path.
'''
path = None
multiple = False
filters = []
preview = False
title = None
icon = None
show_hidden = False
def __init__(self, *args, **kwargs):
self._handle_selection = kwargs.pop(
'on_selection', self._handle_selection
)
# Simulate Kivy's behavior
for i in kwargs:
setattr(self, i, kwargs[i])
@staticmethod
def _handle_selection(selection):
'''
Dummy placeholder for returning selection from chooser.
'''
return selection
def run(self):
self.selection = []
try:
if self.mode != "dir":
args = {}
if self.path:
if isdir(self.path):
args["InitialDir"] = self.path
else:
args["InitialDir"] = dirname(self.path)
_, ext = splitext(self.path)
args["File"] = self.path
args["DefExt"] = ext and ext[1:] # no period
args["Title"] = self.title if self.title else "Pick a file..."
args["CustomFilter"] = 'Other file types\x00*.*\x00'
args["FilterIndex"] = 1
file = ""
if "File" in args:
file = args["File"]
args["File"] = file + ("\x00" * 4096)
# e.g. open_file(filters=['*.txt', '*.py'])
filters = ""
for f in self.filters:
if type(f) == str:
filters += (f + "\x00") * 2
else:
filters += f[0] + "\x00" + ";".join(f[1:]) + "\x00"
args["Filter"] = filters
flags = win32con.OFN_OVERWRITEPROMPT
flags |= win32con.OFN_HIDEREADONLY
if self.multiple:
flags |= win32con.OFN_ALLOWMULTISELECT
flags |= win32con.OFN_EXPLORER
if self.show_hidden:
flags |= win32con.OFN_FORCESHOWHIDDEN
args["Flags"] = flags
try:
if self.mode == "open":
self.fname, _, _ = win32gui.GetOpenFileNameW(**args)
elif self.mode == "save":
self.fname, _, _ = win32gui.GetSaveFileNameW(**args)
except pywintypes.error as e:
# if canceled, it's not really an error
if not e.winerror:
self._handle_selection(self.selection)
return self.selection
raise
if self.fname:
if self.multiple:
seq = str(self.fname).split("\x00")
if len(seq) > 1:
dir_n, base_n = seq[0], seq[1:]
self.selection = [
join(dir_n, i) for i in base_n
]
else:
self.selection = seq
else:
self.selection = str(self.fname).split("\x00")
else: # dir mode
BIF_EDITBOX = shellcon.BIF_EDITBOX
BIF_NEWDIALOGSTYLE = 0x00000040
# From http://goo.gl/UDqCqo
pidl, name, images = browse(
win32gui.GetDesktopWindow(),
None,
self.title if self.title else "Pick a folder...",
BIF_NEWDIALOGSTYLE | BIF_EDITBOX, None, None
)
# pidl is None when nothing is selected
# and e.g. the dialog is closed afterwards with Cancel
if pidl:
self.selection = [str(get_path(pidl).decode('utf-8'))]
except (RuntimeError, pywintypes.error, Exception):
# ALWAYS! let user know what happened
import traceback
traceback.print_exc()
self._handle_selection(self.selection)
return self.selection
class WinFileChooser(FileChooser):
'''FileChooser implementation for Windows, using win3all.
'''
def _file_selection_dialog(self, **kwargs):
return Win32FileChooser(**kwargs).run()
def instance():
return WinFileChooser()

View file

@ -0,0 +1,19 @@
try:
import keyring
except Exception:
raise NotImplementedError()
from plyer.facades import Keystore
class WinKeystore(Keystore):
def _set_key(self, servicename, key, value, **kwargs):
keyring.set_password(servicename, key, value)
def _get_key(self, servicename, key, **kwargs):
return keyring.get_password(servicename, key)
def instance():
return WinKeystore()

View file

@ -0,0 +1,206 @@
# -- coding: utf-8 --
'''
Module of Windows API for creating taskbar balloon tip
notification in the taskbar's tray notification area.
'''
__all__ = ('WindowsBalloonTip', 'balloon_tip')
import time
import ctypes
import atexit
from threading import RLock
from plyer.platforms.win.libs import win_api_defs
WS_OVERLAPPED = 0x00000000
WS_SYSMENU = 0x00080000
WM_DESTROY = 2
CW_USEDEFAULT = 8
LR_LOADFROMFILE = 16
LR_DEFAULTSIZE = 0x0040
IDI_APPLICATION = 32512
IMAGE_ICON = 1
NOTIFYICON_VERSION_4 = 4
NIM_ADD = 0
NIM_MODIFY = 1
NIM_DELETE = 2
NIM_SETVERSION = 4
NIF_MESSAGE = 1
NIF_ICON = 2
NIF_TIP = 4
NIF_INFO = 0x10
NIIF_USER = 4
NIIF_LARGE_ICON = 0x20
class WindowsBalloonTip:
'''
Implementation of balloon tip notifications through Windows API.
* Register Window class name:
https://msdn.microsoft.com/en-us/library/windows/desktop/ms632596.aspx
* Create an overlapped window using the registered class.
- It's hidden everywhere in GUI unless ShowWindow(handle, SW_SHOW)
function is called.
* Show/remove a tray icon and a balloon tip notification.
Each instance is a separate notification with different parameters.
Can be used with Threads.
'''
_class_atom = 0
_wnd_class_ex = None
_hwnd = None
_hicon = None
_balloon_icon = None
_notify_data = None
_count = 0
_lock = RLock()
@staticmethod
def _get_unique_id():
'''
Keep track of each created balloon tip notification names,
so that they can be easily identified even from outside.
Make sure the count is shared between all the instances
i.e. use a lock, so that _count class variable is incremented
safely when using balloon tip notifications with Threads.
'''
WindowsBalloonTip._lock.acquire()
val = WindowsBalloonTip._count
WindowsBalloonTip._count += 1
WindowsBalloonTip._lock.release()
return val
def __init__(self, title, message, app_name, app_icon='',
timeout=10, **kwargs):
'''
The app_icon parameter, if given, is an .ICO file.
'''
atexit.register(self.__del__)
wnd_class_ex = win_api_defs.get_WNDCLASSEXW()
class_name = 'PlyerTaskbar' + str(WindowsBalloonTip._get_unique_id())
wnd_class_ex.lpszClassName = class_name
# keep ref to it as long as window is alive
wnd_class_ex.lpfnWndProc = win_api_defs.WindowProc(
win_api_defs.DefWindowProcW
)
wnd_class_ex.hInstance = win_api_defs.GetModuleHandleW(None)
if wnd_class_ex.hInstance is None:
raise Exception('Could not get windows module instance.')
class_atom = win_api_defs.RegisterClassExW(wnd_class_ex)
if class_atom == 0:
raise Exception('Could not register the PlyerTaskbar class.')
self._class_atom = class_atom
self._wnd_class_ex = wnd_class_ex
# create window
self._hwnd = win_api_defs.CreateWindowExW(
# dwExStyle, lpClassName, lpWindowName, dwStyle
0, class_atom, '', WS_OVERLAPPED,
# x, y, nWidth, nHeight
0, 0, CW_USEDEFAULT, CW_USEDEFAULT,
# hWndParent, hMenu, hInstance, lpParam
None, None, wnd_class_ex.hInstance, None
)
if self._hwnd is None:
raise Exception('Could not get create window.')
win_api_defs.UpdateWindow(self._hwnd)
# load .ICO file for as balloon tip and tray icon
if app_icon:
icon_flags = LR_LOADFROMFILE | LR_DEFAULTSIZE
hicon = win_api_defs.LoadImageW(
None, app_icon, IMAGE_ICON, 0, 0, icon_flags
)
if hicon is None:
raise Exception('Could not load icon {}'.format(app_icon))
self._balloon_icon = self._hicon = hicon
else:
self._hicon = win_api_defs.LoadIconW(
None,
ctypes.cast(IDI_APPLICATION, win_api_defs.LPCWSTR)
)
# show the notification
self.notify(title, message, app_name)
if timeout:
time.sleep(timeout)
def __del__(self):
'''
Clean visible parts of the notification object, then free all resources
allocated for creating the nofitication Window and icon.
'''
self.remove_notify()
if self._hicon is not None:
win_api_defs.DestroyIcon(self._hicon)
if self._wnd_class_ex is not None:
win_api_defs.UnregisterClassW(
self._class_atom,
self._wnd_class_ex.hInstance
)
if self._hwnd is not None:
win_api_defs.DestroyWindow(self._hwnd)
def notify(self, title, message, app_name):
'''
Displays a balloon in the systray. Can be called multiple times
with different parameter values.
'''
# remove previous visible balloon tip nofitication if available
self.remove_notify()
# add icon and messages to window
hicon = self._hicon
flags = NIF_TIP | NIF_INFO
icon_flag = 0
if hicon is not None:
flags |= NIF_ICON
# if icon is default app's one, don't display it in message
if self._balloon_icon is not None:
icon_flag = NIIF_USER | NIIF_LARGE_ICON
notify_data = win_api_defs.get_NOTIFYICONDATAW(
0, self._hwnd,
id(self), flags, 0, hicon, app_name, 0, 0, message,
NOTIFYICON_VERSION_4, title, icon_flag, win_api_defs.GUID(),
self._balloon_icon
)
self._notify_data = notify_data
if not win_api_defs.Shell_NotifyIconW(NIM_ADD, notify_data):
raise Exception('Shell_NotifyIconW failed.')
if not win_api_defs.Shell_NotifyIconW(NIM_SETVERSION,
notify_data):
raise Exception('Shell_NotifyIconW failed.')
def remove_notify(self):
'''
Removes the notify balloon, if displayed.
'''
if self._notify_data is not None:
win_api_defs.Shell_NotifyIconW(NIM_DELETE, self._notify_data)
self._notify_data = None
def balloon_tip(**kwargs):
'''
Instance for balloon tip notification implementation.
'''
WindowsBalloonTip(**kwargs)

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