summaryrefslogtreecommitdiff
path: root/external/plyer/platforms
diff options
context:
space:
mode:
Diffstat (limited to 'external/plyer/platforms')
-rw-r--r--external/plyer/platforms/__init__.py0
-rw-r--r--external/plyer/platforms/android/__init__.py12
-rw-r--r--external/plyer/platforms/android/accelerometer.py74
-rw-r--r--external/plyer/platforms/android/audio.py58
-rw-r--r--external/plyer/platforms/android/battery.py34
-rw-r--r--external/plyer/platforms/android/camera.py59
-rw-r--r--external/plyer/platforms/android/compass.py74
-rw-r--r--external/plyer/platforms/android/email.py40
-rw-r--r--external/plyer/platforms/android/gps.py79
-rw-r--r--external/plyer/platforms/android/gyroscope.py74
-rw-r--r--external/plyer/platforms/android/irblaster.py54
-rw-r--r--external/plyer/platforms/android/notification.py37
-rw-r--r--external/plyer/platforms/android/orientation.py43
-rw-r--r--external/plyer/platforms/android/sms.py25
-rw-r--r--external/plyer/platforms/android/tts.py24
-rw-r--r--external/plyer/platforms/android/uniqueid.py16
-rw-r--r--external/plyer/platforms/android/vibrator.py48
-rw-r--r--external/plyer/platforms/ios/__init__.py0
-rw-r--r--external/plyer/platforms/ios/accelerometer.py34
-rw-r--r--external/plyer/platforms/ios/battery.py36
-rw-r--r--external/plyer/platforms/ios/compass.py31
-rw-r--r--external/plyer/platforms/ios/email.py41
-rw-r--r--external/plyer/platforms/ios/gps.py45
-rw-r--r--external/plyer/platforms/ios/gyroscope.py31
-rw-r--r--external/plyer/platforms/ios/tts.py35
-rw-r--r--external/plyer/platforms/ios/uniqueid.py17
-rw-r--r--external/plyer/platforms/linux/__init__.py0
-rw-r--r--external/plyer/platforms/linux/accelerometer.py36
-rw-r--r--external/plyer/platforms/linux/battery.py46
-rw-r--r--external/plyer/platforms/linux/email.py36
-rw-r--r--external/plyer/platforms/linux/filechooser.py249
-rw-r--r--external/plyer/platforms/linux/notification.py52
-rw-r--r--external/plyer/platforms/linux/tts.py25
-rw-r--r--external/plyer/platforms/linux/uniqueid.py30
-rw-r--r--external/plyer/platforms/macosx/__init__.py0
-rw-r--r--external/plyer/platforms/macosx/accelerometer.py25
-rw-r--r--external/plyer/platforms/macosx/battery.py47
-rw-r--r--external/plyer/platforms/macosx/email.py38
-rw-r--r--external/plyer/platforms/macosx/filechooser.py108
-rw-r--r--external/plyer/platforms/macosx/libs/__init__.py0
-rw-r--r--external/plyer/platforms/macosx/libs/osx_motion_sensor.py118
-rw-r--r--external/plyer/platforms/macosx/notification.py27
-rw-r--r--external/plyer/platforms/macosx/tts.py25
-rw-r--r--external/plyer/platforms/macosx/uniqueid.py32
-rw-r--r--external/plyer/platforms/win/__init__.py0
-rw-r--r--external/plyer/platforms/win/battery.py21
-rw-r--r--external/plyer/platforms/win/email.py34
-rw-r--r--external/plyer/platforms/win/filechooser.py112
-rw-r--r--external/plyer/platforms/win/libs/__init__.py0
-rw-r--r--external/plyer/platforms/win/libs/balloontip.py145
-rw-r--r--external/plyer/platforms/win/libs/batterystatus.py13
-rw-r--r--external/plyer/platforms/win/libs/win_api_defs.py200
-rw-r--r--external/plyer/platforms/win/notification.py13
-rw-r--r--external/plyer/platforms/win/tts.py16
-rw-r--r--external/plyer/platforms/win/uniqueid.py21
55 files changed, 2490 insertions, 0 deletions
diff --git a/external/plyer/platforms/__init__.py b/external/plyer/platforms/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/external/plyer/platforms/__init__.py
diff --git a/external/plyer/platforms/android/__init__.py b/external/plyer/platforms/android/__init__.py
new file mode 100644
index 0000000..6b565a3
--- /dev/null
+++ b/external/plyer/platforms/android/__init__.py
@@ -0,0 +1,12 @@
+from os import environ
+from jnius import autoclass
+
+ANDROID_VERSION = autoclass('android.os.Build$VERSION')
+SDK_INT = ANDROID_VERSION.SDK_INT
+
+if 'PYTHON_SERVICE_ARGUMENT' in environ:
+ PythonService = autoclass('org.renpy.android.PythonService')
+ activity = PythonService.mService
+else:
+ PythonActivity = autoclass('org.renpy.android.PythonActivity')
+ activity = PythonActivity.mActivity
diff --git a/external/plyer/platforms/android/accelerometer.py b/external/plyer/platforms/android/accelerometer.py
new file mode 100644
index 0000000..af07c52
--- /dev/null
+++ b/external/plyer/platforms/android/accelerometer.py
@@ -0,0 +1,74 @@
+'''
+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(AccelerometerSensorListener, self).__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(AndroidAccelerometer, self).__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(self.__class__, self).__del__()
+
+
+def instance():
+ return AndroidAccelerometer()
diff --git a/external/plyer/platforms/android/audio.py b/external/plyer/platforms/android/audio.py
new file mode 100644
index 0000000..2115f19
--- /dev/null
+++ b/external/plyer/platforms/android/audio.py
@@ -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(AndroidAudio, self).__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()
diff --git a/external/plyer/platforms/android/battery.py b/external/plyer/platforms/android/battery.py
new file mode 100644
index 0000000..2ade1d2
--- /dev/null
+++ b/external/plyer/platforms/android/battery.py
@@ -0,0 +1,34 @@
+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):
+ def _get_state(self):
+ status = {"isCharging": None, "percentage": None}
+
+ ifilter = IntentFilter(Intent.ACTION_BATTERY_CHANGED)
+
+ batteryStatus = cast('android.content.Intent',
+ activity.registerReceiver(None, ifilter))
+
+ query = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1)
+ isCharging = (query == BatteryManager.BATTERY_STATUS_CHARGING or
+ query == BatteryManager.BATTERY_STATUS_FULL)
+
+ level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)
+ scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
+ percentage = level / float(scale)
+
+ status['isCharging'] = isCharging
+ status['percentage'] = percentage
+
+ return status
+
+
+def instance():
+ return AndroidBattery()
diff --git a/external/plyer/platforms/android/camera.py b/external/plyer/platforms/android/camera.py
new file mode 100644
index 0000000..344296d
--- /dev/null
+++ b/external/plyer/platforms/android/camera.py
@@ -0,0 +1,59 @@
+import android
+import android.activity
+from os import unlink
+from jnius import autoclass, cast
+from plyer.facades import Camera
+from plyer.platforms.android import activity
+
+Intent = autoclass('android.content.Intent')
+PythonActivity = autoclass('org.renpy.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._unlink(self.filename)
+
+ def _unlink(self, fn):
+ try:
+ unlink(fn)
+ except:
+ pass
+
+
+def instance():
+ return AndroidCamera()
diff --git a/external/plyer/platforms/android/compass.py b/external/plyer/platforms/android/compass.py
new file mode 100644
index 0000000..7fb19d6
--- /dev/null
+++ b/external/plyer/platforms/android/compass.py
@@ -0,0 +1,74 @@
+'''
+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 MagneticFieldSensorListener(PythonJavaClass):
+ __javainterfaces__ = ['android/hardware/SensorEventListener']
+
+ def __init__(self):
+ super(MagneticFieldSensorListener, self).__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(AndroidCompass, self).__init__()
+ self.bState = False
+
+ def _enable(self):
+ if (not self.bState):
+ self.listener = MagneticFieldSensorListener()
+ self.listener.enable()
+ self.bState = True
+
+ def _disable(self):
+ if (self.bState):
+ self.bState = False
+ self.listener.disable()
+ del self.listener
+
+ def _get_orientation(self):
+ if (self.bState):
+ return tuple(self.listener.values)
+ else:
+ return (None, None, None)
+
+ def __del__(self):
+ if(self.bState):
+ self._disable()
+ super(self.__class__, self).__del__()
+
+
+def instance():
+ return AndroidCompass()
diff --git a/external/plyer/platforms/android/email.py b/external/plyer/platforms/android/email.py
new file mode 100644
index 0000000..79923e4
--- /dev/null
+++ b/external/plyer/platforms/android/email.py
@@ -0,0 +1,40 @@
+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):
+ 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():
+ return AndroidEmail()
diff --git a/external/plyer/platforms/android/gps.py b/external/plyer/platforms/android/gps.py
new file mode 100644
index 0000000..fbe580f
--- /dev/null
+++ b/external/plyer/platforms/android/gps.py
@@ -0,0 +1,79 @@
+'''
+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(_LocationListener, self).__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())
+
+ @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):
+ # XXX defaults should be configurable by the user, later
+ providers = self._location_manager.getProviders(False).toArray()
+ for provider in providers:
+ self._location_manager.requestLocationUpdates(
+ provider,
+ 1000, # minTime, in milliseconds
+ 1, # minDistance, in meters
+ self._location_listener,
+ Looper.getMainLooper())
+
+ def _stop(self):
+ self._location_manager.removeUpdates(self._location_listener)
+
+
+def instance():
+ return AndroidGPS()
diff --git a/external/plyer/platforms/android/gyroscope.py b/external/plyer/platforms/android/gyroscope.py
new file mode 100644
index 0000000..58747d7
--- /dev/null
+++ b/external/plyer/platforms/android/gyroscope.py
@@ -0,0 +1,74 @@
+'''
+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(GyroscopeSensorListener, self).__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 AndroidGyroscope(Gyroscope):
+ def __init__(self):
+ super(AndroidGyroscope, self).__init__()
+ self.bState = False
+
+ def _enable(self):
+ if (not self.bState):
+ self.listener = GyroscopeSensorListener()
+ self.listener.enable()
+ self.bState = True
+
+ def _disable(self):
+ if (self.bState):
+ self.bState = False
+ self.listener.disable()
+ del self.listener
+
+ def _get_orientation(self):
+ if (self.bState):
+ return tuple(self.listener.values)
+ else:
+ return (None, None, None)
+
+ def __del__(self):
+ if(self.bState):
+ self._disable()
+ super(self.__class__, self).__del__()
+
+
+def instance():
+ return AndroidGyroscope()
diff --git a/external/plyer/platforms/android/irblaster.py b/external/plyer/platforms/android/irblaster.py
new file mode 100644
index 0000000..6c44717
--- /dev/null
+++ b/external/plyer/platforms/android/irblaster.py
@@ -0,0 +1,54 @@
+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()
diff --git a/external/plyer/platforms/android/notification.py b/external/plyer/platforms/android/notification.py
new file mode 100644
index 0000000..bfc3a25
--- /dev/null
+++ b/external/plyer/platforms/android/notification.py
@@ -0,0 +1,37 @@
+from jnius import autoclass
+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')
+Drawable = autoclass("{}.R$drawable".format(activity.getPackageName()))
+
+
+class AndroidNotification(Notification):
+ def _get_notification_service(self):
+ if not hasattr(self, '_ns'):
+ self._ns = activity.getSystemService(Context.NOTIFICATION_SERVICE)
+ return self._ns
+
+ def _notify(self, **kwargs):
+ icon = getattr(Drawable, kwargs.get('icon_android', 'icon'))
+ noti = NotificationBuilder(activity)
+ noti.setContentTitle(AndroidString(
+ kwargs.get('title').encode('utf-8')))
+ noti.setContentText(AndroidString(
+ kwargs.get('message').encode('utf-8')))
+ noti.setSmallIcon(icon)
+ noti.setAutoCancel(True)
+
+ if SDK_INT >= 16:
+ noti = noti.build()
+ else:
+ noti = noti.getNotification()
+
+ self._get_notification_service().notify(0, noti)
+
+
+def instance():
+ return AndroidNotification()
+
diff --git a/external/plyer/platforms/android/orientation.py b/external/plyer/platforms/android/orientation.py
new file mode 100644
index 0000000..e98e34d
--- /dev/null
+++ b/external/plyer/platforms/android/orientation.py
@@ -0,0 +1,43 @@
+from jnius import autoclass, cast
+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()
diff --git a/external/plyer/platforms/android/sms.py b/external/plyer/platforms/android/sms.py
new file mode 100644
index 0000000..8650968
--- /dev/null
+++ b/external/plyer/platforms/android/sms.py
@@ -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()
diff --git a/external/plyer/platforms/android/tts.py b/external/plyer/platforms/android/tts.py
new file mode 100644
index 0000000..eae2974
--- /dev/null
+++ b/external/plyer/platforms/android/tts.py
@@ -0,0 +1,24 @@
+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) # TODO: locale specification as option
+ retries = 0 # First try rarely succeeds due to some timing issue
+ while retries < 100 and \
+ tts.speak(kwargs.get('message').encode('utf-8'),
+ TextToSpeech.QUEUE_FLUSH, None) == -1:
+ # -1 indicates error. Let's wait and then try again
+ sleep(0.1)
+ retries += 1
+
+
+def instance():
+ return AndroidTextToSpeech()
diff --git a/external/plyer/platforms/android/uniqueid.py b/external/plyer/platforms/android/uniqueid.py
new file mode 100644
index 0000000..b8561de
--- /dev/null
+++ b/external/plyer/platforms/android/uniqueid.py
@@ -0,0 +1,16 @@
+from jnius import autoclass
+from plyer.platforms.android import activity
+from plyer.facades import UniqueID
+
+Secure = autoclass('android.provider.Settings$Secure')
+
+
+class AndroidUniqueID(UniqueID):
+
+ def _get_uid(self):
+ return Secure.getString(activity.getContentResolver(),
+ Secure.ANDROID_ID)
+
+
+def instance():
+ return AndroidUniqueID()
diff --git a/external/plyer/platforms/android/vibrator.py b/external/plyer/platforms/android/vibrator.py
new file mode 100644
index 0000000..c28fe8e
--- /dev/null
+++ b/external/plyer/platforms/android/vibrator.py
@@ -0,0 +1,48 @@
+'''Implementation Vibrator for Android.'''
+
+from jnius import autoclass
+from plyer.facades import Vibrator
+from plyer.platforms.android import activity
+from plyer.platforms.android import SDK_INT
+
+Context = autoclass('android.content.Context')
+vibrator = activity.getSystemService(Context.VIBRATOR_SERVICE)
+
+
+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:
+ vibrator.vibrate(int(1000 * time))
+
+ def _pattern(self, pattern=None, repeat=None, **kwargs):
+ pattern = [int(1000 * time) for time in pattern]
+
+ if vibrator:
+ vibrator.vibrate(pattern, repeat)
+
+ def _exists(self, **kwargs):
+ if SDK_INT >= 11:
+ return vibrator.hasVibrator()
+ elif activity.getSystemService(Context.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()
diff --git a/external/plyer/platforms/ios/__init__.py b/external/plyer/platforms/ios/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/external/plyer/platforms/ios/__init__.py
diff --git a/external/plyer/platforms/ios/accelerometer.py b/external/plyer/platforms/ios/accelerometer.py
new file mode 100644
index 0000000..bf1ef02
--- /dev/null
+++ b/external/plyer/platforms/ios/accelerometer.py
@@ -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(IosAccelerometer, self).__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()
diff --git a/external/plyer/platforms/ios/battery.py b/external/plyer/platforms/ios/battery.py
new file mode 100644
index 0000000..55aa2c6
--- /dev/null
+++ b/external/plyer/platforms/ios/battery.py
@@ -0,0 +1,36 @@
+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):
+ def __init__(self):
+ super(iOSBattery, self).__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:
+ isCharging = None
+ elif self.device.batteryState == 2:
+ isCharging = True
+ else:
+ isCharging = False
+
+ percentage = self.device.batteryLevel * 100.
+
+ status['isCharging'] = isCharging
+ status['percentage'] = percentage
+
+ return status
+
+
+def instance():
+ return iOSBattery()
diff --git a/external/plyer/platforms/ios/compass.py b/external/plyer/platforms/ios/compass.py
new file mode 100644
index 0000000..6e5c935
--- /dev/null
+++ b/external/plyer/platforms/ios/compass.py
@@ -0,0 +1,31 @@
+'''
+iOS Compass
+---------------------
+'''
+
+from plyer.facades import Compass
+from pyobjus import autoclass
+
+
+class IosCompass(Compass):
+
+ def __init__(self):
+ super(IosCompass, self).__init__()
+ self.bridge = autoclass('bridge').alloc().init()
+ self.bridge.motionManager.setMagnetometerUpdateInterval_(0.1)
+
+ def _enable(self):
+ self.bridge.startMagnetometer()
+
+ def _disable(self):
+ self.bridge.stopMagnetometer()
+
+ def _get_orientation(self):
+ return (
+ self.bridge.mg_x,
+ self.bridge.mg_y,
+ self.bridge.mg_z)
+
+
+def instance():
+ return IosCompass()
diff --git a/external/plyer/platforms/ios/email.py b/external/plyer/platforms/ios/email.py
new file mode 100644
index 0000000..7e55e4e
--- /dev/null
+++ b/external/plyer/platforms/ios/email.py
@@ -0,0 +1,41 @@
+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 iOSXEmail(Email):
+ 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():
+ return iOSXEmail()
diff --git a/external/plyer/platforms/ios/gps.py b/external/plyer/platforms/ios/gps.py
new file mode 100644
index 0000000..4d6d665
--- /dev/null
+++ b/external/plyer/platforms/ios/gps.py
@@ -0,0 +1,45 @@
+'''
+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):
+ 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_didUpdateLocations_(self, manager, locations):
+ location = manager.location
+
+ self.on_location(
+ lat=location.coordinate.a,
+ lon=location.coordinate.b,
+ speed=location.speed,
+ bearing=location.course,
+ altitude=location.altitude)
+
+
+def instance():
+ return IosGPS()
diff --git a/external/plyer/platforms/ios/gyroscope.py b/external/plyer/platforms/ios/gyroscope.py
new file mode 100644
index 0000000..e8b93cf
--- /dev/null
+++ b/external/plyer/platforms/ios/gyroscope.py
@@ -0,0 +1,31 @@
+'''
+iOS Gyroscope
+---------------------
+'''
+
+from plyer.facades import Gyroscope
+from pyobjus import autoclass
+
+
+class IosGyroscope(Gyroscope):
+
+ def __init__(self):
+ super(IosGyroscope, self).__init__()
+ self.bridge = autoclass('bridge').alloc().init()
+ self.bridge.motionManager.setGyroscopeUpdateInterval_(0.1)
+
+ def _enable(self):
+ self.bridge.startGyroscope()
+
+ def _disable(self):
+ self.bridge.stopGyroscope()
+
+ def _get_orientation(self):
+ return (
+ self.bridge.gy_x,
+ self.bridge.gy_y,
+ self.bridge.gy_z)
+
+
+def instance():
+ return IosGyroscope()
diff --git a/external/plyer/platforms/ios/tts.py b/external/plyer/platforms/ios/tts.py
new file mode 100644
index 0000000..a711483
--- /dev/null
+++ b/external/plyer/platforms/ios/tts.py
@@ -0,0 +1,35 @@
+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(iOSTextToSpeech, self).__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()
diff --git a/external/plyer/platforms/ios/uniqueid.py b/external/plyer/platforms/ios/uniqueid.py
new file mode 100644
index 0000000..1587f4b
--- /dev/null
+++ b/external/plyer/platforms/ios/uniqueid.py
@@ -0,0 +1,17 @@
+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):
+
+ def _get_uid(self):
+ uuid = UIDevice.currentDevice().identifierForVendor.UUIDString()
+ return uuid.UTF8String()
+
+
+def instance():
+ return iOSUniqueID()
diff --git a/external/plyer/platforms/linux/__init__.py b/external/plyer/platforms/linux/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/external/plyer/platforms/linux/__init__.py
diff --git a/external/plyer/platforms/linux/accelerometer.py b/external/plyer/platforms/linux/accelerometer.py
new file mode 100644
index 0000000..7272c33
--- /dev/null
+++ b/external/plyer/platforms/linux/accelerometer.py
@@ -0,0 +1,36 @@
+'''
+Linux accelerometer
+---------------------
+'''
+
+from plyer.facades import Accelerometer
+import os
+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()
diff --git a/external/plyer/platforms/linux/battery.py b/external/plyer/platforms/linux/battery.py
new file mode 100644
index 0000000..0cdb763
--- /dev/null
+++ b/external/plyer/platforms/linux/battery.py
@@ -0,0 +1,46 @@
+from subprocess import Popen, PIPE
+from plyer.facades import Battery
+from plyer.utils import whereis_exe
+
+from os import environ
+
+
+class LinuxBattery(Battery):
+ def _get_state(self):
+ old_lang = environ.get('LANG')
+ environ['LANG'] = 'C'
+
+ status = {"isCharging": None, "percentage": None}
+
+ # We are supporting only one battery now
+ dev = "/org/freedesktop/UPower/device/battery_BAT0"
+ upower_process = Popen(["upower", "-d", dev],
+ stdout=PIPE)
+ output = upower_process.communicate()[0]
+
+ environ['LANG'] = old_lang
+
+ if not output:
+ return status
+
+ power_supply = percentage = None
+ for l in output.splitlines():
+ if 'power supply' in l:
+ power_supply = l.rpartition(':')[-1].strip()
+ if 'percentage' in l:
+ percentage = float(l.rpartition(':')[-1].strip()[:-1])
+
+ if(power_supply):
+ status['isCharging'] = power_supply != "yes"
+
+ status['percentage'] = percentage
+
+ return status
+
+
+def instance():
+ import sys
+ if whereis_exe('upower'):
+ return LinuxBattery()
+ sys.stderr.write("upower not found.")
+ return Battery()
diff --git a/external/plyer/platforms/linux/email.py b/external/plyer/platforms/linux/email.py
new file mode 100644
index 0000000..ceb5497
--- /dev/null
+++ b/external/plyer/platforms/linux/email.py
@@ -0,0 +1,36 @@
+import subprocess
+from urllib import quote
+try:
+ from urllib.parse import quote
+except ImportError:
+ from urllib import quote
+from plyer.utils import whereis_exe
+
+
+class LinuxEmail(Email):
+ 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():
+ import sys
+ if whereis_exe('xdg-open'):
+ return LinuxEmail()
+ sys.stderr.write("xdg-open not found.")
+ return Email()
diff --git a/external/plyer/platforms/linux/filechooser.py b/external/plyer/platforms/linux/filechooser.py
new file mode 100644
index 0000000..545487b
--- /dev/null
+++ b/external/plyer/platforms/linux/filechooser.py
@@ -0,0 +1,249 @@
+'''
+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(object):
+ '''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, **kwargs):
+ # Simulate Kivy's behavior
+ for i in kwargs:
+ setattr(self, i, kwargs[i])
+
+ _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()
+ self.selection = self._split_output(out)
+ return self.selection
+ else:
+ return None
+ time.sleep(0.1)
+
+ 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 += [
+ "--getopenfilename",
+ (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 which("yad"):
+ desktop = "yad"
+ elif which("zenity"):
+ desktop = "gnome"
+
+ def _file_selection_dialog(self, desktop_override=desktop, **kwargs):
+ if not desktop_override:
+ desktop_override = 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()
diff --git a/external/plyer/platforms/linux/notification.py b/external/plyer/platforms/linux/notification.py
new file mode 100644
index 0000000..d78f130
--- /dev/null
+++ b/external/plyer/platforms/linux/notification.py
@@ -0,0 +1,52 @@
+import subprocess
+from plyer.facades import Notification
+from plyer.utils import whereis_exe
+
+
+class NotifySendNotification(Notification):
+ ''' Pops up a notification using notify-send
+ '''
+ def _notify(self, **kwargs):
+ subprocess.call(["notify-send",
+ kwargs.get('title'),
+ kwargs.get('message')])
+
+
+class NotifyDbus(Notification):
+ ''' notify using dbus interface
+ '''
+
+ 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', 5000)
+ 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)
+
+
+def instance():
+ import sys
+ try:
+ import dbus
+ return NotifyDbus()
+ except ImportError:
+ sys.stderr.write("python-dbus not installed. try:"
+ "`sudo pip install python-dbus`.")
+ if whereis_exe('notify-send'):
+ return NotifySendNotification()
+ sys.stderr.write("notify-send not found.")
+ return Notification()
diff --git a/external/plyer/platforms/linux/tts.py b/external/plyer/platforms/linux/tts.py
new file mode 100644
index 0000000..0a609e1
--- /dev/null
+++ b/external/plyer/platforms/linux/tts.py
@@ -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):
+ subprocess.call(["flite", "-t", kwargs.get('message'), "play"])
+
+
+def instance():
+ if whereis_exe('espeak'):
+ return EspeakTextToSpeech()
+ elif whereis_exe('flite'):
+ return FlitetextToSpeech()
+ return TTS()
diff --git a/external/plyer/platforms/linux/uniqueid.py b/external/plyer/platforms/linux/uniqueid.py
new file mode 100644
index 0000000..f7fff89
--- /dev/null
+++ b/external/plyer/platforms/linux/uniqueid.py
@@ -0,0 +1,30 @@
+from subprocess import Popen, PIPE
+from plyer.facades import UniqueID
+from plyer.utils import whereis_exe
+
+from os import environ
+
+
+class LinuxUniqueID(UniqueID):
+ def _get_uid(self):
+ old_lang = environ.get('LANG')
+ environ['LANG'] = 'C'
+ lshw_process = Popen(["lshw", "-quiet"], stdout=PIPE, stderr=PIPE)
+ grep_process = Popen(["grep", "-m1", "serial:"],
+ stdin=lshw_process.stdout, stdout=PIPE)
+ lshw_process.stdout.close()
+ output = grep_process.communicate()[0]
+ environ['LANG'] = old_lang
+
+ if output:
+ return output.split()[1]
+ else:
+ return None
+
+
+def instance():
+ import sys
+ if whereis_exe('lshw'):
+ return LinuxUniqueID()
+ sys.stderr.write("lshw not found.")
+ return UniqueID()
diff --git a/external/plyer/platforms/macosx/__init__.py b/external/plyer/platforms/macosx/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/external/plyer/platforms/macosx/__init__.py
diff --git a/external/plyer/platforms/macosx/accelerometer.py b/external/plyer/platforms/macosx/accelerometer.py
new file mode 100644
index 0000000..ec1fe77
--- /dev/null
+++ b/external/plyer/platforms/macosx/accelerometer.py
@@ -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:
+ 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()
diff --git a/external/plyer/platforms/macosx/battery.py b/external/plyer/platforms/macosx/battery.py
new file mode 100644
index 0000000..fe1c525
--- /dev/null
+++ b/external/plyer/platforms/macosx/battery.py
@@ -0,0 +1,47 @@
+from subprocess import Popen, PIPE
+from plyer.facades import Battery
+from plyer.utils import whereis_exe
+
+from os import environ
+
+
+class OSXBattery(Battery):
+ 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
+
+ IsCharging = MaxCapacity = CurrentCapacity = None
+ for l in output.splitlines():
+ if 'IsCharging' in l:
+ IsCharging = l.rpartition('=')[-1].strip()
+ if 'MaxCapacity' in l:
+ MaxCapacity = float(l.rpartition('=')[-1].strip())
+ if 'CurrentCapacity' in l:
+ CurrentCapacity = float(l.rpartition('=')[-1].strip())
+
+ if (IsCharging):
+ status['isCharging'] = IsCharging == "Yes"
+
+ if (CurrentCapacity and MaxCapacity):
+ status['percentage'] = 100. * CurrentCapacity / MaxCapacity
+
+ return status
+
+
+def instance():
+ import sys
+ if whereis_exe('ioreg'):
+ return OSXBattery()
+ sys.stderr.write("ioreg not found.")
+ return Battery()
diff --git a/external/plyer/platforms/macosx/email.py b/external/plyer/platforms/macosx/email.py
new file mode 100644
index 0000000..8e29fa9
--- /dev/null
+++ b/external/plyer/platforms/macosx/email.py
@@ -0,0 +1,38 @@
+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):
+ 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():
+ import sys
+ if whereis_exe('open'):
+ return MacOSXEmail()
+ sys.stderr.write("open not found.")
+ return Email()
diff --git a/external/plyer/platforms/macosx/filechooser.py b/external/plyer/platforms/macosx/filechooser.py
new file mode 100644
index 0000000..5774cc4
--- /dev/null
+++ b/external/plyer/platforms/macosx/filechooser.py
@@ -0,0 +1,108 @@
+'''
+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(object):
+ '''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, **kwargs):
+ # Simulate Kivy's behavior
+ for i in kwargs:
+ setattr(self, i, kwargs[i])
+
+ def run(self):
+ panel = None
+ if self.mode in ("open", "dir"):
+ panel = NSOpenPanel.openPanel()
+ else:
+ panel = NSSavePanel.savePanel()
+
+ panel.setCanCreateDirectories_(True)
+
+ panel.setCanChooseDirectories_(self.mode == "dir")
+ panel.setCanChooseFiles_(self.mode != "dir")
+ panel.setShowsHiddenFiles_(self.show_hidden)
+
+ if self.title:
+ panel.setTitle_(objc_str(self.title))
+
+ if self.mode != "save" and self.multiple:
+ panel.setAllowsMultipleSelection_(True)
+
+ # 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 len(self.filters) > 0:
+ filthies = []
+ for f in self.filters:
+ if type(f) == str:
+ if not self.use_extensions:
+ if f.strip().endswith("*"):
+ continue
+ pystr = f.strip().split("*")[-1].split(".")[-1]
+ filthies.append(objc_str(pystr))
+ else:
+ for i in f[1:]:
+ if not self.use_extensions:
+ if f.strip().endswith("*"):
+ continue
+ pystr = f.strip().split("*")[-1].split(".")[-1]
+ filthies.append(objc_str(pystr))
+
+ ftypes_arr = objc_arr(filthies)
+ panel.setAllowedFileTypes_(ftypes_arr)
+ panel.setAllowsOtherFileTypes_(not self.use_extensions)
+
+ if self.path:
+ url = NSURL.fileURLWithPath_(self.path)
+ panel.setDirectoryURL_(url)
+
+ if panel.runModal_():
+ if self.mode == "save" or not self.multiple:
+ return [panel.filename().UTF8String()]
+ else:
+ return [i.UTF8String() for i in panel.filenames()]
+ return None
+
+
+class MacOSXFileChooser(FileChooser):
+ '''FileChooser implementation for Windows, using win3all.
+ '''
+ def _file_selection_dialog(self, **kwargs):
+ return MacFileChooser(**kwargs).run()
+
+
+def instance():
+ return MacOSXFileChooser()
diff --git a/external/plyer/platforms/macosx/libs/__init__.py b/external/plyer/platforms/macosx/libs/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/external/plyer/platforms/macosx/libs/__init__.py
diff --git a/external/plyer/platforms/macosx/libs/osx_motion_sensor.py b/external/plyer/platforms/macosx/libs/osx_motion_sensor.py
new file mode 100644
index 0000000..cd4fae4
--- /dev/null
+++ b/external/plyer/platforms/macosx/libs/osx_motion_sensor.py
@@ -0,0 +1,118 @@
+import ctypes
+from ctypes import (Structure, cdll, sizeof,
+ c_int, 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 = 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)])
diff --git a/external/plyer/platforms/macosx/notification.py b/external/plyer/platforms/macosx/notification.py
new file mode 100644
index 0000000..a52ebe3
--- /dev/null
+++ b/external/plyer/platforms/macosx/notification.py
@@ -0,0 +1,27 @@
+from plyer.facades import Notification
+import Foundation
+import objc
+import AppKit
+
+
+class OSXNotification(Notification):
+ def _notify(self, **kwargs):
+ NSUserNotification = objc.lookUpClass('NSUserNotification')
+ NSUserNotificationCenter = objc.lookUpClass('NSUserNotificationCenter')
+ notification = NSUserNotification.alloc().init()
+ notification.setTitle_(kwargs.get('title').encode('utf-8'))
+ #notification.setSubtitle_(str(subtitle))
+ notification.setInformativeText_(kwargs.get('message').encode('utf-8'))
+ notification.setSoundName_("NSUserNotificationDefaultSoundName")
+ #notification.setHasActionButton_(False)
+ #notification.setOtherButtonTitle_("View")
+ #notification.setUserInfo_({"action":"open_url", "value":url})
+ NSUserNotificationCenter.defaultUserNotificationCenter() \
+ .setDelegate_(self)
+ NSUserNotificationCenter.defaultUserNotificationCenter() \
+ .scheduleNotification_(notification)
+
+
+def instance():
+ return OSXNotification()
+
diff --git a/external/plyer/platforms/macosx/tts.py b/external/plyer/platforms/macosx/tts.py
new file mode 100644
index 0000000..755e820
--- /dev/null
+++ b/external/plyer/platforms/macosx/tts.py
@@ -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()
diff --git a/external/plyer/platforms/macosx/uniqueid.py b/external/plyer/platforms/macosx/uniqueid.py
new file mode 100644
index 0000000..51ba169
--- /dev/null
+++ b/external/plyer/platforms/macosx/uniqueid.py
@@ -0,0 +1,32 @@
+from subprocess import Popen, PIPE
+from plyer.facades import UniqueID
+from plyer.utils import whereis_exe
+
+from os import environ
+
+
+class OSXUniqueID(UniqueID):
+ 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]
+
+ environ['LANG'] = old_lang
+
+ if output:
+ return output.split()[3][1:-1]
+ else:
+ return None
+
+
+def instance():
+ import sys
+ if whereis_exe('ioreg'):
+ return OSXUniqueID()
+ sys.stderr.write("ioreg not found.")
+ return UniqueID()
diff --git a/external/plyer/platforms/win/__init__.py b/external/plyer/platforms/win/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/external/plyer/platforms/win/__init__.py
diff --git a/external/plyer/platforms/win/battery.py b/external/plyer/platforms/win/battery.py
new file mode 100644
index 0000000..04006f8
--- /dev/null
+++ b/external/plyer/platforms/win/battery.py
@@ -0,0 +1,21 @@
+from plyer.platforms.win.libs.batterystatus import battery_status
+from plyer.facades import Battery
+
+
+class WinBattery(Battery):
+ def _get_state(self):
+ status = {"isCharging": None, "percentage": None}
+
+ query = battery_status()
+
+ if (not query):
+ return status
+
+ status["isCharging"] = query["BatteryFlag"] == 8
+ status["percentage"] = query["BatteryLifePercent"]
+
+ return status
+
+
+def instance():
+ return WinBattery()
diff --git a/external/plyer/platforms/win/email.py b/external/plyer/platforms/win/email.py
new file mode 100644
index 0000000..e33f5cf
--- /dev/null
+++ b/external/plyer/platforms/win/email.py
@@ -0,0 +1,34 @@
+import os
+try:
+ from urllib.parse import quote
+except ImportError:
+ from urllib import quote
+from plyer.facades import Email
+
+
+class WindowsEmail(Email):
+ 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))
+
+ try:
+ os.startfile(uri)
+ except WindowsError:
+ print("Warning: unable to find a program able to send emails.")
+
+
+def instance():
+ return WindowsEmail()
diff --git a/external/plyer/platforms/win/filechooser.py b/external/plyer/platforms/win/filechooser.py
new file mode 100644
index 0000000..4d284dc
--- /dev/null
+++ b/external/plyer/platforms/win/filechooser.py
@@ -0,0 +1,112 @@
+'''
+Windows file chooser
+--------------------
+'''
+
+from plyer.facades import FileChooser
+from win32com.shell import shell, shellcon
+import os
+import win32gui
+import win32con
+import pywintypes
+
+
+class Win32FileChooser(object):
+ '''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
+ * multiple
+ * show_hidden
+ * filters
+ * path
+ '''
+
+ path = None
+ multiple = False
+ filters = []
+ preview = False
+ title = None
+ icon = None
+ show_hidden = False
+
+ def __init__(self, **kwargs):
+ # Simulate Kivy's behavior
+ for i in kwargs:
+ setattr(self, i, kwargs[i])
+
+ def run(self):
+ try:
+ if self.mode != "dir":
+ args = {}
+
+ if self.path:
+ args["InitialDir"] = os.path.dirname(self.path)
+ path = os.path.splitext(os.path.dirname(self.path))
+ args["File"] = path[0]
+ args["DefExt"] = path[1]
+ args["Title"] = self.title if self.title else "Pick a file..."
+ args["CustomFilter"] = 'Other file types\x00*.*\x00'
+ args["FilterIndex"] = 1
+
+ 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_EXTENSIONDIFFERENT |
+ win32con.OFN_OVERWRITEPROMPT)
+ if self.multiple:
+ flags |= win32con.OFN_ALLOWmultiple | win32con.OFN_EXPLORER
+ if self.show_hidden:
+ flags |= win32con.OFN_FORCESHOWHIDDEN
+ args["Flags"] = flags
+
+ if self.mode == "open":
+ self.fname, _, _ = win32gui.GetOpenFileNameW(**args)
+ elif self.mode == "save":
+ self.fname, _, _ = win32gui.GetSaveFileNameW(**args)
+
+ if self.fname:
+ if self.multiple:
+ seq = str(self.fname).split("\x00")
+ dir_n, base_n = seq[0], seq[1:]
+ self.selection = [os.path.join(dir_n, i)
+ for i in base_n]
+ else:
+ self.selection = str(self.fname).split("\x00")
+ else:
+ # From http://goo.gl/UDqCqo
+ pidl, display_name, image_list = shell.SHBrowseForFolder(
+ win32gui.GetDesktopWindow(),
+ None,
+ self.title if self.title else "Pick a folder...",
+ 0, None, None
+ )
+ self.selection = [str(shell.SHGetPathFromIDList(pidl))]
+
+ return self.selection
+ except (RuntimeError, pywintypes.error):
+ return None
+
+
+class WinFileChooser(FileChooser):
+ '''FileChooser implementation for Windows, using win3all.
+ '''
+
+ def _file_selection_dialog(self, **kwargs):
+ return Win32FileChooser(**kwargs).run()
+
+
+def instance():
+ return WinFileChooser()
diff --git a/external/plyer/platforms/win/libs/__init__.py b/external/plyer/platforms/win/libs/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/external/plyer/platforms/win/libs/__init__.py
diff --git a/external/plyer/platforms/win/libs/balloontip.py b/external/plyer/platforms/win/libs/balloontip.py
new file mode 100644
index 0000000..171b25f
--- /dev/null
+++ b/external/plyer/platforms/win/libs/balloontip.py
@@ -0,0 +1,145 @@
+# -- coding: utf-8 --
+
+__all__ = ('WindowsBalloonTip', 'balloon_tip')
+
+
+import time
+import ctypes
+from plyer.platforms.win.libs import win_api_defs
+from plyer.compat import PY2
+from threading import RLock
+
+
+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(object):
+
+ _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():
+ 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):
+ ''' app_icon if given is a icon file.
+ '''
+
+ wnd_class_ex = win_api_defs.get_WNDCLASSEXW()
+ class_name = 'PlyerTaskbar' + str(WindowsBalloonTip._get_unique_id())
+ if PY2:
+ class_name = class_name.decode('utf8')
+ 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(0, class_atom,
+ '', WS_OVERLAPPED, 0, 0, CW_USEDEFAULT,
+ CW_USEDEFAULT, 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 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(icon_path_name))
+ self._balloon_icon = self._hicon = hicon
+ else:
+ self._hicon = win_api_defs.LoadIconW(None,
+ ctypes.cast(IDI_APPLICATION, win_api_defs.LPCWSTR))
+ self.notify(title, message, app_name)
+ if timeout:
+ time.sleep(timeout)
+
+ def __del__(self):
+ 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.
+ '''
+ 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):
+ WindowsBalloonTip(**kwargs)
diff --git a/external/plyer/platforms/win/libs/batterystatus.py b/external/plyer/platforms/win/libs/batterystatus.py
new file mode 100644
index 0000000..ddb22cc
--- /dev/null
+++ b/external/plyer/platforms/win/libs/batterystatus.py
@@ -0,0 +1,13 @@
+__all__ = ('battery_status')
+
+
+import ctypes
+from plyer.platforms.win.libs import win_api_defs
+
+
+def battery_status():
+ status = win_api_defs.SYSTEM_POWER_STATUS()
+ if not win_api_defs.GetSystemPowerStatus(ctypes.pointer(status)):
+ raise Exception('Could not get system power status.')
+
+ return dict((field, getattr(status, field)) for field, _ in status._fields_)
diff --git a/external/plyer/platforms/win/libs/win_api_defs.py b/external/plyer/platforms/win/libs/win_api_defs.py
new file mode 100644
index 0000000..7aed430
--- /dev/null
+++ b/external/plyer/platforms/win/libs/win_api_defs.py
@@ -0,0 +1,200 @@
+''' Defines ctypes windows api.
+'''
+
+__all__ = ('GUID', 'get_DLLVERSIONINFO', 'MAKEDLLVERULL',
+ 'get_NOTIFYICONDATAW', 'CreateWindowExW', 'WindowProc',
+ 'DefWindowProcW', 'get_WNDCLASSEXW', 'GetModuleHandleW',
+ 'RegisterClassExW', 'UpdateWindow', 'LoadImageW',
+ 'Shell_NotifyIconW', 'DestroyIcon', 'UnregisterClassW',
+ 'DestroyWindow', 'LoadIconW')
+
+import ctypes
+from ctypes import Structure, windll, sizeof, POINTER, WINFUNCTYPE
+from ctypes.wintypes import (DWORD, HICON, HWND, UINT, WCHAR, WORD, BYTE,
+ LPCWSTR, INT, LPVOID, HINSTANCE, HMENU, LPARAM, WPARAM,
+ HBRUSH, HMODULE, ATOM, BOOL, HANDLE)
+LRESULT = LPARAM
+HRESULT = HANDLE
+HCURSOR = HICON
+
+
+class GUID(Structure):
+ _fields_ = [
+ ('Data1', DWORD),
+ ('Data2', WORD),
+ ('Data3', WORD),
+ ('Data4', BYTE * 8)
+ ]
+
+
+class DLLVERSIONINFO(Structure):
+ _fields_ = [
+ ('cbSize', DWORD),
+ ('dwMajorVersion', DWORD),
+ ('dwMinorVersion', DWORD),
+ ('dwBuildNumber', DWORD),
+ ('dwPlatformID', DWORD),
+ ]
+
+
+def get_DLLVERSIONINFO(*largs):
+ version_info = DLLVERSIONINFO(*largs)
+ version_info.cbSize = sizeof(DLLVERSIONINFO)
+ return version_info
+
+
+def MAKEDLLVERULL(major, minor, build, sp):
+ return (major << 48) | (minor << 32) | (build << 16) | sp
+
+
+NOTIFYICONDATAW_fields = [
+ ("cbSize", DWORD),
+ ("hWnd", HWND),
+ ("uID", UINT),
+ ("uFlags", UINT),
+ ("uCallbackMessage", UINT),
+ ("hIcon", HICON),
+ ("szTip", WCHAR * 128),
+ ("dwState", DWORD),
+ ("dwStateMask", DWORD),
+ ("szInfo", WCHAR * 256),
+ ("uVersion", UINT),
+ ("szInfoTitle", WCHAR * 64),
+ ("dwInfoFlags", DWORD),
+ ("guidItem", GUID),
+ ("hBalloonIcon", HICON),
+]
+
+
+class NOTIFYICONDATAW(Structure):
+ _fields_ = NOTIFYICONDATAW_fields[:]
+
+
+class NOTIFYICONDATAW_V3(Structure):
+ _fields_ = NOTIFYICONDATAW_fields[:-1]
+
+
+class NOTIFYICONDATAW_V2(Structure):
+ _fields_ = NOTIFYICONDATAW_fields[:-2]
+
+
+class NOTIFYICONDATAW_V1(Structure):
+ _fields_ = NOTIFYICONDATAW_fields[:6]
+
+
+NOTIFYICONDATA_V3_SIZE = sizeof(NOTIFYICONDATAW_V3)
+NOTIFYICONDATA_V2_SIZE = sizeof(NOTIFYICONDATAW_V2)
+NOTIFYICONDATA_V1_SIZE = sizeof(NOTIFYICONDATAW_V1)
+
+
+def get_NOTIFYICONDATAW(*largs):
+ notify_data = NOTIFYICONDATAW(*largs)
+
+ # get shell32 version to find correct NOTIFYICONDATAW size
+ DllGetVersion = windll.Shell32.DllGetVersion
+ DllGetVersion.argtypes = [POINTER(DLLVERSIONINFO)]
+ DllGetVersion.restype = HRESULT
+
+ version = get_DLLVERSIONINFO()
+ if DllGetVersion(version):
+ raise Exception('Cannot get Windows version numbers.')
+ v = MAKEDLLVERULL(version.dwMajorVersion, version.dwMinorVersion,
+ version.dwBuildNumber, version.dwPlatformID)
+
+ # from the version info find the NOTIFYICONDATA size
+ if v >= MAKEDLLVERULL(6, 0, 6, 0):
+ notify_data.cbSize = sizeof(NOTIFYICONDATAW)
+ elif v >= MAKEDLLVERULL(6, 0, 0, 0):
+ notify_data.cbSize = NOTIFYICONDATA_V3_SIZE
+ elif v >= MAKEDLLVERULL(5, 0, 0, 0):
+ notify_data.cbSize = NOTIFYICONDATA_V2_SIZE
+ else:
+ notify_data.cbSize = NOTIFYICONDATA_V1_SIZE
+ return notify_data
+
+
+CreateWindowExW = windll.User32.CreateWindowExW
+CreateWindowExW.argtypes = [DWORD, ATOM, LPCWSTR, DWORD, INT, INT, INT, INT,
+ HWND, HMENU, HINSTANCE, LPVOID]
+CreateWindowExW.restype = HWND
+
+GetModuleHandleW = windll.Kernel32.GetModuleHandleW
+GetModuleHandleW.argtypes = [LPCWSTR]
+GetModuleHandleW.restype = HMODULE
+
+WindowProc = WINFUNCTYPE(LRESULT, HWND, UINT, WPARAM, LPARAM)
+DefWindowProcW = windll.User32.DefWindowProcW
+DefWindowProcW.argtypes = [HWND, UINT, WPARAM, LPARAM]
+DefWindowProcW.restype = LRESULT
+
+
+class WNDCLASSEXW(Structure):
+ _fields_ = [
+ ('cbSize', UINT),
+ ('style', UINT),
+ ('lpfnWndProc', WindowProc),
+ ('cbClsExtra', INT),
+ ('cbWndExtra', INT),
+ ('hInstance', HINSTANCE),
+ ('hIcon', HICON),
+ ('hCursor', HCURSOR),
+ ('hbrBackground', HBRUSH),
+ ('lpszMenuName', LPCWSTR),
+ ('lpszClassName', LPCWSTR),
+ ('hIconSm', HICON),
+ ]
+
+
+def get_WNDCLASSEXW(*largs):
+ wnd_class = WNDCLASSEXW(*largs)
+ wnd_class.cbSize = sizeof(WNDCLASSEXW)
+ return wnd_class
+
+RegisterClassExW = windll.User32.RegisterClassExW
+RegisterClassExW.argtypes = [POINTER(WNDCLASSEXW)]
+RegisterClassExW.restype = ATOM
+
+UpdateWindow = windll.User32.UpdateWindow
+UpdateWindow.argtypes = [HWND]
+UpdateWindow.restype = BOOL
+
+LoadImageW = windll.User32.LoadImageW
+LoadImageW.argtypes = [HINSTANCE, LPCWSTR, UINT, INT, INT, UINT]
+LoadImageW.restype = HANDLE
+
+Shell_NotifyIconW = windll.Shell32.Shell_NotifyIconW
+Shell_NotifyIconW.argtypes = [DWORD, POINTER(NOTIFYICONDATAW)]
+Shell_NotifyIconW.restype = BOOL
+
+DestroyIcon = windll.User32.DestroyIcon
+DestroyIcon.argtypes = [HICON]
+DestroyIcon.restype = BOOL
+
+UnregisterClassW = windll.User32.UnregisterClassW
+UnregisterClassW.argtypes = [ATOM, HINSTANCE]
+UnregisterClassW.restype = BOOL
+
+DestroyWindow = windll.User32.DestroyWindow
+DestroyWindow.argtypes = [HWND]
+DestroyWindow.restype = BOOL
+
+LoadIconW = windll.User32.LoadIconW
+LoadIconW.argtypes = [HINSTANCE, LPCWSTR]
+LoadIconW.restype = HICON
+
+
+class SYSTEM_POWER_STATUS(ctypes.Structure):
+ _fields_ = [
+ ('ACLineStatus', BYTE),
+ ('BatteryFlag', BYTE),
+ ('BatteryLifePercent', BYTE),
+ ('Reserved1', BYTE),
+ ('BatteryLifeTime', DWORD),
+ ('BatteryFullLifeTime', DWORD),
+ ]
+
+SystemPowerStatusP = ctypes.POINTER(SYSTEM_POWER_STATUS)
+
+GetSystemPowerStatus = ctypes.windll.kernel32.GetSystemPowerStatus
+GetSystemPowerStatus.argtypes = [SystemPowerStatusP]
+GetSystemPowerStatus.restype = BOOL
diff --git a/external/plyer/platforms/win/notification.py b/external/plyer/platforms/win/notification.py
new file mode 100644
index 0000000..ea46e08
--- /dev/null
+++ b/external/plyer/platforms/win/notification.py
@@ -0,0 +1,13 @@
+from threading import Thread as thread
+
+from plyer.facades import Notification
+from plyer.platforms.win.libs.balloontip import balloon_tip
+
+
+class WindowsNotification(Notification):
+ def _notify(self, **kwargs):
+ thread(target=balloon_tip, kwargs=kwargs).start()
+
+
+def instance():
+ return WindowsNotification()
diff --git a/external/plyer/platforms/win/tts.py b/external/plyer/platforms/win/tts.py
new file mode 100644
index 0000000..e2539c3
--- /dev/null
+++ b/external/plyer/platforms/win/tts.py
@@ -0,0 +1,16 @@
+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')])
+
+
+def instance():
+ if whereis_exe('espeak.exe'):
+ return EspeakTextToSpeech()
+ return TTS()
diff --git a/external/plyer/platforms/win/uniqueid.py b/external/plyer/platforms/win/uniqueid.py
new file mode 100644
index 0000000..bfcf996
--- /dev/null
+++ b/external/plyer/platforms/win/uniqueid.py
@@ -0,0 +1,21 @@
+try:
+ import _winreg as regedit
+except:
+ try:
+ import winreg as regedit
+ except:
+ raise NotImplemented()
+
+from plyer.facades import UniqueID
+
+
+class WinUniqueID(UniqueID):
+ def _get_uid(self):
+ hKey = regedit.OpenKey(regedit.HKEY_LOCAL_MACHINE,
+ r"SOFTWARE\\Microsoft\\Cryptography")
+ value, _ = regedit.QueryValueEx(hKey, "MachineGuid")
+ return value
+
+
+def instance():
+ return WinUniqueID()