mirror of
https://github.com/jtgans/g13gui.git
synced 2025-06-20 00:14:09 -04:00
applets: Add in the framework for Applets!
This allows us to have separate applications from the G13 Configurator render to the G13's display, just like the way Logitech's design worked. It even includes an applet switcher, and an example clock applet. There's quite a few deficiencies in this model, not the least of which is the fact that we're using dbus-python as the main communications channel. Things that are broken include: - We can't tell when an applet dies except when we fail a call. - Applets have to be running at the same time G13 Configurator is to handle the initial register call. We can likely deal with these somehow using DBus signals.
This commit is contained in:
parent
81be3f2cf9
commit
18e536d7f7
0
g13gui/g13gui/applet/__init__.py
Normal file
0
g13gui/g13gui/applet/__init__.py
Normal file
160
g13gui/g13gui/applet/applet.py
Normal file
160
g13gui/g13gui/applet/applet.py
Normal file
@ -0,0 +1,160 @@
|
||||
import gi
|
||||
import dbus
|
||||
import dbus.service
|
||||
import dbus.mainloop.glib
|
||||
import time
|
||||
|
||||
from dbus.mainloop.glib import DBusGMainLoop
|
||||
from dbus.exceptions import DBusException
|
||||
from dbus.types import ByteArray
|
||||
|
||||
from g13gui.applet.loopbackdisplaydevice import LoopbackDisplayDevice
|
||||
from g13gui.bitwidgets.display import Display
|
||||
from g13gui.bitwidgets.screen import Screen
|
||||
|
||||
gi.require_version('GLib', '2.0')
|
||||
from gi.repository import GLib
|
||||
|
||||
|
||||
class Buttons(object):
|
||||
L1 = 1
|
||||
L2 = 2
|
||||
L3 = 3
|
||||
L4 = 4
|
||||
|
||||
|
||||
class Applet(dbus.service.Object):
|
||||
BUS_INTERFACE = 'com.theonelab.g13.Applet'
|
||||
BUS_PATH = '/com/theonelab/g13/Applet'
|
||||
|
||||
def __init__(self, name):
|
||||
dbus.service.Object.__init__(self, dbus.SessionBus(),
|
||||
Applet.BUS_PATH)
|
||||
|
||||
self._name = name
|
||||
self._dd = LoopbackDisplayDevice()
|
||||
self._d = Display(self._dd)
|
||||
self._s = Screen(self._d)
|
||||
self._s.hide()
|
||||
|
||||
self._registered = False
|
||||
self._manager = None
|
||||
|
||||
def register(self):
|
||||
try:
|
||||
self._manager = self._bus.get_object(
|
||||
'com.theonelab.g13.AppletManager',
|
||||
'/com/theonelab/g13/AppletManager')
|
||||
except DBusException as err:
|
||||
self._manager = None
|
||||
return True
|
||||
|
||||
self._manager.Register(self._name)
|
||||
self._registered = True
|
||||
GLib.timeout_add_seconds(1, self._ping)
|
||||
|
||||
return False
|
||||
|
||||
def _ping(self):
|
||||
if self._manager:
|
||||
try:
|
||||
self._manager.Ping()
|
||||
except DBusException as err:
|
||||
print('Lost connection with AppletManager: %s' % err)
|
||||
self._registered = False
|
||||
GLib.timeout_add_seconds(1, self.register)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def run(self):
|
||||
self._bus = dbus.SessionBus()
|
||||
|
||||
GLib.timeout_add_seconds(1, self.register)
|
||||
|
||||
loop = GLib.MainLoop()
|
||||
loop.run()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def displayDevice(self):
|
||||
return self._dd
|
||||
|
||||
@property
|
||||
def display(self):
|
||||
return self._d
|
||||
|
||||
@property
|
||||
def screen(self):
|
||||
return self._s
|
||||
|
||||
def onKeyPressed(self, timestamp, key):
|
||||
pass
|
||||
|
||||
def onKeyReleased(self, timestamp, key):
|
||||
pass
|
||||
|
||||
def onShown(self, timestamp):
|
||||
pass
|
||||
|
||||
def onHidden(self):
|
||||
pass
|
||||
|
||||
def maybePresentScreen(self):
|
||||
if self.screen.visible and self._manager:
|
||||
self.screen.nextFrame()
|
||||
frame = self.displayDevice.frame
|
||||
frame = ByteArray(frame)
|
||||
self._manager.Present(frame, byte_arrays=True)
|
||||
|
||||
@dbus.service.method(BUS_INTERFACE,
|
||||
in_signature='d', out_signature='ay',
|
||||
byte_arrays=True)
|
||||
def Present(self, timestamp):
|
||||
self.screen.show()
|
||||
self.onShown(timestamp)
|
||||
self.screen.nextFrame()
|
||||
return ByteArray(self.displayDevice.frame)
|
||||
|
||||
@dbus.service.method(BUS_INTERFACE)
|
||||
def Unpresent(self):
|
||||
self.screen.hide()
|
||||
self.onHidden()
|
||||
|
||||
def _setButtonPressed(self, state, button):
|
||||
buttonIdx = button - 1
|
||||
button = self._s.buttonBar.button(buttonIdx)
|
||||
if button:
|
||||
button.pressed = state
|
||||
|
||||
def onUpdateScreen(self):
|
||||
pass
|
||||
|
||||
@dbus.service.method(BUS_INTERFACE,
|
||||
in_signature='di', out_signature='ay',
|
||||
byte_arrays=True)
|
||||
def KeyPressed(self, timestamp, key):
|
||||
self.onKeyPressed(timestamp, key)
|
||||
self._setButtonPressed(True, key)
|
||||
self.onUpdateScreen()
|
||||
self.screen.nextFrame()
|
||||
return ByteArray(self.displayDevice.frame)
|
||||
|
||||
@dbus.service.method(BUS_INTERFACE,
|
||||
in_signature='di', out_signature='ay',
|
||||
byte_arrays=True)
|
||||
def KeyReleased(self, timestamp, key):
|
||||
self.onKeyPressed(timestamp, key)
|
||||
self._setButtonPressed(False, key)
|
||||
self.onUpdateScreen()
|
||||
self.screen.nextFrame()
|
||||
return ByteArray(self.displayDevice.frame)
|
||||
|
||||
|
||||
def RunApplet(cls, *args, **kwargs):
|
||||
DBusGMainLoop(set_as_default=True)
|
||||
applet = cls(*args, **kwargs)
|
||||
applet.run()
|
24
g13gui/g13gui/applet/loopbackdisplaydevice.py
Normal file
24
g13gui/g13gui/applet/loopbackdisplaydevice.py
Normal file
@ -0,0 +1,24 @@
|
||||
from builtins import property
|
||||
|
||||
from g13gui.bitwidgets.displaydevice import DisplayDevice
|
||||
from g13gui.g13.displaydevice import G13DisplayDevice
|
||||
|
||||
|
||||
class LoopbackDisplayDevice(G13DisplayDevice):
|
||||
"""A loopback display device for the G13 manager.
|
||||
|
||||
This one differs from the built-in G13DisplayDevice by preventing a direct
|
||||
write to the G13Manager's setLCDbuffer. This is specifically designed for
|
||||
the Applet use case, where the methods return LCD frames, rather than write
|
||||
directly.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@property
|
||||
def frame(self):
|
||||
return self._frame
|
||||
|
||||
def _pushFrame(self, lpbm):
|
||||
self._frame = lpbm
|
115
g13gui/g13gui/applet/manager.py
Normal file
115
g13gui/g13gui/applet/manager.py
Normal file
@ -0,0 +1,115 @@
|
||||
import gi
|
||||
import dbus
|
||||
import dbus.service
|
||||
import dbus.mainloop.glib
|
||||
import time
|
||||
|
||||
from builtins import property
|
||||
|
||||
from g13gui.observer.subject import Subject
|
||||
from g13gui.observer.subject import ChangeType
|
||||
from g13gui.applet.switcher import Switcher
|
||||
from g13gui.g13.common import G13Keys
|
||||
|
||||
gi.require_version('GLib', '2.0')
|
||||
from gi.repository import GLib
|
||||
|
||||
|
||||
class AppletManager(dbus.service.Object, Subject):
|
||||
INTERFACE_NAME = 'com.theonelab.g13.AppletManager'
|
||||
BUS_NAME = 'com.theonelab.g13.AppletManager'
|
||||
BUS_PATH = '/com/theonelab/g13/AppletManager'
|
||||
|
||||
def __init__(self, manager):
|
||||
self._bus = dbus.SessionBus()
|
||||
self._busName = dbus.service.BusName(AppletManager.BUS_NAME, self._bus)
|
||||
dbus.service.Object.__init__(self, self._bus,
|
||||
AppletManager.BUS_PATH)
|
||||
Subject.__init__(self)
|
||||
|
||||
self._manager = manager
|
||||
|
||||
# [name] -> (sender, proxy)
|
||||
self._applets = {}
|
||||
|
||||
self._switcher = Switcher(self)
|
||||
self._activeApplet = self._switcher
|
||||
|
||||
self._applets['Switcher'] = (self._switcher, self._switcher)
|
||||
self.addChange(ChangeType.ADD, 'applet', 'Switcher')
|
||||
self.notifyChanged()
|
||||
|
||||
@property
|
||||
def activeApplet(self):
|
||||
return self._activeApplet
|
||||
|
||||
@activeApplet.setter
|
||||
def activeApplet(self, appletName):
|
||||
(name, appletProxy) = self._applets[appletName]
|
||||
self._activeApplet.Unpresent()
|
||||
self.setProperty('activeApplet', appletProxy)
|
||||
self.onPresent()
|
||||
|
||||
def raiseSwitcher(self):
|
||||
self._activeApplet = self._switcher
|
||||
self.onPresent()
|
||||
|
||||
@property
|
||||
def appletNames(self):
|
||||
return self._applets.keys()
|
||||
|
||||
def _updateLCD(self, frame):
|
||||
self._manager.setLCDBuffer(frame)
|
||||
|
||||
def onPresent(self):
|
||||
frame = self._activeApplet.Present(time.time(), byte_arrays=True)
|
||||
frame = bytes(frame)
|
||||
self._updateLCD(frame)
|
||||
|
||||
def onKeyPressed(self, key):
|
||||
# Swap to the switcher
|
||||
if key == G13Keys.BD:
|
||||
self.activeApplet = 'Switcher'
|
||||
return
|
||||
|
||||
frame = self._activeApplet.KeyPressed(time.time(), key.value['bit'])
|
||||
self._updateLCD(frame)
|
||||
|
||||
def onKeyReleased(self, key):
|
||||
frame = self._activeApplet.KeyReleased(time.time(), key.value['bit'])
|
||||
self._updateLCD(frame)
|
||||
|
||||
def _registerApplet(self, name, sender):
|
||||
proxy = self._bus.get_object(sender, '/com/theonelab/g13/Applet')
|
||||
self._applets[name] = (sender, proxy)
|
||||
self.addChange(ChangeType.ADD, 'applet', name)
|
||||
self.notifyChanged()
|
||||
|
||||
@dbus.service.method(dbus_interface=INTERFACE_NAME,
|
||||
in_signature='s', sender_keyword='sender')
|
||||
def Register(self, name, sender):
|
||||
if sender is None:
|
||||
print('Attempt to register None as sender applet!')
|
||||
return False
|
||||
|
||||
print('Registered applet %s as %s' % (name, sender))
|
||||
GLib.idle_add(self._registerApplet, str(name), sender)
|
||||
|
||||
def _presentScreen(self, screen, sender):
|
||||
self._updateLCD(screen)
|
||||
|
||||
@dbus.service.method(dbus_interface=INTERFACE_NAME,
|
||||
in_signature='ay', sender_keyword='sender',
|
||||
byte_arrays=True)
|
||||
def Present(self, screen, sender):
|
||||
# if self._activeApplet.bus_name != sender:
|
||||
# return
|
||||
GLib.idle_add(self._presentScreen, screen, sender)
|
||||
|
||||
@dbus.service.method(dbus_interface=INTERFACE_NAME,
|
||||
out_signature='b',
|
||||
sender_keyword='sender')
|
||||
def Ping(self, sender):
|
||||
if sender not in [s[0] for s in self._applets]:
|
||||
return False
|
||||
return True
|
101
g13gui/g13gui/applet/switcher.py
Normal file
101
g13gui/g13gui/applet/switcher.py
Normal file
@ -0,0 +1,101 @@
|
||||
import gi
|
||||
import time
|
||||
import threading
|
||||
|
||||
from builtins import property
|
||||
|
||||
from g13gui.observer.observer import Observer
|
||||
from g13gui.observer.subject import ChangeType
|
||||
from g13gui.applet.applet import Buttons
|
||||
from g13gui.applet.loopbackdisplaydevice import LoopbackDisplayDevice
|
||||
from g13gui.bitwidgets.display import Display
|
||||
from g13gui.bitwidgets.screen import Screen
|
||||
from g13gui.bitwidgets.label import Label
|
||||
from g13gui.bitwidgets.button import Button
|
||||
from g13gui.bitwidgets.button import Glyphs
|
||||
from g13gui.bitwidgets.listview import ListView
|
||||
|
||||
gi.require_version('GLib', '2.0')
|
||||
from gi.repository import GLib
|
||||
|
||||
|
||||
class Switcher(Observer):
|
||||
def __init__(self, appletManager):
|
||||
Observer.__init__(self)
|
||||
|
||||
self._appletManager = appletManager
|
||||
self._applets = []
|
||||
|
||||
self._appletManager.registerObserver(self, {'activeApplet', 'applet'})
|
||||
self.changeTrigger(self.onNewApplet,
|
||||
changeType=ChangeType.ADD,
|
||||
keys={'applet'})
|
||||
|
||||
self._initWidgets()
|
||||
|
||||
@property
|
||||
def bus_name(self):
|
||||
return self
|
||||
|
||||
def onNewApplet(self, subject, changeType, key, data):
|
||||
self._applets = sorted(self._appletManager.appletNames)
|
||||
self._lv.model = self._applets
|
||||
self._lv.update()
|
||||
|
||||
self._s.nextFrame()
|
||||
frame = self._dd.frame
|
||||
self._appletManager.Present(frame, self)
|
||||
|
||||
def _initWidgets(self):
|
||||
self._dd = LoopbackDisplayDevice()
|
||||
self._d = Display(self._dd)
|
||||
self._s = Screen(self._d)
|
||||
|
||||
self._lv = ListView(self._applets)
|
||||
self._lv.showAll()
|
||||
self._s.addChild(self._lv)
|
||||
|
||||
button = Button(Glyphs.DOWN_ARROW)
|
||||
self._s.buttonBar.setButton(1, button)
|
||||
button = Button(Glyphs.UP_ARROW)
|
||||
self._s.buttonBar.setButton(2, button)
|
||||
button = Button(Glyphs.CHECKMARK)
|
||||
self._s.buttonBar.setButton(3, button)
|
||||
self._s.buttonBar.showAll()
|
||||
|
||||
def _setButtonPressed(self, state, button):
|
||||
buttonIdx = button - 1
|
||||
button = self._s.buttonBar.button(buttonIdx)
|
||||
if button:
|
||||
button.pressed = state
|
||||
|
||||
def Present(self, timestamp, **kwargs):
|
||||
self._s.show()
|
||||
self._lv.update()
|
||||
self._s.nextFrame()
|
||||
frame = self._dd.frame
|
||||
return frame
|
||||
|
||||
def Unpresent(self):
|
||||
self._s.hide()
|
||||
|
||||
def KeyPressed(self, timestamp, key):
|
||||
self._setButtonPressed(True, key)
|
||||
return self.Present(timestamp)
|
||||
|
||||
def _setActiveApplet(self):
|
||||
selectedName = self._lv.markedItem()
|
||||
self._appletManager.activeApplet = selectedName
|
||||
|
||||
def KeyReleased(self, timestamp, key):
|
||||
self._setButtonPressed(False, key)
|
||||
|
||||
if key == Buttons.L2: # down
|
||||
self._lv.nextSelection()
|
||||
elif key == Buttons.L3: # up
|
||||
self._lv.prevSelection()
|
||||
elif key == Buttons.L4: # select
|
||||
self._lv.markSelection()
|
||||
GLib.idle_add(self._setActiveApplet)
|
||||
|
||||
return self.Present(timestamp)
|
0
g13gui/g13gui/applets/__init__.py
Normal file
0
g13gui/g13gui/applets/__init__.py
Normal file
41
g13gui/g13gui/applets/clock.py
Normal file
41
g13gui/g13gui/applets/clock.py
Normal file
@ -0,0 +1,41 @@
|
||||
import gi
|
||||
import time
|
||||
|
||||
from g13gui.applet.applet import Applet
|
||||
from g13gui.applet.applet import RunApplet
|
||||
from g13gui.bitwidgets.label import Label
|
||||
from g13gui.bitwidgets.fonts import Fonts
|
||||
|
||||
gi.require_version('GLib', '2.0')
|
||||
from gi.repository import GLib
|
||||
|
||||
|
||||
class ClockApplet(Applet):
|
||||
NAME = 'Clock'
|
||||
|
||||
def __init__(self):
|
||||
Applet.__init__(self, ClockApplet.NAME)
|
||||
|
||||
self._timeLabel = Label(43, 8, '18:54:00', font=Fonts.LARGE)
|
||||
self._timeLabel.showAll()
|
||||
self.screen.addChild(self._timeLabel)
|
||||
|
||||
self._updateTimeLabel()
|
||||
|
||||
def _updateTimeLabel(self):
|
||||
(tm_year, tm_month, tm_mday, tm_hour,
|
||||
tm_min, tm_sec, tm_wday, tm_yday, tm_isdst) = time.localtime()
|
||||
self._timeLabel.text = '%d:%0.2d:%0.2d' % (tm_hour, tm_min, tm_sec)
|
||||
|
||||
def _pushTime(self):
|
||||
self._updateTimeLabel()
|
||||
self.maybePresentScreen()
|
||||
return self.screen.visible
|
||||
|
||||
def onShown(self, timestamp):
|
||||
self._updateTimeLabel()
|
||||
GLib.timeout_add_seconds(1, self._pushTime)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
RunApplet(ClockApplet)
|
Loading…
Reference in New Issue
Block a user