Cleanup
[carla.git] / source / frontend / carla_host_control.py
blob7defa84351ef35744544e98f9a41f0675aa115b7
1 #!/usr/bin/env python3
2 # SPDX-FileCopyrightText: 2011-2024 Filipe Coelho <falktx@falktx.com>
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 # ----------------------------------------------------------------------------------------------------------------------
6 # Imports (Global)
8 from qt_compat import qt_config
10 if qt_config == 5:
11 from PyQt5.QtCore import QEventLoop
12 elif qt_config == 6:
13 from PyQt6.QtCore import QEventLoop
15 # ------------------------------------------------------------------------------------------------------------
16 # Imports (Custom)
18 import ui_carla_osc_connect
20 from carla_backend_qt import CarlaHostQtPlugin
21 from carla_host import *
23 # ------------------------------------------------------------------------------------------------------------
24 # Imports (liblo)
26 from liblo import (
27 Address,
28 AddressError,
29 ServerError,
30 Server,
31 make_method,
32 send as lo_send,
33 TCP as LO_TCP,
34 UDP as LO_UDP,
37 from random import random
39 # ------------------------------------------------------------------------------------------------------------
41 DEBUG = False
43 # ----------------------------------------------------------------------------------------------------------------------
44 # OSC connect Dialog
46 class ConnectDialog(QDialog):
47 def __init__(self, parent):
48 QDialog.__init__(self, parent)
49 self.ui = ui_carla_osc_connect.Ui_Dialog()
50 self.ui.setupUi(self)
52 self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint)
54 # -------------------------------------------------------------------------------------------------------------
55 # Load settings
57 self.loadSettings()
59 # -------------------------------------------------------------------------------------------------------------
60 # Set-up connections
62 self.finished.connect(self.slot_saveSettings)
63 self.ui.le_host.textChanged.connect(self.slot_hostChanged)
65 # -----------------------------------------------------------------------------------------------------------------
67 def getResult(self):
68 return (self.ui.le_host.text(),
69 self.ui.sb_tcp_port.value(),
70 self.ui.sb_udp_port.value())
72 def checkIfButtonBoxShouldBeEnabled(self, host):
73 enabled = len(host) > 0
74 self.ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled(enabled)
76 def loadSettings(self):
77 settings = QSafeSettings("falkTX", "CarlaOSCConnect")
79 self.ui.le_host.setText(settings.value("Host", "127.0.0.1", str))
80 self.ui.sb_tcp_port.setValue(settings.value("TCPPort", CARLA_DEFAULT_OSC_TCP_PORT_NUMBER, int))
81 self.ui.sb_udp_port.setValue(settings.value("UDPPort", CARLA_DEFAULT_OSC_UDP_PORT_NUMBER, int))
83 self.checkIfButtonBoxShouldBeEnabled(self.ui.le_host.text())
85 # ------------------------------------------------------------------------------------------------------------------
87 @pyqtSlot(str)
88 def slot_hostChanged(self, text):
89 self.checkIfButtonBoxShouldBeEnabled(text)
91 @pyqtSlot()
92 def slot_saveSettings(self):
93 settings = QSafeSettings("falkTX", "CarlaOSCConnect")
94 settings.setValue("Host", self.ui.le_host.text())
95 settings.setValue("TCPPort", self.ui.sb_tcp_port.value())
96 settings.setValue("UDPPort", self.ui.sb_udp_port.value())
98 # ------------------------------------------------------------------------------------------------------------------
100 def done(self, r):
101 QDialog.done(self, r)
102 self.close()
104 # ------------------------------------------------------------------------------------------------------------
105 # Host OSC object
107 class CarlaHostOSC(CarlaHostQtPlugin):
108 def __init__(self):
109 CarlaHostQtPlugin.__init__(self)
111 self.lo_server_tcp = None
112 self.lo_server_udp = None
113 self.lo_target_tcp = None
114 self.lo_target_udp = None
115 self.lo_target_tcp_name = ""
116 self.lo_target_udp_name = ""
118 self.resetPendingMessages()
120 # -------------------------------------------------------------------
122 def resetPendingMessages(self):
123 self.lastMessageId = 1
124 self.pendingMessages = []
125 self.responses = {}
127 def printAndReturnError(self, error):
128 print(error)
129 self.fLastError = error
130 return False
132 def sendMsg(self, lines):
133 if len(lines) < 1:
134 return self.printAndReturnError("not enough arguments")
136 method = lines.pop(0)
138 if method == "set_engine_option":
139 return True
141 if self.lo_target_tcp is None:
142 return self.printAndReturnError("lo_target_tcp is None")
143 if self.lo_target_tcp_name is None:
144 return self.printAndReturnError("lo_target_tcp_name is None")
146 if method in ("clear_engine_xruns",
147 "cancel_engine_action",
148 #"load_file",
149 #"load_project",
150 #"save_project",
151 #"clear_project_filename",
152 "patchbay_connect",
153 "patchbay_disconnect",
154 "patchbay_set_group_pos",
155 "patchbay_refresh",
156 "transport_play",
157 "transport_pause",
158 "transport_bpm",
159 "transport_relocate",
160 "add_plugin",
161 "remove_plugin",
162 "remove_all_plugins",
163 "rename_plugin",
164 "clone_plugin",
165 "replace_plugin",
166 "switch_plugins",
167 #"load_plugin_state",
168 #"save_plugin_state",
170 path = "/ctrl/" + method
171 needResp = True
173 elif method in (#"set_option",
174 "set_active",
175 "set_drywet",
176 "set_volume",
177 "set_balance_left",
178 "set_balance_right",
179 "set_panning",
180 #"set_ctrl_channel",
181 "set_parameter_value",
182 "set_parameter_midi_channel",
183 "set_parameter_midi_cc",
184 "set_program",
185 "set_midi_program",
186 #"set_custom_data",
187 #"set_chunk_data",
188 #"prepare_for_save",
189 #"reset_parameters",
190 #"randomize_parameters",
192 pluginId = lines.pop(0)
193 needResp = False
194 path = "/%s/%i/%s" % (self.lo_target_tcp_name, pluginId, method)
196 elif method == "send_midi_note":
197 pluginId = lines.pop(0)
198 needResp = False
199 channel, note, velocity = lines
201 if velocity:
202 path = "/%s/%i/note_on" % (self.lo_target_tcp_name, pluginId)
203 else:
204 path = "/%s/%i/note_off" % (self.lo_target_tcp_name, pluginId)
205 lines.pop(2)
207 else:
208 return self.printAndReturnError("invalid method '%s'" % method)
210 if len(self.pendingMessages) != 0:
211 return self.printAndReturnError("A previous operation is still pending, please wait")
213 args = [int(line) if isinstance(line, bool) else line for line in lines]
214 #print(path, args)
216 if not needResp:
217 lo_send(self.lo_target_tcp, path, *args)
218 return True
220 messageId = self.lastMessageId
221 self.lastMessageId += 1
222 self.pendingMessages.append(messageId)
224 lo_send(self.lo_target_tcp, path, messageId, *args)
226 while messageId in self.pendingMessages:
227 QApplication.processEvents(QEventLoop.AllEvents, 100)
229 error = self.responses.pop(messageId)
231 if not error:
232 return True
234 self.fLastError = error
235 return False
237 def sendMsgAndSetError(self, lines):
238 return self.sendMsg(lines)
240 # -------------------------------------------------------------------
242 def engine_init(self, driverName, clientName):
243 return self.lo_target_tcp is not None
245 def engine_close(self):
246 return True
248 def engine_idle(self):
249 return
251 def is_engine_running(self):
252 return self.lo_target_tcp is not None
254 def set_engine_about_to_close(self):
255 return
257 # ---------------------------------------------------------------------------------------------------------------------
258 # OSC Control server
260 class CarlaControlServerTCP(Server):
261 def __init__(self, host):
262 Server.__init__(self, proto=LO_TCP)
264 if False:
265 host = CarlaHostOSC()
267 self.host = host
269 def idle(self):
270 self.fReceivedMsgs = False
272 while self.recv(0) and self.fReceivedMsgs:
273 pass
275 def getFullURL(self):
276 return "%sctrl" % self.get_url()
278 @make_method('/ctrl/cb', 'iiiiifs')
279 def carla_cb(self, path, args):
280 if DEBUG: print(path, args)
281 self.fReceivedMsgs = True
282 action, pluginId, value1, value2, value3, valuef, valueStr = args
283 self.host._setViaCallback(action, pluginId, value1, value2, value3, valuef, valueStr)
284 engineCallback(self.host, action, pluginId, value1, value2, value3, valuef, valueStr)
286 @make_method('/ctrl/info', 'iiiihiisssssss')
287 def carla_info(self, path, args):
288 if DEBUG: print(path, args)
289 self.fReceivedMsgs = True
291 pluginId, type_, category, hints, uniqueId, optsAvail, optsEnabled,
292 name, filename, iconName, realName, label, maker, copyright,
293 ) = args
295 hints &= ~PLUGIN_HAS_CUSTOM_UI
297 pinfo = {
298 'type': type_,
299 'category': category,
300 'hints': hints,
301 'optionsAvailable': optsAvail,
302 'optionsEnabled': optsEnabled,
303 'uniqueId': uniqueId,
304 'filename': filename,
305 'name': name,
306 'label': label,
307 'maker': maker,
308 'copyright': copyright,
309 'iconName': iconName
312 self.host._set_pluginInfoUpdate(pluginId, pinfo)
313 self.host._set_pluginRealName(pluginId, realName)
315 @make_method('/ctrl/ports', 'iiiiiiii')
316 def carla_ports(self, path, args):
317 if DEBUG: print(path, args)
318 self.fReceivedMsgs = True
319 pluginId, audioIns, audioOuts, midiIns, midiOuts, paramIns, paramOuts, paramTotal = args
320 self.host._set_audioCountInfo(pluginId, {'ins': audioIns, 'outs': audioOuts})
321 self.host._set_midiCountInfo(pluginId, {'ins': midiOuts, 'outs': midiOuts})
322 self.host._set_parameterCountInfo(pluginId, paramTotal, {'ins': paramIns, 'outs': paramOuts})
324 @make_method('/ctrl/paramInfo', 'iissss')
325 def carla_paramInfo(self, path, args):
326 if DEBUG: print(path, args)
327 self.fReceivedMsgs = True
328 pluginId, paramId, name, unit, comment, groupName = args
330 paramInfo = {
331 'name': name,
332 'symbol': "",
333 'unit': unit,
334 'comment': comment,
335 'groupName': groupName,
336 'scalePointCount': 0,
337 'scalePoints': [],
339 self.host._set_parameterInfo(pluginId, paramId, paramInfo)
341 @make_method('/ctrl/paramData', 'iiiiiifff')
342 def carla_paramData(self, path, args):
343 if DEBUG: print(path, args)
344 self.fReceivedMsgs = True
345 pluginId, paramId, type_, hints, midiChan, mappedCtrl, mappedMin, mappedMax, value = args
347 hints &= ~(PARAMETER_USES_SCALEPOINTS | PARAMETER_USES_CUSTOM_TEXT)
349 paramData = {
350 'type': type_,
351 'hints': hints,
352 'index': paramId,
353 'rindex': -1,
354 'midiChannel': midiChan,
355 'mappedControlIndex': mappedCtrl,
356 'mappedMinimum': mappedMin,
357 'mappedMaximum': mappedMax,
359 self.host._set_parameterData(pluginId, paramId, paramData)
360 self.host._set_parameterValue(pluginId, paramId, value)
362 @make_method('/ctrl/paramRanges', 'iiffffff')
363 def carla_paramRanges(self, path, args):
364 if DEBUG: print(path, args)
365 self.fReceivedMsgs = True
366 pluginId, paramId, def_, min_, max_, step, stepSmall, stepLarge = args
368 paramRanges = {
369 'def': def_,
370 'min': min_,
371 'max': max_,
372 'step': step,
373 'stepSmall': stepSmall,
374 'stepLarge': stepLarge,
376 self.host._set_parameterRanges(pluginId, paramId, paramRanges)
378 @make_method('/ctrl/count', 'iiiiii')
379 def carla_count(self, path, args):
380 if DEBUG: print(path, args)
381 self.fReceivedMsgs = True
382 pluginId, pcount, mpcount, cdcount, cp, cmp = args
383 self.host._set_programCount(pluginId, pcount)
384 self.host._set_midiProgramCount(pluginId, mpcount)
385 self.host._set_customDataCount(pluginId, cdcount)
386 self.host._set_pluginInfoUpdate(pluginId, { 'programCurrent': cp, 'midiProgramCurrent': cmp })
388 @make_method('/ctrl/pcount', 'iii')
389 def carla_pcount(self, path, args):
390 if DEBUG: print(path, args)
391 self.fReceivedMsgs = True
392 pluginId, pcount, mpcount = args
393 self.host._set_programCount(pluginId, pcount)
394 self.host._set_midiProgramCount(pluginId, mpcount)
396 @make_method('/ctrl/prog', 'iis')
397 def carla_prog(self, path, args):
398 if DEBUG: print(path, args)
399 self.fReceivedMsgs = True
400 pluginId, progId, progName = args
401 self.host._set_programName(pluginId, progId, progName)
403 @make_method('/ctrl/mprog', 'iiiis')
404 def carla_mprog(self, path, args):
405 if DEBUG: print(path, args)
406 self.fReceivedMsgs = True
407 pluginId, midiProgId, bank, program, name = args
408 self.host._set_midiProgramData(pluginId, midiProgId, {'bank': bank, 'program': program, 'name': name})
410 @make_method('/ctrl/cdata', 'iisss')
411 def carla_cdata(self, path, args):
412 if DEBUG: print(path, args)
413 self.fReceivedMsgs = True
414 pluginId, index, type_, key, value = args
415 self.host._set_customData(pluginId, index, { 'type': type_, 'key': key, 'value': value })
417 @make_method('/ctrl/iparams', 'ifffffff')
418 def carla_iparams(self, path, args):
419 if DEBUG: print(path, args)
420 self.fReceivedMsgs = True
421 pluginId, active, drywet, volume, balLeft, balRight, pan, ctrlChan = args
422 self.host._set_internalValue(pluginId, PARAMETER_ACTIVE, active)
423 self.host._set_internalValue(pluginId, PARAMETER_DRYWET, drywet)
424 self.host._set_internalValue(pluginId, PARAMETER_VOLUME, volume)
425 self.host._set_internalValue(pluginId, PARAMETER_BALANCE_LEFT, balLeft)
426 self.host._set_internalValue(pluginId, PARAMETER_BALANCE_RIGHT, balRight)
427 self.host._set_internalValue(pluginId, PARAMETER_PANNING, pan)
428 self.host._set_internalValue(pluginId, PARAMETER_CTRL_CHANNEL, ctrlChan)
430 @make_method('/ctrl/resp', 'is')
431 def carla_resp(self, path, args):
432 if DEBUG: print(path, args)
433 self.fReceivedMsgs = True
434 messageId, error = args
435 self.host.responses[messageId] = error
436 self.host.pendingMessages.remove(messageId)
438 @make_method('/ctrl/exit', '')
439 def carla_exit(self, path, args):
440 if DEBUG: print(path, args)
441 self.fReceivedMsgs = True
442 #self.host.lo_target_tcp = None
443 self.host.QuitCallback.emit()
445 @make_method('/ctrl/exit-error', 's')
446 def carla_exit_error(self, path, args):
447 if DEBUG: print(path, args)
448 self.fReceivedMsgs = True
449 error, = args
450 self.host.lo_target_tcp = None
451 self.host.QuitCallback.emit()
452 self.host.ErrorCallback.emit(error)
454 @make_method(None, None)
455 def fallback(self, path, args):
456 print("ControlServerTCP::fallback(\"%s\") - unknown message, args =" % path, args)
457 self.fReceivedMsgs = True
459 # ---------------------------------------------------------------------------------------------------------------------
461 class CarlaControlServerUDP(Server):
462 def __init__(self, host):
463 Server.__init__(self, proto=LO_UDP)
465 if False:
466 host = CarlaHostOSC()
468 self.host = host
470 def idle(self):
471 self.fReceivedMsgs = False
473 while self.recv(0) and self.fReceivedMsgs:
474 pass
476 def getFullURL(self):
477 return "%sctrl" % self.get_url()
479 @make_method('/ctrl/runtime', 'fiihiiif')
480 def carla_runtime(self, path, args):
481 self.fReceivedMsgs = True
482 load, xruns, playing, frame, bar, beat, tick, bpm = args
483 self.host._set_runtime_info(load, xruns)
484 self.host._set_transport(bool(playing), frame, bar, beat, tick, bpm)
486 @make_method('/ctrl/param', 'iif')
487 def carla_param_fixme(self, path, args):
488 self.fReceivedMsgs = True
489 pluginId, paramId, paramValue = args
490 self.host._set_parameterValue(pluginId, paramId, paramValue)
492 @make_method('/ctrl/peaks', 'iffff')
493 def carla_peaks(self, path, args):
494 self.fReceivedMsgs = True
495 pluginId, in1, in2, out1, out2 = args
496 self.host._set_peaks(pluginId, in1, in2, out1, out2)
498 @make_method(None, None)
499 def fallback(self, path, args):
500 print("ControlServerUDP::fallback(\"%s\") - unknown message, args =" % path, args)
501 self.fReceivedMsgs = True
503 # ---------------------------------------------------------------------------------------------------------------------
504 # Main Window
506 class HostWindowOSC(HostWindow):
507 def __init__(self, host, oscAddr = None):
508 self.fCustomOscAddress = oscAddr
509 HostWindow.__init__(self, host, True)
510 self.host = host
512 if False:
513 # kdevelop likes this :)
514 host = CarlaHostOSC()
515 self.host = host
517 # ----------------------------------------------------------------------------------------------------
518 # Connect actions to functions
520 self.ui.act_file_connect.triggered.connect(self.slot_fileConnect)
521 self.ui.act_file_refresh.triggered.connect(self.slot_fileRefresh)
523 # ----------------------------------------------------------------------------------------------------
524 # Final setup
526 if oscAddr:
527 QTimer.singleShot(0, self.connectOsc)
529 def connectOsc(self, addrTCP = None, addrUDP = None):
530 if self.fCustomOscAddress is not None:
531 addrTCP = self.fCustomOscAddress.replace("osc.udp://","osc.tcp://")
532 addrUDP = self.fCustomOscAddress.replace("osc.tcp://","osc.udp://")
534 else:
535 if addrTCP is not None:
536 self.fOscAddressTCP = addrTCP
537 if addrUDP is not None:
538 self.fOscAddressUDP = addrUDP
540 lo_target_tcp_name = addrTCP.rsplit("/", 1)[-1]
541 lo_target_udp_name = addrUDP.rsplit("/", 1)[-1]
543 err = None
545 try:
546 lo_target_tcp = Address(addrTCP)
547 lo_server_tcp = CarlaControlServerTCP(self.host)
548 lo_send(lo_target_tcp, "/register", lo_server_tcp.getFullURL())
550 lo_target_udp = Address(addrUDP)
551 lo_server_udp = CarlaControlServerUDP(self.host)
552 lo_send(lo_target_udp, "/register", lo_server_udp.getFullURL())
554 except AddressError as e:
555 err = e
556 except OSError as e:
557 err = e
558 except:
559 err = Exception()
561 if err is not None:
562 fullError = self.tr("Failed to connect to the Carla instance.")
564 if len(err.args) > 0:
565 fullError += " %s\n%s\n" % (self.tr("Error was:"), err.args[0])
567 fullError += "\n"
568 fullError += self.tr("Make sure the remote Carla is running and the URL and Port are correct.") + "\n"
569 fullError += self.tr("If it still does not work, check your current device and the remote's firewall.")
571 CustomMessageBox(self,
572 QMessageBox.Warning,
573 self.tr("Error"),
574 self.tr("Connection failed"),
575 fullError,
576 QMessageBox.Ok,
577 QMessageBox.Ok)
578 return
580 self.host.lo_server_tcp = lo_server_tcp
581 self.host.lo_target_tcp = lo_target_tcp
582 self.host.lo_target_tcp_name = lo_target_tcp_name
584 self.host.lo_server_udp = lo_server_udp
585 self.host.lo_target_udp = lo_target_udp
586 self.host.lo_target_udp_name = lo_target_udp_name
588 self.ui.act_file_refresh.setEnabled(True)
590 self.startTimers()
592 def disconnectOsc(self):
593 self.killTimers()
594 self.unregister()
595 self.removeAllPlugins()
596 patchcanvas.clear()
598 self.ui.act_file_refresh.setEnabled(False)
600 # --------------------------------------------------------------------------------------------------------
602 def unregister(self):
603 if self.host.lo_server_tcp is not None:
604 if self.host.lo_target_tcp is not None:
605 try:
606 lo_send(self.host.lo_target_tcp, "/unregister", self.host.lo_server_tcp.getFullURL())
607 except:
608 pass
609 self.host.lo_target_tcp = None
611 while self.host.lo_server_tcp.recv(0):
612 pass
613 #self.host.lo_server_tcp.free()
614 self.host.lo_server_tcp = None
616 if self.host.lo_server_udp is not None:
617 if self.host.lo_target_udp is not None:
618 try:
619 lo_send(self.host.lo_target_udp, "/unregister", self.host.lo_server_udp.getFullURL())
620 except:
621 pass
622 self.host.lo_target_udp = None
624 while self.host.lo_server_udp.recv(0):
625 pass
626 #self.host.lo_server_udp.free()
627 self.host.lo_server_udp = None
629 self.host.lo_target_tcp_name = ""
630 self.host.lo_target_udp_name = ""
632 # --------------------------------------------------------------------------------------------------------
633 # Timers
635 def idleFast(self):
636 HostWindow.idleFast(self)
638 if self.host.lo_server_tcp is not None:
639 self.host.lo_server_tcp.idle()
640 else:
641 self.disconnectOsc()
643 if self.host.lo_server_udp is not None:
644 self.host.lo_server_udp.idle()
645 else:
646 self.disconnectOsc()
648 # --------------------------------------------------------------------------------------------------------
650 def removeAllPlugins(self):
651 self.host.fPluginsInfo = {}
652 HostWindow.removeAllPlugins(self)
654 # --------------------------------------------------------------------------------------------------------
656 def loadSettings(self, firstTime):
657 settings = HostWindow.loadSettings(self, firstTime)
658 if self.fCustomOscAddress is not None:
659 self.fOscAddressTCP = settings.value("RemoteAddressTCP", "osc.tcp://127.0.0.1:22752/Carla", str)
660 self.fOscAddressUDP = settings.value("RemoteAddressUDP", "osc.udp://127.0.0.1:22752/Carla", str)
662 def saveSettings(self):
663 settings = HostWindow.saveSettings(self)
664 if self.fOscAddressTCP:
665 settings.setValue("RemoteAddressTCP", self.fOscAddressTCP)
666 if self.fOscAddressUDP:
667 settings.setValue("RemoteAddressUDP", self.fOscAddressUDP)
669 # --------------------------------------------------------------------------------------------------------
671 @pyqtSlot()
672 def slot_fileConnect(self):
673 dialog = ConnectDialog(self)
675 if not dialog.exec_():
676 return
678 host, tcpPort, udpPort = dialog.getResult()
680 self.disconnectOsc()
681 self.connectOsc("osc.tcp://%s:%i/Carla" % (host, tcpPort),
682 "osc.udp://%s:%i/Carla" % (host, udpPort))
684 @pyqtSlot()
685 def slot_fileRefresh(self):
686 if None in (self.host.lo_server_tcp, self.host.lo_server_udp, self.host.lo_target_tcp, self.host.lo_target_udp):
687 return
689 lo_send(self.host.lo_target_udp, "/unregister", self.host.lo_server_udp.getFullURL())
690 while self.host.lo_server_udp.recv(0):
691 pass
692 #self.host.lo_server_udp.free()
694 lo_send(self.host.lo_target_tcp, "/unregister", self.host.lo_server_tcp.getFullURL())
695 while self.host.lo_server_tcp.recv(0):
696 pass
697 #self.host.lo_server_tcp.free()
699 self.removeAllPlugins()
700 patchcanvas.clear()
702 self.host.lo_server_tcp = CarlaControlServerTCP(self.host)
703 self.host.lo_server_udp = CarlaControlServerUDP(self.host)
705 try:
706 lo_send(self.host.lo_target_tcp, "/register", self.host.lo_server_tcp.getFullURL())
707 except:
708 self.disconnectOsc()
709 return
711 try:
712 lo_send(self.host.lo_target_udp, "/register", self.host.lo_server_udp.getFullURL())
713 except:
714 self.disconnectOsc()
715 return
717 # --------------------------------------------------------------------------------------------------------
719 @pyqtSlot()
720 def slot_handleSIGTERM(self):
721 print("Got SIGTERM -> Closing now")
722 self.host.pendingMessages = []
723 self.close()
725 @pyqtSlot()
726 def slot_handleQuitCallback(self):
727 self.disconnectOsc()
728 HostWindow.slot_handleQuitCallback(self)
730 # --------------------------------------------------------------------------------------------------------
732 def closeEvent(self, event):
733 self.killTimers()
734 self.unregister()
736 HostWindow.closeEvent(self, event)
738 # ------------------------------------------------------------------------------------------------------------