Compare commits
45 Commits
Author | SHA1 | Date |
---|---|---|
nixo | add6955bb8 | |
Piotr Drąg | 538480f022 | |
Peter Hutterer | d4dcccf36d | |
Peter Hutterer | a6a33ce6b4 | |
Peter Hutterer | 2cc9fc778f | |
Peter Hutterer | cb72111910 | |
Piotr Drąg | f01651bf5e | |
Peter Hutterer | b1b0be84ea | |
Peter Hutterer | 13830e02c5 | |
Peter Hutterer | 0e12369866 | |
Peter Hutterer | 74ffe763b6 | |
Peter Hutterer | 1c893d14e8 | |
Peter Hutterer | fde36b1271 | |
Peter Hutterer | 960904d481 | |
Peter Hutterer | 9eedbe108a | |
Peter Hutterer | b40dee5900 | |
Peter Hutterer | 43c844ea1e | |
Peter Hutterer | 2d95848cee | |
Peter Hutterer | bffe41fbb6 | |
Peter Hutterer | 1dc1acaba3 | |
Peter Hutterer | bb9f9b6c26 | |
Peter Hutterer | e37016dc7b | |
Peter Hutterer | 97a4ab04d0 | |
Peter Hutterer | 90ac2c7150 | |
Peter Hutterer | 658fe44d76 | |
Peter Hutterer | 08afa02690 | |
Peter Hutterer | cd84de4f32 | |
Peter Hutterer | 07cefa3c26 | |
Peter Hutterer | 695b7c43be | |
Peter Hutterer | 71ca2ca569 | |
Peter Hutterer | 2d142b4be3 | |
Peter Hutterer | 28f92882ec | |
Peter Hutterer | f477e86a96 | |
Peter Hutterer | ed29c0daae | |
Peter Hutterer | 488512231c | |
Peter Hutterer | c0628f1f19 | |
Peter Hutterer | 9c399c8eac | |
Peter Hutterer | 505f1c786f | |
Peter Hutterer | cab716938f | |
Peter Hutterer | 6c0ddd96bc | |
Peter Hutterer | 827ccbfa07 | |
Peter Hutterer | 1fb36ebc40 | |
Peter Hutterer | db87390c48 | |
Peter Hutterer | f24967ad12 | |
Peter Hutterer | e6ea60d6c1 |
|
@ -7,7 +7,7 @@ jobs:
|
|||
steps:
|
||||
- run:
|
||||
command: |
|
||||
dnf install -y meson gettext python3-devel pygobject3-devel python3-flake8 desktop-file-utils libappstream-glib python3-pytest python3-pyxdg python3-pyyaml
|
||||
dnf install -y meson gettext python3-devel pygobject3-devel python3-flake8 desktop-file-utils libappstream-glib python3-pytest python3-pyxdg python3-pyyaml python3-svgwrite
|
||||
- checkout
|
||||
- run:
|
||||
command: |
|
||||
|
|
|
@ -9,5 +9,6 @@
|
|||
<file preprocess="xml-stripblanks">ui/SetupPerspective.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/ErrorPerspective.ui</file>
|
||||
<file preprocess="xml-stripblanks">input-tablet-missing-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">ui/AppMenu.ui</file>
|
||||
</gresource>
|
||||
</gresources>
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
<interface>
|
||||
<menu id="primary-menu">
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Portrait</attribute>
|
||||
<attribute name="action">win.orientation</attribute>
|
||||
<attribute name="target">portrait</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Landscape</attribute>
|
||||
<attribute name="action">win.orientation</attribute>
|
||||
<attribute name="target">landscape</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Help</attribute>
|
||||
<attribute name="action">app.help</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">About</attribute>
|
||||
<attribute name="action">app.about</attribute>
|
||||
</item>
|
||||
</section>
|
||||
</menu>
|
||||
</interface>
|
31
meson.build
31
meson.build
|
@ -1,9 +1,9 @@
|
|||
project('tuhi',
|
||||
version: '0.2',
|
||||
version: '0.3',
|
||||
license: 'GPLv2',
|
||||
meson_version: '>= 0.48.0')
|
||||
# The tag date of the project_version(), update when the version bumps.
|
||||
version_date='2019-08-26'
|
||||
version_date='2019-09-12'
|
||||
|
||||
# Dependencies
|
||||
dependency('python3', required: true)
|
||||
|
@ -28,9 +28,26 @@ i18n = import('i18n')
|
|||
subdir('po')
|
||||
subdir('data')
|
||||
|
||||
# Find the directory to install our Python code
|
||||
pymod = import('python')
|
||||
py3 = pymod.find_installation()
|
||||
|
||||
# external python modules that are required for running Tuhi
|
||||
python_modules = [
|
||||
'svgwrite',
|
||||
'xdg',
|
||||
'gi',
|
||||
'yaml',
|
||||
]
|
||||
if meson.version().version_compare('>=0.51')
|
||||
py3 = pymod.find_installation(modules: python_modules)
|
||||
else
|
||||
py3 = pymod.find_installation()
|
||||
|
||||
foreach module: python_modules
|
||||
if run_command(py3, '-c', 'import @0@'.format(module)).returncode() != 0
|
||||
error('Failed to find required python module \'@0@\'.'.format(module))
|
||||
endif
|
||||
endforeach
|
||||
endif
|
||||
python_dir = py3.get_install_dir()
|
||||
install_subdir('tuhi',
|
||||
install_dir: python_dir,
|
||||
|
@ -55,6 +72,7 @@ config_tuhi_devel.set('libexecdir', '')
|
|||
config_tuhi_devel.set('devel', '''
|
||||
tuhi_gui = '@1@/tuhi-gui.devel'
|
||||
tuhi_server = '@0@/tuhi-server.py'
|
||||
tuhi_live = '@0@/tuhi-live.py'
|
||||
print('Running from source tree, using local files')
|
||||
'''.format(meson.source_root(), meson.build_root()))
|
||||
|
||||
|
@ -94,6 +112,11 @@ configure_file(input: 'tuhi-server.py',
|
|||
copy: true,
|
||||
install_dir: libexecdir)
|
||||
|
||||
configure_file(input: 'tuhi-live.py',
|
||||
output: 'tuhi-live',
|
||||
copy: true,
|
||||
install_dir: libexecdir)
|
||||
|
||||
meson.add_install_script('meson_install.sh')
|
||||
|
||||
desktop_file = i18n.merge_file(input: 'data/org.freedesktop.Tuhi.desktop.in',
|
||||
|
|
|
@ -4,14 +4,14 @@
|
|||
"runtime-version": "3.32",
|
||||
"sdk": "org.gnome.Sdk",
|
||||
"command": "tuhi",
|
||||
"finish-args": [
|
||||
"finish-args": [
|
||||
"--share=ipc",
|
||||
"--socket=x11",
|
||||
"--socket=wayland",
|
||||
"--talk-name=org.freedesktop.tuhi1",
|
||||
"--own-name=org.freedesktop.tuhi1",
|
||||
"--system-talk-name=org.bluez"
|
||||
],
|
||||
],
|
||||
"modules": [
|
||||
{
|
||||
"name": "pyxdg",
|
||||
|
@ -19,7 +19,9 @@
|
|||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "git://anongit.freedesktop.org/xdg/pyxdg"
|
||||
"url": "https://gitlab.freedesktop.org/xdg/pyxdg.git",
|
||||
"tag": "rel-0.26",
|
||||
"commit": "7db14dcf4c4305c3859a2d9fcf9f5da2db328330"
|
||||
}
|
||||
],
|
||||
"build-commands": [
|
||||
|
@ -46,7 +48,9 @@
|
|||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://github.com/mozman/svgwrite.git"
|
||||
"url": "https://github.com/mozman/svgwrite.git",
|
||||
"tag": "v1.3.1",
|
||||
"commit": "13633ad13d7a4b3253d1304d31db8fc2c8d1dd9e"
|
||||
}
|
||||
],
|
||||
"build-commands": [
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
# Language list must be in alphabetical order
|
||||
pl
|
||||
|
|
13
po/POTFILES
13
po/POTFILES
|
@ -2,16 +2,15 @@ data/org.freedesktop.Tuhi.appdata.xml.in.in
|
|||
data/org.freedesktop.Tuhi.desktop.in
|
||||
|
||||
data/ui/AboutDialog.ui.in
|
||||
data/ui/AppMenu.ui
|
||||
data/ui/Drawing.ui
|
||||
data/ui/DrawingPerspective.ui
|
||||
data/ui/ErrorPerspective.ui
|
||||
data/ui/MainWindow.ui
|
||||
data/ui/SetupPerspective.ui
|
||||
|
||||
tuhigui/application.py
|
||||
tuhigui/config.py
|
||||
tuhigui/drawing.py
|
||||
tuhigui/drawingperspective.py
|
||||
tuhigui/svg.py
|
||||
tuhigui/tuhi.py
|
||||
tuhigui/window.py
|
||||
tuhi/gui/application.py
|
||||
tuhi/gui/config.py
|
||||
tuhi/gui/drawing.py
|
||||
tuhi/gui/drawingperspective.py
|
||||
tuhi/gui/window.py
|
||||
|
|
|
@ -0,0 +1,208 @@
|
|||
# Polish translation for tuhi.
|
||||
# Copyright © 2019 the tuhi authors.
|
||||
# This file is distributed under the same license as the tuhi package.
|
||||
# Piotr Drąg <piotrdrag@gmail.com>, 2019.
|
||||
# Aviary.pl <community-poland@mozilla.org>, 2019.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: tuhi\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-09-15 12:38+0200\n"
|
||||
"PO-Revision-Date: 2019-09-15 12:55+0200\n"
|
||||
"Last-Translator: Piotr Drąg <piotrdrag@gmail.com>\n"
|
||||
"Language-Team: Polish <community-poland@mozilla.org>\n"
|
||||
"Language: pl\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
|
||||
"|| n%100>=20) ? 1 : 2);\n"
|
||||
|
||||
#: data/org.freedesktop.Tuhi.appdata.xml.in.in:7
|
||||
#: data/org.freedesktop.Tuhi.desktop.in:3
|
||||
msgid "Tuhi"
|
||||
msgstr "Tuhi"
|
||||
|
||||
#: data/org.freedesktop.Tuhi.appdata.xml.in.in:8
|
||||
#: data/org.freedesktop.Tuhi.desktop.in:4
|
||||
msgid "Utility to download drawings from the Wacom Ink range of devices"
|
||||
msgstr "Narzędzie do pobierania rysunków z rodziny urządzeń Wacom Ink"
|
||||
|
||||
#: data/org.freedesktop.Tuhi.appdata.xml.in.in:10
|
||||
msgid ""
|
||||
"Tuhi is a graphical user interface to download drawings stored on tablet "
|
||||
"devices from the Wacom Ink range, e.g. Intuos Pro Paper or Bamboo Slate."
|
||||
msgstr ""
|
||||
"Tuhi to graficzny interfejs użytkownika do pobierania rysunków "
|
||||
"przechowywanych na tabletach z rodziny Wacom Ink, np. Intuos Pro Paper "
|
||||
"i Bamboo Slate."
|
||||
|
||||
#: data/org.freedesktop.Tuhi.appdata.xml.in.in:15
|
||||
msgid ""
|
||||
"Tuhi requires Tuhi, the daemon to actually communicate with the devices. "
|
||||
"ThiGui is merely a front end to Tuhi, Tuhi must be installed and running "
|
||||
"when Tuhi is launched."
|
||||
msgstr ""
|
||||
"TuhiGUI wymaga Tuhi, usługi komunikującej się z urządzeniem. TuhiGUI to "
|
||||
"interfejs dla usługi Tuhi, która musi być zainstalowana i uruchomiona, aby "
|
||||
"narzędzie Tuhi mogło działać."
|
||||
|
||||
#: data/org.freedesktop.Tuhi.appdata.xml.in.in:32
|
||||
msgid "Tuhi's main window"
|
||||
msgstr "Główne okno Tuhi"
|
||||
|
||||
#: data/org.freedesktop.Tuhi.appdata.xml.in.in:36
|
||||
msgid "Tuhi's main window (zoomed)"
|
||||
msgstr "Główne okno Tuhi (powiększone)"
|
||||
|
||||
#. Translators: Do NOT translate or transliterate this text (this is an icon file name)!
|
||||
#: data/org.freedesktop.Tuhi.desktop.in:7
|
||||
msgid "org.freedesktop.Tuhi"
|
||||
msgstr "org.freedesktop.Tuhi"
|
||||
|
||||
#. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon!
|
||||
#: data/org.freedesktop.Tuhi.desktop.in:12
|
||||
msgid "tablet;wacom;ink;"
|
||||
msgstr "tablet;wacom;ink;"
|
||||
|
||||
#: data/ui/AboutDialog.ui.in:13
|
||||
msgid "Visit Tuhi’s website"
|
||||
msgstr "Witryna programu Tuhi"
|
||||
|
||||
#: data/ui/AppMenu.ui:5
|
||||
msgid "Portrait"
|
||||
msgstr "Pionowo"
|
||||
|
||||
#: data/ui/AppMenu.ui:10
|
||||
msgid "Landscape"
|
||||
msgstr "Poziomo"
|
||||
|
||||
#: data/ui/AppMenu.ui:17
|
||||
msgid "Help"
|
||||
msgstr "Pomoc"
|
||||
|
||||
#: data/ui/AppMenu.ui:21
|
||||
msgid "About"
|
||||
msgstr "O programie"
|
||||
|
||||
#: data/ui/DrawingPerspective.ui:68
|
||||
msgid "Undo delete drawing"
|
||||
msgstr "Cofnij usunięcie rysunku"
|
||||
|
||||
#: data/ui/DrawingPerspective.ui:132
|
||||
msgid "Press the button on the device to synchronize drawings"
|
||||
msgstr "Proszę nacisnąć przycisk na urządzeniu, aby zsynchronizować rysunki"
|
||||
|
||||
#: data/ui/DrawingPerspective.ui:143 data/ui/SetupPerspective.ui:121
|
||||
msgid "page0"
|
||||
msgstr "strona 0"
|
||||
|
||||
#: data/ui/ErrorPerspective.ui:21
|
||||
msgid ""
|
||||
"TuhiGUI is an interactive GUI to download data from Tuhi.\n"
|
||||
"\n"
|
||||
"Tuhi connects to tablets of the Wacom Ink range. It allows you to download "
|
||||
"the drawings stored on those devices as SVGs for processing later.\n"
|
||||
"\n"
|
||||
"Tuhi is a DBus server that needs to be running for the Tuhi GUI to connect "
|
||||
"to it. Connecting to the DBus server should take less than a second. If you "
|
||||
"read this far, your Tuhi DBus server is not running or responsing and needs "
|
||||
"to be restarted."
|
||||
msgstr ""
|
||||
"TuhiGUI to interaktywny interfejs użytkownika do pobierania danych z usługi "
|
||||
"Tuhi.\n"
|
||||
"\n"
|
||||
"Tuhi łączy się z tabletami z rodziny Wacom Ink. Umożliwia pobieranie obrazów "
|
||||
"przechowywanych na tych urządzeniach jako pliki SVG do przetwarzania "
|
||||
"w późniejszym czasie.\n"
|
||||
"\n"
|
||||
"Tuhi to serwer D-Bus, który musi być uruchomiony, aby interfejs Tuhi mógł "
|
||||
"się z nim połączyć. Połączenie z serwerem D-Bus powinno zająć mniej niż "
|
||||
"sekundę. Jeśli jeszcze czytasz ten tekst, to znaczy że serwer D-Bus Tuhi nie "
|
||||
"jest uruchomiony lub nie odpowiada i wymaga ponownego uruchomienia."
|
||||
|
||||
#: data/ui/ErrorPerspective.ui:69
|
||||
msgid "Connecting to Tuhi"
|
||||
msgstr "Łączenie z usługą Tuhi"
|
||||
|
||||
#: data/ui/ErrorPerspective.ui:96
|
||||
msgid ""
|
||||
"This should take less than a second. Make sure the Tuhi DBus server is "
|
||||
"running."
|
||||
msgstr ""
|
||||
"To powinno zająć mniej niż sekundę. Proszę się upewnić, że serwer D-Bus Tuhi "
|
||||
"jest uruchomiony."
|
||||
|
||||
#: data/ui/MainWindow.ui:166
|
||||
msgid "Authorization error while connecting to the device "
|
||||
msgstr "Błąd upoważnienia podczas łączenia z urządzeniem "
|
||||
|
||||
#: data/ui/MainWindow.ui:176
|
||||
msgid "Register"
|
||||
msgstr "Zarejestruj"
|
||||
|
||||
#: data/ui/SetupPerspective.ui:7
|
||||
msgid "Initial Device Setup"
|
||||
msgstr "Pierwsza konfiguracja urządzenia"
|
||||
|
||||
#: data/ui/SetupPerspective.ui:30
|
||||
msgid "Quit"
|
||||
msgstr "Zakończ"
|
||||
|
||||
#: data/ui/SetupPerspective.ui:70
|
||||
msgid "Hold the button on the device until the blue light is flashing."
|
||||
msgstr ""
|
||||
"Proszę przytrzymać przycisk na urządzeniu, aż niebieska dioda zacznie migać."
|
||||
|
||||
#: data/ui/SetupPerspective.ui:103
|
||||
msgid "Searching for device"
|
||||
msgstr "Wyszukiwanie urządzenia"
|
||||
|
||||
#: data/ui/SetupPerspective.ui:137
|
||||
msgid "Connecting to LE Paper"
|
||||
msgstr "Łączenie z LE Paper"
|
||||
|
||||
#: data/ui/SetupPerspective.ui:170
|
||||
msgid "Connecting to device..."
|
||||
msgstr "Łączenie z urządzeniem…"
|
||||
|
||||
#: data/ui/SetupPerspective.ui:188
|
||||
msgid "page1"
|
||||
msgstr "1. strona"
|
||||
|
||||
#: data/ui/SetupPerspective.ui:206
|
||||
msgid "Press the button on the device now!"
|
||||
msgstr "Proszę teraz nacisnąć przycisk na urządzeniu."
|
||||
|
||||
#: data/ui/SetupPerspective.ui:240
|
||||
msgid "waiting for reply"
|
||||
msgstr "oczekiwanie na odpowiedź"
|
||||
|
||||
#: data/ui/SetupPerspective.ui:258
|
||||
msgid "page2"
|
||||
msgstr "2. strona"
|
||||
|
||||
#: tuhi/gui/drawing.py:100
|
||||
msgid "Please choose a file"
|
||||
msgstr "Proszę wybrać plik"
|
||||
|
||||
#. Translators: the default filename to save to
|
||||
#: tuhi/gui/drawing.py:108
|
||||
msgid "untitled.svg"
|
||||
msgstr "bez tytułu.svg"
|
||||
|
||||
#. Translators: filter name to show all/any files
|
||||
#: tuhi/gui/drawing.py:112
|
||||
msgid "Any files"
|
||||
msgstr "Wszystkie pliki"
|
||||
|
||||
#. Translators: filter to show svg files only
|
||||
#: tuhi/gui/drawing.py:116
|
||||
msgid "SVG files"
|
||||
msgstr "Pliki SVG"
|
||||
|
||||
#: tuhi/gui/window.py:68
|
||||
#, python-brace-format
|
||||
msgid "Connecting to {device.name}"
|
||||
msgstr "Łączenie z urządzeniem {device.name}"
|
|
@ -379,6 +379,25 @@ class TestProtocolAny(unittest.TestCase):
|
|||
msg = p.execute(Interactions.REGISTER_PRESS_BUTTON, uuid=uuid)
|
||||
self.assertEqual(msg.uuid, uuid)
|
||||
|
||||
def test_error_invalid_state(self):
|
||||
def _cb(request, requires_reply=True, userdata=None, timeout=5):
|
||||
return NordicData([0xb3, 0x1, 0x1])
|
||||
|
||||
p = Protocol(self.protocol_version, callback=_cb)
|
||||
|
||||
# a "random" collection of requests that we want to check for
|
||||
with self.assertRaises(DeviceError) as cm:
|
||||
p.execute(Interactions.CONNECT, uuid='abcdef123456')
|
||||
self.assertEqual(cm.exception.errorcode, DeviceError.ErrorCode.GENERAL_ERROR)
|
||||
|
||||
with self.assertRaises(DeviceError) as cm:
|
||||
p.execute(Interactions.GET_STROKES)
|
||||
self.assertEqual(cm.exception.errorcode, DeviceError.ErrorCode.GENERAL_ERROR)
|
||||
|
||||
with self.assertRaises(DeviceError) as cm:
|
||||
p.execute(Interactions.SET_MODE, Mode.PAPER)
|
||||
self.assertEqual(cm.exception.errorcode, DeviceError.ErrorCode.GENERAL_ERROR)
|
||||
|
||||
|
||||
class TestProtocolSpark(TestProtocolAny):
|
||||
protocol_version = ProtocolVersion.SPARK
|
||||
|
|
384
tools/kete.py
384
tools/kete.py
|
@ -20,7 +20,6 @@ import errno
|
|||
import os
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
import readline
|
||||
import struct
|
||||
import threading
|
||||
|
@ -31,12 +30,14 @@ from pathlib import Path
|
|||
|
||||
try:
|
||||
from tuhi.svg import JsonSvg
|
||||
import tuhi.dbusclient
|
||||
except ModuleNotFoundError:
|
||||
# If PYTHONPATH isn't set up or we never installed Tuhi, the module
|
||||
# isn't available. And since we don't install kete, we can assume that
|
||||
# we're still in the git repo, so messing with the path is "fine".
|
||||
sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)) + '/..') # noqa
|
||||
from tuhi.svg import JsonSvg
|
||||
import tuhi.dbusclient
|
||||
|
||||
|
||||
CONFIG_PATH = Path(xdg.BaseDirectory.xdg_data_home, 'tuhi-kete')
|
||||
|
@ -98,13 +99,6 @@ logger = logging.getLogger('tuhi-kete')
|
|||
logger.addHandler(logger_handler)
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
TUHI_DBUS_NAME = 'org.freedesktop.tuhi1'
|
||||
ORG_FREEDESKTOP_TUHI1_MANAGER = 'org.freedesktop.tuhi1.Manager'
|
||||
ORG_FREEDESKTOP_TUHI1_DEVICE = 'org.freedesktop.tuhi1.Device'
|
||||
ROOT_PATH = '/org/freedesktop/tuhi1'
|
||||
|
||||
ORG_BLUEZ_DEVICE1 = 'org.bluez.Device1'
|
||||
|
||||
# remove ':' from the completer delimiters of readline so we can match on
|
||||
# device addresses
|
||||
completer_delims = readline.get_completer_delims()
|
||||
|
@ -118,340 +112,52 @@ def b2hex(bs):
|
|||
return ' '.join([''.join(s) for s in zip(hx[::2], hx[1::2])])
|
||||
|
||||
|
||||
class DBusError(Exception):
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
|
||||
|
||||
class _DBusObject(GObject.Object):
|
||||
_connection = None
|
||||
|
||||
def __init__(self, name, interface, objpath):
|
||||
GObject.GObject.__init__(self)
|
||||
|
||||
if _DBusObject._connection is None:
|
||||
self._connect_to_session()
|
||||
|
||||
self.interface = interface
|
||||
self.objpath = objpath
|
||||
|
||||
try:
|
||||
self.proxy = Gio.DBusProxy.new_sync(self._connection,
|
||||
Gio.DBusProxyFlags.NONE, None,
|
||||
name, objpath, interface, None)
|
||||
except GLib.Error as e:
|
||||
if (e.domain == 'g-io-error-quark' and
|
||||
e.code == Gio.IOErrorEnum.DBUS_ERROR):
|
||||
raise DBusError(e.message)
|
||||
else:
|
||||
raise e
|
||||
|
||||
if self.proxy.get_name_owner() is None:
|
||||
raise DBusError(f'No-one is handling {name}, is the daemon running?')
|
||||
|
||||
self.proxy.connect('g-properties-changed', self._on_properties_changed)
|
||||
self.proxy.connect('g-signal', self._on_signal_received)
|
||||
|
||||
def _connect_to_session(self):
|
||||
try:
|
||||
_DBusObject._connection = Gio.bus_get_sync(Gio.BusType.SESSION, None)
|
||||
except GLib.Error as e:
|
||||
if (e.domain == 'g-io-error-quark' and
|
||||
e.code == Gio.IOErrorEnum.DBUS_ERROR):
|
||||
raise DBusError(e.message)
|
||||
else:
|
||||
raise e
|
||||
|
||||
def _on_properties_changed(self, proxy, changed_props, invalidated_props):
|
||||
# Implement this in derived classes to respond to property changes
|
||||
pass
|
||||
|
||||
def _on_signal_received(self, proxy, sender, signal, parameters):
|
||||
# Implement this in derived classes to respond to signals
|
||||
pass
|
||||
|
||||
def property(self, name):
|
||||
p = self.proxy.get_cached_property(name)
|
||||
if p is not None:
|
||||
return p.unpack()
|
||||
return p
|
||||
|
||||
def terminate(self):
|
||||
del(self.proxy)
|
||||
|
||||
|
||||
class _DBusSystemObject(_DBusObject):
|
||||
'''
|
||||
Same as the _DBusObject, but connects to the system bus instead
|
||||
'''
|
||||
def __init__(self, name, interface, objpath):
|
||||
self._connect_to_system()
|
||||
super().__init__(name, interface, objpath)
|
||||
|
||||
def _connect_to_system(self):
|
||||
try:
|
||||
self._connection = Gio.bus_get_sync(Gio.BusType.SYSTEM, None)
|
||||
except GLib.Error as e:
|
||||
if (e.domain == 'g-io-error-quark' and
|
||||
e.code == Gio.IOErrorEnum.DBUS_ERROR):
|
||||
raise DBusError(e.message)
|
||||
else:
|
||||
raise e
|
||||
|
||||
|
||||
class BlueZDevice(_DBusSystemObject):
|
||||
def __init__(self, objpath):
|
||||
super().__init__('org.bluez', ORG_BLUEZ_DEVICE1, objpath)
|
||||
self.proxy.connect('g-properties-changed', self._on_properties_changed)
|
||||
|
||||
@GObject.Property
|
||||
def connected(self):
|
||||
return self.proxy.get_cached_property('Connected').unpack()
|
||||
|
||||
def _on_properties_changed(self, obj, properties, invalidated_properties):
|
||||
properties = properties.unpack()
|
||||
|
||||
if 'Connected' in properties:
|
||||
self.notify('connected')
|
||||
|
||||
|
||||
class TuhiKeteDevice(_DBusObject):
|
||||
def __init__(self, manager, objpath):
|
||||
_DBusObject.__init__(self, TUHI_DBUS_NAME,
|
||||
ORG_FREEDESKTOP_TUHI1_DEVICE,
|
||||
objpath)
|
||||
self.manager = manager
|
||||
self.is_registering = False
|
||||
self.live = False
|
||||
self._bluez_device = BlueZDevice(self.property('BlueZDevice'))
|
||||
self._bluez_device.connect('notify::connected', self._on_connected)
|
||||
|
||||
@classmethod
|
||||
def is_device_address(cls, string):
|
||||
if re.match(r'[0-9a-f]{2}(:[0-9a-f]{2}){5}$', string.lower()):
|
||||
return string
|
||||
raise argparse.ArgumentTypeError(f'"{string}" is not a valid device address')
|
||||
|
||||
@GObject.Property
|
||||
def address(self):
|
||||
return self._bluez_device.property('Address')
|
||||
|
||||
@GObject.Property
|
||||
def name(self):
|
||||
return self._bluez_device.property('Name')
|
||||
|
||||
@GObject.Property
|
||||
def listening(self):
|
||||
return self.property('Listening')
|
||||
|
||||
@GObject.Property
|
||||
def drawings_available(self):
|
||||
return self.property('DrawingsAvailable')
|
||||
|
||||
@GObject.Property
|
||||
def battery_percent(self):
|
||||
return self.property('BatteryPercent')
|
||||
|
||||
@GObject.Property
|
||||
def battery_state(self):
|
||||
return self.property('BatteryState')
|
||||
|
||||
@GObject.Property
|
||||
def connected(self):
|
||||
return self._bluez_device.connected
|
||||
|
||||
def _on_connected(self, bluez_device, pspec):
|
||||
self.notify('connected')
|
||||
|
||||
def register(self):
|
||||
logger.debug(f'{self}: Register')
|
||||
# FIXME: Register() doesn't return anything useful yet, so we wait until
|
||||
# the device is in the Manager's Devices property
|
||||
self.s1 = self.manager.connect('notify::devices', self._on_mgr_devices_updated)
|
||||
self.is_registering = True
|
||||
self.proxy.Register()
|
||||
|
||||
def start_listening(self):
|
||||
self.proxy.StartListening()
|
||||
|
||||
def stop_listening(self):
|
||||
try:
|
||||
self.proxy.StopListening()
|
||||
except GLib.Error as e:
|
||||
if (e.domain != 'g-dbus-error-quark' or
|
||||
e.code != Gio.IOErrorEnum.EXISTS or
|
||||
Gio.dbus_error_get_remote_error(e) != 'org.freedesktop.DBus.Error.ServiceUnknown'):
|
||||
raise e
|
||||
|
||||
def start_live(self, fd):
|
||||
fd_list = Gio.UnixFDList.new()
|
||||
fd_list.append(fd)
|
||||
|
||||
res, fds = self.proxy.call_with_unix_fd_list_sync('org.freedesktop.tuhi1.Device.StartLive',
|
||||
GLib.Variant('(h)', (fd,)),
|
||||
Gio.DBusCallFlags.NO_AUTO_START,
|
||||
-1,
|
||||
fd_list,
|
||||
None)
|
||||
if res[0] == 0:
|
||||
self.live = True
|
||||
|
||||
def stop_live(self):
|
||||
self.proxy.StopLive()
|
||||
self.live = False
|
||||
|
||||
def json(self, timestamp):
|
||||
SUPPORTED_FILE_FORMAT = 1
|
||||
return self.proxy.GetJSONData('(ut)', SUPPORTED_FILE_FORMAT, timestamp)
|
||||
|
||||
def _on_signal_received(self, proxy, sender, signal, parameters):
|
||||
if signal == 'ButtonPressRequired':
|
||||
logger.info(f'{self}: Press button on device now')
|
||||
elif signal == 'ListeningStopped':
|
||||
err = parameters[0]
|
||||
if err == -errno.EACCES:
|
||||
logger.error(f'{self}: wrong device, please re-register.')
|
||||
elif err < 0:
|
||||
logger.error(f'{self}: an error occured: {os.strerror(-err)}')
|
||||
self.notify('listening')
|
||||
elif signal == 'SyncState':
|
||||
state = parameters[0]
|
||||
if state:
|
||||
logger.debug(f'{self}: Downloading from device')
|
||||
else:
|
||||
logger.debug(f'{self}: Download done')
|
||||
|
||||
def _on_properties_changed(self, proxy, changed_props, invalidated_props):
|
||||
if changed_props is None:
|
||||
return
|
||||
|
||||
changed_props = changed_props.unpack()
|
||||
|
||||
if 'DrawingsAvailable' in changed_props:
|
||||
self.notify('drawings-available')
|
||||
elif 'Listening' in changed_props:
|
||||
self.notify('listening')
|
||||
elif 'BatteryPercent' in changed_props:
|
||||
self.notify('battery-percent')
|
||||
elif 'BatteryState' in changed_props:
|
||||
self.notify('battery-state')
|
||||
|
||||
def __repr__(self):
|
||||
return f'{self.address} - {self.name}'
|
||||
|
||||
def _on_mgr_devices_updated(self, manager, pspec):
|
||||
if not self.is_registering:
|
||||
return
|
||||
|
||||
for d in manager.devices:
|
||||
if d.address == self.address:
|
||||
self.is_registering = False
|
||||
self.manager.disconnect(self.s1)
|
||||
del(self.s1)
|
||||
logger.info(f'{self}: Registration successful')
|
||||
|
||||
def terminate(self):
|
||||
try:
|
||||
self.manager.disconnect(self.s1)
|
||||
except AttributeError:
|
||||
pass
|
||||
self._bluez_device.terminate()
|
||||
super(TuhiKeteDevice, self).terminate()
|
||||
|
||||
|
||||
class TuhiKeteManager(_DBusObject):
|
||||
__gsignals__ = {
|
||||
'unregistered-device':
|
||||
(GObject.SignalFlags.RUN_FIRST, None, (GObject.TYPE_PYOBJECT,)),
|
||||
}
|
||||
|
||||
class TuhiKeteManager(tuhi.dbusclient.TuhiDBusClientManager):
|
||||
def __init__(self):
|
||||
_DBusObject.__init__(self, TUHI_DBUS_NAME,
|
||||
ORG_FREEDESKTOP_TUHI1_MANAGER,
|
||||
ROOT_PATH)
|
||||
super().__init__()
|
||||
self.connect('unregistered_device', self._on_unregistered_device)
|
||||
|
||||
self._devices = {}
|
||||
self._unregistered_devices = {}
|
||||
self.sigs = {}
|
||||
for d in self.devices:
|
||||
self.sigs[d] = []
|
||||
self._connect_device(d)
|
||||
|
||||
for objpath in self.property('Devices'):
|
||||
device = TuhiKeteDevice(self, objpath)
|
||||
self._devices[device.address] = device
|
||||
|
||||
@GObject.Property
|
||||
def devices(self):
|
||||
return [v for k, v in self._devices.items()]
|
||||
|
||||
@GObject.Property
|
||||
def unregistered_devices(self):
|
||||
return [v for k, v in self._unregistered_devices.items()]
|
||||
|
||||
@GObject.Property
|
||||
def searching(self):
|
||||
return self.proxy.get_cached_property('Searching')
|
||||
|
||||
def start_search(self):
|
||||
self._unregistered_devices = {}
|
||||
self.proxy.StartSearch()
|
||||
|
||||
def stop_search(self):
|
||||
def _disconnect_device_signals(self, device):
|
||||
try:
|
||||
self.proxy.StopSearch()
|
||||
except GLib.Error as e:
|
||||
if (e.domain != 'g-dbus-error-quark' or
|
||||
e.code != Gio.IOErrorEnum.EXISTS or
|
||||
Gio.dbus_error_get_remote_error(e) != 'org.freedesktop.DBus.Error.ServiceUnknown'):
|
||||
raise e
|
||||
self._unregistered_devices = {}
|
||||
for s in self.sigs[device]:
|
||||
device.disconnect(s)
|
||||
self.sigs[device] = []
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def terminate(self):
|
||||
for dev in self._devices.values():
|
||||
dev.terminate()
|
||||
self._devices = {}
|
||||
self._unregistered_devices = {}
|
||||
super(TuhiKeteManager, self).terminate()
|
||||
def _on_unregistered_device(self, manager, device):
|
||||
self._disconnect_device_signals(device)
|
||||
|
||||
def _on_properties_changed(self, proxy, changed_props, invalidated_props):
|
||||
if changed_props is None:
|
||||
return
|
||||
def log_press_required(device):
|
||||
logger.info(f'{device}: Press button on device now')
|
||||
device.connect('button-press-required', log_press_required)
|
||||
|
||||
changed_props = changed_props.unpack()
|
||||
def log_registered(device):
|
||||
logger.info(f'{device}: Registration successful')
|
||||
device.connect('registered', log_registered)
|
||||
device.connect('registered', self._connect_device)
|
||||
|
||||
if 'Devices' in changed_props:
|
||||
objpaths = changed_props['Devices']
|
||||
for objpath in objpaths:
|
||||
try:
|
||||
d = self._unregistered_devices[objpath]
|
||||
self._devices[d.address] = d
|
||||
del self._unregistered_devices[objpath]
|
||||
except KeyError:
|
||||
# if we called Register() on an existing device it's not
|
||||
# in unregistered devices
|
||||
pass
|
||||
self.notify('devices')
|
||||
if 'Searching' in changed_props:
|
||||
self.notify('searching')
|
||||
def _connect_device(self, device):
|
||||
self._disconnect_device_signals(device)
|
||||
|
||||
def _handle_unregistered_device(self, objpath):
|
||||
for addr, dev in self._devices.items():
|
||||
if dev.objpath == objpath:
|
||||
self.emit('unregistered-device', dev)
|
||||
return
|
||||
def log_sync_state(device, pspec):
|
||||
if device.sync_state:
|
||||
logger.debug(f'{device}: Communicating with device')
|
||||
else:
|
||||
logger.debug(f'{device}: Communication complete')
|
||||
device.connect('notify::sync-state', log_sync_state)
|
||||
|
||||
device = TuhiKeteDevice(self, objpath)
|
||||
self._unregistered_devices[objpath] = device
|
||||
|
||||
logger.debug(f'New unregistered device: {device}')
|
||||
self.emit('unregistered-device', device)
|
||||
|
||||
def _on_signal_received(self, proxy, sender, signal, parameters):
|
||||
if signal == 'SearchStopped':
|
||||
self.notify('searching')
|
||||
elif signal == 'UnregisteredDevice':
|
||||
objpath = parameters[0]
|
||||
self._handle_unregistered_device(objpath)
|
||||
|
||||
def __getitem__(self, btaddr):
|
||||
return self._devices[btaddr]
|
||||
def log_device_error(d, err):
|
||||
if err == -errno.EACCES:
|
||||
logger.error(f'{device}: wrong device, please re-register.')
|
||||
elif err < 0:
|
||||
logger.error(f'{device}: an error occured: {os.strerror(-err)}')
|
||||
device.connect('device-error', log_device_error)
|
||||
|
||||
|
||||
class Worker(GObject.Object):
|
||||
|
@ -743,7 +449,7 @@ class TuhiKeteShell(cmd.Cmd):
|
|||
readline.set_history_length(100)
|
||||
|
||||
Gio.bus_watch_name(Gio.BusType.SESSION,
|
||||
TUHI_DBUS_NAME,
|
||||
tuhi.dbusclient.TUHI_DBUS_NAME,
|
||||
Gio.BusNameWatcherFlags.NONE,
|
||||
self._on_name_appeared,
|
||||
self._on_name_vanished)
|
||||
|
@ -875,7 +581,7 @@ class TuhiKeteShell(cmd.Cmd):
|
|||
add_help=False)
|
||||
parser.add_argument('-h', action='help', help=argparse.SUPPRESS)
|
||||
parser.add_argument('address', metavar='12:34:56:AB:CD:EF',
|
||||
type=TuhiKeteDevice.is_device_address,
|
||||
type=tuhi.dbusclient.TuhiDBusClientDevice.is_device_address,
|
||||
default=None,
|
||||
help='the address of the device to listen to')
|
||||
parser.add_argument('mode', choices=['on', 'off'], nargs='?',
|
||||
|
@ -986,7 +692,7 @@ class TuhiKeteShell(cmd.Cmd):
|
|||
add_help=False)
|
||||
parser.add_argument('-h', action='help', help=argparse.SUPPRESS)
|
||||
parser.add_argument('address', metavar='12:34:56:AB:CD:EF',
|
||||
type=TuhiKeteDevice.is_device_address,
|
||||
type=tuhi.dbusclient.TuhiDBusClientDevice.is_device_address,
|
||||
default=None,
|
||||
help='the address of the device to fetch drawing from')
|
||||
parser.add_argument('index', metavar='{<index>|all}',
|
||||
|
@ -1083,7 +789,7 @@ class TuhiKeteShell(cmd.Cmd):
|
|||
add_help=False)
|
||||
parser.add_argument('-h', action='help', help=argparse.SUPPRESS)
|
||||
parser.add_argument('address', metavar='12:34:56:AB:CD:EF',
|
||||
type=TuhiKeteDevice.is_device_address,
|
||||
type=tuhi.dbusclient.TuhiDBusClientDevice.is_device_address,
|
||||
default=None,
|
||||
help='the address of the device to register')
|
||||
|
||||
|
@ -1137,7 +843,7 @@ class TuhiKeteShell(cmd.Cmd):
|
|||
add_help=False)
|
||||
parser.add_argument('-h', action='help', help=argparse.SUPPRESS)
|
||||
parser.add_argument('address', metavar='12:34:56:AB:CD:EF',
|
||||
type=TuhiKeteDevice.is_device_address,
|
||||
type=tuhi.dbusclient.TuhiDBusClientDevice.is_device_address,
|
||||
default=None, nargs='?',
|
||||
help='the address of the device to listen to')
|
||||
|
||||
|
@ -1189,7 +895,7 @@ class TuhiKeteShell(cmd.Cmd):
|
|||
add_help=False)
|
||||
parser.add_argument('-h', action='help', help=argparse.SUPPRESS)
|
||||
parser.add_argument('address', metavar='12:34:56:AB:CD:EF',
|
||||
type=TuhiKeteDevice.is_device_address,
|
||||
type=tuhi.dbusclient.TuhiDBusClientDevice.is_device_address,
|
||||
default=None, nargs='?',
|
||||
help='the address of the device to listen to')
|
||||
parser.add_argument('mode', choices=['on', 'off'], nargs='?',
|
||||
|
@ -1246,7 +952,7 @@ def main(args):
|
|||
with TuhiKeteShell() as shell:
|
||||
shell.run()
|
||||
|
||||
except DBusError as e:
|
||||
except tuhi.dbusclient.DBusError as e:
|
||||
logger.error(e.message)
|
||||
|
||||
|
||||
|
|
|
@ -19,6 +19,16 @@ import sys
|
|||
import multiprocessing
|
||||
from multiprocessing import reduction
|
||||
|
||||
try:
|
||||
import tuhi.dbusclient
|
||||
except ModuleNotFoundError:
|
||||
# If PYTHONPATH isn't set up or we never installed Tuhi, the module
|
||||
# isn't available. And since we don't install tuhi-live, we can assume that
|
||||
# we're still in the git repo, so messing with the path is "fine".
|
||||
sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)) + '/..') # noqa
|
||||
import tuhi.dbusclient
|
||||
|
||||
|
||||
manager = None
|
||||
logger = None
|
||||
|
||||
|
@ -35,10 +45,8 @@ def open_uhid_process(queue_in, conn_out):
|
|||
|
||||
|
||||
def maybe_start_tuhi(queue):
|
||||
sys.path
|
||||
|
||||
try:
|
||||
should_start, verbose = queue.get()
|
||||
should_start, args = queue.get()
|
||||
except KeyboardInterrupt:
|
||||
return 0
|
||||
|
||||
|
@ -48,14 +56,15 @@ def maybe_start_tuhi(queue):
|
|||
sys.path.append(os.getcwd())
|
||||
|
||||
import tuhi.base
|
||||
if verbose:
|
||||
tuhi.base.logger.setLevel(logging.DEBUG)
|
||||
t = tuhi.base.Tuhi()
|
||||
while True:
|
||||
try:
|
||||
t.run()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
import signal
|
||||
|
||||
# we don't want to kill Tuhi on ctrl+c because we won't be able to reset
|
||||
# live mode. Instead we rely on tuhi-live to take us down when it exits
|
||||
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||
|
||||
args = ['tuhi-live'] + args # argparse in tuhi.base.main skips argv[0]
|
||||
|
||||
tuhi.base.main(args)
|
||||
|
||||
|
||||
def start_tuhi_server(args):
|
||||
|
@ -84,7 +93,7 @@ def start_tuhi_server(args):
|
|||
except GLib.Error as e:
|
||||
if (e.domain == 'g-io-error-quark' and
|
||||
e.code == Gio.IOErrorEnum.DBUS_ERROR):
|
||||
raise kete.DBusError(e.message)
|
||||
raise tuhi.dbusclient.DBusError(e.message)
|
||||
else:
|
||||
raise e
|
||||
|
||||
|
@ -93,50 +102,50 @@ def start_tuhi_server(args):
|
|||
try:
|
||||
proxy = Gio.DBusProxy.new_sync(connection,
|
||||
Gio.DBusProxyFlags.NONE, None,
|
||||
kete.TUHI_DBUS_NAME,
|
||||
kete.ROOT_PATH,
|
||||
kete.ORG_FREEDESKTOP_TUHI1_MANAGER,
|
||||
tuhi.dbusclient.TUHI_DBUS_NAME,
|
||||
tuhi.dbusclient.ROOT_PATH,
|
||||
tuhi.dbusclient.ORG_FREEDESKTOP_TUHI1_MANAGER,
|
||||
None)
|
||||
except GLib.Error as e:
|
||||
if (e.domain == 'g-io-error-quark' and
|
||||
e.code == Gio.IOErrorEnum.DBUS_ERROR):
|
||||
raise kete.DBusError(e.message)
|
||||
raise tuhi.dbusclient.DBusError(e.message)
|
||||
else:
|
||||
raise e
|
||||
|
||||
started = proxy.get_name_owner() is not None
|
||||
|
||||
if not started:
|
||||
print(f'No-one is handling {kete.TUHI_DBUS_NAME}, attempting to start a daemon')
|
||||
print(f'No-one is handling {tuhi.dbusclient.TUHI_DBUS_NAME}, attempting to start a daemon')
|
||||
|
||||
queue.put((not started, args.verbose))
|
||||
queue.put((not started, args))
|
||||
|
||||
|
||||
def run_live(request_fd_queue, conn_fd):
|
||||
import kete
|
||||
from gi.repository import Gio, GLib
|
||||
|
||||
def on_name_appeared(connection, name, client):
|
||||
global manager
|
||||
logger.info('Connected to the Tuhi daemon')
|
||||
manager = kete.TuhiKeteManager()
|
||||
manager = tuhi.dbusclient.TuhiDBusClientManager()
|
||||
|
||||
for device in manager.devices:
|
||||
if device.live:
|
||||
logger.info(f'{device} is already live, stopping first')
|
||||
device.stop_live()
|
||||
logger.info(f'starting live on {device}, please press button on the device')
|
||||
request_fd_queue.put(os.getpid())
|
||||
fd = reduction.recv_handle(conn_fd)
|
||||
device.start_live(fd)
|
||||
|
||||
Gio.bus_watch_name(Gio.BusType.SESSION,
|
||||
kete.TUHI_DBUS_NAME,
|
||||
tuhi.dbusclient.TUHI_DBUS_NAME,
|
||||
Gio.BusNameWatcherFlags.NONE,
|
||||
on_name_appeared,
|
||||
None)
|
||||
|
||||
mainloop = GLib.MainLoop()
|
||||
|
||||
connected_devices = 0
|
||||
|
||||
def on_disconnect(dev, pspec):
|
||||
mainloop.quit()
|
||||
|
||||
|
@ -147,10 +156,6 @@ def run_live(request_fd_queue, conn_fd):
|
|||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
for device in manager.devices:
|
||||
if device.live:
|
||||
connected_devices += 1
|
||||
|
||||
for device in manager.devices:
|
||||
if device.live and device.connected:
|
||||
logger.info(f'stopping live on {device}')
|
||||
|
@ -186,22 +191,21 @@ def drop_privileges():
|
|||
|
||||
|
||||
def parse(args):
|
||||
desc = 'tool to start the live mode on all devices tuhi knows about'
|
||||
parser = argparse.ArgumentParser(description=desc)
|
||||
parser.add_argument('-v', '--verbose',
|
||||
help='Show some debugging informations',
|
||||
parser = argparse.ArgumentParser(description='Tool to start live mode')
|
||||
parser.add_argument('--flatpak-compatibility-mode',
|
||||
help='Use the flatpak xdg directories',
|
||||
action='store_true',
|
||||
default=False)
|
||||
|
||||
return parser.parse_args(args[1:])
|
||||
ns, remaining_args = parser.parse_known_args(args[1:])
|
||||
return ns, remaining_args
|
||||
|
||||
|
||||
def main(args=sys.argv):
|
||||
if not os.geteuid() == 0:
|
||||
sys.exit('Script must be run as root')
|
||||
|
||||
args = parse(args)
|
||||
|
||||
our_args, remaining_args = parse(args)
|
||||
request_fd_queue = multiprocessing.Queue()
|
||||
conn_in, conn_out = multiprocessing.Pipe()
|
||||
|
||||
|
@ -211,7 +215,19 @@ def main(args=sys.argv):
|
|||
|
||||
drop_privileges()
|
||||
|
||||
start_tuhi_server(args)
|
||||
if our_args.flatpak_compatibility_mode:
|
||||
from pathlib import Path
|
||||
|
||||
# tuhi-live is usually started through sudo, so let's get to the
|
||||
# user's home directory here.
|
||||
userhome = Path(os.path.expanduser('~' + os.getlogin()))
|
||||
basedir = userhome / '.var' / 'app' / 'org.freedesktop.Tuhi'
|
||||
print(f'Using flatpak xdg dirs in {basedir}')
|
||||
os.environ['XDG_DATA_HOME'] = os.fspath(basedir / 'data')
|
||||
os.environ['XDG_CONFIG_HOME'] = os.fspath(basedir / 'config')
|
||||
os.environ['XDG_CACHE_HOME'] = os.fspath(basedir / 'cache')
|
||||
|
||||
start_tuhi_server(remaining_args)
|
||||
run_live(request_fd_queue, conn_in)
|
||||
|
||||
|
||||
|
|
65
tuhi-gui.in
65
tuhi-gui.in
|
@ -5,9 +5,15 @@ import sys
|
|||
import os
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
# 3.30 is the first one with Gtk.Template
|
||||
gi.check_version('3.30') # NOQA
|
||||
except ValueError as e:
|
||||
print(e, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
gi.require_version('Gio', '2.0') # NOQA
|
||||
gi.require_version('Gtk', '3.0') # NOQA
|
||||
from gi.repository import Gio, Gtk, Gdk
|
||||
from gi.repository import Gio
|
||||
|
||||
|
||||
@devel@ # NOQA
|
||||
|
@ -15,61 +21,12 @@ resource = Gio.resource_load(os.fspath(Path('@pkgdatadir@', 'tuhi.gresource')))
|
|||
Gio.Resource._register(resource)
|
||||
|
||||
|
||||
def install_excepthook():
|
||||
old_hook = sys.excepthook
|
||||
|
||||
def new_hook(etype, evalue, etb):
|
||||
old_hook(etype, evalue, etb)
|
||||
while Gtk.main_level():
|
||||
Gtk.main_quit()
|
||||
sys.exit()
|
||||
sys.excepthook = new_hook
|
||||
|
||||
|
||||
def gtk_style():
|
||||
css = b"""
|
||||
flowboxchild:selected {
|
||||
background-color: white;
|
||||
}
|
||||
.bg-white {
|
||||
background-color: white;
|
||||
}
|
||||
.bg-paper {
|
||||
border-radius: 5px;
|
||||
background-color: #ebe9e8;
|
||||
}
|
||||
.drawing {
|
||||
background-color: white;
|
||||
border-radius: 5px;
|
||||
}
|
||||
"""
|
||||
|
||||
screen = Gdk.Screen.get_default()
|
||||
if screen is None:
|
||||
print('Error: Unable to connect to screen. Make sure DISPLAY or WAYLAND_DISPLAY are set', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
style_provider = Gtk.CssProvider()
|
||||
style_provider.load_from_data(css)
|
||||
Gtk.StyleContext.add_provider_for_screen(
|
||||
screen,
|
||||
style_provider,
|
||||
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import gettext
|
||||
import locale
|
||||
import signal
|
||||
from tuhi.gui.application import Application
|
||||
|
||||
install_excepthook()
|
||||
gtk_style()
|
||||
|
||||
locale.bindtextdomain('tuhi', '@localedir@')
|
||||
locale.textdomain('tuhi')
|
||||
gettext.bindtextdomain('tuhi', '@localedir@')
|
||||
gettext.textdomain('tuhi')
|
||||
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||
exit_status = Application().run(sys.argv)
|
||||
sys.exit(exit_status)
|
||||
|
||||
from tuhi.gui.application import main
|
||||
main(sys.argv)
|
||||
|
|
24
tuhi.in
24
tuhi.in
|
@ -14,6 +14,7 @@
|
|||
import sys
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
import argparse
|
||||
|
||||
tuhi_server = Path('@libexecdir@', 'tuhi-server')
|
||||
tuhi_gui = Path('@libexecdir@', 'tuhi-gui')
|
||||
|
@ -22,10 +23,27 @@ tuhi_gui = Path('@libexecdir@', 'tuhi-gui')
|
|||
@devel@ # NOQA
|
||||
|
||||
if __name__ == '__main__':
|
||||
args = sys.argv[1:]
|
||||
tuhi = subprocess.Popen([tuhi_server] + args)
|
||||
if sys.version_info < (3, 6):
|
||||
sys.exit('Python 3.6 or later required')
|
||||
|
||||
parser = argparse.ArgumentParser(description='Tuhi')
|
||||
parser.add_argument('--flatpak-compatibility-mode',
|
||||
help='Use the flatpak xdg directories',
|
||||
action='store_true',
|
||||
default=False)
|
||||
ns, remainder = parser.parse_known_args()
|
||||
if ns.flatpak_compatibility_mode:
|
||||
import os
|
||||
|
||||
basedir = Path.home() / '.var' / 'app' / 'org.freedesktop.Tuhi'
|
||||
print(f'Using flatpak xdg dirs in {basedir}')
|
||||
os.environ['XDG_DATA_HOME'] = os.fspath(basedir / 'data')
|
||||
os.environ['XDG_CONFIG_HOME'] = os.fspath(basedir / 'config')
|
||||
os.environ['XDG_CACHE_HOME'] = os.fspath(basedir / 'cache')
|
||||
|
||||
tuhi = subprocess.Popen([tuhi_server] + remainder)
|
||||
try:
|
||||
subprocess.run([tuhi_gui] + args)
|
||||
subprocess.run([tuhi_gui] + remainder)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
tuhi.terminate()
|
||||
|
|
|
@ -210,7 +210,7 @@ class BlueZDevice(GObject.Object):
|
|||
self.emit('connected')
|
||||
return
|
||||
|
||||
self.logger.info(f'Connecting')
|
||||
self.logger.debug(f'Connecting')
|
||||
i.Connect(result_handler=self._on_connect_result)
|
||||
|
||||
def _on_connect_result(self, obj, result, user_data):
|
||||
|
@ -234,7 +234,7 @@ class BlueZDevice(GObject.Object):
|
|||
self.emit('disconnected')
|
||||
return
|
||||
|
||||
self.logger.info(f'Disconnecting')
|
||||
self.logger.debug(f'Disconnecting')
|
||||
i.Disconnect(result_handler=self._on_disconnect_result)
|
||||
|
||||
def _on_disconnect_result(self, obj, result, user_data):
|
||||
|
@ -246,9 +246,9 @@ class BlueZDevice(GObject.Object):
|
|||
|
||||
if 'Connected' in properties:
|
||||
if properties['Connected']:
|
||||
self.logger.info('Connection established')
|
||||
self.logger.debug('Connection established')
|
||||
else:
|
||||
self.logger.info('Disconnected')
|
||||
self.logger.debug('Disconnected')
|
||||
self.emit('disconnected')
|
||||
if 'ServicesResolved' in properties:
|
||||
if properties['ServicesResolved']:
|
||||
|
|
|
@ -18,7 +18,7 @@ import os
|
|||
import logging
|
||||
import re
|
||||
|
||||
logger = logging.getLogger('tuhi.gui.dbus')
|
||||
logger = logging.getLogger('tuhi.dbusclient')
|
||||
|
||||
TUHI_DBUS_NAME = 'org.freedesktop.tuhi1'
|
||||
ORG_FREEDESKTOP_TUHI1_MANAGER = 'org.freedesktop.tuhi1.Manager'
|
||||
|
@ -37,7 +37,7 @@ class _DBusObject(GObject.Object):
|
|||
_connection = None
|
||||
|
||||
def __init__(self, name, interface, objpath):
|
||||
GObject.GObject.__init__(self)
|
||||
super().__init__()
|
||||
|
||||
# this is not handled asynchronously because if we fail to
|
||||
# get the session bus, we have other issues
|
||||
|
@ -149,20 +149,18 @@ class BlueZDevice(_DBusSystemObject):
|
|||
self.notify('connected')
|
||||
|
||||
|
||||
class TuhiKeteDevice(_DBusObject):
|
||||
class TuhiDBusClientDevice(_DBusObject):
|
||||
__gsignals__ = {
|
||||
'button-press-required':
|
||||
(GObject.SignalFlags.RUN_FIRST, None, (GObject.TYPE_PYOBJECT,)),
|
||||
(GObject.SignalFlags.RUN_FIRST, None, ()),
|
||||
'registered':
|
||||
(GObject.SignalFlags.RUN_FIRST, None, (GObject.TYPE_PYOBJECT,)),
|
||||
(GObject.SignalFlags.RUN_FIRST, None, ()),
|
||||
'device-error':
|
||||
(GObject.SignalFlags.RUN_FIRST, None, (int,)),
|
||||
}
|
||||
|
||||
def __init__(self, manager, objpath):
|
||||
_DBusObject.__init__(self, TUHI_DBUS_NAME,
|
||||
ORG_FREEDESKTOP_TUHI1_DEVICE,
|
||||
objpath)
|
||||
super().__init__(TUHI_DBUS_NAME, ORG_FREEDESKTOP_TUHI1_DEVICE, objpath)
|
||||
self.manager = manager
|
||||
self.is_registering = False
|
||||
self._bluez_device = BlueZDevice(self.property('BlueZDevice'))
|
||||
|
@ -211,6 +209,10 @@ class TuhiKeteDevice(_DBusObject):
|
|||
def sync_state(self):
|
||||
return self._sync_state
|
||||
|
||||
@GObject.Property
|
||||
def live(self):
|
||||
return self.property('Live')
|
||||
|
||||
def _on_connected(self, bluez_device, pspec):
|
||||
self.notify('connected')
|
||||
|
||||
|
@ -241,7 +243,7 @@ class TuhiKeteDevice(_DBusObject):
|
|||
def _on_signal_received(self, proxy, sender, signal, parameters):
|
||||
if signal == 'ButtonPressRequired':
|
||||
logger.info(f'{self}: Press button on device now')
|
||||
self.emit('button-press-required', self)
|
||||
self.emit('button-press-required')
|
||||
elif signal == 'ListeningStopped':
|
||||
err = parameters[0]
|
||||
if err == -errno.EACCES:
|
||||
|
@ -268,6 +270,8 @@ class TuhiKeteDevice(_DBusObject):
|
|||
self.notify('battery-percent')
|
||||
elif 'BatteryState' in changed_props:
|
||||
self.notify('battery-state')
|
||||
elif 'Live' in changed_props:
|
||||
self.notify('live')
|
||||
|
||||
def __repr__(self):
|
||||
return f'{self.address} - {self.name}'
|
||||
|
@ -282,7 +286,21 @@ class TuhiKeteDevice(_DBusObject):
|
|||
self.manager.disconnect(self.s1)
|
||||
del(self.s1)
|
||||
logger.info(f'{self}: Registration successful')
|
||||
self.emit('registered', self)
|
||||
self.emit('registered')
|
||||
|
||||
def start_live(self, fd):
|
||||
fd_list = Gio.UnixFDList.new()
|
||||
fd_list.append(fd)
|
||||
|
||||
res, fds = self.proxy.call_with_unix_fd_list_sync('org.freedesktop.tuhi1.Device.StartLive',
|
||||
GLib.Variant('(h)', (fd,)),
|
||||
Gio.DBusCallFlags.NO_AUTO_START,
|
||||
-1,
|
||||
fd_list,
|
||||
None)
|
||||
|
||||
def stop_live(self):
|
||||
self.proxy.StopLive()
|
||||
|
||||
def terminate(self):
|
||||
try:
|
||||
|
@ -290,19 +308,17 @@ class TuhiKeteDevice(_DBusObject):
|
|||
except AttributeError:
|
||||
pass
|
||||
self._bluez_device.terminate()
|
||||
super(TuhiKeteDevice, self).terminate()
|
||||
super().terminate()
|
||||
|
||||
|
||||
class TuhiKeteManager(_DBusObject):
|
||||
class TuhiDBusClientManager(_DBusObject):
|
||||
__gsignals__ = {
|
||||
'unregistered-device':
|
||||
(GObject.SignalFlags.RUN_FIRST, None, (GObject.TYPE_PYOBJECT,)),
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
_DBusObject.__init__(self, TUHI_DBUS_NAME,
|
||||
ORG_FREEDESKTOP_TUHI1_MANAGER,
|
||||
ROOT_PATH)
|
||||
super().__init__(TUHI_DBUS_NAME, ORG_FREEDESKTOP_TUHI1_MANAGER, ROOT_PATH)
|
||||
|
||||
self._devices = {}
|
||||
self._unregistered_devices = {}
|
||||
|
@ -316,7 +332,7 @@ class TuhiKeteManager(_DBusObject):
|
|||
def _init(self, *args, **kwargs):
|
||||
logger.info('manager is online')
|
||||
for objpath in self.property('Devices'):
|
||||
device = TuhiKeteDevice(self, objpath)
|
||||
device = TuhiDBusClientDevice(self, objpath)
|
||||
self._devices[device.address] = device
|
||||
|
||||
@GObject.Property
|
||||
|
@ -350,7 +366,7 @@ class TuhiKeteManager(_DBusObject):
|
|||
dev.terminate()
|
||||
self._devices = {}
|
||||
self._unregistered_devices = {}
|
||||
super(TuhiKeteManager, self).terminate()
|
||||
super().terminate()
|
||||
|
||||
def _on_properties_changed(self, proxy, changed_props, invalidated_props):
|
||||
if changed_props is None:
|
||||
|
@ -379,7 +395,7 @@ class TuhiKeteManager(_DBusObject):
|
|||
self.emit('unregistered-device', dev)
|
||||
return
|
||||
|
||||
device = TuhiKeteDevice(self, objpath)
|
||||
device = TuhiDBusClientDevice(self, objpath)
|
||||
self._unregistered_devices[objpath] = device
|
||||
|
||||
logger.debug(f'New unregistered device: {device}')
|
|
@ -11,18 +11,17 @@
|
|||
# GNU General Public License for more details.
|
||||
#
|
||||
|
||||
from gi.repository import Gio, GLib, Gtk
|
||||
import logging
|
||||
|
||||
from .window import MainWindow
|
||||
from .config import Config
|
||||
import xdg.BaseDirectory
|
||||
from pathlib import Path
|
||||
|
||||
import logging
|
||||
import sys
|
||||
import xdg.BaseDirectory
|
||||
import gi
|
||||
gi.require_version("Gio", "2.0")
|
||||
gi.require_version("Gtk", "3.0")
|
||||
|
||||
from gi.repository import Gio, GLib, Gtk, Gdk # NOQA
|
||||
|
||||
logging.basicConfig(format='%(asctime)s %(levelname)s: %(name)s: %(message)s',
|
||||
level=logging.INFO,
|
||||
|
@ -52,6 +51,9 @@ class Application(Gtk.Application):
|
|||
GLib.OptionFlags.NONE,
|
||||
GLib.OptionArg.NONE,
|
||||
'download first drawing only but do not remove it from the device')
|
||||
|
||||
self.set_accels_for_action('app.quit', ['<Ctrl>Q'])
|
||||
|
||||
self._tuhi = None
|
||||
|
||||
def do_startup(self):
|
||||
|
@ -102,3 +104,61 @@ class Application(Gtk.Application):
|
|||
def _help(self, action, param):
|
||||
import time
|
||||
Gtk.show_uri(None, 'https://github.com/tuhiproject/tuhi/wiki', time.time())
|
||||
|
||||
|
||||
def install_excepthook():
|
||||
old_hook = sys.excepthook
|
||||
|
||||
def new_hook(etype, evalue, etb):
|
||||
old_hook(etype, evalue, etb)
|
||||
while Gtk.main_level():
|
||||
Gtk.main_quit()
|
||||
sys.exit()
|
||||
sys.excepthook = new_hook
|
||||
|
||||
|
||||
def gtk_style():
|
||||
css = b"""
|
||||
flowboxchild:selected {
|
||||
background-color: white;
|
||||
}
|
||||
.bg-white {
|
||||
background-color: white;
|
||||
}
|
||||
.bg-paper {
|
||||
border-radius: 5px;
|
||||
background-color: #ebe9e8;
|
||||
}
|
||||
.drawing {
|
||||
background-color: white;
|
||||
border-radius: 5px;
|
||||
}
|
||||
"""
|
||||
|
||||
screen = Gdk.Screen.get_default()
|
||||
if screen is None:
|
||||
print('Error: Unable to connect to screen. Make sure DISPLAY or WAYLAND_DISPLAY are set', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
style_provider = Gtk.CssProvider()
|
||||
style_provider.load_from_data(css)
|
||||
Gtk.StyleContext.add_provider_for_screen(screen,
|
||||
style_provider,
|
||||
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
|
||||
|
||||
|
||||
def main(argv):
|
||||
if sys.version_info < (3, 6):
|
||||
sys.exit('Python 3.6 or later required')
|
||||
|
||||
import gettext
|
||||
import locale
|
||||
import signal
|
||||
|
||||
install_excepthook()
|
||||
gtk_style()
|
||||
|
||||
locale.textdomain('tuhi')
|
||||
gettext.textdomain('tuhi')
|
||||
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||
exit_status = Application().run(argv)
|
||||
sys.exit(exit_status)
|
||||
|
|
|
@ -69,7 +69,7 @@ class Config(GObject.Object):
|
|||
self.config[section][key] = value
|
||||
self._write()
|
||||
|
||||
@GObject.property
|
||||
@GObject.Property
|
||||
def orientation(self):
|
||||
try:
|
||||
return self.config['Device']['Orientation']
|
||||
|
@ -81,7 +81,7 @@ class Config(GObject.Object):
|
|||
assert(orientation in ['landscape', 'portrait'])
|
||||
self._add_key('Device', 'Orientation', orientation)
|
||||
|
||||
@GObject.property
|
||||
@GObject.Property
|
||||
def drawings(self):
|
||||
return self._drawings
|
||||
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
#
|
||||
|
||||
from gettext import gettext as _
|
||||
from gi.repository import GObject, Gtk, GdkPixbuf, Gdk
|
||||
|
||||
import xdg.BaseDirectory
|
||||
import os
|
||||
|
@ -22,6 +21,8 @@ from tuhi.svg import JsonSvg
|
|||
|
||||
import gi
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import GObject, Gtk, GdkPixbuf, Gdk # NOQA
|
||||
|
||||
|
||||
DATA_PATH = Path(xdg.BaseDirectory.xdg_cache_home, 'tuhi', 'svg')
|
||||
|
||||
|
|
|
@ -11,15 +11,15 @@
|
|||
# GNU General Public License for more details.
|
||||
#
|
||||
|
||||
from gi.repository import GObject, Gtk
|
||||
from .drawing import Drawing
|
||||
from .config import Config
|
||||
|
||||
import time
|
||||
import gi
|
||||
import logging
|
||||
|
||||
import gi
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import GObject, Gtk # NOQA
|
||||
|
||||
logger = logging.getLogger('tuhi.gui.drawingperspective')
|
||||
|
||||
|
@ -55,7 +55,7 @@ class Flowbox(Gtk.Box):
|
|||
self.flowbox_drawings.remove(child)
|
||||
self.flowbox_drawings.foreach(delete_matching_child, drawing)
|
||||
|
||||
@GObject.property
|
||||
@GObject.Property
|
||||
def is_empty(self):
|
||||
return not self.flowbox_drawings.get_children()
|
||||
|
||||
|
@ -75,6 +75,7 @@ class DrawingPerspective(Gtk.Stack):
|
|||
self.known_drawings = {} # type {timestamp: Drawing()}
|
||||
self.flowboxes = {}
|
||||
self._zoom = 0
|
||||
self._want_listen = True
|
||||
|
||||
def _cache_drawings(self, device, pspec):
|
||||
# The config backend filters duplicates anyway, so don't care here
|
||||
|
@ -126,8 +127,13 @@ class DrawingPerspective(Gtk.Stack):
|
|||
def device(self, device):
|
||||
self._device = device
|
||||
|
||||
device.connect('notify::connected', self._on_connected)
|
||||
device.connect('notify::listening', self._on_listening_stopped)
|
||||
self._signals = []
|
||||
sig = device.connect('notify::connected', self._on_connected)
|
||||
self._signals.append(sig)
|
||||
sig = device.connect('notify::listening', self._on_listening_stopped)
|
||||
self._signals.append(sig)
|
||||
sig = device.connect('device-error', self._on_device_error)
|
||||
self._signals.append(sig)
|
||||
|
||||
# This is a bit convoluted. We need to cache all drawings
|
||||
# because Tuhi doesn't have guaranteed storage. So any json that
|
||||
|
@ -168,11 +174,20 @@ class DrawingPerspective(Gtk.Stack):
|
|||
pass
|
||||
|
||||
def _on_listening_stopped(self, device, pspec):
|
||||
if not device.listening:
|
||||
if not device.listening and self._want_listen:
|
||||
logger.debug(f'{device.name} - listening stopped, restarting')
|
||||
# We never want to stop listening
|
||||
device.start_listening()
|
||||
|
||||
def _on_device_error(self, device, error):
|
||||
import errno
|
||||
if error == -errno.EACCES:
|
||||
# No point to keep getting notified
|
||||
for sig in self._signals:
|
||||
device.disconnect(sig)
|
||||
self._signals = []
|
||||
self._want_listen = False
|
||||
|
||||
@Gtk.Template.Callback('_on_undo_close_clicked')
|
||||
def _on_undo_close_clicked(self, button):
|
||||
self.overlay_undo.set_reveal_child(False)
|
||||
|
|
|
@ -11,49 +11,19 @@
|
|||
# GNU General Public License for more details.
|
||||
#
|
||||
|
||||
from gettext import gettext as _
|
||||
from gi.repository import Gtk, Gio, GLib, GObject
|
||||
|
||||
from .drawingperspective import DrawingPerspective
|
||||
from .tuhi import TuhiKeteManager
|
||||
from .config import Config
|
||||
from tuhi.dbusclient import TuhiDBusClientManager
|
||||
|
||||
from gettext import gettext as _
|
||||
import logging
|
||||
|
||||
import gi
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk, Gio, GLib, GObject # NOQA
|
||||
|
||||
logger = logging.getLogger('tuhi.gui.window')
|
||||
|
||||
MENU_XML = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<menu id="primary-menu">
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Portrait</attribute>
|
||||
<attribute name="action">win.orientation</attribute>
|
||||
<attribute name="target">portrait</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Landscape</attribute>
|
||||
<attribute name="action">win.orientation</attribute>
|
||||
<attribute name="target">landscape</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Help</attribute>
|
||||
<attribute name="action">app.help</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">About</attribute>
|
||||
<attribute name="action">app.about</attribute>
|
||||
</item>
|
||||
</section>
|
||||
</menu>
|
||||
</interface>
|
||||
"""
|
||||
|
||||
|
||||
@Gtk.Template(resource_path="/org/freedesktop/Tuhi/ui/ErrorPerspective.ui")
|
||||
class ErrorPerspective(Gtk.Box):
|
||||
|
@ -100,14 +70,14 @@ class SetupDialog(Gtk.Dialog):
|
|||
self._sig = device.connect('button-press-required', self._on_button_press_required)
|
||||
device.register()
|
||||
|
||||
def _on_button_press_required(self, tuhi, device):
|
||||
tuhi.disconnect(self._sig)
|
||||
def _on_button_press_required(self, device):
|
||||
device.disconnect(self._sig)
|
||||
|
||||
self.stack.set_visible_child_name('page2')
|
||||
self._sig = device.connect('registered', self._on_registered)
|
||||
|
||||
def _on_registered(self, tuhi, device):
|
||||
tuhi.disconnect(self._sig)
|
||||
def _on_registered(self, device):
|
||||
device.disconnect(self._sig)
|
||||
self.device = device
|
||||
self.response(Gtk.ResponseType.OK)
|
||||
|
||||
|
@ -132,7 +102,7 @@ class MainWindow(Gtk.ApplicationWindow):
|
|||
super().__init__(**kwargs)
|
||||
|
||||
self.maximize()
|
||||
self._tuhi = TuhiKeteManager()
|
||||
self._tuhi = TuhiDBusClientManager()
|
||||
|
||||
action = Gio.SimpleAction.new_stateful('orientation', GLib.VariantType('s'),
|
||||
GLib.Variant('s', 'landscape'))
|
||||
|
@ -140,7 +110,7 @@ class MainWindow(Gtk.ApplicationWindow):
|
|||
action.set_state(GLib.Variant.new_string(Config().orientation))
|
||||
self.add_action(action)
|
||||
|
||||
builder = Gtk.Builder.new_from_string(MENU_XML, -1)
|
||||
builder = Gtk.Builder.new_from_resource('/org/freedesktop/Tuhi/ui/AppMenu.ui')
|
||||
menu = builder.get_object("primary-menu")
|
||||
self.menubutton1.set_menu_model(menu)
|
||||
|
||||
|
@ -244,6 +214,7 @@ class MainWindow(Gtk.ApplicationWindow):
|
|||
# register.
|
||||
for sig in self._signals:
|
||||
device.disconnect(sig)
|
||||
self._signals = []
|
||||
|
||||
def _add_perspective(self, perspective):
|
||||
self.stack_perspectives.add_named(perspective, perspective.name)
|
||||
|
|
|
@ -144,7 +144,7 @@ def as_hex_string(data):
|
|||
elif isinstance(data, list):
|
||||
return ' '.join([f'{x:02x}' for x in data])
|
||||
|
||||
raise ValueError('Unsupported data format {data.__class__} for {data}')
|
||||
raise ValueError('Unsupported data format {data.__class__.__name__} for {data}')
|
||||
|
||||
|
||||
def _get_protocol_dictionary(protocol):
|
||||
|
@ -337,8 +337,8 @@ class NordicData(list):
|
|||
if self.length != len(data):
|
||||
raise UnexpectedDataError(bs, f'Invalid data: length field {self.length}, data length is {len(data)}')
|
||||
|
||||
def __repr__(self):
|
||||
return f'{self.name if self.Name else "UNKNOWN"}{self.opcode:02x} / {self.length:02x} / {as_hex_string(self)}'
|
||||
def __str__(self):
|
||||
return f'{self.name if self.name else "UNKNOWN"} {self.opcode:02x} / {self.length:02x} / {as_hex_string(self)}'
|
||||
|
||||
|
||||
class ProtocolError(Exception):
|
||||
|
@ -361,7 +361,7 @@ class MissingReplyError(ProtocolError):
|
|||
def __init__(self, request, message=None):
|
||||
self.request = request
|
||||
|
||||
def __repr__(self):
|
||||
def __str__(self):
|
||||
return f'Missing reply for request {self.request}. {self.message}'
|
||||
|
||||
|
||||
|
@ -390,8 +390,8 @@ class UnexpectedReply(ProtocolError):
|
|||
super().__init__(message)
|
||||
self.msg = msg
|
||||
|
||||
def __repr__(self):
|
||||
return f'{self.__class__}: {self.msg}: {self.message}'
|
||||
def __str__(self):
|
||||
return f'{self.__class__.__name__}: {self.msg}: {self.message}'
|
||||
|
||||
|
||||
class UnexpectedDataError(ProtocolError):
|
||||
|
@ -412,8 +412,8 @@ class UnexpectedDataError(ProtocolError):
|
|||
super().__init__(*args, **kwargs)
|
||||
self.bytes = bytes
|
||||
|
||||
def __repr__(self):
|
||||
return f'{self.__class__}: {self.bytes} - {self.message}'
|
||||
def __str__(self):
|
||||
return f'{self.__class__.__name__}: {self.bytes} - {self.message}'
|
||||
|
||||
|
||||
class DeviceError(ProtocolError):
|
||||
|
@ -451,11 +451,8 @@ class DeviceError(ProtocolError):
|
|||
if self.errorcode == DeviceError.ErrorCode.INVALID_STATE:
|
||||
self.errno = errno.EBADE
|
||||
|
||||
def __repr__(self):
|
||||
return f'DeviceError.{self.errorcode.name}'
|
||||
|
||||
def __str__(self):
|
||||
return repr(self)
|
||||
return f'DeviceError.{self.errorcode.name}'
|
||||
|
||||
|
||||
class Msg(object):
|
||||
|
@ -526,18 +523,16 @@ class Msg(object):
|
|||
self._args = args
|
||||
|
||||
def _handle_reply(self, reply):
|
||||
'''Override this in the subclass to handle the reply.
|
||||
'''
|
||||
Override this in the subclass to handle the reply. Note that the
|
||||
default 0xb3 message is handled automaticaly, this is only for
|
||||
non-default replies.
|
||||
|
||||
This is the default reply handler that deals with the 0xb3 ACK/Error
|
||||
messages and throws the respective exceptions.
|
||||
No return value, just throw the appropriate exception on failure.
|
||||
|
||||
:param reply: A :class:`NordicData` object
|
||||
'''
|
||||
if reply.opcode != 0xb3:
|
||||
raise UnexpectedReply(self)
|
||||
|
||||
if reply[0] != 0x00:
|
||||
raise DeviceError(reply[0])
|
||||
raise NotImplementedError(f'{reply} needs customized handling')
|
||||
|
||||
def execute(self):
|
||||
'''
|
||||
|
@ -557,7 +552,14 @@ class Msg(object):
|
|||
if self.reply is None:
|
||||
raise MissingReplyError(self.request)
|
||||
try:
|
||||
self._handle_reply(self.reply)
|
||||
# 0xb3 is always handled by us, anything else requires a
|
||||
# custom reply handler
|
||||
if self.reply.opcode == 0xb3:
|
||||
if self.reply[0] != 0x00:
|
||||
raise DeviceError(self.reply[0])
|
||||
else:
|
||||
self._handle_reply(self.reply)
|
||||
|
||||
# no exception? we can assume success
|
||||
self.errorcode = DeviceError.ErrorCode.SUCCESS
|
||||
except DeviceError as e:
|
||||
|
@ -565,8 +567,8 @@ class Msg(object):
|
|||
raise e
|
||||
return self # allow chaining
|
||||
|
||||
def __repr__(self):
|
||||
return f'{self.__class__}: {self.interaction} - {self.request} → {self.reply}'
|
||||
def __str__(self):
|
||||
return f'{self.__class__.__name__}: {self.interaction} - {self.request} → {self.reply}'
|
||||
|
||||
|
||||
class MsgConnectIntuosPro(Msg):
|
||||
|
@ -1374,16 +1376,13 @@ class StrokeParsingError(ProtocolError):
|
|||
self.message = message
|
||||
self.data = data
|
||||
|
||||
def __repr__(self):
|
||||
def __str__(self):
|
||||
if self.data:
|
||||
datastr = f' data: {list2hex(self.data)}'
|
||||
else:
|
||||
datastr = ''
|
||||
return f'{self.message}{datastr}'
|
||||
|
||||
def __str__(self):
|
||||
return self.__repr__()
|
||||
|
||||
|
||||
class StrokeDataType(enum.Enum):
|
||||
UNKNOWN = enum.auto()
|
||||
|
@ -1636,7 +1635,7 @@ class StrokePacketUnknown(StrokePacket):
|
|||
self.size = 1 + nbytes
|
||||
self.data = data[:self.size]
|
||||
|
||||
def __repr__(self):
|
||||
def __str__(self):
|
||||
return f'Unknown packet: {list2hex(self.data)}'
|
||||
|
||||
|
||||
|
@ -1672,7 +1671,7 @@ class StrokeFileHeader(StrokePacket):
|
|||
except KeyError:
|
||||
raise StrokeParsingError(f'Unknown file format:', data[:4])
|
||||
|
||||
def __repr__(self):
|
||||
def __str__(self):
|
||||
t = time.strftime("%y%m%d%H%M%S", time.gmtime(self.timestamp))
|
||||
return f'FileHeader: time: {t}, stroke count: {self.nstrokes}'
|
||||
|
||||
|
@ -1759,7 +1758,7 @@ class StrokeHeader(StrokePacket):
|
|||
self.pen_id = little_u64(pen_packet[:8])
|
||||
self.size += 1 + nbytes
|
||||
|
||||
def __repr__(self):
|
||||
def __str__(self):
|
||||
if self.timestamp is not None:
|
||||
t = time.strftime(f'%y%m%d%H%M%S', time.gmtime(self.timestamp))
|
||||
else:
|
||||
|
@ -1849,7 +1848,7 @@ class StrokeDelta(object):
|
|||
|
||||
self.size = offset
|
||||
|
||||
def __repr__(self):
|
||||
def __str__(self):
|
||||
def printstring(delta, abs):
|
||||
return f'{delta:+5d}' if delta is not None \
|
||||
else f'{abs:5d}' if abs is not None \
|
||||
|
@ -1888,7 +1887,7 @@ class StrokePoint(StrokeDelta):
|
|||
# self.y = little_u16(data[4:6])
|
||||
# self.pressure = little_u16(data[6:8])
|
||||
|
||||
def __repr__(self):
|
||||
def __str__(self):
|
||||
return f'StrokePoint: {self.x}/{self.y} pressure: {self.p}'
|
||||
|
||||
|
||||
|
@ -1912,7 +1911,7 @@ class StrokeEndOfStroke(StrokePacket):
|
|||
self.size = nbytes + 1
|
||||
self.data = data[:self.size]
|
||||
|
||||
def __repr__(self):
|
||||
def __str__(self):
|
||||
return f'EndOfStroke: {list2hex(self.data)}'
|
||||
|
||||
|
||||
|
|
|
@ -479,7 +479,7 @@ class WacomProtocolBase(WacomProtocolLowLevelComm):
|
|||
device.connect_gatt_value(WACOM_OFFLINE_CHRC_PEN_DATA_UUID,
|
||||
self._on_pen_data_received)
|
||||
|
||||
@GObject.property
|
||||
@GObject.Property
|
||||
def dimensions(self):
|
||||
return (self.width, self.height)
|
||||
|
||||
|
@ -805,12 +805,13 @@ class WacomProtocolSlate(WacomProtocolSpark):
|
|||
self._on_sysevent_data_received)
|
||||
|
||||
def live_mode(self, mode, uhid):
|
||||
# Slate tablet has two models A5 and A4
|
||||
# Here, we read real tablet dimensions before
|
||||
# starting live mode
|
||||
self.update_dimensions()
|
||||
self.x_max = self.width - 1000
|
||||
self.y_max = self.height - 500
|
||||
if mode:
|
||||
# Slate tablet has two models A5 and A4
|
||||
# Here, we read real tablet dimensions before
|
||||
# starting live mode
|
||||
self.update_dimensions()
|
||||
self.x_max = int(self.width / self.point_size) - 1000
|
||||
self.y_max = int(self.height / self.point_size) - 500
|
||||
|
||||
return super().live_mode(mode, uhid)
|
||||
|
||||
|
@ -1027,7 +1028,7 @@ class WacomDevice(GObject.Object):
|
|||
|
||||
mode = args[0]
|
||||
|
||||
logger.debug(f'{self._device.address}: starting')
|
||||
logger.debug(f'{self._device.address}: starting for mode {mode.name}')
|
||||
self._is_running = True
|
||||
exception = None
|
||||
try:
|
||||
|
|
Loading…
Reference in New Issue