Audio plugin host https://kx.studio/carla
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.

472 lines
20KB

  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # Carla plugin host
  4. # Copyright (C) 2011-2022 Filipe Coelho <falktx@falktx.com>
  5. #
  6. # This program is free software; you can redistribute it and/or
  7. # modify it under the terms of the GNU General Public License as
  8. # published by the Free Software Foundation; either version 2 of
  9. # the License, or 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 doc/GPL.txt file.
  17. # ---------------------------------------------------------------------------------------------------------------------
  18. # Imports (Global)
  19. import os
  20. from PyQt5.QtCore import pyqtSlot, Qt, QT_VERSION
  21. from PyQt5.QtGui import QPixmap
  22. from PyQt5.QtWidgets import QDialog, QWidget
  23. # ---------------------------------------------------------------------------------------------------------------------
  24. # Imports (Carla)
  25. from carla_shared import (
  26. HAIKU,
  27. LINUX,
  28. MACOS,
  29. WINDOWS,
  30. gCarla,
  31. getIcon,
  32. kIs64bit,
  33. )
  34. from utils import QSafeSettings
  35. # ---------------------------------------------------------------------------------------------------------------------
  36. # Imports (Local)
  37. from .discovery import killDiscovery
  38. from .discoverythread import SearchPluginsThread
  39. from .pluginlistrefreshdialog_ui import Ui_PluginRefreshW
  40. # ---------------------------------------------------------------------------------------------------------------------
  41. # Plugin Refresh Dialog
  42. class PluginRefreshW(QDialog):
  43. def __init__(self, parent: QWidget, host, useSystemIcons: bool, hasLoadedLv2Plugins: bool):
  44. QDialog.__init__(self, parent)
  45. self.host = host
  46. self.ui = Ui_PluginRefreshW()
  47. self.ui.setupUi(self)
  48. # -------------------------------------------------------------------------------------------------------------
  49. # Internal stuff
  50. toolNative = "carla-discovery-native.exe" if WINDOWS else "carla-discovery-native"
  51. hasNative = os.path.exists(os.path.join(host.pathBinaries, toolNative))
  52. hasPosix32 = os.path.exists(os.path.join(host.pathBinaries, "carla-discovery-posix32"))
  53. hasPosix64 = os.path.exists(os.path.join(host.pathBinaries, "carla-discovery-posix64"))
  54. hasWin32 = os.path.exists(os.path.join(host.pathBinaries, "carla-discovery-win32.exe"))
  55. hasWin64 = os.path.exists(os.path.join(host.pathBinaries, "carla-discovery-win64.exe"))
  56. self.fThread = SearchPluginsThread(self, host.pathBinaries)
  57. # -------------------------------------------------------------------------------------------------------------
  58. # Set-up Icons
  59. if useSystemIcons:
  60. self.ui.b_start.setIcon(getIcon('arrow-right', 16, 'svgz'))
  61. self.ui.b_close.setIcon(getIcon('window-close', 16, 'svgz'))
  62. if QT_VERSION >= 0x50600:
  63. size = int(16 * self.devicePixelRatioF())
  64. else:
  65. size = 16
  66. self.fIconYes = QPixmap(getIcon('dialog-ok-apply', 16, 'svgz').pixmap(size))
  67. self.fIconNo = QPixmap(getIcon('dialog-error', 16, 'svgz').pixmap(size))
  68. else:
  69. self.fIconYes = QPixmap(":/16x16/dialog-ok-apply.svgz")
  70. self.fIconNo = QPixmap(":/16x16/dialog-error.svgz")
  71. # -------------------------------------------------------------------------------------------------------------
  72. # Set-up GUI
  73. # FIXME remove LRDF
  74. self.ui.ico_rdflib.setPixmap(self.fIconNo)
  75. self.ui.b_skip.setVisible(False)
  76. if HAIKU:
  77. self.ui.ch_posix32.setText("Haiku 32bit")
  78. self.ui.ch_posix64.setText("Haiku 64bit")
  79. elif LINUX:
  80. self.ui.ch_posix32.setText("Linux 32bit")
  81. self.ui.ch_posix64.setText("Linux 64bit")
  82. elif MACOS:
  83. self.ui.ch_posix32.setText("MacOS 32bit")
  84. self.ui.ch_posix64.setText("MacOS 64bit")
  85. if hasPosix32 and not WINDOWS:
  86. self.ui.ico_posix32.setPixmap(self.fIconYes)
  87. else:
  88. self.ui.ico_posix32.setPixmap(self.fIconNo)
  89. self.ui.ch_posix32.setEnabled(False)
  90. if hasPosix64 and not WINDOWS:
  91. self.ui.ico_posix64.setPixmap(self.fIconYes)
  92. else:
  93. self.ui.ico_posix64.setPixmap(self.fIconNo)
  94. self.ui.ch_posix64.setEnabled(False)
  95. if hasWin32:
  96. self.ui.ico_win32.setPixmap(self.fIconYes)
  97. else:
  98. self.ui.ico_win32.setPixmap(self.fIconNo)
  99. self.ui.ch_win32.setEnabled(False)
  100. if hasWin64:
  101. self.ui.ico_win64.setPixmap(self.fIconYes)
  102. else:
  103. self.ui.ico_win64.setPixmap(self.fIconNo)
  104. self.ui.ch_win64.setEnabled(False)
  105. if WINDOWS:
  106. if kIs64bit:
  107. hasNonNative = hasWin32
  108. self.ui.ch_win64.setEnabled(False)
  109. self.ui.ch_win64.setVisible(False)
  110. self.ui.ico_win64.setVisible(False)
  111. self.ui.label_win64.setVisible(False)
  112. else:
  113. hasNonNative = hasWin64
  114. self.ui.ch_win32.setEnabled(False)
  115. self.ui.ch_win32.setVisible(False)
  116. self.ui.ico_win32.setVisible(False)
  117. self.ui.label_win32.setVisible(False)
  118. self.ui.ch_posix32.setEnabled(False)
  119. self.ui.ch_posix32.setVisible(False)
  120. self.ui.ch_posix64.setEnabled(False)
  121. self.ui.ch_posix64.setVisible(False)
  122. self.ui.ico_posix32.hide()
  123. self.ui.ico_posix64.hide()
  124. self.ui.label_posix32.hide()
  125. self.ui.label_posix64.hide()
  126. self.ui.ico_rdflib.hide()
  127. self.ui.label_rdflib.hide()
  128. else:
  129. if kIs64bit:
  130. hasNonNative = bool(hasPosix32 or hasWin32 or hasWin64)
  131. self.ui.ch_posix64.setEnabled(False)
  132. self.ui.ch_posix64.setVisible(False)
  133. self.ui.ico_posix64.setVisible(False)
  134. self.ui.label_posix64.setVisible(False)
  135. else:
  136. hasNonNative = bool(hasPosix64 or hasWin32 or hasWin64)
  137. self.ui.ch_posix32.setEnabled(False)
  138. self.ui.ch_posix32.setVisible(False)
  139. self.ui.ico_posix32.setVisible(False)
  140. self.ui.label_posix32.setVisible(False)
  141. if not (LINUX or hasWin32 or hasWin64):
  142. self.ui.ch_vst3.setEnabled(False)
  143. self.ui.ch_vst3.setVisible(False)
  144. if MACOS:
  145. self.setWindowModality(Qt.WindowModal)
  146. else:
  147. self.ui.ch_au.setEnabled(False)
  148. self.ui.ch_au.setVisible(False)
  149. if hasNative:
  150. self.ui.ico_native.setPixmap(self.fIconYes)
  151. else:
  152. self.ui.ico_native.setPixmap(self.fIconNo)
  153. self.ui.ch_native.setEnabled(False)
  154. self.ui.ch_sf2.setEnabled(False)
  155. if not hasNonNative:
  156. self.ui.ch_ladspa.setEnabled(False)
  157. self.ui.ch_dssi.setEnabled(False)
  158. self.ui.ch_vst.setEnabled(False)
  159. self.ui.ch_vst3.setEnabled(False)
  160. if not hasLoadedLv2Plugins:
  161. self.ui.lv2_restart_notice.hide()
  162. self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint)
  163. # -------------------------------------------------------------------------------------------------------------
  164. # Load settings
  165. self.loadSettings()
  166. # -------------------------------------------------------------------------------------------------------------
  167. # Hide bridges if disabled
  168. # NOTE: We Assume win32 carla build will not run win64 plugins
  169. if (WINDOWS and not kIs64bit) or not host.showPluginBridges:
  170. self.ui.ch_native.setChecked(True)
  171. self.ui.ch_native.setEnabled(False)
  172. self.ui.ch_native.setVisible(False)
  173. self.ui.ch_posix32.setChecked(False)
  174. self.ui.ch_posix32.setEnabled(False)
  175. self.ui.ch_posix32.setVisible(False)
  176. self.ui.ch_posix64.setChecked(False)
  177. self.ui.ch_posix64.setEnabled(False)
  178. self.ui.ch_posix64.setVisible(False)
  179. self.ui.ch_win32.setChecked(False)
  180. self.ui.ch_win32.setEnabled(False)
  181. self.ui.ch_win32.setVisible(False)
  182. self.ui.ch_win64.setChecked(False)
  183. self.ui.ch_win64.setEnabled(False)
  184. self.ui.ch_win64.setVisible(False)
  185. self.ui.ico_posix32.hide()
  186. self.ui.ico_posix64.hide()
  187. self.ui.ico_win32.hide()
  188. self.ui.ico_win64.hide()
  189. self.ui.label_posix32.hide()
  190. self.ui.label_posix64.hide()
  191. self.ui.label_win32.hide()
  192. self.ui.label_win64.hide()
  193. self.ui.sep_format.hide()
  194. elif not (WINDOWS or host.showWineBridges):
  195. self.ui.ch_win32.setChecked(False)
  196. self.ui.ch_win32.setEnabled(False)
  197. self.ui.ch_win32.setVisible(False)
  198. self.ui.ch_win64.setChecked(False)
  199. self.ui.ch_win64.setEnabled(False)
  200. self.ui.ch_win64.setVisible(False)
  201. self.ui.ico_win32.hide()
  202. self.ui.ico_win64.hide()
  203. self.ui.label_win32.hide()
  204. self.ui.label_win64.hide()
  205. # Disable non-supported features
  206. features = gCarla.utils.get_supported_features() if gCarla.utils else ()
  207. if "sf2" not in features:
  208. self.ui.ch_sf2.setChecked(False)
  209. self.ui.ch_sf2.setEnabled(False)
  210. if MACOS and "juce" not in features:
  211. self.ui.ch_au.setChecked(False)
  212. self.ui.ch_au.setEnabled(False)
  213. # -------------------------------------------------------------------------------------------------------------
  214. # Resize to minimum size, as it's very likely UI stuff was hidden
  215. self.resize(self.minimumSize())
  216. # -------------------------------------------------------------------------------------------------------------
  217. # Set-up connections
  218. self.finished.connect(self.slot_saveSettings)
  219. self.ui.b_start.clicked.connect(self.slot_start)
  220. self.ui.b_skip.clicked.connect(self.slot_skip)
  221. self.ui.ch_native.clicked.connect(self.slot_checkTools)
  222. self.ui.ch_posix32.clicked.connect(self.slot_checkTools)
  223. self.ui.ch_posix64.clicked.connect(self.slot_checkTools)
  224. self.ui.ch_win32.clicked.connect(self.slot_checkTools)
  225. self.ui.ch_win64.clicked.connect(self.slot_checkTools)
  226. self.ui.ch_ladspa.clicked.connect(self.slot_checkTools)
  227. self.ui.ch_dssi.clicked.connect(self.slot_checkTools)
  228. self.ui.ch_lv2.clicked.connect(self.slot_checkTools)
  229. self.ui.ch_vst.clicked.connect(self.slot_checkTools)
  230. self.ui.ch_vst3.clicked.connect(self.slot_checkTools)
  231. self.ui.ch_au.clicked.connect(self.slot_checkTools)
  232. self.ui.ch_sf2.clicked.connect(self.slot_checkTools)
  233. self.ui.ch_sfz.clicked.connect(self.slot_checkTools)
  234. self.ui.ch_jsfx.clicked.connect(self.slot_checkTools)
  235. self.fThread.pluginLook.connect(self.slot_handlePluginLook)
  236. self.fThread.finished.connect(self.slot_handlePluginThreadFinished)
  237. # -------------------------------------------------------------------------------------------------------------
  238. # Post-connect setup
  239. self.slot_checkTools()
  240. # -----------------------------------------------------------------------------------------------------------------
  241. def loadSettings(self):
  242. settings = QSafeSettings("falkTX", "CarlaRefresh2")
  243. check = settings.value("PluginDatabase/SearchLADSPA", True, bool) and self.ui.ch_ladspa.isEnabled()
  244. self.ui.ch_ladspa.setChecked(check)
  245. check = settings.value("PluginDatabase/SearchDSSI", True, bool) and self.ui.ch_dssi.isEnabled()
  246. self.ui.ch_dssi.setChecked(check)
  247. check = settings.value("PluginDatabase/SearchLV2", True, bool) and self.ui.ch_lv2.isEnabled()
  248. self.ui.ch_lv2.setChecked(check)
  249. check = settings.value("PluginDatabase/SearchVST2", True, bool) and self.ui.ch_vst.isEnabled()
  250. self.ui.ch_vst.setChecked(check)
  251. check = settings.value("PluginDatabase/SearchVST3", True, bool) and self.ui.ch_vst3.isEnabled()
  252. self.ui.ch_vst3.setChecked(check)
  253. if MACOS:
  254. check = settings.value("PluginDatabase/SearchAU", True, bool) and self.ui.ch_au.isEnabled()
  255. else:
  256. check = False
  257. self.ui.ch_au.setChecked(check)
  258. check = settings.value("PluginDatabase/SearchSF2", False, bool) and self.ui.ch_sf2.isEnabled()
  259. self.ui.ch_sf2.setChecked(check)
  260. check = settings.value("PluginDatabase/SearchSFZ", False, bool) and self.ui.ch_sfz.isEnabled()
  261. self.ui.ch_sfz.setChecked(check)
  262. check = settings.value("PluginDatabase/SearchJSFX", True, bool) and self.ui.ch_jsfx.isEnabled()
  263. self.ui.ch_jsfx.setChecked(check)
  264. check = settings.value("PluginDatabase/SearchNative", True, bool) and self.ui.ch_native.isEnabled()
  265. self.ui.ch_native.setChecked(check)
  266. check = settings.value("PluginDatabase/SearchPOSIX32", False, bool) and self.ui.ch_posix32.isEnabled()
  267. self.ui.ch_posix32.setChecked(check)
  268. check = settings.value("PluginDatabase/SearchPOSIX64", False, bool) and self.ui.ch_posix64.isEnabled()
  269. self.ui.ch_posix64.setChecked(check)
  270. check = settings.value("PluginDatabase/SearchWin32", False, bool) and self.ui.ch_win32.isEnabled()
  271. self.ui.ch_win32.setChecked(check)
  272. check = settings.value("PluginDatabase/SearchWin64", False, bool) and self.ui.ch_win64.isEnabled()
  273. self.ui.ch_win64.setChecked(check)
  274. self.ui.ch_do_checks.setChecked(settings.value("PluginDatabase/DoChecks", False, bool))
  275. # -----------------------------------------------------------------------------------------------------------------
  276. @pyqtSlot()
  277. def slot_saveSettings(self):
  278. settings = QSafeSettings("falkTX", "CarlaRefresh2")
  279. settings.setValue("PluginDatabase/SearchLADSPA", self.ui.ch_ladspa.isChecked())
  280. settings.setValue("PluginDatabase/SearchDSSI", self.ui.ch_dssi.isChecked())
  281. settings.setValue("PluginDatabase/SearchLV2", self.ui.ch_lv2.isChecked())
  282. settings.setValue("PluginDatabase/SearchVST2", self.ui.ch_vst.isChecked())
  283. settings.setValue("PluginDatabase/SearchVST3", self.ui.ch_vst3.isChecked())
  284. settings.setValue("PluginDatabase/SearchAU", self.ui.ch_au.isChecked())
  285. settings.setValue("PluginDatabase/SearchSF2", self.ui.ch_sf2.isChecked())
  286. settings.setValue("PluginDatabase/SearchSFZ", self.ui.ch_sfz.isChecked())
  287. settings.setValue("PluginDatabase/SearchJSFX", self.ui.ch_jsfx.isChecked())
  288. settings.setValue("PluginDatabase/SearchNative", self.ui.ch_native.isChecked())
  289. settings.setValue("PluginDatabase/SearchPOSIX32", self.ui.ch_posix32.isChecked())
  290. settings.setValue("PluginDatabase/SearchPOSIX64", self.ui.ch_posix64.isChecked())
  291. settings.setValue("PluginDatabase/SearchWin32", self.ui.ch_win32.isChecked())
  292. settings.setValue("PluginDatabase/SearchWin64", self.ui.ch_win64.isChecked())
  293. settings.setValue("PluginDatabase/DoChecks", self.ui.ch_do_checks.isChecked())
  294. # -----------------------------------------------------------------------------------------------------------------
  295. @pyqtSlot()
  296. def slot_start(self):
  297. self.ui.progressBar.setMinimum(0)
  298. self.ui.progressBar.setMaximum(100)
  299. self.ui.progressBar.setValue(0)
  300. self.ui.b_start.setEnabled(False)
  301. self.ui.b_skip.setVisible(True)
  302. self.ui.b_close.setVisible(False)
  303. self.ui.group_types.setEnabled(False)
  304. self.ui.group_options.setEnabled(False)
  305. if gCarla.utils:
  306. if self.ui.ch_do_checks.isChecked():
  307. gCarla.utils.unsetenv("CARLA_DISCOVERY_NO_PROCESSING_CHECKS")
  308. else:
  309. gCarla.utils.setenv("CARLA_DISCOVERY_NO_PROCESSING_CHECKS", "true")
  310. native, posix32, posix64, win32, win64 = (self.ui.ch_native.isChecked(),
  311. self.ui.ch_posix32.isChecked(), self.ui.ch_posix64.isChecked(),
  312. self.ui.ch_win32.isChecked(), self.ui.ch_win64.isChecked())
  313. ladspa, dssi, lv2, vst, vst3, au, sf2, sfz, jsfx = (self.ui.ch_ladspa.isChecked(), self.ui.ch_dssi.isChecked(),
  314. self.ui.ch_lv2.isChecked(), self.ui.ch_vst.isChecked(),
  315. self.ui.ch_vst3.isChecked(), self.ui.ch_au.isChecked(),
  316. self.ui.ch_sf2.isChecked(), self.ui.ch_sfz.isChecked(),
  317. self.ui.ch_jsfx.isChecked())
  318. self.fThread.setSearchBinaryTypes(native, posix32, posix64, win32, win64)
  319. self.fThread.setSearchPluginTypes(ladspa, dssi, lv2, vst, vst3, au, sf2, sfz, jsfx)
  320. self.fThread.start()
  321. # -----------------------------------------------------------------------------------------------------------------
  322. @pyqtSlot()
  323. def slot_skip(self):
  324. killDiscovery()
  325. # -----------------------------------------------------------------------------------------------------------------
  326. @pyqtSlot()
  327. def slot_checkTools(self):
  328. enabled1 = bool(self.ui.ch_native.isChecked() or
  329. self.ui.ch_posix32.isChecked() or self.ui.ch_posix64.isChecked() or
  330. self.ui.ch_win32.isChecked() or self.ui.ch_win64.isChecked())
  331. enabled2 = bool(self.ui.ch_ladspa.isChecked() or self.ui.ch_dssi.isChecked() or
  332. self.ui.ch_lv2.isChecked() or self.ui.ch_vst.isChecked() or
  333. self.ui.ch_vst3.isChecked() or self.ui.ch_au.isChecked() or
  334. self.ui.ch_sf2.isChecked() or self.ui.ch_sfz.isChecked() or
  335. self.ui.ch_jsfx.isChecked())
  336. self.ui.b_start.setEnabled(enabled1 and enabled2)
  337. # -----------------------------------------------------------------------------------------------------------------
  338. @pyqtSlot(float, str)
  339. def slot_handlePluginLook(self, percent, plugin):
  340. self.ui.progressBar.setFormat(plugin)
  341. self.ui.progressBar.setValue(int(percent))
  342. # -----------------------------------------------------------------------------------------------------------------
  343. @pyqtSlot()
  344. def slot_handlePluginThreadFinished(self):
  345. self.ui.progressBar.setMinimum(0)
  346. self.ui.progressBar.setMaximum(1)
  347. self.ui.progressBar.setValue(1)
  348. self.ui.progressBar.setFormat(self.tr("Done"))
  349. self.ui.b_start.setEnabled(True)
  350. self.ui.b_skip.setVisible(False)
  351. self.ui.b_close.setVisible(True)
  352. self.ui.group_types.setEnabled(True)
  353. self.ui.group_options.setEnabled(True)
  354. # -----------------------------------------------------------------------------------------------------------------
  355. def closeEvent(self, event):
  356. if self.fThread.isRunning():
  357. self.fThread.stop()
  358. killDiscovery()
  359. #self.fThread.terminate()
  360. self.fThread.wait()
  361. if self.fThread.hasSomethingChanged():
  362. self.accept()
  363. else:
  364. self.reject()
  365. QDialog.closeEvent(self, event)
  366. # ---------------------------------------------------------------------------------------------------------------------
  367. # Testing
  368. if __name__ == '__main__':
  369. import sys
  370. # pylint: disable=ungrouped-imports
  371. from PyQt5.QtWidgets import QApplication
  372. # pylint: enable=ungrouped-imports
  373. class _host:
  374. pathBinaries = ""
  375. showPluginBridges = False
  376. showWineBridges = False
  377. _host = _host()
  378. _app = QApplication(sys.argv)
  379. _gui = PluginRefreshW(None, _host, True, False)
  380. _app.exec_()
  381. # ---------------------------------------------------------------------------------------------------------------------