diff options
Diffstat (limited to 'external/plyer/platforms')
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() |