Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/testrunner.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:

python-linux-3-10-x:
name: python 3 linux
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.10.x]
Expand Down
6 changes: 3 additions & 3 deletions castervoice/asynch/hmc/h_launch.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,17 @@ def _get_title(hmc_type):


def main():
import PySide2.QtWidgets
from castervoice.lib.qt import QtWidgets, qapp_exec
from castervoice.asynch.hmc.homunculus import Homunculus
from castervoice.lib.merge.communication import Communicator
server_address = (Communicator.LOCALHOST, Communicator().com_registry["hmc"])
# Enabled by default logging causes RPC to malfunction when the GUI runs on
# pythonw. Explicitly disable logging for the XML server.
server = SimpleXMLRPCServer(server_address, logRequests=False, allow_none=True)
app = PySide2.QtWidgets.QApplication(sys.argv)
app = QtWidgets.QApplication(sys.argv)
window = Homunculus(server, sys.argv)
window.show()
exit_code = app.exec_()
exit_code = qapp_exec(app)
server.shutdown()
sys.exit(exit_code)

Expand Down
53 changes: 30 additions & 23 deletions castervoice/asynch/hmc/homunculus.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,38 @@

import dragonfly

# TODO: Remove this try wrapper when CI server supports Qt
try:
import PySide2.QtCore
from PySide2.QtWidgets import QApplication
from PySide2.QtWidgets import QCheckBox
from PySide2.QtWidgets import QDialog
from PySide2.QtWidgets import QFileDialog
from PySide2.QtWidgets import QFormLayout
from PySide2.QtWidgets import QLabel
from PySide2.QtWidgets import QLineEdit
from PySide2.QtWidgets import QScrollArea
from PySide2.QtWidgets import QTextEdit
from PySide2.QtWidgets import QVBoxLayout
from PySide2.QtWidgets import QWidget
except ImportError:
sys.exit(0)

try: # Style C -- may be imported into Caster, or externally
BASE_PATH = os.path.realpath(__file__).rsplit(os.path.sep + "castervoice", 1)[0]
if BASE_PATH not in sys.path:
sys.path.append(BASE_PATH)
finally:
from castervoice.lib import settings

RPC_DIR_EVENT = PySide2.QtCore.QEvent.Type(PySide2.QtCore.QEvent.registerEventType(-1))
try:
from castervoice.lib.qt import QtCore, QtWidgets, qt_attr
except ImportError:
sys.exit(0)

QApplication = QtWidgets.QApplication
QCheckBox = QtWidgets.QCheckBox
QDialog = QtWidgets.QDialog
QFileDialog = QtWidgets.QFileDialog
QFormLayout = QtWidgets.QFormLayout
QLabel = QtWidgets.QLabel
QLineEdit = QtWidgets.QLineEdit
QScrollArea = QtWidgets.QScrollArea
QTextEdit = QtWidgets.QTextEdit
QVBoxLayout = QtWidgets.QVBoxLayout
QWidget = QtWidgets.QWidget

ALIGN_CENTER = qt_attr(QtCore, ("Qt", "AlignCenter"), ("Qt", "AlignmentFlag", "AlignCenter"))
SHOW_DIRS_ONLY = qt_attr(
QtWidgets,
("QFileDialog", "ShowDirsOnly"),
("QFileDialog", "Option", "ShowDirsOnly"),
)

RPC_DIR_EVENT = QtCore.QEvent.Type(QtCore.QEvent.registerEventType(-1))


class Homunculus(QDialog):
Expand Down Expand Up @@ -64,7 +71,7 @@ def setup_base_window(self, data=None):
self.setWindowTitle(settings.HOMUNCULUS_VERSION)
self.data = data.split("|") if data else [0, 0]
label = QLabel(" ".join(self.data[0].split(settings.HMC_SEPARATOR))) if data else QLabel("Enter response then say 'complete'") # pylint: disable=no-member
label.setAlignment(PySide2.QtCore.Qt.AlignCenter)
label.setAlignment(ALIGN_CENTER)
self.ext_box = QTextEdit()
self.mainLayout.addWidget(label)
self.mainLayout.addWidget(self.ext_box)
Expand Down Expand Up @@ -98,7 +105,7 @@ def setup_recording_window(self, history):
self.setGeometry(x, y, 640, 480)
self.setWindowTitle(settings.HOMUNCULUS_VERSION + settings.HMC_TITLE_RECORDING)
label = QLabel("Macro Recording Options")
label.setAlignment(PySide2.QtCore.Qt.AlignCenter)
label.setAlignment(ALIGN_CENTER)
self.mainLayout.addWidget(label)
label = QLabel("Command Words:")
self.word_box = QLineEdit()
Expand All @@ -108,7 +115,7 @@ def setup_recording_window(self, history):
self.repeatable = QCheckBox("Make Repeatable")
self.mainLayout.addWidget(self.repeatable)
label = QLabel("Dictation History")
label.setAlignment(PySide2.QtCore.Qt.AlignCenter)
label.setAlignment(ALIGN_CENTER)
self.mainLayout.addWidget(label)
self.word_state = []
cb_number = 1
Expand Down Expand Up @@ -144,7 +151,7 @@ def check_range_of_boxes(self, details):
self.word_state[i].setChecked(True)

def ask_directory(self):
result = QFileDialog.getExistingDirectory(self, "Please select directory", os.environ["HOME"], QFileDialog.ShowDirsOnly)
result = QFileDialog.getExistingDirectory(self, "Please select directory", os.environ["HOME"], SHOW_DIRS_ONLY)
self.word_box.setText(result)

def event(self, event):
Expand Down Expand Up @@ -214,7 +221,7 @@ def xmlrpc_get_message_confirm(self):

def xmlrpc_do_action_directory(self, action, details=None):
if action == "dir":
PySide2.QtCore.QCoreApplication.postEvent(self, PySide2.QtCore.QEvent(RPC_DIR_EVENT))
QtCore.QCoreApplication.postEvent(self, QtCore.QEvent(RPC_DIR_EVENT))

def xmlrpc_get_message_directory(self):
response = None
Expand Down
81 changes: 46 additions & 35 deletions castervoice/asynch/hud.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,36 +9,47 @@
import signal
import sys
import threading
import PySide2.QtCore
import PySide2.QtGui
import dragonfly
from xmlrpc.server import SimpleXMLRPCServer
from PySide2.QtWidgets import QApplication
from PySide2.QtWidgets import QMainWindow
from PySide2.QtWidgets import QTextEdit
from PySide2.QtWidgets import QTreeView
from PySide2.QtWidgets import QVBoxLayout
from PySide2.QtWidgets import QWidget
try: # Style C -- may be imported into Caster, or externally
BASE_PATH = os.path.realpath(__file__).rsplit(os.path.sep + "castervoice", 1)[0]
if BASE_PATH not in sys.path:
sys.path.append(BASE_PATH)
finally:
from castervoice.lib.merge.communication import Communicator
from castervoice.lib import settings

CLEAR_HUD_EVENT = PySide2.QtCore.QEvent.Type(PySide2.QtCore.QEvent.registerEventType(-1))
HIDE_HUD_EVENT = PySide2.QtCore.QEvent.Type(PySide2.QtCore.QEvent.registerEventType(-1))
SHOW_HUD_EVENT = PySide2.QtCore.QEvent.Type(PySide2.QtCore.QEvent.registerEventType(-1))
HIDE_RULES_EVENT = PySide2.QtCore.QEvent.Type(PySide2.QtCore.QEvent.registerEventType(-1))
SHOW_RULES_EVENT = PySide2.QtCore.QEvent.Type(PySide2.QtCore.QEvent.registerEventType(-1))
SEND_COMMAND_EVENT = PySide2.QtCore.QEvent.Type(PySide2.QtCore.QEvent.registerEventType(-1))


class RPCEvent(PySide2.QtCore.QEvent):
from castervoice.lib.qt import QtCore, QtGui, QtWidgets, qt_attr, qapp_exec

QApplication = QtWidgets.QApplication
QMainWindow = QtWidgets.QMainWindow
QTextEdit = QtWidgets.QTextEdit
QTreeView = QtWidgets.QTreeView
QVBoxLayout = QtWidgets.QVBoxLayout
QWidget = QtWidgets.QWidget

WINDOW_STAYS_ON_TOP_HINT = qt_attr(
QtCore,
("Qt", "WindowStaysOnTopHint"),
("Qt", "WindowType", "WindowStaysOnTopHint"),
)
TEXT_CURSOR_END = qt_attr(
QtGui,
("QTextCursor", "End"),
("QTextCursor", "MoveOperation", "End"),
)

CLEAR_HUD_EVENT = QtCore.QEvent.Type(QtCore.QEvent.registerEventType(-1))
HIDE_HUD_EVENT = QtCore.QEvent.Type(QtCore.QEvent.registerEventType(-1))
SHOW_HUD_EVENT = QtCore.QEvent.Type(QtCore.QEvent.registerEventType(-1))
HIDE_RULES_EVENT = QtCore.QEvent.Type(QtCore.QEvent.registerEventType(-1))
SHOW_RULES_EVENT = QtCore.QEvent.Type(QtCore.QEvent.registerEventType(-1))
SEND_COMMAND_EVENT = QtCore.QEvent.Type(QtCore.QEvent.registerEventType(-1))


class RPCEvent(QtCore.QEvent):

def __init__(self, type, text):
PySide2.QtCore.QEvent.__init__(self, type)
QtCore.QEvent.__init__(self, type)
self._text = text

@property
Expand All @@ -52,29 +63,29 @@ class RulesWindow(QWidget):
_MARGIN = 30

def __init__(self, text):
QWidget.__init__(self, f=(PySide2.QtCore.Qt.WindowStaysOnTopHint))
QWidget.__init__(self, f=WINDOW_STAYS_ON_TOP_HINT)
x = dragonfly.monitors[0].rectangle.dx - (RulesWindow._WIDTH + RulesWindow._MARGIN)
y = 300
dx = RulesWindow._WIDTH
dy = dragonfly.monitors[0].rectangle.dy - (y + 2 * RulesWindow._MARGIN)
self.setGeometry(x, y, dx, dy)
self.setWindowTitle("Active Rules")
rules_tree = PySide2.QtGui.QStandardItemModel()
rules_tree = QtGui.QStandardItemModel()
rules_tree.setColumnCount(2)
rules_tree.setHorizontalHeaderLabels(['phrase', 'action'])
rules_dict = json.loads(text)
rules = rules_tree.invisibleRootItem()
for g in rules_dict:
gram = PySide2.QtGui.QStandardItem(g["name"]) if len(g["rules"]) > 1 else None
gram = QtGui.QStandardItem(g["name"]) if len(g["rules"]) > 1 else None
for r in g["rules"]:
rule = PySide2.QtGui.QStandardItem(r["name"])
rule = QtGui.QStandardItem(r["name"])
rule.setRowCount(len(r["specs"]))
rule.setColumnCount(2)
row = 0
for s in r["specs"]:
phrase, _, action = s.partition('::')
rule.setChild(row, 0, PySide2.QtGui.QStandardItem(phrase))
rule.setChild(row, 1, PySide2.QtGui.QStandardItem(action))
rule.setChild(row, 0, QtGui.QStandardItem(phrase))
rule.setChild(row, 1, QtGui.QStandardItem(action))
row += 1
if gram is None:
rules.appendRow(rule)
Expand All @@ -84,7 +95,7 @@ def __init__(self, text):
rules.appendRow(gram)
tree_view = QTreeView(self)
tree_view.setModel(rules_tree)
tree_view.setColumnWidth(0, RulesWindow._WIDTH / 2)
tree_view.setColumnWidth(0, RulesWindow._WIDTH // 2)
layout = QVBoxLayout()
layout.addWidget(tree_view)
self.setLayout(layout)
Expand All @@ -97,7 +108,7 @@ class HUDWindow(QMainWindow):
_MARGIN = 30

def __init__(self, server):
QMainWindow.__init__(self, flags=(PySide2.QtCore.Qt.WindowStaysOnTopHint))
QMainWindow.__init__(self, flags=WINDOW_STAYS_ON_TOP_HINT)
x = dragonfly.monitors[0].rectangle.dx - (HUDWindow._WIDTH + HUDWindow._MARGIN)
y = HUDWindow._MARGIN
dx = HUDWindow._WIDTH
Expand Down Expand Up @@ -137,7 +148,7 @@ def event(self, event):
# self.output.append('<br>')
self.output.append(formatted_text)
cursor = self.output.textCursor()
cursor.movePosition(PySide2.QtGui.QTextCursor.End)
cursor.movePosition(TEXT_CURSOR_END)
self.output.setTextCursor(cursor)
self.output.ensureCursorVisible()
self.commands_count += 1
Expand Down Expand Up @@ -176,33 +187,33 @@ def setup_xmlrpc_server(self):


def xmlrpc_clear(self):
PySide2.QtCore.QCoreApplication.postEvent(self, PySide2.QtCore.QEvent(CLEAR_HUD_EVENT))
QtCore.QCoreApplication.postEvent(self, QtCore.QEvent(CLEAR_HUD_EVENT))
return 0

def xmlrpc_ping(self):
return 0

def xmlrpc_hide_hud(self):
PySide2.QtCore.QCoreApplication.postEvent(self, PySide2.QtCore.QEvent(HIDE_HUD_EVENT))
QtCore.QCoreApplication.postEvent(self, QtCore.QEvent(HIDE_HUD_EVENT))
return 0

def xmlrpc_show_hud(self):
PySide2.QtCore.QCoreApplication.postEvent(self, PySide2.QtCore.QEvent(SHOW_HUD_EVENT))
QtCore.QCoreApplication.postEvent(self, QtCore.QEvent(SHOW_HUD_EVENT))
return 0

def xmlrpc_hide_rules(self):
PySide2.QtCore.QCoreApplication.postEvent(self, PySide2.QtCore.QEvent(HIDE_RULES_EVENT))
QtCore.QCoreApplication.postEvent(self, QtCore.QEvent(HIDE_RULES_EVENT))
return 0

def xmlrpc_kill(self):
QApplication.quit()

def xmlrpc_send(self, text):
PySide2.QtCore.QCoreApplication.postEvent(self, RPCEvent(SEND_COMMAND_EVENT, text))
QtCore.QCoreApplication.postEvent(self, RPCEvent(SEND_COMMAND_EVENT, text))
return len(text)

def xmlrpc_show_rules(self, text):
PySide2.QtCore.QCoreApplication.postEvent(self, RPCEvent(SHOW_RULES_EVENT, text))
QtCore.QCoreApplication.postEvent(self, RPCEvent(SHOW_RULES_EVENT, text))
return len(text)


Expand All @@ -225,6 +236,6 @@ def handler(signum, frame):
app = QApplication(sys.argv)
window = HUDWindow(server)
window.show()
exit_code = app.exec_()
exit_code = qapp_exec(app)
server.shutdown()
sys.exit(exit_code)
Loading