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.

474 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 MACOS:
  142. self.setWindowModality(Qt.WindowModal)
  143. else:
  144. self.ui.ch_au.setEnabled(False)
  145. self.ui.ch_au.setVisible(False)
  146. if hasNative:
  147. self.ui.ico_native.setPixmap(self.fIconYes)
  148. else:
  149. self.ui.ico_native.setPixmap(self.fIconNo)
  150. self.ui.ch_native.setEnabled(False)
  151. self.ui.ch_sf2.setEnabled(False)
  152. if not hasNonNative:
  153. self.ui.ch_ladspa.setEnabled(False)
  154. self.ui.ch_dssi.setEnabled(False)
  155. self.ui.ch_vst.setEnabled(False)
  156. self.ui.ch_vst3.setEnabled(False)
  157. self.ui.ch_clap.setEnabled(False)
  158. if not hasLoadedLv2Plugins:
  159. self.ui.lv2_restart_notice.hide()
  160. self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint)
  161. # -------------------------------------------------------------------------------------------------------------
  162. # Load settings
  163. self.loadSettings()
  164. # -------------------------------------------------------------------------------------------------------------
  165. # Hide bridges if disabled
  166. # NOTE: We Assume win32 carla build will not run win64 plugins
  167. if (WINDOWS and not kIs64bit) or not host.showPluginBridges:
  168. self.ui.ch_native.setChecked(True)
  169. self.ui.ch_native.setEnabled(False)
  170. self.ui.ch_native.setVisible(False)
  171. self.ui.ch_posix32.setChecked(False)
  172. self.ui.ch_posix32.setEnabled(False)
  173. self.ui.ch_posix32.setVisible(False)
  174. self.ui.ch_posix64.setChecked(False)
  175. self.ui.ch_posix64.setEnabled(False)
  176. self.ui.ch_posix64.setVisible(False)
  177. self.ui.ch_win32.setChecked(False)
  178. self.ui.ch_win32.setEnabled(False)
  179. self.ui.ch_win32.setVisible(False)
  180. self.ui.ch_win64.setChecked(False)
  181. self.ui.ch_win64.setEnabled(False)
  182. self.ui.ch_win64.setVisible(False)
  183. self.ui.ico_posix32.hide()
  184. self.ui.ico_posix64.hide()
  185. self.ui.ico_win32.hide()
  186. self.ui.ico_win64.hide()
  187. self.ui.label_posix32.hide()
  188. self.ui.label_posix64.hide()
  189. self.ui.label_win32.hide()
  190. self.ui.label_win64.hide()
  191. self.ui.sep_format.hide()
  192. elif not (WINDOWS or host.showWineBridges):
  193. self.ui.ch_win32.setChecked(False)
  194. self.ui.ch_win32.setEnabled(False)
  195. self.ui.ch_win32.setVisible(False)
  196. self.ui.ch_win64.setChecked(False)
  197. self.ui.ch_win64.setEnabled(False)
  198. self.ui.ch_win64.setVisible(False)
  199. self.ui.ico_win32.hide()
  200. self.ui.ico_win64.hide()
  201. self.ui.label_win32.hide()
  202. self.ui.label_win64.hide()
  203. # Disable non-supported features
  204. features = gCarla.utils.get_supported_features() if gCarla.utils else ()
  205. if "sf2" not in features:
  206. self.ui.ch_sf2.setChecked(False)
  207. self.ui.ch_sf2.setEnabled(False)
  208. if MACOS and "juce" not in features:
  209. self.ui.ch_au.setChecked(False)
  210. self.ui.ch_au.setEnabled(False)
  211. # -------------------------------------------------------------------------------------------------------------
  212. # Resize to minimum size, as it's very likely UI stuff was hidden
  213. self.resize(self.minimumSize())
  214. # -------------------------------------------------------------------------------------------------------------
  215. # Set-up connections
  216. self.finished.connect(self.slot_saveSettings)
  217. self.ui.b_start.clicked.connect(self.slot_start)
  218. self.ui.b_skip.clicked.connect(self.slot_skip)
  219. self.ui.ch_native.clicked.connect(self.slot_checkTools)
  220. self.ui.ch_posix32.clicked.connect(self.slot_checkTools)
  221. self.ui.ch_posix64.clicked.connect(self.slot_checkTools)
  222. self.ui.ch_win32.clicked.connect(self.slot_checkTools)
  223. self.ui.ch_win64.clicked.connect(self.slot_checkTools)
  224. self.ui.ch_ladspa.clicked.connect(self.slot_checkTools)
  225. self.ui.ch_dssi.clicked.connect(self.slot_checkTools)
  226. self.ui.ch_lv2.clicked.connect(self.slot_checkTools)
  227. self.ui.ch_vst.clicked.connect(self.slot_checkTools)
  228. self.ui.ch_vst3.clicked.connect(self.slot_checkTools)
  229. self.ui.ch_clap.clicked.connect(self.slot_checkTools)
  230. self.ui.ch_au.clicked.connect(self.slot_checkTools)
  231. self.ui.ch_sf2.clicked.connect(self.slot_checkTools)
  232. self.ui.ch_sfz.clicked.connect(self.slot_checkTools)
  233. self.ui.ch_jsfx.clicked.connect(self.slot_checkTools)
  234. self.fThread.pluginLook.connect(self.slot_handlePluginLook)
  235. self.fThread.finished.connect(self.slot_handlePluginThreadFinished)
  236. # -------------------------------------------------------------------------------------------------------------
  237. # Post-connect setup
  238. self.slot_checkTools()
  239. # -----------------------------------------------------------------------------------------------------------------
  240. def loadSettings(self):
  241. settings = QSafeSettings("falkTX", "CarlaRefresh2")
  242. check = settings.value("PluginDatabase/SearchLADSPA", True, bool) and self.ui.ch_ladspa.isEnabled()
  243. self.ui.ch_ladspa.setChecked(check)
  244. check = settings.value("PluginDatabase/SearchDSSI", True, bool) and self.ui.ch_dssi.isEnabled()
  245. self.ui.ch_dssi.setChecked(check)
  246. check = settings.value("PluginDatabase/SearchLV2", True, bool) and self.ui.ch_lv2.isEnabled()
  247. self.ui.ch_lv2.setChecked(check)
  248. check = settings.value("PluginDatabase/SearchVST2", True, bool) and self.ui.ch_vst.isEnabled()
  249. self.ui.ch_vst.setChecked(check)
  250. check = settings.value("PluginDatabase/SearchVST3", True, bool) and self.ui.ch_vst3.isEnabled()
  251. self.ui.ch_vst3.setChecked(check)
  252. check = settings.value("PluginDatabase/SearchCLAP", True, bool) and self.ui.ch_clap.isEnabled()
  253. self.ui.ch_clap.setChecked(check)
  254. if MACOS:
  255. check = settings.value("PluginDatabase/SearchAU", True, bool) and self.ui.ch_au.isEnabled()
  256. else:
  257. check = False
  258. self.ui.ch_au.setChecked(check)
  259. check = settings.value("PluginDatabase/SearchSF2", False, bool) and self.ui.ch_sf2.isEnabled()
  260. self.ui.ch_sf2.setChecked(check)
  261. check = settings.value("PluginDatabase/SearchSFZ", False, bool) and self.ui.ch_sfz.isEnabled()
  262. self.ui.ch_sfz.setChecked(check)
  263. check = settings.value("PluginDatabase/SearchJSFX", True, bool) and self.ui.ch_jsfx.isEnabled()
  264. self.ui.ch_jsfx.setChecked(check)
  265. check = settings.value("PluginDatabase/SearchNative", True, bool) and self.ui.ch_native.isEnabled()
  266. self.ui.ch_native.setChecked(check)
  267. check = settings.value("PluginDatabase/SearchPOSIX32", False, bool) and self.ui.ch_posix32.isEnabled()
  268. self.ui.ch_posix32.setChecked(check)
  269. check = settings.value("PluginDatabase/SearchPOSIX64", False, bool) and self.ui.ch_posix64.isEnabled()
  270. self.ui.ch_posix64.setChecked(check)
  271. check = settings.value("PluginDatabase/SearchWin32", False, bool) and self.ui.ch_win32.isEnabled()
  272. self.ui.ch_win32.setChecked(check)
  273. check = settings.value("PluginDatabase/SearchWin64", False, bool) and self.ui.ch_win64.isEnabled()
  274. self.ui.ch_win64.setChecked(check)
  275. self.ui.ch_do_checks.setChecked(settings.value("PluginDatabase/DoChecks", False, bool))
  276. # -----------------------------------------------------------------------------------------------------------------
  277. @pyqtSlot()
  278. def slot_saveSettings(self):
  279. settings = QSafeSettings("falkTX", "CarlaRefresh2")
  280. settings.setValue("PluginDatabase/SearchLADSPA", self.ui.ch_ladspa.isChecked())
  281. settings.setValue("PluginDatabase/SearchDSSI", self.ui.ch_dssi.isChecked())
  282. settings.setValue("PluginDatabase/SearchLV2", self.ui.ch_lv2.isChecked())
  283. settings.setValue("PluginDatabase/SearchVST2", self.ui.ch_vst.isChecked())
  284. settings.setValue("PluginDatabase/SearchVST3", self.ui.ch_vst3.isChecked())
  285. settings.setValue("PluginDatabase/SearchCLAP", self.ui.ch_clap.isChecked())
  286. settings.setValue("PluginDatabase/SearchAU", self.ui.ch_au.isChecked())
  287. settings.setValue("PluginDatabase/SearchSF2", self.ui.ch_sf2.isChecked())
  288. settings.setValue("PluginDatabase/SearchSFZ", self.ui.ch_sfz.isChecked())
  289. settings.setValue("PluginDatabase/SearchJSFX", self.ui.ch_jsfx.isChecked())
  290. settings.setValue("PluginDatabase/SearchNative", self.ui.ch_native.isChecked())
  291. settings.setValue("PluginDatabase/SearchPOSIX32", self.ui.ch_posix32.isChecked())
  292. settings.setValue("PluginDatabase/SearchPOSIX64", self.ui.ch_posix64.isChecked())
  293. settings.setValue("PluginDatabase/SearchWin32", self.ui.ch_win32.isChecked())
  294. settings.setValue("PluginDatabase/SearchWin64", self.ui.ch_win64.isChecked())
  295. settings.setValue("PluginDatabase/DoChecks", self.ui.ch_do_checks.isChecked())
  296. # -----------------------------------------------------------------------------------------------------------------
  297. @pyqtSlot()
  298. def slot_start(self):
  299. self.ui.progressBar.setMinimum(0)
  300. self.ui.progressBar.setMaximum(100)
  301. self.ui.progressBar.setValue(0)
  302. self.ui.b_start.setEnabled(False)
  303. self.ui.b_skip.setVisible(True)
  304. self.ui.b_close.setVisible(False)
  305. self.ui.group_types.setEnabled(False)
  306. self.ui.group_options.setEnabled(False)
  307. if gCarla.utils:
  308. if self.ui.ch_do_checks.isChecked():
  309. gCarla.utils.unsetenv("CARLA_DISCOVERY_NO_PROCESSING_CHECKS")
  310. else:
  311. gCarla.utils.setenv("CARLA_DISCOVERY_NO_PROCESSING_CHECKS", "true")
  312. native, posix32, posix64, win32, win64 = (self.ui.ch_native.isChecked(),
  313. self.ui.ch_posix32.isChecked(), self.ui.ch_posix64.isChecked(),
  314. self.ui.ch_win32.isChecked(), self.ui.ch_win64.isChecked())
  315. ladspa, dssi, lv2, vst, vst3, clap, au, sf2, sfz, jsfx = (self.ui.ch_ladspa.isChecked(), self.ui.ch_dssi.isChecked(),
  316. self.ui.ch_lv2.isChecked(), self.ui.ch_vst.isChecked(),
  317. self.ui.ch_vst3.isChecked(), self.ui.ch_clap.isChecked(),
  318. self.ui.ch_au.isChecked(), self.ui.ch_sf2.isChecked(),
  319. self.ui.ch_sfz.isChecked(), self.ui.ch_jsfx.isChecked())
  320. self.fThread.setSearchBinaryTypes(native, posix32, posix64, win32, win64)
  321. self.fThread.setSearchPluginTypes(ladspa, dssi, lv2, vst, vst3, clap, au, sf2, sfz, jsfx)
  322. self.fThread.start()
  323. # -----------------------------------------------------------------------------------------------------------------
  324. @pyqtSlot()
  325. def slot_skip(self):
  326. killDiscovery()
  327. # -----------------------------------------------------------------------------------------------------------------
  328. @pyqtSlot()
  329. def slot_checkTools(self):
  330. enabled1 = bool(self.ui.ch_native.isChecked() or
  331. self.ui.ch_posix32.isChecked() or self.ui.ch_posix64.isChecked() or
  332. self.ui.ch_win32.isChecked() or self.ui.ch_win64.isChecked())
  333. enabled2 = bool(self.ui.ch_ladspa.isChecked() or self.ui.ch_dssi.isChecked() or
  334. self.ui.ch_lv2.isChecked() or self.ui.ch_vst.isChecked() or
  335. self.ui.ch_vst3.isChecked() or self.ui.ch_clap.isChecked() or
  336. self.ui.ch_au.isChecked() or self.ui.ch_sf2.isChecked() or
  337. self.ui.ch_sfz.isChecked() or self.ui.ch_jsfx.isChecked())
  338. self.ui.b_start.setEnabled(enabled1 and enabled2)
  339. # -----------------------------------------------------------------------------------------------------------------
  340. @pyqtSlot(float, str)
  341. def slot_handlePluginLook(self, percent, plugin):
  342. self.ui.progressBar.setFormat(plugin)
  343. self.ui.progressBar.setValue(int(percent))
  344. # -----------------------------------------------------------------------------------------------------------------
  345. @pyqtSlot()
  346. def slot_handlePluginThreadFinished(self):
  347. self.ui.progressBar.setMinimum(0)
  348. self.ui.progressBar.setMaximum(1)
  349. self.ui.progressBar.setValue(1)
  350. self.ui.progressBar.setFormat(self.tr("Done"))
  351. self.ui.b_start.setEnabled(True)
  352. self.ui.b_skip.setVisible(False)
  353. self.ui.b_close.setVisible(True)
  354. self.ui.group_types.setEnabled(True)
  355. self.ui.group_options.setEnabled(True)
  356. # -----------------------------------------------------------------------------------------------------------------
  357. def closeEvent(self, event):
  358. if self.fThread.isRunning():
  359. self.fThread.stop()
  360. killDiscovery()
  361. #self.fThread.terminate()
  362. self.fThread.wait()
  363. if self.fThread.hasSomethingChanged():
  364. self.accept()
  365. else:
  366. self.reject()
  367. QDialog.closeEvent(self, event)
  368. # ---------------------------------------------------------------------------------------------------------------------
  369. # Testing
  370. if __name__ == '__main__':
  371. import sys
  372. # pylint: disable=ungrouped-imports
  373. from PyQt5.QtWidgets import QApplication
  374. # pylint: enable=ungrouped-imports
  375. class _host:
  376. pathBinaries = ""
  377. showPluginBridges = False
  378. showWineBridges = False
  379. _host = _host()
  380. _app = QApplication(sys.argv)
  381. _gui = PluginRefreshW(None, _host, True, False)
  382. _app.exec_()
  383. # ---------------------------------------------------------------------------------------------------------------------