Browse Source

Add splash-screen

Joachim M. Giæver 1 year ago
parent
commit
59bd72d37f

+ 7 - 0
snap/gui/ozwadmin.desktop

@@ -0,0 +1,7 @@
+[Desktop Entry]
+Name=OZWAdmin
+Comment=OpenZWave Administration Gui
+Exec=ozwadmin
+Type=Application
+Icon=${SNAP}/meta/gui/ozwadmin.png
+Categories=System;

BIN
snap/gui/ozwadmin.png


+ 78 - 22
snap/snapcraft.yaml

@@ -3,7 +3,7 @@ base: core20
 version: 'V0.1.74'
 adopt-info: ozw-admin
 
-grade: devel # must be 'stable' to release into candidate/stable channels
+grade: 'stable'
 confinement: strict 
 
 layout:
@@ -26,32 +26,33 @@ package-repositories:
     suites: [xenial-security]
     key-id: 40976EAF437D05B5
     url: http://security.ubuntu.com/ubuntu
+  - type: apt
+    ppa: deadsnakes/ppa
 
 plugs:
-  ozw-configuration:
+  ozw-config:
     interface: content
-    content: ozw-configuration
+    content: ozw-config
     target: $SNAP/usr/local/share/OpenZWave/ozw-admin
-  qt-ozw-db:
+    default-provider: ozwdaemon
+  ozw-db:
     interface: content
-    content: qt-ozw-db
-    target: $SNAP/usr/local/share/OpenZWave
+    content: ozw-db
+    target: $SNAP/usr/local/share/OpenZWave/qt-openzwavedatabase.rcc
+    default-provider: ozwdaemon
   qt-ozw-lib:
     interface: content
     content: qt-ozw-lib
     target: $SNAP/usr/qt-ozw-lib
-
-slots:
-  a11y-bus:
-    interface: dbus
-    bus: session
-    name: org.a11y.Bus
+    default-provider: ozwdaemon
 
 apps:
   ozwadmin:
     command: usr/local/bin/ozwadmin
     command-chain:
-      - wrapper
+      - misc/ensure-plugs
+      - misc/wrapper
+    common-id: ozwadmin
     plugs:
       - desktop
       - desktop-legacy
@@ -59,8 +60,9 @@ apps:
       - network
       - network-bind
       - hardware-observe
-    slots:
-      - a11y-bus
+      - ozw-config
+      - ozw-db
+      - qt-ozw-lib
 
 parts:
   ozw-admin:
@@ -113,9 +115,7 @@ parts:
       - libgl1-mesa-dri
       - mesa-utils
     override-build: |
-      cp ${SNAPCRAFT_PART_BUILD}/scripts/ozwadmin.* "${SNAPCRAFT_PART_INSTALL}"
-      #sed -i -E "s#^Exec=(.+)#Exec=usr/local/bin/ozwadmin#" "${SNAPCRAFT_PART_INSTALL}/ozwadmin.desktop"
-      sed -i '$ d' "${SNAPCRAFT_PART_INSTALL}/ozwadmin.desktop"
+      cp ${SNAPCRAFT_PART_BUILD}/scripts/ozwadmin.png "${SNAPCRAFT_PART_INSTALL}"
       if [ -f "Makefile" ]; then
         echo "Clean $SNAPCRAFT_PART_BUILD"
         qmake -r
@@ -155,9 +155,65 @@ parts:
       make -j$(nproc)
       make -j$(nproc) install INSTALL_ROOT="${SNAPCRAFT_PART_INSTALL}"
     organize:
-      ozwadmin.png: snap/gui/ozwadmin.png
-      ozwadmin.desktop: snap/gui/ozwadmin.desktop
-  local-src:
+      ozwadmin.png: meta/gui/ozwadmin.png
+    filesets:
+      ozwadmin-usr:
+        - usr/bin
+        - usr/include
+        - usr/local
+        - usr/share
+        - usr/lib/X11
+        - usr/lib/*/libQt5SerialPort*
+        - usr/lib/*/libQt5Svg*
+        - usr/lib/*/libQt5Xml*
+        - usr/lib/*/qt5/plugins/iconengines
+        - usr/lib/*/qt5/plugins/imageformats/libqsvg.so
+    stage:
+      - $ozwadmin-usr
+      - etc/
+      - lib*/*
+      - meta/
+  wrapper:
     plugin: dump
-    source: src/
+    source: src/wrapper
     source-type: local
+    organize:
+      ensure-plugs: misc/ensure-plugs
+      wrapper: misc/wrapper
+  splash-screen:
+    plugin: python
+    source: src/python
+    source-type: local
+    python-packages:
+      - wheel
+    requirements:
+      - requirements.txt
+    build-packages:
+      - python3-dev
+      - python3-setuptools
+      - python3-wheel
+      - python3-pip
+    stage-packages:
+      - libgl1-mesa-dri
+      - mesa-utils
+      - libfreetype6
+      - libfontconfig1
+      - libxcb-icccm4
+      - libxcb-image0
+      - libxcb-keysyms1
+      - libxcb-randr0
+      - libxcb-render-util0
+      - libxcb-shape0
+      - libxcb-xinerama0
+      - libxcb-xkb1
+      - libxcb-sync1
+      - libxcb-xfixes0
+      - libxcb-shm0
+      - libxkbcommon-x11-0
+    stage:
+      - bin/
+      - etc/
+      - lib*/*
+      - share/
+      - usr/
+      - pyvenv.cfg

+ 3 - 0
src/python/requirements.txt

@@ -0,0 +1,3 @@
+PySide2==5.15.2
+PyYAML==5.3.1
+shiboken2==5.15.2

+ 24 - 0
src/python/setup.py

@@ -0,0 +1,24 @@
+#!/usr/bin/env python3
+
+import setuptools
+
+with open('requirements.txt') as f:
+    requirements = f.read().splitlines()
+
+setuptools.setup(
+    name = "ozwadmin-splash",
+    version = "0.0.1",
+    author = "Joachim M. Giæver",
+    description = "Simple splash screen",
+    long_description = "Splash to help connect nessecary plugs to the snap package of ozwadmin",
+    url = "https://git.giaever.org/home-assistant-snap/ozwadmin",
+    packages = setuptools.find_packages(),
+    classifiers = [
+        "Programming Language :: Python :: 3"
+    ],
+    install_requires = requirements,
+    python_requires = '>=3.8',
+    entry_points = {
+        'console_scripts': 'ozwadmin-splash=splash.main:run'
+    }
+)

+ 3 - 0
src/python/splash/__init__.py

@@ -0,0 +1,3 @@
+#!/usr/bin/env python3
+import sys, os
+sys.path.append(os.path.dirname(__file__))

BIN
src/python/splash/__pycache__/__init__.cpython-38.pyc


BIN
src/python/splash/__pycache__/__main__.cpython-38.pyc


BIN
src/python/splash/__pycache__/app.cpython-38.pyc


BIN
src/python/splash/__pycache__/main.cpython-38.pyc


BIN
src/python/splash/__pycache__/main_window.cpython-38.pyc


BIN
src/python/splash/__pycache__/worker.cpython-38.pyc


+ 47 - 0
src/python/splash/app.py

@@ -0,0 +1,47 @@
+#!/usr/bin/env python3
+
+import os, pathlib, subprocess, re
+import yaml
+
+
+from PySide2 import Qt, QtCore, QtWidgets, QtGui
+
+class App(QtWidgets.QApplication):
+    def __init__(self, argv):
+        super().__init__(argv)
+        self.__env = Environment()
+        self.__boot = True if 'boot' in argv else False
+
+    def get_icon(self) -> str:
+        return f"{self.__env.get_base()}/gui/ozwadmin.png"
+
+    def get_yaml(self) -> str:
+        with open(self.__env.get_yaml()) as yaml_file:
+            return yaml.full_load(yaml_file)
+
+    def get_env(self) -> str:
+        return self.__env
+
+    def boot(self) -> bool:
+        return self.__boot
+
+class Environment(object):
+    def __init__(self):
+        self.__env = 'snap' if os.getenv('SNAP') is not None else 'local'
+        self.__base = f"{os.getenv('SNAP')}/meta" if self.__env == 'snap' else f"{pathlib.Path(__file__).parent.absolute()}/../../../snap"
+
+    def get_yaml(self) -> str:
+        return f"{self.__base}/{'snap' if self.__base.endswith('meta') else 'snapcraft'}.yaml"
+
+    def get_base(self) -> str:
+        return self.__base
+
+    def is_connected(self, plug, provider) -> bool:
+
+        if self.__env != "snap":
+            op = subprocess.check_output(['snap', 'connections', 'ozwadmin'], encoding="UTF-8")
+            regex = r"ozwadmin\:" + plug + r"\s+" + (provider if provider is not None else '' ) + r"\:" + plug
+            return True if re.search(regex, op) else False
+
+        p = subprocess.run(['snapctl', 'is-connected', plug], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        return p.returncode == 0

+ 11 - 0
src/python/splash/main.py

@@ -0,0 +1,11 @@
+#!/usr/bin/env python3
+import sys, app, main_window
+
+def run():
+    _app = app.App(sys.argv)
+    main = main_window.MainWindow(_app)
+    main.show()
+    sys.exit(_app.exec_())
+
+if __name__ == '__main__':
+    run()

+ 113 - 0
src/python/splash/main_window.py

@@ -0,0 +1,113 @@
+#!/usr/bin/env python3
+
+import worker
+from PySide2 import QtCore, QtWidgets, QtGui
+
+class MainWindow(QtWidgets.QWidget):
+    def __init__(self, app):
+        super().__init__()
+
+        self.app = app
+
+        self.setGeometry(500, 400, 500, 400)
+
+        self.setWindowModified(True)
+        self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
+        self.setWindowIcon(QtGui.QIcon(app.get_icon()))
+
+        qRect = self.frameGeometry()
+        qRect.moveCenter(QtWidgets.QDesktopWidget().availableGeometry().center())
+        self.move(qRect.topLeft())
+
+        layout = QtWidgets.QVBoxLayout()
+        self.setLayout(layout)
+
+        img = QtGui.QPixmap(app.get_icon())
+        logo = QtWidgets.QLabel(self)
+        logo.setPixmap(img)
+        logo.setAlignment(QtCore.Qt.AlignBaseline| QtCore.Qt.AlignCenter)
+        layout.addWidget(logo, 1, QtCore.Qt.AlignBaseline| QtCore.Qt.AlignCenter)
+
+        title = QtWidgets.QLabel(self)
+        title.setText("OZW Admin")
+        title.setFont(QtGui.QFont("Times", 28, QtGui.QFont.Bold))
+        layout.addWidget(title, 0, QtCore.Qt.AlignBaseline| QtCore.Qt.AlignCenter)
+
+
+        self.header = QtWidgets.QLabel(self)
+        self.header.setText("Checking plugs")
+        self.header.setAlignment(QtCore.Qt.AlignCenter)
+        layout.addWidget(self.header, 0, QtCore.Qt.AlignBaseline| QtCore.Qt.AlignCenter)
+
+        self.desc = QtWidgets.QLabel(self)
+        self.desc.setText('...')
+        self.desc.setAlignment(QtCore.Qt.AlignCenter)
+        layout.addWidget(self.desc, 0, QtCore.Qt.AlignBaseline| QtCore.Qt.AlignCenter)
+
+        self.pgbar = QtWidgets.QProgressBar(self)
+        layout.addWidget(self.pgbar)
+
+        self.plugs = []
+        self.pgval = 0
+
+        self.pw = worker.PlugsWorker(self)
+        self.exit_code = 0
+
+    def close(self):
+        return super().close()
+
+    def quit(self):
+        self.pw.stop()
+        self.close()
+        self.app.exit(self.exit_code)
+
+    def updateExitCode(self, code):
+        if code in [0, 255, 256]:
+            self.exit_code = code
+
+    def showEvent(self, event):
+        super().showEvent(event)
+        self.pw.run()
+
+    def updateFinised(self):
+        if self.exit_code != 0:
+            self.header.setText(f'{self.header.text()} - A manual restart is required!')
+            self.desc.setText("Exit and launch OZWAdmin again.")
+
+            self.layout().removeWidget(self.pgbar)
+            self.pgbar.deleteLater()
+
+            button = QtWidgets.QPushButton("Exit")
+            button.clicked.connect(self.quit)
+            self.layout().addWidget(button)
+
+        else:
+            self.quit()
+
+    def updateProgress(self):
+        self.pgval = (self.pgval + 1)
+        self.pgbar.setValue(self.pgval)
+
+    def updateText(self, header, desc):
+        self.header.setText(header)
+        self.desc.setText(desc)
+
+    def get_plugs(self):
+        if len(self.plugs) != 0:
+            return self.plugs
+
+        yaml = self.app.get_yaml()
+        for app_data in yaml['apps'].values():
+            for plug in app_data['plugs']:
+                provider = None
+                for xplug, values in yaml['plugs'].items():
+                    if xplug != plug:
+                        continue
+                    provider = values['default-provider']
+                    break
+                self.plugs.append({'provider': provider, 'name': plug})
+
+        self.pgbar.setRange(0,len(self.plugs))
+
+        return self.plugs
+

+ 3 - 0
src/python/splash/pyvenv.cfg

@@ -0,0 +1,3 @@
+home = /usr/bin
+include-system-site-packages = false
+version = 3.8.5

+ 119 - 0
src/python/splash/test.py

@@ -0,0 +1,119 @@
+import sys,os,subprocess,time,traceback
+import random
+from PySide2 import QtCore
+from PySide2.QtWidgets import *
+from PySide2.QtGui import *
+
+class Worker(QtCore.QRunnable):
+    """Worker thread for running background tasks."""
+
+    def __init__(self, fn, *args, **kwargs):
+        super(Worker, self).__init__()
+        # Store constructor arguments (re-used for processing)
+        self.fn = fn
+        self.args = args
+        self.kwargs = kwargs
+        self.signals = WorkerSignals()
+        self.kwargs['progress_callback'] = self.signals.progress
+
+    @QtCore.Slot()
+    def run(self):
+        try:
+            result = self.fn(
+                *self.args, **self.kwargs,
+            )
+        except:
+            traceback.print_exc()
+            exctype, value = sys.exc_info()[:2]
+            self.signals.error.emit((exctype, value, traceback.format_exc()))
+        else:
+            self.signals.result.emit(result)
+        finally:
+            self.signals.finished.emit()
+
+class WorkerSignals(QtCore.QObject):
+    """
+    Defines the signals available from a running worker thread.
+    Supported signals are:
+    finished
+        No data
+    error
+        `tuple` (exctype, value, traceback.format_exc() )
+    result
+        `object` data returned from processing, anything
+    """
+    finished = QtCore.Signal()
+    error = QtCore.Signal(tuple)
+    result = QtCore.Signal(object)
+    progress = QtCore.Signal(int)
+
+
+class App(QDialog):
+    """GUI Application using PySide2 widgets"""
+    def __init__(self):
+        QDialog.__init__(self)
+        self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
+        self.setGeometry(QtCore.QRect(200, 200, 500, 500))
+        self.threadpool = QtCore.QThreadPool()
+
+        layout = QVBoxLayout(self)
+        self.setLayout(layout)
+        self.startbutton = QPushButton('START')
+        self.startbutton.clicked.connect(self.run)
+        layout.addWidget(self.startbutton)
+        self.stopbutton = QPushButton('STOP')
+        self.stopbutton.clicked.connect(self.stop)
+        layout.addWidget(self.stopbutton)
+        self.progressbar = QProgressBar(self)
+        self.progressbar.setRange(0,1)
+        layout.addWidget(self.progressbar)
+        self.info = QTextEdit(self)
+        self.info.append('Hello')
+        layout.addWidget(self.info)
+        return
+
+    def progress_fn(self, msg):
+        """Update progress"""
+
+        self.info.append(str(msg))        
+        return
+
+    def run_threaded_process(self, process, progress_fn, on_complete):
+        """Execute a function in the background with a worker"""
+
+        worker = Worker(fn=process)
+        self.threadpool.start(worker)
+        worker.signals.finished.connect(on_complete)
+        worker.signals.progress.connect(progress_fn)
+        self.progressbar.setRange(0,0)
+        return
+
+    def run(self):
+        """call process"""
+
+        self.stopped = False
+        self.run_threaded_process(self.test, self.progress_fn, self.completed)
+
+    def stop(self):
+        self.stopped=True
+        return
+
+    def completed(self):
+        self.progressbar.setRange(0,1)
+        return
+
+    def test(self, progress_callback):
+        """Do some process here"""
+
+        total = 500
+        for i in range(0,total):
+            time.sleep(.2)
+            x = random.randint(1,1e4)
+            progress_callback.emit(x)
+            if self.stopped == True:
+                return
+
+app = QApplication([])
+xapp = App()
+xapp.show()
+sys.exit(app.exec_())

+ 97 - 0
src/python/splash/worker.py

@@ -0,0 +1,97 @@
+#!/usr/bin/env python3
+
+import sys, traceback, time
+from PySide2 import QtCore
+
+class Worker(QtCore.QRunnable):
+    """Worker thread for running background tasks."""
+
+    def __init__(self, fn, *args, **kwargs):
+        super(Worker, self).__init__()
+        # Store constructor arguments (re-used for processing)
+        self.fn = fn
+        self.args = args
+        self.kwargs = kwargs
+        self.signals = WorkerSignals()
+        self.kwargs['progress_callback'] = self.signals.progress
+
+    @QtCore.Slot()
+    def run(self):
+        try:
+            result = self.fn(
+                *self.args, **self.kwargs,
+            )
+        except:
+            traceback.print_exc()
+            exctype, value = sys.exc_info()[:2]
+            self.signals.error.emit((exctype, value, traceback.format_exc()))
+        else:
+            self.signals.result.emit(result)
+        finally:
+            self.signals.finished.emit()
+
+class WorkerSignals(QtCore.QObject):
+    finished = QtCore.Signal()
+    error = QtCore.Signal(tuple)
+    result = QtCore.Signal(object)
+    progress = QtCore.Signal(tuple)
+
+class PlugsWorker(QtCore.QObject):
+
+    def __init__(self, parent):
+        super().__init__()
+        self.parent = parent
+        self.pool = QtCore.QThreadPool()
+        self.stopped = False
+        self.plugs = self.parent.get_plugs()
+
+    def stop(self):
+        self.stopped = True
+        self.pool.waitForDone()
+
+    def progress(self, state):
+        state, plug = state
+        if state:
+            self.parent.updateProgress()
+            self.parent.updateText(
+                f'Plug «{plug["name"]}» connected!',
+                'Continuing'
+            )
+        else:
+            header = f'Missing plug «{plug["name"]}», please open the terminal and run:'
+            if plug['provider'] is None:
+                desc = f'$ snap connect ozwadmin:{plug["name"]}'
+            else:
+                desc = f'$ snap connect ozwadmin:{plug["name"]} {plug["provider"]}:{plug["name"]}'
+            self.parent.updateText(header, desc)
+
+    def error(self, err):
+        self.stop()
+        self.parent.updateText("Error", str(err))
+        self.parent.updateExitCode(256)
+        return
+
+    def completed(self):
+        self.parent.updateText('Plugs connected', "Continuing launch")
+        self.parent.updateFinised()
+        return
+
+    def run(self):
+        worker = Worker(fn=self.test)
+        self.pool.start(worker)
+        worker.signals.progress.connect(self.progress)
+        worker.signals.finished.connect(self.completed)
+        return
+
+    def test(self, progress_callback):
+        for plug in self.plugs:
+            if self.stopped:
+                break
+            connected = False
+            while not connected and not self.stopped:
+                connected = self.parent.app.get_env().is_connected(plug['name'], plug['provider'])
+                progress_callback.emit((connected, plug))
+                if not connected:
+                    self.parent.updateExitCode(255)
+                    time.sleep(0.5)
+        return

+ 0 - 23
src/wrapper

@@ -1,23 +0,0 @@
-#!/usr/bin/env bash
-
-set -e
-
-if snapctl is-connected qt-ozw-lib; then
-    LIB_QT="$(dirname $(find ${SNAP}/usr/qt-ozw-lib* -name libqt-openzwave.so -print -quit))"
-    LIB_OZW="$(dirname $(find ${SNAP}/usr/qt-ozw-lib* -name libopenzwave.so -print -quit))"
-    if [ -z "${LD_LIBRARY_PATH}" ]; then
-        export LD_LIBRARY_PATH="${LIB_QT}:${$LIB_OZW}"
-    else
-        export LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${LIB_QT}:${LIB_OZW}"
-    fi
-    export QT_PLUGIN_PATH="$(dirname $(dirname $(find "${SNAP}/usr/lib" -name libqxcb.so -print -quit)))"
-    export LIBGL_DRIVERS_PATH=$(find "${SNAP}" -type d -name dri -print -quit)
-
-    mkdir -p --mode=0700 "${XDG_RUNTIME_DIR}"
-    unset SESSION_MANAGER
-    exec "$@"
-else
-    echo "Please connect 'qt-ozw-lib'"
-    echo "$ snap connect ${SNAP_NAME}:qt-ozw-lib ozwdaemon:qt-ozw-lib"
-    exit 1
-fi

+ 20 - 0
src/wrapper/ensure-plugs

@@ -0,0 +1,20 @@
+#!/usr/bin/env bash
+
+ORIG_LD_LIBRARY_PATH="${LD_LIBRARY_PATH}"
+export LD_LIBRARY_PATH="$(find ${SNAP}/lib -path *PySide2/Qt* -name lib -print -quit):${LD_LIBRARY_PATH}"
+export PYTHONPATH="${SNAP}/usr/lib/python3.8"
+export LIBGL_DRIVERS_PATH=$(find "${SNAP}" -type d -name dri -print -quit)
+export QT_PLUGIN_PATH="$(dirname $(dirname $(find "${SNAP}/lib" -path *PySide2/Qt* -name libqxcb.so -print -quit)))"
+export QT_QPA_PLATFORM_PLUGIN_PATH="${QT_PLUGIN_PATH}"
+#export QT_DEBUG_PLUGINS=1
+
+$SNAP/bin/ozwadmin-splash
+
+RES=$?
+if [ ${RES} -ne 0 ]; then
+    exit ${RES}
+fi
+
+echo "$@"
+export LD_LIBRARY_PATH="${ORIG_LD_LIBRARY_PATH}"
+exec "$@"

+ 32 - 0
src/wrapper/wrapper

@@ -0,0 +1,32 @@
+#!/usr/bin/env bash
+
+if ! snapctl is-connected qt-ozw-lib; then
+    echo "Please connect 'qt-ozw-lib'"
+    echo "$ snap connect ${SNAP_NAME}:qt-ozw-lib ozwdaemon:qt-ozw-lib"
+    exit 1
+fi
+
+if ! snapctl is-connected ozw-config; then
+    echo "Please connect 'qt-ozw-config'"
+    echo "$ snap connect ${SNAP_NAME}:ozw-config ozwdaemon:ozw-config"
+    exit 1
+fi
+
+if ! snapctl is-connected ozw-db; then
+    echo "Please connect 'ozw-db'"
+    echo "$ snap connect ${SNAP_NAME}:ozw-config ozwdaemon:ozw-db"
+    exit 1
+fi
+
+LIB_QT_EXTRA="$(dirname $(find ${SNAP}/usr/lib -name libQt5Svg.so* -print -quit))"
+LIB_QT_OZW="$(dirname $(find ${SNAP}/usr/qt-ozw-lib* -name libqt-openzwave.so -print -quit))"
+LIB_OZW="$(dirname $(find ${SNAP}/usr/qt-ozw-lib* -name libopenzwave.so -print -quit))"
+export LD_LIBRARY_PATH="${LIB_QT_OZW}:${LIB_OZW}:${LD_LIBRARY_PATH}"
+QT_PLUGIN_PATH="$(dirname $(dirname $(find "${SNAP}/usr/qt-ozw-lib" -name libqxcb.so -print -quit)))"
+export QT_PLUGIN_PATH="${QT_PLUGIN_PATH}:$(dirname $(find "${SNAP}/usr/lib" -name iconengines -print -quit))"
+export QT_QPA_PLATFORM_PLUGIN_PATH="${QT_PLUGIN_PATH}"
+export LIBGL_DRIVERS_PATH=$(find "${SNAP}" -type d -name dri -print -quit)
+
+mkdir -p --mode=0700 "${XDG_RUNTIME_DIR}"
+unset SESSION_MANAGER
+exec "$@"