Collection of tools useful for audio production
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1448 lines
54KB

  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # JACK Patchbay
  4. # Copyright (C) 2010-2018 Filipe Coelho <falktx@falktx.com>
  5. #
  6. # This program is free software; you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation; either version 2 of the License, or
  9. # any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # For a full copy of the GNU General Public License see the COPYING file
  17. # ------------------------------------------------------------------------------------------------------------
  18. # Imports (Custom Stuff)
  19. import ui_catia
  20. from shared_canvasjack import *
  21. from shared_settings import *
  22. from shared_i18n import *
  23. # ------------------------------------------------------------------------------------------------------------
  24. # Try Import DBus
  25. try:
  26. import dbus
  27. from dbus.mainloop.pyqt5 import DBusQtMainLoop
  28. haveDBus = True
  29. except:
  30. haveDBus = False
  31. # ------------------------------------------------------------------------------------------------------------
  32. # Try Import OpenGL
  33. try:
  34. from PyQt5.QtOpenGL import QGLWidget
  35. hasGL = True
  36. except:
  37. hasGL = False
  38. # ------------------------------------------------------------------------------------------------------------
  39. # Check for ALSA-MIDI
  40. haveALSA = False
  41. if LINUX:
  42. for iPATH in PATH:
  43. if not os.path.exists(os.path.join(iPATH, "aconnect")):
  44. continue
  45. haveALSA = True
  46. if sys.version_info >= (3, 0):
  47. from subprocess import getoutput
  48. else:
  49. from commands import getoutput
  50. if DEBUG:
  51. print("Using experimental ALSA-MIDI support")
  52. break
  53. # ------------------------------------------------------------------------------------------------------------
  54. # Global Variables
  55. global gA2JClientName
  56. gA2JClientName = None
  57. # ------------------------------------------------------------------------------------------------------------
  58. # Static Variables
  59. GROUP_TYPE_NULL = 0
  60. GROUP_TYPE_ALSA = 1
  61. GROUP_TYPE_JACK = 2
  62. iGroupId = 0
  63. iGroupName = 1
  64. iGroupType = 2
  65. iPortId = 0
  66. iPortName = 1
  67. iPortNameR = 2
  68. iPortGroupName = 3
  69. iConnId = 0
  70. iConnOutput = 1
  71. iConnInput = 2
  72. URI_CANVAS_ICON = "http://kxstudio.sf.net/ns/canvas/icon"
  73. # ------------------------------------------------------------------------------------------------------------
  74. # Catia Main Window
  75. class CatiaMainW(AbstractCanvasJackClass):
  76. def __init__(self, parent=None):
  77. AbstractCanvasJackClass.__init__(self, "Catia", ui_catia.Ui_CatiaMainW, parent)
  78. self.fGroupList = []
  79. self.fGroupSplitList = []
  80. self.fPortList = []
  81. self.fConnectionList = []
  82. self.fLastGroupId = 1
  83. self.fLastPortId = 1
  84. self.fLastConnectionId = 1
  85. self.loadSettings(True)
  86. # -------------------------------------------------------------
  87. # Set-up GUI
  88. setIcons(self, ["canvas", "jack", "transport", "misc"])
  89. self.ui.act_quit.setIcon(getIcon("application-exit"))
  90. self.ui.act_configure.setIcon(getIcon("configure"))
  91. self.ui.cb_buffer_size.clear()
  92. self.ui.cb_sample_rate.clear()
  93. for bufferSize in BUFFER_SIZE_LIST:
  94. self.ui.cb_buffer_size.addItem(str(bufferSize))
  95. for sampleRate in SAMPLE_RATE_LIST:
  96. self.ui.cb_sample_rate.addItem(str(sampleRate))
  97. self.ui.act_jack_bf_list = (self.ui.act_jack_bf_16, self.ui.act_jack_bf_32, self.ui.act_jack_bf_64, self.ui.act_jack_bf_128,
  98. self.ui.act_jack_bf_256, self.ui.act_jack_bf_512, self.ui.act_jack_bf_1024, self.ui.act_jack_bf_2048,
  99. self.ui.act_jack_bf_4096, self.ui.act_jack_bf_8192)
  100. if not haveALSA:
  101. self.ui.act_settings_show_alsa.setChecked(False)
  102. self.ui.act_settings_show_alsa.setEnabled(False)
  103. # -------------------------------------------------------------
  104. # Set-up Canvas
  105. self.scene = patchcanvas.PatchScene(self, self.ui.graphicsView)
  106. self.ui.graphicsView.setScene(self.scene)
  107. self.ui.graphicsView.setRenderHint(QPainter.Antialiasing, bool(self.fSavedSettings["Canvas/Antialiasing"] == patchcanvas.ANTIALIASING_FULL))
  108. if self.fSavedSettings["Canvas/UseOpenGL"] and hasGL:
  109. self.ui.graphicsView.setViewport(QGLWidget(self.ui.graphicsView))
  110. self.ui.graphicsView.setRenderHint(QPainter.HighQualityAntialiasing, self.fSavedSettings["Canvas/HighQualityAntialiasing"])
  111. pOptions = patchcanvas.options_t()
  112. pOptions.theme_name = self.fSavedSettings["Canvas/Theme"]
  113. pOptions.auto_hide_groups = self.fSavedSettings["Canvas/AutoHideGroups"]
  114. pOptions.use_bezier_lines = self.fSavedSettings["Canvas/UseBezierLines"]
  115. pOptions.antialiasing = self.fSavedSettings["Canvas/Antialiasing"]
  116. pOptions.eyecandy = self.fSavedSettings["Canvas/EyeCandy"]
  117. pFeatures = patchcanvas.features_t()
  118. pFeatures.group_info = False
  119. pFeatures.group_rename = False
  120. pFeatures.port_info = True
  121. pFeatures.port_rename = bool(self.fSavedSettings["Main/JackPortAlias"] > 0)
  122. pFeatures.handle_group_pos = True
  123. patchcanvas.setOptions(pOptions)
  124. patchcanvas.setFeatures(pFeatures)
  125. patchcanvas.init("Catia", self.scene, self.canvasCallback, DEBUG)
  126. # -------------------------------------------------------------
  127. # Try to connect to jack
  128. if self.jackStarted():
  129. self.initAlsaPorts()
  130. # -------------------------------------------------------------
  131. # Check DBus
  132. if haveDBus:
  133. if gDBus.jack:
  134. pass
  135. else:
  136. self.ui.act_tools_jack_start.setEnabled(False)
  137. self.ui.act_tools_jack_stop.setEnabled(False)
  138. self.ui.act_jack_configure.setEnabled(False)
  139. self.ui.b_jack_configure.setEnabled(False)
  140. if gDBus.a2j:
  141. if gDBus.a2j.is_started():
  142. self.a2jStarted()
  143. else:
  144. self.a2jStopped()
  145. else:
  146. self.ui.act_tools_a2j_start.setEnabled(False)
  147. self.ui.act_tools_a2j_stop.setEnabled(False)
  148. self.ui.act_tools_a2j_export_hw.setEnabled(False)
  149. self.ui.menu_A2J_Bridge.setEnabled(False)
  150. else:
  151. # No DBus
  152. self.ui.act_tools_jack_start.setEnabled(False)
  153. self.ui.act_tools_jack_stop.setEnabled(False)
  154. self.ui.act_jack_configure.setEnabled(False)
  155. self.ui.b_jack_configure.setEnabled(False)
  156. self.ui.act_tools_a2j_start.setEnabled(False)
  157. self.ui.act_tools_a2j_stop.setEnabled(False)
  158. self.ui.act_tools_a2j_export_hw.setEnabled(False)
  159. self.ui.menu_A2J_Bridge.setEnabled(False)
  160. # -------------------------------------------------------------
  161. # Set-up Timers
  162. self.fTimer120 = self.startTimer(self.fSavedSettings["Main/RefreshInterval"])
  163. self.fTimer600 = self.startTimer(self.fSavedSettings["Main/RefreshInterval"] * 5)
  164. # -------------------------------------------------------------
  165. # Set-up Connections
  166. self.setCanvasConnections()
  167. self.setJackConnections(["jack", "buffer-size", "transport", "misc"])
  168. self.ui.act_tools_jack_start.triggered.connect(self.slot_JackServerStart)
  169. self.ui.act_tools_jack_stop.triggered.connect(self.slot_JackServerStop)
  170. self.ui.act_tools_a2j_start.triggered.connect(self.slot_A2JBridgeStart)
  171. self.ui.act_tools_a2j_stop.triggered.connect(self.slot_A2JBridgeStop)
  172. self.ui.act_tools_a2j_export_hw.triggered.connect(self.slot_A2JBridgeExportHW)
  173. self.ui.act_settings_show_alsa.triggered.connect(self.slot_showAlsaMIDI)
  174. self.ui.act_configure.triggered.connect(self.slot_configureCatia)
  175. self.ui.act_help_about.triggered.connect(self.slot_aboutCatia)
  176. self.ui.act_help_about_qt.triggered.connect(app.aboutQt)
  177. self.XRunCallback.connect(self.slot_XRunCallback)
  178. self.BufferSizeCallback.connect(self.slot_BufferSizeCallback)
  179. self.SampleRateCallback.connect(self.slot_SampleRateCallback)
  180. self.ClientRenameCallback.connect(self.slot_ClientRenameCallback)
  181. self.PortRegistrationCallback.connect(self.slot_PortRegistrationCallback)
  182. self.PortConnectCallback.connect(self.slot_PortConnectCallback)
  183. self.PortRenameCallback.connect(self.slot_PortRenameCallback)
  184. self.ShutdownCallback.connect(self.slot_ShutdownCallback)
  185. # -------------------------------------------------------------
  186. # Set-up DBus
  187. if gDBus.jack or gDBus.a2j:
  188. gDBus.bus.add_signal_receiver(self.DBusSignalReceiver, destination_keyword="dest", path_keyword="path",
  189. member_keyword="member", interface_keyword="interface", sender_keyword="sender")
  190. # -------------------------------------------------------------
  191. def canvasCallback(self, action, value1, value2, valueStr):
  192. if action == patchcanvas.ACTION_GROUP_INFO:
  193. pass
  194. elif action == patchcanvas.ACTION_GROUP_RENAME:
  195. pass
  196. elif action == patchcanvas.ACTION_GROUP_SPLIT:
  197. groupId = value1
  198. patchcanvas.splitGroup(groupId)
  199. elif action == patchcanvas.ACTION_GROUP_JOIN:
  200. groupId = value1
  201. patchcanvas.joinGroup(groupId)
  202. elif action == patchcanvas.ACTION_PORT_INFO:
  203. portId = value1
  204. for port in self.fPortList:
  205. if port[iPortId] == portId:
  206. portNameR = port[iPortNameR]
  207. portNameG = port[iPortGroupName]
  208. break
  209. else:
  210. return
  211. if portNameR.startswith("[ALSA-"):
  212. portId, portName = portNameR.split("] ", 1)[1].split(" ", 1)
  213. flags = []
  214. if portNameR.startswith("[ALSA-Input] "):
  215. flags.append(self.tr("Input"))
  216. elif portNameR.startswith("[ALSA-Output] "):
  217. flags.append(self.tr("Output"))
  218. flagsText = " | ".join(flags)
  219. typeText = self.tr("ALSA MIDI")
  220. info = self.tr(""
  221. "<table>"
  222. "<tr><td align='right'><b>Group Name:</b></td><td>&nbsp;%s</td></tr>"
  223. "<tr><td align='right'><b>Port Id:</b></td><td>&nbsp;%s</td></tr>"
  224. "<tr><td align='right'><b>Port Name:</b></td><td>&nbsp;%s</td></tr>"
  225. "<tr><td colspan='2'>&nbsp;</td></tr>"
  226. "<tr><td align='right'><b>Port Flags:</b></td><td>&nbsp;%s</td></tr>"
  227. "<tr><td align='right'><b>Port Type:</b></td><td>&nbsp;%s</td></tr>"
  228. "</table>" % (portNameG, portId, portName, flagsText, typeText))
  229. else:
  230. portPtr = jacklib.port_by_name(gJack.client, portNameR)
  231. portFlags = jacklib.port_flags(portPtr)
  232. groupName = portNameR.split(":", 1)[0]
  233. portShortName = str(jacklib.port_short_name(portPtr), encoding="utf-8")
  234. aliases = jacklib.port_get_aliases(portPtr)
  235. if aliases[0] == 1:
  236. alias1text = aliases[1]
  237. alias2text = "(none)"
  238. elif aliases[0] == 2:
  239. alias1text = aliases[1]
  240. alias2text = aliases[2]
  241. else:
  242. alias1text = "(none)"
  243. alias2text = "(none)"
  244. flags = []
  245. if portFlags & jacklib.JackPortIsInput:
  246. flags.append(self.tr("Input"))
  247. if portFlags & jacklib.JackPortIsOutput:
  248. flags.append(self.tr("Output"))
  249. if portFlags & jacklib.JackPortIsPhysical:
  250. flags.append(self.tr("Physical"))
  251. if portFlags & jacklib.JackPortCanMonitor:
  252. flags.append(self.tr("Can Monitor"))
  253. if portFlags & jacklib.JackPortIsTerminal:
  254. flags.append(self.tr("Terminal"))
  255. flagsText = " | ".join(flags)
  256. portTypeStr = str(jacklib.port_type(portPtr), encoding="utf-8")
  257. if portTypeStr == jacklib.JACK_DEFAULT_AUDIO_TYPE:
  258. typeText = self.tr("JACK Audio")
  259. elif portTypeStr == jacklib.JACK_DEFAULT_MIDI_TYPE:
  260. typeText = self.tr("JACK MIDI")
  261. else:
  262. typeText = self.tr("Unknown")
  263. portLatency = jacklib.port_get_latency(portPtr)
  264. portTotalLatency = jacklib.port_get_total_latency(gJack.client, portPtr)
  265. latencyText = self.tr("%.1f ms (%i frames)" % (portLatency * 1000 / int(self.fSampleRate), portLatency))
  266. latencyTotalText = self.tr("%.1f ms (%i frames)" % (portTotalLatency * 1000 / int(self.fSampleRate), portTotalLatency))
  267. info = self.tr(""
  268. "<table>"
  269. "<tr><td align='right'><b>Group Name:</b></td><td>&nbsp;%s</td></tr>"
  270. "<tr><td align='right'><b>Port Name:</b></td><td>&nbsp;%s</td></tr>"
  271. "<tr><td align='right'><b>Full Port Name:</b></td><td>&nbsp;%s</td></tr>"
  272. "<tr><td align='right'><b>Port Alias #1:</b></td><td>&nbsp;%s</td></tr>"
  273. "<tr><td align='right'><b>Port Alias #2:</b></td><td>&nbsp;%s</td></tr>"
  274. "<tr><td colspan='2'>&nbsp;</td></tr>"
  275. "<tr><td align='right'><b>Port Flags:</b></td><td>&nbsp;%s</td></tr>"
  276. "<tr><td align='right'><b>Port Type:</b></td><td>&nbsp;%s</td></tr>"
  277. "<tr><td colspan='2'>&nbsp;</td></tr>"
  278. "<tr><td align='right'><b>Port Latency:</b></td><td>&nbsp;%s</td></tr>"
  279. "<tr><td align='right'><b>Total Port Latency:</b></td><td>&nbsp;%s</td></tr>"
  280. "</table>" % (groupName, portShortName, portNameR, alias1text, alias2text, flagsText, typeText, latencyText, latencyTotalText))
  281. QMessageBox.information(self, self.tr("Port Information"), info)
  282. elif action == patchcanvas.ACTION_PORT_RENAME:
  283. global gA2JClientName
  284. portId = value1
  285. portShortName = asciiString(valueStr)
  286. for port in self.fPortList:
  287. if port[iPortId] == portId:
  288. portNameR = port[iPortNameR]
  289. if portNameR.startswith("[ALSA-"):
  290. QMessageBox.warning(self, self.tr("Cannot continue"), self.tr(""
  291. "Rename functions rely on JACK aliases and cannot be done in ALSA ports"))
  292. return
  293. if portNameR.split(":", 1)[0] == gA2JClientName:
  294. a2jSplit = portNameR.split(":", 3)
  295. portName = "%s:%s: %s" % (a2jSplit[0], a2jSplit[1], portShortName)
  296. else:
  297. portName = "%s:%s" % (port[iPortGroupName], portShortName)
  298. break
  299. else:
  300. return
  301. portPtr = jacklib.port_by_name(gJack.client, portNameR)
  302. aliases = jacklib.port_get_aliases(portPtr)
  303. if aliases[0] == 2:
  304. # JACK only allows 2 aliases, remove 2nd
  305. jacklib.port_unset_alias(portPtr, aliases[2])
  306. # If we're going for 1st alias, unset it too
  307. if self.fSavedSettings["Main/JackPortAlias"] == 1:
  308. jacklib.port_unset_alias(portPtr, aliases[1])
  309. elif aliases[0] == 1 and self.fSavedSettings["Main/JackPortAlias"] == 1:
  310. jacklib.port_unset_alias(portPtr, aliases[1])
  311. if aliases[0] == 0 and self.fSavedSettings["Main/JackPortAlias"] == 2:
  312. # If 2nd alias is enabled and port had no previous aliases, set the 1st alias now
  313. jacklib.port_set_alias(portPtr, portName)
  314. if jacklib.port_set_alias(portPtr, portName) == 0:
  315. patchcanvas.renamePort(portId, portShortName)
  316. elif action == patchcanvas.ACTION_PORTS_CONNECT:
  317. portIdA = value1
  318. portIdB = value2
  319. portRealNameA = ""
  320. portRealNameB = ""
  321. for port in self.fPortList:
  322. if port[iPortId] == portIdA:
  323. portRealNameA = port[iPortNameR]
  324. if port[iPortId] == portIdB:
  325. portRealNameB = port[iPortNameR]
  326. if portRealNameA.startswith("[ALSA-"):
  327. portIdAlsaA = portRealNameA.split(" ", 2)[1]
  328. portIdAlsaB = portRealNameB.split(" ", 2)[1]
  329. if os.system("aconnect %s %s" % (portIdAlsaA, portIdAlsaB)) == 0:
  330. self.canvas_connectPorts(portIdA, portIdB)
  331. elif portRealNameA and portRealNameB:
  332. jacklib.connect(gJack.client, portRealNameA, portRealNameB)
  333. elif action == patchcanvas.ACTION_PORTS_DISCONNECT:
  334. connectionId = value1
  335. for connection in self.fConnectionList:
  336. if connection[iConnId] == connectionId:
  337. portIdA = connection[iConnOutput]
  338. portIdB = connection[iConnInput]
  339. break
  340. else:
  341. return
  342. portRealNameA = ""
  343. portRealNameB = ""
  344. for port in self.fPortList:
  345. if port[iPortId] == portIdA:
  346. portRealNameA = port[iPortNameR]
  347. if port[iPortId] == portIdB:
  348. portRealNameB = port[iPortNameR]
  349. if portRealNameA.startswith("[ALSA-"):
  350. portIdAlsaA = portRealNameA.split(" ", 2)[1]
  351. portIdAlsaB = portRealNameB.split(" ", 2)[1]
  352. if os.system("aconnect -d %s %s" % (portIdAlsaA, portIdAlsaB)) == 0:
  353. self.canvas_disconnectPorts(portIdA, portIdB)
  354. elif portRealNameA and portRealNameB:
  355. jacklib.disconnect(gJack.client, portRealNameA, portRealNameB)
  356. def initPorts(self):
  357. self.fGroupList = []
  358. self.fGroupSplitList = []
  359. self.fPortList = []
  360. self.fConnectionList = []
  361. self.fLastGroupId = 1
  362. self.fLastPortId = 1
  363. self.fLastConnectionId = 1
  364. self.initJackPorts()
  365. self.initAlsaPorts()
  366. def initJack(self):
  367. self.fXruns = 0
  368. self.fNextSampleRate = 0.0
  369. self.fLastBPM = None
  370. self.fLastTransportState = None
  371. bufferSize = int(jacklib.get_buffer_size(gJack.client))
  372. sampleRate = int(jacklib.get_sample_rate(gJack.client))
  373. realtime = bool(int(jacklib.is_realtime(gJack.client)))
  374. self.ui_setBufferSize(bufferSize)
  375. self.ui_setSampleRate(sampleRate)
  376. self.ui_setRealTime(realtime)
  377. self.ui_setXruns(0)
  378. self.refreshDSPLoad()
  379. self.refreshTransport()
  380. self.initJackCallbacks()
  381. self.initJackPorts()
  382. self.scene.zoom_fit()
  383. self.scene.zoom_reset()
  384. jacklib.activate(gJack.client)
  385. def initJackCallbacks(self):
  386. jacklib.set_buffer_size_callback(gJack.client, self.JackBufferSizeCallback, None)
  387. jacklib.set_sample_rate_callback(gJack.client, self.JackSampleRateCallback, None)
  388. jacklib.set_xrun_callback(gJack.client, self.JackXRunCallback, None)
  389. jacklib.set_port_registration_callback(gJack.client, self.JackPortRegistrationCallback, None)
  390. jacklib.set_port_connect_callback(gJack.client, self.JackPortConnectCallback, None)
  391. jacklib.set_session_callback(gJack.client, self.JackSessionCallback, None)
  392. jacklib.on_shutdown(gJack.client, self.JackShutdownCallback, None)
  393. jacklib.set_client_rename_callback(gJack.client, self.JackClientRenameCallback, None)
  394. jacklib.set_port_rename_callback(gJack.client, self.JackPortRenameCallback, None)
  395. def initJackPorts(self):
  396. if not gJack.client:
  397. return
  398. global gA2JClientName
  399. # Get all jack ports, put a2j ones to the bottom of the list
  400. a2jNameList = []
  401. portNameList = c_char_p_p_to_list(jacklib.get_ports(gJack.client, "", "", 0))
  402. h = 0
  403. for i in range(len(portNameList)):
  404. if portNameList[i - h].split(":")[0] == gA2JClientName:
  405. portName = portNameList.pop(i - h)
  406. a2jNameList.append(portName)
  407. h += 1
  408. for a2jName in a2jNameList:
  409. portNameList.append(a2jName)
  410. del a2jNameList
  411. # Add jack ports
  412. for portName in portNameList:
  413. portPtr = jacklib.port_by_name(gJack.client, portName)
  414. self.canvas_addJackPort(portPtr, portName)
  415. # Add jack connections
  416. for portName in portNameList:
  417. portPtr = jacklib.port_by_name(gJack.client, portName)
  418. # Only make connections from an output port
  419. if jacklib.port_flags(portPtr) & jacklib.JackPortIsInput:
  420. continue
  421. portConnectionNames = c_char_p_p_to_list(jacklib.port_get_all_connections(gJack.client, portPtr))
  422. for portConName in portConnectionNames:
  423. self.canvas_connectPortsByName(portName, portConName)
  424. def initAlsaPorts(self):
  425. if not (haveALSA and self.ui.act_settings_show_alsa.isChecked()):
  426. return
  427. # Get ALSA MIDI ports (outputs)
  428. output = getoutput("env LANG=C LC_ALL=C aconnect -i").split("\n")
  429. lastGroupId = -1
  430. lastGroupName = ""
  431. for line in output:
  432. # Make 'System' match JACK's 'system'
  433. if line == "client 0: 'System' [type=kernel]":
  434. line = "client 0: 'system' [type=kernel]"
  435. if line.startswith("client "):
  436. lineSplit = line.split(": ", 1)
  437. lineSplit2 = lineSplit[1].replace("'", "", 1).split("' [type=", 1)
  438. groupId = int(lineSplit[0].replace("client ", ""))
  439. groupName = lineSplit2[0]
  440. groupType = lineSplit2[1].rsplit("]", 1)[0]
  441. lastGroupId = self.canvas_getGroupId(groupName)
  442. if lastGroupId == -1:
  443. # Group doesn't exist yet
  444. lastGroupId = self.canvas_addAlsaGroup(groupId, groupName, bool(groupType == "kernel"))
  445. lastGroupName = groupName
  446. elif line.startswith(" ") and lastGroupId >= 0 and lastGroupName:
  447. lineSplit = line.split(" '", 1)
  448. portId = int(lineSplit[0].strip())
  449. portName = lineSplit[1].rsplit("'", 1)[0].strip()
  450. self.canvas_addAlsaPort(lastGroupId, lastGroupName, portName, "%i:%i %s" % (groupId, portId, portName), False)
  451. else:
  452. lastGroupId = -1
  453. lastGroupName = ""
  454. # Get ALSA MIDI ports (inputs)
  455. output = getoutput("env LANG=C LC_ALL=C aconnect -o").split("\n")
  456. lastGroupId = -1
  457. lastGroupName = ""
  458. for line in output:
  459. # Make 'System' match JACK's 'system'
  460. if line == "client 0: 'System' [type=kernel]":
  461. line = "client 0: 'system' [type=kernel]"
  462. if line.startswith("client "):
  463. lineSplit = line.split(": ", 1)
  464. lineSplit2 = lineSplit[1].replace("'", "", 1).split("' [type=", 1)
  465. groupId = int(lineSplit[0].replace("client ", ""))
  466. groupName = lineSplit2[0]
  467. groupType = lineSplit2[1].rsplit("]", 1)[0]
  468. lastGroupId = self.canvas_getGroupId(groupName)
  469. if lastGroupId == -1:
  470. # Group doesn't exist yet
  471. lastGroupId = self.canvas_addAlsaGroup(groupId, groupName, bool(groupType == "kernel"))
  472. lastGroupName = groupName
  473. elif line.startswith(" ") and lastGroupId >= 0 and lastGroupName:
  474. lineSplit = line.split(" '", 1)
  475. portId = int(lineSplit[0].strip())
  476. portName = lineSplit[1].rsplit("'", 1)[0].strip()
  477. self.canvas_addAlsaPort(lastGroupId, lastGroupName, portName, "%i:%i %s" % (groupId, portId, portName), True)
  478. else:
  479. lastGroupId = -1
  480. lastGroupName = ""
  481. # Get ALSA MIDI connections
  482. output = getoutput("env LANG=C LC_ALL=C aconnect -ol").split("\n")
  483. lastGroupId = -1
  484. lastPortId = -1
  485. for line in output:
  486. # Make 'System' match JACK's 'system'
  487. if line == "client 0: 'System' [type=kernel]":
  488. line = "client 0: 'system' [type=kernel]"
  489. if line.startswith("client "):
  490. lineSplit = line.split(": ", 1)
  491. lineSplit2 = lineSplit[1].replace("'", "", 1).split("' [type=", 1)
  492. groupId = int(lineSplit[0].replace("client ", ""))
  493. groupName = lineSplit2[0]
  494. lastGroupId = self.canvas_getGroupId(groupName)
  495. elif line.startswith(" ") and lastGroupId >= 0:
  496. lineSplit = line.split(" '", 1)
  497. portId = int(lineSplit[0].strip())
  498. portName = lineSplit[1].rsplit("'", 1)[0].strip()
  499. for port in self.fPortList:
  500. if port[iPortNameR] == "[ALSA-Input] %i:%i %s" % (groupId, portId, portName):
  501. lastPortId = port[iPortId]
  502. break
  503. else:
  504. lastPortId = -1
  505. elif line.startswith("\tConnect") and lastGroupId >= 0 and lastPortId >= 0:
  506. if line.startswith("\tConnected From"):
  507. lineSplit = line.split(": ", 1)[1]
  508. lineConns = lineSplit.split(", ")
  509. for lineConn in lineConns:
  510. lineConnSplit = lineConn.replace("'","").split(":", 1)
  511. alsaGroupId = int(lineConnSplit[0].split("[real:",1)[0])
  512. alsaPortId = int(lineConnSplit[1].split("[real:",1)[0])
  513. portNameRtest = "[ALSA-Output] %i:%i " % (alsaGroupId, alsaPortId)
  514. for port in self.fPortList:
  515. if port[iPortNameR].startswith(portNameRtest):
  516. self.canvas_connectPorts(port[iPortId], lastPortId)
  517. break
  518. else:
  519. lastGroupId = -1
  520. lastPortId = -1
  521. def canvas_getGroupId(self, groupName):
  522. for group in self.fGroupList:
  523. if group[iGroupName] == groupName:
  524. return group[iGroupId]
  525. return -1
  526. def canvas_addAlsaGroup(self, alsaGroupId, groupName, hwSplit):
  527. groupId = self.fLastGroupId
  528. if hwSplit:
  529. patchcanvas.addGroup(groupId, groupName, patchcanvas.SPLIT_YES, patchcanvas.ICON_HARDWARE)
  530. else:
  531. patchcanvas.addGroup(groupId, groupName)
  532. groupObj = [None, None, None]
  533. groupObj[iGroupId] = groupId
  534. groupObj[iGroupName] = groupName
  535. groupObj[iGroupType] = GROUP_TYPE_ALSA
  536. self.fGroupList.append(groupObj)
  537. self.fLastGroupId += 1
  538. return groupId
  539. def canvas_addJackGroup(self, groupName):
  540. ret, data, dataSize = jacklib.custom_get_data(gJack.client, groupName, URI_CANVAS_ICON)
  541. groupId = self.fLastGroupId
  542. groupSplit = patchcanvas.SPLIT_UNDEF
  543. groupIcon = patchcanvas.ICON_APPLICATION
  544. if ret == 0:
  545. iconName = voidptr2str(data)
  546. jacklib.free(data)
  547. if iconName == "hardware":
  548. groupSplit = patchcanvas.SPLIT_YES
  549. groupIcon = patchcanvas.ICON_HARDWARE
  550. #elif iconName =="carla":
  551. #groupIcon = patchcanvas.ICON_CARLA
  552. elif iconName =="distrho":
  553. groupIcon = patchcanvas.ICON_DISTRHO
  554. elif iconName =="file":
  555. groupIcon = patchcanvas.ICON_FILE
  556. elif iconName =="plugin":
  557. groupIcon = patchcanvas.ICON_PLUGIN
  558. patchcanvas.addGroup(groupId, groupName, groupSplit, groupIcon)
  559. groupObj = [None, None, None]
  560. groupObj[iGroupId] = groupId
  561. groupObj[iGroupName] = groupName
  562. groupObj[iGroupType] = GROUP_TYPE_JACK
  563. self.fGroupList.append(groupObj)
  564. self.fLastGroupId += 1
  565. return groupId
  566. def canvas_removeGroup(self, groupName):
  567. groupId = -1
  568. for group in self.fGroupList:
  569. if group[iGroupName] == groupName:
  570. groupId = group[iGroupId]
  571. self.fGroupList.remove(group)
  572. break
  573. else:
  574. print(self.tr("Catia - remove group failed"))
  575. return
  576. patchcanvas.removeGroup(groupId)
  577. def canvas_addAlsaPort(self, groupId, groupName, portName, portNameR, isPortInput):
  578. portId = self.fLastPortId
  579. portMode = patchcanvas.PORT_MODE_INPUT if isPortInput else patchcanvas.PORT_MODE_OUTPUT
  580. portType = patchcanvas.PORT_TYPE_MIDI_ALSA
  581. patchcanvas.addPort(groupId, portId, portName, portMode, portType)
  582. portObj = [None, None, None, None]
  583. portObj[iPortId] = portId
  584. portObj[iPortName] = portName
  585. portObj[iPortNameR] = "[ALSA-%s] %s" % ("Input" if isPortInput else "Output", portNameR)
  586. portObj[iPortGroupName] = groupName
  587. self.fPortList.append(portObj)
  588. self.fLastPortId += 1
  589. return portId
  590. def canvas_addJackPort(self, portPtr, portName):
  591. global gA2JClientName
  592. portId = self.fLastPortId
  593. groupId = -1
  594. portNameR = portName
  595. aliasN = self.fSavedSettings["Main/JackPortAlias"]
  596. if aliasN in (1, 2):
  597. aliases = jacklib.port_get_aliases(portPtr)
  598. if aliases[0] == 2 and aliasN == 2:
  599. portName = aliases[2]
  600. elif aliases[0] >= 1 and aliasN == 1:
  601. portName = aliases[1]
  602. portFlags = jacklib.port_flags(portPtr)
  603. groupName = portName.split(":", 1)[0]
  604. if portFlags & jacklib.JackPortIsInput:
  605. portMode = patchcanvas.PORT_MODE_INPUT
  606. elif portFlags & jacklib.JackPortIsOutput:
  607. portMode = patchcanvas.PORT_MODE_OUTPUT
  608. else:
  609. portMode = patchcanvas.PORT_MODE_NULL
  610. if groupName == gA2JClientName:
  611. portType = patchcanvas.PORT_TYPE_MIDI_A2J
  612. groupName = portName.replace("%s:" % gA2JClientName, "", 1).split(" [", 1)[0]
  613. portShortName = portName.split("): ", 1)[1]
  614. else:
  615. portShortName = portName.replace("%s:" % groupName, "", 1)
  616. portTypeStr = str(jacklib.port_type(portPtr), encoding="utf-8")
  617. if portTypeStr == jacklib.JACK_DEFAULT_AUDIO_TYPE:
  618. portType = patchcanvas.PORT_TYPE_AUDIO_JACK
  619. elif portTypeStr == jacklib.JACK_DEFAULT_MIDI_TYPE:
  620. portType = patchcanvas.PORT_TYPE_MIDI_JACK
  621. else:
  622. portType = patchcanvas.PORT_TYPE_NULL
  623. for group in self.fGroupList:
  624. if group[iGroupName] == groupName:
  625. groupId = group[iGroupId]
  626. break
  627. else:
  628. # For ports with no group
  629. groupId = self.canvas_addJackGroup(groupName)
  630. patchcanvas.addPort(groupId, portId, portShortName, portMode, portType)
  631. portObj = [None, None, None, None]
  632. portObj[iPortId] = portId
  633. portObj[iPortName] = portName
  634. portObj[iPortNameR] = portNameR
  635. portObj[iPortGroupName] = groupName
  636. self.fPortList.append(portObj)
  637. self.fLastPortId += 1
  638. if groupId not in self.fGroupSplitList and (portFlags & jacklib.JackPortIsPhysical) > 0:
  639. patchcanvas.splitGroup(groupId)
  640. patchcanvas.setGroupIcon(groupId, patchcanvas.ICON_HARDWARE)
  641. self.fGroupSplitList.append(groupId)
  642. return portId
  643. def canvas_removeJackPort(self, portId):
  644. patchcanvas.removePort(portId)
  645. for port in self.fPortList:
  646. if port[iPortId] == portId:
  647. groupName = port[iPortGroupName]
  648. self.fPortList.remove(port)
  649. break
  650. else:
  651. return
  652. # Check if group has no more ports; if yes remove it
  653. for port in self.fPortList:
  654. if port[iPortGroupName] == groupName:
  655. break
  656. else:
  657. self.canvas_removeGroup(groupName)
  658. def canvas_renamePort(self, portId, portShortName):
  659. patchcanvas.renamePort(portId, portShortName)
  660. def canvas_connectPorts(self, portOutId, portInId):
  661. connectionId = self.fLastConnectionId
  662. patchcanvas.connectPorts(connectionId, portOutId, portInId)
  663. connObj = [None, None, None]
  664. connObj[iConnId] = connectionId
  665. connObj[iConnOutput] = portOutId
  666. connObj[iConnInput] = portInId
  667. self.fConnectionList.append(connObj)
  668. self.fLastConnectionId += 1
  669. return connectionId
  670. def canvas_connectPortsByName(self, portOutName, portInName):
  671. portOutId = -1
  672. portInId = -1
  673. for port in self.fPortList:
  674. if port[iPortNameR] == portOutName:
  675. portOutId = port[iPortId]
  676. elif port[iPortNameR] == portInName:
  677. portInId = port[iPortId]
  678. if portOutId >= 0 and portInId >= 0:
  679. break
  680. else:
  681. print(self.tr("Catia - connect jack ports failed"))
  682. return -1
  683. return self.canvas_connectPorts(portOutId, portInId)
  684. def canvas_disconnectPorts(self, portOutId, portInId):
  685. for connection in self.fConnectionList:
  686. if connection[iConnOutput] == portOutId and connection[iConnInput] == portInId:
  687. patchcanvas.disconnectPorts(connection[iConnId])
  688. self.fConnectionList.remove(connection)
  689. break
  690. def canvas_disconnectPortsByName(self, portOutName, portInName):
  691. portOutId = -1
  692. portInId = -1
  693. for port in self.fPortList:
  694. if port[iPortNameR] == portOutName:
  695. portOutId = port[iPortId]
  696. elif port[iPortNameR] == portInName:
  697. portInId = port[iPortId]
  698. if portOutId == -1 or portInId == -1:
  699. print(self.tr("Catia - disconnect ports failed"))
  700. return
  701. self.canvas_disconnectPorts(portOutId, portInId)
  702. def jackStarted(self):
  703. if not gJack.client:
  704. gJack.client = jacklib.client_open("catia", jacklib.JackNoStartServer | jacklib.JackSessionID, None)
  705. if not gJack.client:
  706. self.jackStopped()
  707. return False
  708. canRender = render.canRender()
  709. self.ui.act_jack_render.setEnabled(canRender)
  710. self.ui.b_jack_render.setEnabled(canRender)
  711. self.menuJackServer(True)
  712. self.menuJackTransport(True)
  713. self.ui.cb_buffer_size.setEnabled(True)
  714. self.ui.cb_sample_rate.setEnabled(bool(gDBus.jack)) # DBus.jack and jacksettings.getSampleRate() != -1
  715. self.ui.menu_Jack_Buffer_Size.setEnabled(True)
  716. self.ui.pb_dsp_load.setMaximum(100)
  717. self.ui.pb_dsp_load.setValue(0)
  718. self.ui.pb_dsp_load.update()
  719. self.initJack()
  720. return True
  721. def jackStopped(self):
  722. if haveDBus:
  723. self.DBusReconnect()
  724. # client already closed
  725. gJack.client = None
  726. # refresh canvas (remove jack ports)
  727. patchcanvas.clear()
  728. self.initPorts()
  729. if self.fNextSampleRate:
  730. self.jack_setSampleRate(self.fNextSampleRate)
  731. if gDBus.jack:
  732. bufferSize = jacksettings.getBufferSize()
  733. sampleRate = jacksettings.getSampleRate()
  734. bufferSizeTest = bool(bufferSize != -1)
  735. sampleRateTest = bool(sampleRate != -1)
  736. if bufferSizeTest:
  737. self.ui_setBufferSize(bufferSize)
  738. if sampleRateTest:
  739. self.ui_setSampleRate(sampleRate)
  740. self.ui_setRealTime(jacksettings.isRealtime())
  741. self.ui.cb_buffer_size.setEnabled(bufferSizeTest)
  742. self.ui.cb_sample_rate.setEnabled(sampleRateTest)
  743. self.ui.menu_Jack_Buffer_Size.setEnabled(bufferSizeTest)
  744. else:
  745. self.ui.cb_buffer_size.setEnabled(False)
  746. self.ui.cb_sample_rate.setEnabled(False)
  747. self.ui.menu_Jack_Buffer_Size.setEnabled(False)
  748. self.ui.act_jack_render.setEnabled(False)
  749. self.ui.b_jack_render.setEnabled(False)
  750. self.menuJackServer(False)
  751. self.menuJackTransport(False)
  752. self.ui_setXruns(-1)
  753. if self.fCurTransportView == TRANSPORT_VIEW_HMS:
  754. self.ui.label_time.setText("00:00:00")
  755. elif self.fCurTransportView == TRANSPORT_VIEW_BBT:
  756. self.ui.label_time.setText("000|0|0000")
  757. elif self.fCurTransportView == TRANSPORT_VIEW_FRAMES:
  758. self.ui.label_time.setText("000'000'000")
  759. self.ui.pb_dsp_load.setValue(0)
  760. self.ui.pb_dsp_load.setMaximum(0)
  761. self.ui.pb_dsp_load.update()
  762. def a2jStarted(self):
  763. self.menuA2JBridge(True)
  764. def a2jStopped(self):
  765. self.menuA2JBridge(False)
  766. def menuJackServer(self, started):
  767. if gDBus.jack:
  768. self.ui.act_tools_jack_start.setEnabled(not started)
  769. self.ui.act_tools_jack_stop.setEnabled(started)
  770. self.menuA2JBridge(False)
  771. def menuJackTransport(self, enabled):
  772. self.ui.act_transport_play.setEnabled(enabled)
  773. self.ui.act_transport_stop.setEnabled(enabled)
  774. self.ui.act_transport_backwards.setEnabled(enabled)
  775. self.ui.act_transport_forwards.setEnabled(enabled)
  776. self.ui.menu_Transport.setEnabled(enabled)
  777. self.ui.group_transport.setEnabled(enabled)
  778. def menuA2JBridge(self, started):
  779. if gDBus.jack and not gDBus.jack.IsStarted():
  780. self.ui.act_tools_a2j_start.setEnabled(False)
  781. self.ui.act_tools_a2j_stop.setEnabled(False)
  782. self.ui.act_tools_a2j_export_hw.setEnabled(bool(gDBus.a2j) and not gDBus.a2j.is_started())
  783. else:
  784. self.ui.act_tools_a2j_start.setEnabled(not started)
  785. self.ui.act_tools_a2j_stop.setEnabled(started)
  786. self.ui.act_tools_a2j_export_hw.setEnabled(not started)
  787. def DBusSignalReceiver(self, *args, **kwds):
  788. if kwds["interface"] == "org.freedesktop.DBus" and kwds["path"] == "/org/freedesktop/DBus" and kwds["member"] == "NameOwnerChanged":
  789. appInterface, appId, newId = args
  790. if not newId:
  791. # Something crashed
  792. if appInterface == "org.gna.home.a2jmidid":
  793. QTimer.singleShot(0, self.slot_handleCrash_a2j)
  794. elif appInterface == "org.jackaudio.service":
  795. QTimer.singleShot(0, self.slot_handleCrash_jack)
  796. elif kwds['interface'] == "org.jackaudio.JackControl":
  797. if kwds['member'] == "ServerStarted":
  798. self.jackStarted()
  799. elif kwds['member'] == "ServerStopped":
  800. self.jackStopped()
  801. elif kwds['interface'] == "org.gna.home.a2jmidid.control":
  802. if kwds['member'] == "bridge_started":
  803. self.a2jStarted()
  804. elif kwds['member'] == "bridge_stopped":
  805. self.a2jStopped()
  806. def DBusReconnect(self):
  807. global gA2JClientName
  808. try:
  809. gDBus.jack = gDBus.bus.get_object("org.jackaudio.service", "/org/jackaudio/Controller")
  810. jacksettings.initBus(gDBus.bus)
  811. except:
  812. gDBus.jack = None
  813. try:
  814. gDBus.a2j = dbus.Interface(gDBus.bus.get_object("org.gna.home.a2jmidid", "/"), "org.gna.home.a2jmidid.control")
  815. gA2JClientName = str(gDBus.a2j.get_jack_client_name())
  816. except:
  817. gDBus.a2j = None
  818. gA2JClientName = None
  819. def JackXRunCallback(self, arg):
  820. if DEBUG: print("JackXRunCallback()")
  821. self.XRunCallback.emit()
  822. return 0
  823. def JackBufferSizeCallback(self, bufferSize, arg):
  824. if DEBUG: print("JackBufferSizeCallback(%i)" % bufferSize)
  825. self.BufferSizeCallback.emit(bufferSize)
  826. return 0
  827. def JackSampleRateCallback(self, sampleRate, arg):
  828. if DEBUG: print("JackSampleRateCallback(%i)" % sampleRate)
  829. self.SampleRateCallback.emit(sampleRate)
  830. return 0
  831. def JackClientRenameCallback(self, oldName, newName, arg):
  832. if DEBUG: print("JackClientRenameCallback(\"%s\", \"%s\")" % (oldName, newName))
  833. self.ClientRenameCallback.emit(str(oldName, encoding="utf-8"), str(newName, encoding="utf-8"))
  834. return 0
  835. def JackPortRegistrationCallback(self, portId, registerYesNo, arg):
  836. if DEBUG: print("JackPortRegistrationCallback(%i, %i)" % (portId, registerYesNo))
  837. self.PortRegistrationCallback.emit(portId, bool(registerYesNo))
  838. return 0
  839. def JackPortConnectCallback(self, portA, portB, connectYesNo, arg):
  840. if DEBUG: print("JackPortConnectCallback(%i, %i, %i)" % (portA, portB, connectYesNo))
  841. self.PortConnectCallback.emit(portA, portB, bool(connectYesNo))
  842. return 0
  843. def JackPortRenameCallback(self, portId, oldName, newName, arg):
  844. if DEBUG: print("JackPortRenameCallback(%i, \"%s\", \"%s\")" % (portId, oldName, newName))
  845. self.PortRenameCallback.emit(portId, str(oldName, encoding="utf-8"), str(newName, encoding="utf-8"))
  846. return 0
  847. def JackSessionCallback(self, event, arg):
  848. if WINDOWS:
  849. filepath = os.path.join(sys.argv[0])
  850. else:
  851. if sys.argv[0].startswith("/"):
  852. filepath = "catia"
  853. else:
  854. filepath = os.path.join(sys.path[0], "catia.py")
  855. event.command_line = str(filepath).encode("utf-8")
  856. jacklib.session_reply(gJack.client, event)
  857. if event.type == jacklib.JackSessionSaveAndQuit:
  858. app.quit()
  859. #jacklib.session_event_free(event)
  860. def JackShutdownCallback(self, arg):
  861. if DEBUG: print("JackShutdownCallback()")
  862. self.ShutdownCallback.emit()
  863. return 0
  864. @pyqtSlot(bool)
  865. def slot_showAlsaMIDI(self, yesNo):
  866. # refresh canvas (remove jack ports)
  867. patchcanvas.clear()
  868. self.initPorts()
  869. @pyqtSlot()
  870. def slot_JackServerStart(self):
  871. ret = False
  872. if gDBus.jack:
  873. try:
  874. ret = bool(gDBus.jack.StartServer())
  875. except:
  876. QMessageBox.warning(self, self.tr("Warning"), self.tr("Failed to start JACK, please check the logs for more information."))
  877. #self.jackStopped()
  878. return ret
  879. @pyqtSlot()
  880. def slot_JackServerStop(self):
  881. ret = False
  882. if gDBus.jack:
  883. ret = bool(gDBus.jack.StopServer())
  884. return ret
  885. @pyqtSlot()
  886. def slot_JackClearXruns(self):
  887. if gJack.client:
  888. self.fXruns = 0
  889. self.ui_setXruns(0)
  890. @pyqtSlot()
  891. def slot_A2JBridgeStart(self):
  892. ret = False
  893. if gDBus.a2j:
  894. ret = bool(gDBus.a2j.start())
  895. return ret
  896. @pyqtSlot()
  897. def slot_A2JBridgeStop(self):
  898. ret = False
  899. if gDBus.a2j:
  900. ret = bool(gDBus.a2j.stop())
  901. return ret
  902. @pyqtSlot()
  903. def slot_A2JBridgeExportHW(self):
  904. if gDBus.a2j:
  905. ask = QMessageBox.question(self, self.tr("A2J Hardware Export"), self.tr("Enable Hardware Export on the A2J Bridge?"), QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel, QMessageBox.No)
  906. if ask == QMessageBox.Yes:
  907. gDBus.a2j.set_hw_export(True)
  908. elif ask == QMessageBox.No:
  909. gDBus.a2j.set_hw_export(False)
  910. @pyqtSlot()
  911. def slot_XRunCallback(self):
  912. self.fXruns += 1
  913. self.ui_setXruns(self.fXruns)
  914. @pyqtSlot(int)
  915. def slot_BufferSizeCallback(self, bufferSize):
  916. self.ui_setBufferSize(bufferSize)
  917. @pyqtSlot(int)
  918. def slot_SampleRateCallback(self, sampleRate):
  919. self.ui_setSampleRate(sampleRate)
  920. self.ui_setRealTime(bool(int(jacklib.is_realtime(gJack.client))))
  921. self.ui_setXruns(0)
  922. @pyqtSlot(str, str)
  923. def slot_ClientRenameCallback(self, oldName, newName):
  924. pass # TODO
  925. @pyqtSlot(int, bool)
  926. def slot_PortRegistrationCallback(self, portIdJack, registerYesNo):
  927. portPtr = jacklib.port_by_id(gJack.client, portIdJack)
  928. portNameR = str(jacklib.port_name(portPtr), encoding="utf-8")
  929. if registerYesNo:
  930. self.canvas_addJackPort(portPtr, portNameR)
  931. else:
  932. for port in self.fPortList:
  933. if port[iPortNameR] == portNameR:
  934. portIdCanvas = port[iPortId]
  935. break
  936. else:
  937. return
  938. self.canvas_removeJackPort(portIdCanvas)
  939. @pyqtSlot(int, int, bool)
  940. def slot_PortConnectCallback(self, portIdJackA, portIdJackB, connectYesNo):
  941. portPtrA = jacklib.port_by_id(gJack.client, portIdJackA)
  942. portPtrB = jacklib.port_by_id(gJack.client, portIdJackB)
  943. portRealNameA = str(jacklib.port_name(portPtrA), encoding="utf-8")
  944. portRealNameB = str(jacklib.port_name(portPtrB), encoding="utf-8")
  945. if connectYesNo:
  946. self.canvas_connectPortsByName(portRealNameA, portRealNameB)
  947. else:
  948. self.canvas_disconnectPortsByName(portRealNameA, portRealNameB)
  949. @pyqtSlot(int, str, str)
  950. def slot_PortRenameCallback(self, portIdJack, oldName, newName):
  951. portPtr = jacklib.port_by_id(gJack.client, portIdJack)
  952. portShortName = str(jacklib.port_short_name(portPtr), encoding="utf-8")
  953. for port in self.fPortList:
  954. if port[iPortNameR] == oldName:
  955. portIdCanvas = port[iPortId]
  956. port[iPortNameR] = newName
  957. break
  958. else:
  959. return
  960. # Only set new name in canvas if no alias is active for this port
  961. aliases = jacklib.port_get_aliases(portPtr)
  962. if aliases[0] == 1 and self.fSavedSettings["Main/JackPortAlias"] == 1:
  963. pass
  964. elif aliases[0] == 2 and self.fSavedSettings["Main/JackPortAlias"] == 2:
  965. pass
  966. else:
  967. self.canvas_renamePort(portIdCanvas, portShortName)
  968. @pyqtSlot()
  969. def slot_ShutdownCallback(self):
  970. self.jackStopped()
  971. @pyqtSlot()
  972. def slot_handleCrash_a2j(self):
  973. global gA2JClientName
  974. try:
  975. gDBus.a2j = dbus.Interface(gDBus.bus.get_object("org.gna.home.a2jmidid", "/"), "org.gna.home.a2jmidid.control")
  976. gA2JClientName = str(gDBus.a2j.get_jack_client_name())
  977. except:
  978. gDBus.a2j = None
  979. gA2JClientName = None
  980. if gDBus.a2j:
  981. if gDBus.a2j.is_started():
  982. self.a2jStarted()
  983. else:
  984. self.a2jStopped()
  985. else:
  986. self.ui.act_tools_a2j_start.setEnabled(False)
  987. self.ui.act_tools_a2j_stop.setEnabled(False)
  988. self.ui.act_tools_a2j_export_hw.setEnabled(False)
  989. self.ui.menu_A2J_Bridge.setEnabled(False)
  990. @pyqtSlot()
  991. def slot_handleCrash_jack(self):
  992. self.DBusReconnect()
  993. if gDBus.jack:
  994. self.ui.act_jack_configure.setEnabled(True)
  995. self.ui.b_jack_configure.setEnabled(True)
  996. else:
  997. self.ui.act_tools_jack_start.setEnabled(False)
  998. self.ui.act_tools_jack_stop.setEnabled(False)
  999. self.ui.act_jack_configure.setEnabled(False)
  1000. self.ui.b_jack_configure.setEnabled(False)
  1001. if gDBus.a2j:
  1002. if gDBus.a2j.is_started():
  1003. self.a2jStarted()
  1004. else:
  1005. self.a2jStopped()
  1006. else:
  1007. self.ui.act_tools_a2j_start.setEnabled(False)
  1008. self.ui.act_tools_a2j_stop.setEnabled(False)
  1009. self.ui.act_tools_a2j_export_hw.setEnabled(False)
  1010. self.ui.menu_A2J_Bridge.setEnabled(False)
  1011. self.jackStopped()
  1012. @pyqtSlot()
  1013. def slot_configureCatia(self):
  1014. dialog = SettingsW(self, "catia", hasGL)
  1015. if dialog.exec_():
  1016. self.loadSettings(False)
  1017. patchcanvas.clear()
  1018. pOptions = patchcanvas.options_t()
  1019. pOptions.theme_name = self.fSavedSettings["Canvas/Theme"]
  1020. pOptions.auto_hide_groups = self.fSavedSettings["Canvas/AutoHideGroups"]
  1021. pOptions.use_bezier_lines = self.fSavedSettings["Canvas/UseBezierLines"]
  1022. pOptions.antialiasing = self.fSavedSettings["Canvas/Antialiasing"]
  1023. pOptions.eyecandy = self.fSavedSettings["Canvas/EyeCandy"]
  1024. pFeatures = patchcanvas.features_t()
  1025. pFeatures.group_info = False
  1026. pFeatures.group_rename = False
  1027. pFeatures.port_info = True
  1028. pFeatures.port_rename = bool(self.fSavedSettings["Main/JackPortAlias"] > 0)
  1029. pFeatures.handle_group_pos = True
  1030. patchcanvas.setOptions(pOptions)
  1031. patchcanvas.setFeatures(pFeatures)
  1032. patchcanvas.init("Catia", self.scene, self.canvasCallback, DEBUG)
  1033. self.initPorts()
  1034. @pyqtSlot()
  1035. def slot_aboutCatia(self):
  1036. QMessageBox.about(self, self.tr("About Catia"), self.tr("<h3>Catia</h3>"
  1037. "<br>Version %s"
  1038. "<br>Catia is a nice JACK Patchbay with A2J Bridge integration.<br>"
  1039. "<br>Copyright (C) 2010-2022 falkTX" % VERSION))
  1040. def saveSettings(self):
  1041. settings = QSettings()
  1042. settings.setValue("Geometry", self.saveGeometry())
  1043. settings.setValue("ShowAlsaMIDI", self.ui.act_settings_show_alsa.isChecked())
  1044. settings.setValue("ShowToolbar", self.ui.frame_toolbar.isVisible())
  1045. settings.setValue("ShowStatusbar", self.ui.frame_statusbar.isVisible())
  1046. settings.setValue("TransportView", self.fCurTransportView)
  1047. def loadSettings(self, geometry):
  1048. settings = QSettings()
  1049. if geometry:
  1050. self.restoreGeometry(settings.value("Geometry", b""))
  1051. showAlsaMidi = settings.value("ShowAlsaMIDI", False, type=bool)
  1052. self.ui.act_settings_show_alsa.setChecked(showAlsaMidi)
  1053. showToolbar = settings.value("ShowToolbar", True, type=bool)
  1054. self.ui.act_settings_show_toolbar.setChecked(showToolbar)
  1055. self.ui.frame_toolbar.setVisible(showToolbar)
  1056. showStatusbar = settings.value("ShowStatusbar", True, type=bool)
  1057. self.ui.act_settings_show_statusbar.setChecked(showStatusbar)
  1058. self.ui.frame_statusbar.setVisible(showStatusbar)
  1059. self.setTransportView(settings.value("TransportView", TRANSPORT_VIEW_HMS, type=int))
  1060. self.fSavedSettings = {
  1061. "Main/RefreshInterval": settings.value("Main/RefreshInterval", 120, type=int),
  1062. "Main/JackPortAlias": settings.value("Main/JackPortAlias", 2, type=int),
  1063. "Canvas/Theme": settings.value("Canvas/Theme", patchcanvas.getDefaultThemeName(), type=str),
  1064. "Canvas/AutoHideGroups": settings.value("Canvas/AutoHideGroups", False, type=bool),
  1065. "Canvas/UseBezierLines": settings.value("Canvas/UseBezierLines", True, type=bool),
  1066. "Canvas/EyeCandy": settings.value("Canvas/EyeCandy", patchcanvas.EYECANDY_SMALL, type=int),
  1067. "Canvas/UseOpenGL": settings.value("Canvas/UseOpenGL", False, type=bool),
  1068. "Canvas/Antialiasing": settings.value("Canvas/Antialiasing", patchcanvas.ANTIALIASING_SMALL, type=int),
  1069. "Canvas/HighQualityAntialiasing": settings.value("Canvas/HighQualityAntialiasing", False, type=bool)
  1070. }
  1071. def timerEvent(self, event):
  1072. if event.timerId() == self.fTimer120:
  1073. if gJack.client:
  1074. self.refreshTransport()
  1075. elif event.timerId() == self.fTimer600:
  1076. if gJack.client:
  1077. self.refreshDSPLoad()
  1078. QMainWindow.timerEvent(self, event)
  1079. def closeEvent(self, event):
  1080. self.saveSettings()
  1081. patchcanvas.clear()
  1082. QMainWindow.closeEvent(self, event)
  1083. # ------------------------------------------------------------------------------------------------------------
  1084. # Main
  1085. if __name__ == '__main__':
  1086. # App initialization
  1087. app = QApplication(sys.argv)
  1088. app.setApplicationName("Catia")
  1089. app.setApplicationVersion(VERSION)
  1090. app.setOrganizationName("Cadence")
  1091. app.setWindowIcon(QIcon(":/scalable/catia.svg"))
  1092. setup_i18n()
  1093. if jacklib is None:
  1094. QMessageBox.critical(None, app.translate("CatiaMainW", "Error"), app.translate("CatiaMainW",
  1095. "JACK is not available in this system, cannot use this application."))
  1096. sys.exit(1)
  1097. if haveDBus:
  1098. gDBus.loop = DBusQtMainLoop(set_as_default=True)
  1099. gDBus.bus = dbus.SessionBus(mainloop=gDBus.loop)
  1100. try:
  1101. gDBus.jack = gDBus.bus.get_object("org.jackaudio.service", "/org/jackaudio/Controller")
  1102. jacksettings.initBus(gDBus.bus)
  1103. except:
  1104. gDBus.jack = None
  1105. try:
  1106. gDBus.a2j = dbus.Interface(gDBus.bus.get_object("org.gna.home.a2jmidid", "/"), "org.gna.home.a2jmidid.control")
  1107. gA2JClientName = str(gDBus.a2j.get_jack_client_name())
  1108. except:
  1109. gDBus.a2j = None
  1110. gA2JClientName = None
  1111. if DEBUG and (gDBus.jack or gDBus.a2j):
  1112. string = "Using DBus for "
  1113. if gDBus.jack:
  1114. string += "JACK"
  1115. if gDBus.a2j:
  1116. string += " and a2jmidid"
  1117. elif gDBus.a2j:
  1118. string += "a2jmidid"
  1119. print(string)
  1120. else:
  1121. gDBus.jack = None
  1122. gDBus.a2j = None
  1123. gA2JClientName = None
  1124. if DEBUG:
  1125. print("Not using DBus")
  1126. # Init GUI
  1127. gui = CatiaMainW()
  1128. # Set-up custom signal handling
  1129. setUpSignals(gui)
  1130. # Show GUI
  1131. gui.show()
  1132. # App-Loop
  1133. ret = app.exec_()
  1134. # Close Jack
  1135. if gJack.client:
  1136. jacklib.deactivate(gJack.client)
  1137. jacklib.client_close(gJack.client)
  1138. # Exit properly
  1139. sys.exit(ret)