mirror of
https://github.com/markqvist/Sideband.git
synced 2025-10-22 02:06:29 -04:00
Included local plyer
This commit is contained in:
parent
25f0d52260
commit
f23855fb68
166 changed files with 15862 additions and 0 deletions
0
sbapp/plyer/platforms/__init__.py
Normal file
0
sbapp/plyer/platforms/__init__.py
Normal file
18
sbapp/plyer/platforms/android/__init__.py
Normal file
18
sbapp/plyer/platforms/android/__init__.py
Normal 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
|
79
sbapp/plyer/platforms/android/accelerometer.py
Normal file
79
sbapp/plyer/platforms/android/accelerometer.py
Normal 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()
|
58
sbapp/plyer/platforms/android/audio.py
Normal file
58
sbapp/plyer/platforms/android/audio.py
Normal 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()
|
65
sbapp/plyer/platforms/android/barometer.py
Normal file
65
sbapp/plyer/platforms/android/barometer.py
Normal 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()
|
47
sbapp/plyer/platforms/android/battery.py
Normal file
47
sbapp/plyer/platforms/android/battery.py
Normal 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()
|
32
sbapp/plyer/platforms/android/bluetooth.py
Normal file
32
sbapp/plyer/platforms/android/bluetooth.py
Normal 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()
|
34
sbapp/plyer/platforms/android/brightness.py
Executable file
34
sbapp/plyer/platforms/android/brightness.py
Executable 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()
|
29
sbapp/plyer/platforms/android/call.py
Normal file
29
sbapp/plyer/platforms/android/call.py
Normal 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()
|
59
sbapp/plyer/platforms/android/camera.py
Normal file
59
sbapp/plyer/platforms/android/camera.py
Normal 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()
|
119
sbapp/plyer/platforms/android/compass.py
Normal file
119
sbapp/plyer/platforms/android/compass.py
Normal 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()
|
33
sbapp/plyer/platforms/android/devicename.py
Normal file
33
sbapp/plyer/platforms/android/devicename.py
Normal 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()
|
58
sbapp/plyer/platforms/android/email.py
Normal file
58
sbapp/plyer/platforms/android/email.py
Normal 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()
|
447
sbapp/plyer/platforms/android/filechooser.py
Normal file
447
sbapp/plyer/platforms/android/filechooser.py
Normal 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()
|
55
sbapp/plyer/platforms/android/flash.py
Normal file
55
sbapp/plyer/platforms/android/flash.py
Normal 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()
|
82
sbapp/plyer/platforms/android/gps.py
Normal file
82
sbapp/plyer/platforms/android/gps.py
Normal 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()
|
84
sbapp/plyer/platforms/android/gravity.py
Normal file
84
sbapp/plyer/platforms/android/gravity.py
Normal 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()
|
119
sbapp/plyer/platforms/android/gyroscope.py
Normal file
119
sbapp/plyer/platforms/android/gyroscope.py
Normal 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()
|
107
sbapp/plyer/platforms/android/humidity.py
Normal file
107
sbapp/plyer/platforms/android/humidity.py
Normal 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()
|
55
sbapp/plyer/platforms/android/irblaster.py
Normal file
55
sbapp/plyer/platforms/android/irblaster.py
Normal 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()
|
25
sbapp/plyer/platforms/android/keystore.py
Normal file
25
sbapp/plyer/platforms/android/keystore.py
Normal 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()
|
63
sbapp/plyer/platforms/android/light.py
Normal file
63
sbapp/plyer/platforms/android/light.py
Normal 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()
|
208
sbapp/plyer/platforms/android/notification.py
Normal file
208
sbapp/plyer/platforms/android/notification.py
Normal 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()
|
43
sbapp/plyer/platforms/android/orientation.py
Normal file
43
sbapp/plyer/platforms/android/orientation.py
Normal 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()
|
69
sbapp/plyer/platforms/android/proximity.py
Normal file
69
sbapp/plyer/platforms/android/proximity.py
Normal 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()
|
25
sbapp/plyer/platforms/android/sms.py
Normal file
25
sbapp/plyer/platforms/android/sms.py
Normal 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()
|
118
sbapp/plyer/platforms/android/spatialorientation.py
Normal file
118
sbapp/plyer/platforms/android/spatialorientation.py
Normal 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()
|
69
sbapp/plyer/platforms/android/storagepath.py
Executable file
69
sbapp/plyer/platforms/android/storagepath.py
Executable 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()
|
252
sbapp/plyer/platforms/android/stt.py
Normal file
252
sbapp/plyer/platforms/android/stt.py
Normal 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()
|
66
sbapp/plyer/platforms/android/temperature.py
Normal file
66
sbapp/plyer/platforms/android/temperature.py
Normal 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()
|
34
sbapp/plyer/platforms/android/tts.py
Normal file
34
sbapp/plyer/platforms/android/tts.py
Normal 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()
|
28
sbapp/plyer/platforms/android/uniqueid.py
Normal file
28
sbapp/plyer/platforms/android/uniqueid.py
Normal 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()
|
63
sbapp/plyer/platforms/android/vibrator.py
Normal file
63
sbapp/plyer/platforms/android/vibrator.py
Normal 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()
|
0
sbapp/plyer/platforms/ios/__init__.py
Normal file
0
sbapp/plyer/platforms/ios/__init__.py
Normal file
34
sbapp/plyer/platforms/ios/accelerometer.py
Normal file
34
sbapp/plyer/platforms/ios/accelerometer.py
Normal 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()
|
31
sbapp/plyer/platforms/ios/barometer.py
Normal file
31
sbapp/plyer/platforms/ios/barometer.py
Normal 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()
|
47
sbapp/plyer/platforms/ios/battery.py
Normal file
47
sbapp/plyer/platforms/ios/battery.py
Normal 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()
|
27
sbapp/plyer/platforms/ios/brightness.py
Normal file
27
sbapp/plyer/platforms/ios/brightness.py
Normal 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()
|
29
sbapp/plyer/platforms/ios/call.py
Normal file
29
sbapp/plyer/platforms/ios/call.py
Normal 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()
|
52
sbapp/plyer/platforms/ios/camera.py
Normal file
52
sbapp/plyer/platforms/ios/camera.py
Normal 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()
|
43
sbapp/plyer/platforms/ios/compass.py
Normal file
43
sbapp/plyer/platforms/ios/compass.py
Normal 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()
|
52
sbapp/plyer/platforms/ios/email.py
Normal file
52
sbapp/plyer/platforms/ios/email.py
Normal 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()
|
81
sbapp/plyer/platforms/ios/filechooser.py
Normal file
81
sbapp/plyer/platforms/ios/filechooser.py
Normal 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()
|
50
sbapp/plyer/platforms/ios/flash.py
Normal file
50
sbapp/plyer/platforms/ios/flash.py
Normal 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()
|
80
sbapp/plyer/platforms/ios/gps.py
Normal file
80
sbapp/plyer/platforms/ios/gps.py
Normal 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()
|
31
sbapp/plyer/platforms/ios/gravity.py
Normal file
31
sbapp/plyer/platforms/ios/gravity.py
Normal 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()
|
55
sbapp/plyer/platforms/ios/gyroscope.py
Normal file
55
sbapp/plyer/platforms/ios/gyroscope.py
Normal 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()
|
23
sbapp/plyer/platforms/ios/keystore.py
Normal file
23
sbapp/plyer/platforms/ios/keystore.py
Normal 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()
|
43
sbapp/plyer/platforms/ios/sms.py
Normal file
43
sbapp/plyer/platforms/ios/sms.py
Normal 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()
|
31
sbapp/plyer/platforms/ios/spatialorientation.py
Normal file
31
sbapp/plyer/platforms/ios/spatialorientation.py
Normal 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()
|
62
sbapp/plyer/platforms/ios/storagepath.py
Normal file
62
sbapp/plyer/platforms/ios/storagepath.py
Normal 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()
|
37
sbapp/plyer/platforms/ios/tts.py
Normal file
37
sbapp/plyer/platforms/ios/tts.py
Normal 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()
|
27
sbapp/plyer/platforms/ios/uniqueid.py
Normal file
27
sbapp/plyer/platforms/ios/uniqueid.py
Normal 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()
|
43
sbapp/plyer/platforms/ios/vibrator.py
Normal file
43
sbapp/plyer/platforms/ios/vibrator.py
Normal 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()
|
0
sbapp/plyer/platforms/linux/__init__.py
Normal file
0
sbapp/plyer/platforms/linux/__init__.py
Normal file
35
sbapp/plyer/platforms/linux/accelerometer.py
Normal file
35
sbapp/plyer/platforms/linux/accelerometer.py
Normal 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()
|
102
sbapp/plyer/platforms/linux/battery.py
Normal file
102
sbapp/plyer/platforms/linux/battery.py
Normal 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()
|
29
sbapp/plyer/platforms/linux/brightness.py
Executable file
29
sbapp/plyer/platforms/linux/brightness.py
Executable 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()
|
116
sbapp/plyer/platforms/linux/cpu.py
Normal file
116
sbapp/plyer/platforms/linux/cpu.py
Normal 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()
|
23
sbapp/plyer/platforms/linux/devicename.py
Normal file
23
sbapp/plyer/platforms/linux/devicename.py
Normal 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()
|
47
sbapp/plyer/platforms/linux/email.py
Normal file
47
sbapp/plyer/platforms/linux/email.py
Normal 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()
|
269
sbapp/plyer/platforms/linux/filechooser.py
Normal file
269
sbapp/plyer/platforms/linux/filechooser.py
Normal 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()
|
19
sbapp/plyer/platforms/linux/keystore.py
Normal file
19
sbapp/plyer/platforms/linux/keystore.py
Normal 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()
|
108
sbapp/plyer/platforms/linux/notification.py
Normal file
108
sbapp/plyer/platforms/linux/notification.py
Normal 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()
|
27
sbapp/plyer/platforms/linux/orientation.py
Normal file
27
sbapp/plyer/platforms/linux/orientation.py
Normal 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()
|
37
sbapp/plyer/platforms/linux/processors.py
Normal file
37
sbapp/plyer/platforms/linux/processors.py
Normal 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()
|
29
sbapp/plyer/platforms/linux/screenshot.py
Normal file
29
sbapp/plyer/platforms/linux/screenshot.py
Normal 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()
|
72
sbapp/plyer/platforms/linux/storagepath.py
Executable file
72
sbapp/plyer/platforms/linux/storagepath.py
Executable 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()
|
25
sbapp/plyer/platforms/linux/tts.py
Normal file
25
sbapp/plyer/platforms/linux/tts.py
Normal 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()
|
47
sbapp/plyer/platforms/linux/uniqueid.py
Normal file
47
sbapp/plyer/platforms/linux/uniqueid.py
Normal 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()
|
482
sbapp/plyer/platforms/linux/wifi.py
Normal file
482
sbapp/plyer/platforms/linux/wifi.py
Normal 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()
|
0
sbapp/plyer/platforms/macosx/__init__.py
Normal file
0
sbapp/plyer/platforms/macosx/__init__.py
Normal file
25
sbapp/plyer/platforms/macosx/accelerometer.py
Normal file
25
sbapp/plyer/platforms/macosx/accelerometer.py
Normal 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()
|
90
sbapp/plyer/platforms/macosx/audio.py
Normal file
90
sbapp/plyer/platforms/macosx/audio.py
Normal 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()
|
59
sbapp/plyer/platforms/macosx/battery.py
Normal file
59
sbapp/plyer/platforms/macosx/battery.py
Normal 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()
|
54
sbapp/plyer/platforms/macosx/bluetooth.py
Normal file
54
sbapp/plyer/platforms/macosx/bluetooth.py
Normal 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()
|
62
sbapp/plyer/platforms/macosx/cpu.py
Normal file
62
sbapp/plyer/platforms/macosx/cpu.py
Normal 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()
|
23
sbapp/plyer/platforms/macosx/devicename.py
Normal file
23
sbapp/plyer/platforms/macosx/devicename.py
Normal 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()
|
49
sbapp/plyer/platforms/macosx/email.py
Normal file
49
sbapp/plyer/platforms/macosx/email.py
Normal 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()
|
126
sbapp/plyer/platforms/macosx/filechooser.py
Normal file
126
sbapp/plyer/platforms/macosx/filechooser.py
Normal 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()
|
19
sbapp/plyer/platforms/macosx/keystore.py
Normal file
19
sbapp/plyer/platforms/macosx/keystore.py
Normal 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()
|
0
sbapp/plyer/platforms/macosx/libs/__init__.py
Normal file
0
sbapp/plyer/platforms/macosx/libs/__init__.py
Normal file
128
sbapp/plyer/platforms/macosx/libs/osx_motion_sensor.py
Normal file
128
sbapp/plyer/platforms/macosx/libs/osx_motion_sensor.py
Normal 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)])
|
21
sbapp/plyer/platforms/macosx/libs/osx_paths.py
Normal file
21
sbapp/plyer/platforms/macosx/libs/osx_paths.py
Normal 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
|
51
sbapp/plyer/platforms/macosx/notification.py
Normal file
51
sbapp/plyer/platforms/macosx/notification.py
Normal 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()
|
27
sbapp/plyer/platforms/macosx/screenshot.py
Normal file
27
sbapp/plyer/platforms/macosx/screenshot.py
Normal 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()
|
62
sbapp/plyer/platforms/macosx/storagepath.py
Normal file
62
sbapp/plyer/platforms/macosx/storagepath.py
Normal 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()
|
25
sbapp/plyer/platforms/macosx/tts.py
Normal file
25
sbapp/plyer/platforms/macosx/tts.py
Normal 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()
|
47
sbapp/plyer/platforms/macosx/uniqueid.py
Normal file
47
sbapp/plyer/platforms/macosx/uniqueid.py
Normal 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()
|
147
sbapp/plyer/platforms/macosx/wifi.py
Normal file
147
sbapp/plyer/platforms/macosx/wifi.py
Normal 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()
|
0
sbapp/plyer/platforms/win/__init__.py
Normal file
0
sbapp/plyer/platforms/win/__init__.py
Normal file
398
sbapp/plyer/platforms/win/audio.py
Normal file
398
sbapp/plyer/platforms/win/audio.py
Normal 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()
|
36
sbapp/plyer/platforms/win/battery.py
Normal file
36
sbapp/plyer/platforms/win/battery.py
Normal 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()
|
252
sbapp/plyer/platforms/win/cpu.py
Normal file
252
sbapp/plyer/platforms/win/cpu.py
Normal 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
|
23
sbapp/plyer/platforms/win/devicename.py
Normal file
23
sbapp/plyer/platforms/win/devicename.py
Normal 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()
|
46
sbapp/plyer/platforms/win/email.py
Normal file
46
sbapp/plyer/platforms/win/email.py
Normal 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()
|
162
sbapp/plyer/platforms/win/filechooser.py
Normal file
162
sbapp/plyer/platforms/win/filechooser.py
Normal 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()
|
19
sbapp/plyer/platforms/win/keystore.py
Normal file
19
sbapp/plyer/platforms/win/keystore.py
Normal 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()
|
0
sbapp/plyer/platforms/win/libs/__init__.py
Normal file
0
sbapp/plyer/platforms/win/libs/__init__.py
Normal file
206
sbapp/plyer/platforms/win/libs/balloontip.py
Normal file
206
sbapp/plyer/platforms/win/libs/balloontip.py
Normal 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
Loading…
Add table
Add a link
Reference in a new issue