1 // SPDX-FileCopyrightText: 2011-2024 Filipe Coelho <falktx@falktx.com>
2 // SPDX-License-Identifier: GPL-2.0-or-later
4 #include "CarlaUtils.h"
6 #include "CarlaBackendUtils.hpp"
7 #include "CarlaBinaryUtils.hpp"
8 #include "CarlaJuceUtils.hpp"
9 #include "CarlaPipeUtils.hpp"
10 #include "CarlaSha1Utils.hpp"
11 #include "CarlaTimeUtils.hpp"
13 #include "water/files/File.h"
14 #include "water/files/FileInputStream.h"
15 #include "water/threads/ChildProcess.h"
16 #include "water/text/StringArray.h"
18 namespace CB
= CARLA_BACKEND_NAMESPACE
;
20 // --------------------------------------------------------------------------------------------------------------------
23 static water::String
findWinePrefix(const water::String filename
, const int recursionLimit
= 10)
25 if (recursionLimit
== 0 || filename
.length() < 5 || ! filename
.contains("/"))
28 const water::String
path(filename
.upToLastOccurrenceOf("/", false, false));
30 if (water::File(water::String(path
+ "/dosdevices").toRawUTF8()).isDirectory())
33 return findWinePrefix(path
, recursionLimit
-1);
37 // --------------------------------------------------------------------------------------------------------------------
39 static const char* const gPluginsDiscoveryNullCharPtr
= "";
41 _CarlaPluginDiscoveryMetadata::_CarlaPluginDiscoveryMetadata() noexcept
42 : name(gPluginsDiscoveryNullCharPtr
),
43 maker(gPluginsDiscoveryNullCharPtr
),
44 category(CB::PLUGIN_CATEGORY_NONE
),
47 _CarlaPluginDiscoveryIO::_CarlaPluginDiscoveryIO() noexcept
57 _CarlaPluginDiscoveryInfo::_CarlaPluginDiscoveryInfo() noexcept
58 : btype(CB::BINARY_NONE
),
59 ptype(CB::PLUGIN_NONE
),
60 filename(gPluginsDiscoveryNullCharPtr
),
61 label(gPluginsDiscoveryNullCharPtr
),
65 // --------------------------------------------------------------------------------------------------------------------
67 struct CarlaPluginDiscoveryOptions
{
68 #if !defined(BUILD_BRIDGE_ALTERNATIVE_ARCH) && !defined(CARLA_OS_WIN)
71 CarlaString executable
;
72 CarlaString fallbackPrefix
;
76 static CarlaPluginDiscoveryOptions
& getInstance() noexcept
78 static CarlaPluginDiscoveryOptions instance
;
83 // --------------------------------------------------------------------------------------------------------------------
85 class CarlaPluginDiscovery
: private CarlaPipeServer
88 CarlaPluginDiscovery(const char* const discoveryTool
,
89 const BinaryType btype
,
90 const PluginType ptype
,
91 const std::vector
<water::File
>&& binaries
,
92 const CarlaPluginDiscoveryCallback discoveryCb
,
93 const CarlaPluginCheckCacheCallback checkCacheCb
,
94 void* const callbackPtr
)
97 fDiscoveryCallback(discoveryCb
),
98 fCheckCacheCallback(checkCacheCb
),
99 fCallbackPtr(callbackPtr
),
100 fPluginPath(nullptr),
101 fPluginsFoundInBinary(false),
103 fBinaryCount(static_cast<uint
>(binaries
.size())),
105 fDiscoveryTool(discoveryTool
),
114 CarlaPluginDiscovery(const char* const discoveryTool
,
115 const BinaryType btype
,
116 const PluginType ptype
,
117 const CarlaPluginDiscoveryCallback discoveryCb
,
118 const CarlaPluginCheckCacheCallback checkCacheCb
,
119 void* const callbackPtr
,
120 const char* const pluginPath
= nullptr)
121 : fBinaryType(btype
),
123 fDiscoveryCallback(discoveryCb
),
124 fCheckCacheCallback(checkCacheCb
),
125 fCallbackPtr(callbackPtr
),
126 fPluginPath(pluginPath
!= nullptr ? carla_strdup_safe(pluginPath
) : nullptr),
127 fPluginsFoundInBinary(false),
130 fDiscoveryTool(discoveryTool
),
139 ~CarlaPluginDiscovery()
141 stopPipeServer(5000);
142 std::free(fNextLabel
);
143 std::free(fNextMaker
);
144 std::free(fNextName
);
145 delete[] fPluginPath
;
154 // automatically skip a plugin if 30s passes without a reply
155 const uint32_t timeNow
= carla_gettime_ms();
157 if (timeNow
- fLastMessageTime
< 30000)
160 carla_stdout("Plugin took too long to respond, skipping...");
161 stopPipeServer(1000);
164 // report binary as having no plugins
165 if (fCheckCacheCallback
!= nullptr && !fPluginsFoundInBinary
&& !fBinaries
.empty())
167 const water::File
file(fBinaries
[fBinaryIndex
]);
168 const water::String
filename(file
.getFullPathName());
170 makeHash(file
, filename
);
172 if (! fCheckCacheCallback(fCallbackPtr
, filename
.toRawUTF8(), fNextSha1Sum
))
173 fDiscoveryCallback(fCallbackPtr
, nullptr, fNextSha1Sum
);
176 if (++fBinaryIndex
== fBinaryCount
)
186 stopPipeServer(1000);
190 bool msgReceived(const char* const msg
) noexcept
192 fLastMessageTime
= carla_gettime_ms();
194 if (std::strcmp(msg
, "warning") == 0 || std::strcmp(msg
, "error") == 0)
196 const char* text
= nullptr;
197 readNextLineAsString(text
, false);
198 carla_stdout("discovery: %s", text
);
202 if (std::strcmp(msg
, "init") == 0)
205 readNextLineAsString(_
, false);
206 new (&fNextInfo
) _CarlaPluginDiscoveryInfo();
210 if (std::strcmp(msg
, "end") == 0)
213 readNextLineAsString(_
, false);
215 if (fNextInfo
.label
== nullptr)
216 fNextInfo
.label
= gPluginsDiscoveryNullCharPtr
;
218 if (fNextInfo
.metadata
.maker
== nullptr)
219 fNextInfo
.metadata
.maker
= gPluginsDiscoveryNullCharPtr
;
221 if (fNextInfo
.metadata
.name
== nullptr)
222 fNextInfo
.metadata
.name
= gPluginsDiscoveryNullCharPtr
;
224 if (fBinaries
.empty())
226 char* filename
= nullptr;
228 if (fPluginType
== CB::PLUGIN_LV2
)
231 const char* const slash
= std::strchr(fNextLabel
, CARLA_OS_SEP
);
232 CARLA_SAFE_ASSERT_BREAK(slash
!= nullptr);
233 filename
= strdup(fNextLabel
);
234 filename
[slash
- fNextLabel
] = '\0';
235 fNextInfo
.filename
= filename
;
236 fNextInfo
.label
= slash
+ 1;
240 fNextInfo
.ptype
= fPluginType
;
241 fDiscoveryCallback(fCallbackPtr
, &fNextInfo
, nullptr);
247 CARLA_SAFE_ASSERT(fNextSha1Sum
.isNotEmpty());
248 const water::String
filename(fBinaries
[fBinaryIndex
].getFullPathName());
249 fNextInfo
.filename
= filename
.toRawUTF8();
250 fNextInfo
.ptype
= fPluginType
;
251 fPluginsFoundInBinary
= true;
252 carla_stdout("Found %s from %s", fNextInfo
.metadata
.name
, fNextInfo
.filename
);
253 fDiscoveryCallback(fCallbackPtr
, &fNextInfo
, fNextSha1Sum
);
256 std::free(fNextLabel
);
257 fNextLabel
= nullptr;
259 std::free(fNextMaker
);
260 fNextMaker
= nullptr;
262 std::free(fNextName
);
268 if (std::strcmp(msg
, "build") == 0)
271 readNextLineAsByte(btype
);
272 fNextInfo
.btype
= static_cast<BinaryType
>(btype
);
276 if (std::strcmp(msg
, "hints") == 0)
278 readNextLineAsUInt(fNextInfo
.metadata
.hints
);
282 if (std::strcmp(msg
, "category") == 0)
284 const char* category
= nullptr;
285 readNextLineAsString(category
, false);
286 fNextInfo
.metadata
.category
= CB::getPluginCategoryFromString(category
);
290 if (std::strcmp(msg
, "name") == 0)
292 fNextInfo
.metadata
.name
= fNextName
= readNextLineAsString();
296 if (std::strcmp(msg
, "label") == 0)
298 fNextInfo
.label
= fNextLabel
= readNextLineAsString();
302 if (std::strcmp(msg
, "maker") == 0)
304 fNextInfo
.metadata
.maker
= fNextMaker
= readNextLineAsString();
308 if (std::strcmp(msg
, "uniqueId") == 0)
310 readNextLineAsULong(fNextInfo
.uniqueId
);
314 if (std::strcmp(msg
, "audio.ins") == 0)
316 readNextLineAsUInt(fNextInfo
.io
.audioIns
);
320 if (std::strcmp(msg
, "audio.outs") == 0)
322 readNextLineAsUInt(fNextInfo
.io
.audioOuts
);
326 if (std::strcmp(msg
, "cv.ins") == 0)
328 readNextLineAsUInt(fNextInfo
.io
.cvIns
);
332 if (std::strcmp(msg
, "cv.outs") == 0)
334 readNextLineAsUInt(fNextInfo
.io
.cvOuts
);
338 if (std::strcmp(msg
, "midi.ins") == 0)
340 readNextLineAsUInt(fNextInfo
.io
.midiIns
);
344 if (std::strcmp(msg
, "midi.outs") == 0)
346 readNextLineAsUInt(fNextInfo
.io
.midiOuts
);
350 if (std::strcmp(msg
, "parameters.ins") == 0)
352 readNextLineAsUInt(fNextInfo
.io
.parameterIns
);
356 if (std::strcmp(msg
, "parameters.outs") == 0)
358 readNextLineAsUInt(fNextInfo
.io
.parameterOuts
);
362 if (std::strcmp(msg
, "exiting") == 0)
364 stopPipeServer(1000);
368 carla_stdout("discovery: unknown message '%s' received", msg
);
373 const BinaryType fBinaryType
;
374 const PluginType fPluginType
;
375 const CarlaPluginDiscoveryCallback fDiscoveryCallback
;
376 const CarlaPluginCheckCacheCallback fCheckCacheCallback
;
377 void* const fCallbackPtr
;
378 const char* fPluginPath
;
380 bool fPluginsFoundInBinary
;
382 const uint fBinaryCount
;
383 const std::vector
<water::File
> fBinaries
;
384 const CarlaString fDiscoveryTool
;
386 uint32_t fLastMessageTime
;
388 CarlaPluginDiscoveryInfo fNextInfo
;
389 CarlaString fNextSha1Sum
;
399 fLastMessageTime
= carla_gettime_ms();
400 fPluginsFoundInBinary
= false;
401 fNextSha1Sum
.clear();
404 const CarlaPluginDiscoveryOptions
& options(CarlaPluginDiscoveryOptions::getInstance());
410 case CB::BINARY_WIN32
:
411 if (options
.wine
.executable
.isNotEmpty())
412 helperTool
= options
.wine
.executable
.buffer();
417 case CB::BINARY_WIN64
:
418 if (options
.wine
.executable
.isNotEmpty())
420 helperTool
= options
.wine
.executable
.buffer();
422 if (helperTool
.isNotEmpty() && helperTool
[0] == CARLA_OS_SEP
&& File(String(helperTool
+ "64").toRawUTF8()).existsAsFile())
437 if (options
.wine
.autoPrefix
&& !fBinaries
.empty())
439 const File
file(fBinaries
[fBinaryIndex
]);
440 const String
filename(file
.getFullPathName());
442 winePrefix
= findWinePrefix(filename
);
445 if (winePrefix
.isEmpty())
447 const char* const envWinePrefix
= std::getenv("WINEPREFIX");
449 if (envWinePrefix
!= nullptr && envWinePrefix
[0] != '\0')
450 winePrefix
= envWinePrefix
;
451 else if (options
.wine
.fallbackPrefix
.isNotEmpty())
452 winePrefix
= options
.wine
.fallbackPrefix
.buffer();
454 winePrefix
= File::getSpecialLocation(File::userHomeDirectory
).getFullPathName() + "/.wine";
457 const CarlaScopedEnvVar
sev1("WINEDEBUG", "-all");
458 const CarlaScopedEnvVar
sev2("WINEPREFIX", winePrefix
.toRawUTF8());
461 const CarlaScopedEnvVar
sev3("CARLA_DISCOVERY_NO_PROCESSING_CHECKS", "1");
463 if (fBinaries
.empty())
465 if (fBinaryType
== CB::BINARY_NATIVE
)
471 case CB::PLUGIN_INTERNAL
:
473 case CB::PLUGIN_JSFX
:
475 if (const uint count
= carla_get_cached_plugin_count(fPluginType
, fPluginPath
))
477 for (uint i
=0; i
<count
; ++i
)
479 const CarlaCachedPluginInfo
* const pinfo
= carla_get_cached_plugin_info(fPluginType
, i
);
481 if (pinfo
== nullptr || !pinfo
->valid
)
484 char* filename
= nullptr;
485 CarlaPluginDiscoveryInfo info
= {};
486 info
.btype
= CB::BINARY_NATIVE
;
487 info
.ptype
= fPluginType
;
488 info
.metadata
.name
= pinfo
->name
;
489 info
.metadata
.maker
= pinfo
->maker
;
490 info
.metadata
.category
= pinfo
->category
;
491 info
.metadata
.hints
= pinfo
->hints
;
492 info
.io
.audioIns
= pinfo
->audioIns
;
493 info
.io
.audioOuts
= pinfo
->audioOuts
;
494 info
.io
.cvIns
= pinfo
->cvIns
;
495 info
.io
.cvOuts
= pinfo
->cvOuts
;
496 info
.io
.midiIns
= pinfo
->midiIns
;
497 info
.io
.midiOuts
= pinfo
->midiOuts
;
498 info
.io
.parameterIns
= pinfo
->parameterIns
;
499 info
.io
.parameterOuts
= pinfo
->parameterOuts
;
501 if (fPluginType
== CB::PLUGIN_LV2
)
503 const char* const slash
= std::strchr(pinfo
->label
, CARLA_OS_SEP
);
504 CARLA_SAFE_ASSERT_BREAK(slash
!= nullptr);
505 filename
= strdup(pinfo
->label
);
506 filename
[slash
- pinfo
->label
] = '\0';
507 info
.filename
= filename
;
508 info
.label
= slash
+ 1;
512 info
.filename
= gPluginsDiscoveryNullCharPtr
;
513 info
.label
= pinfo
->label
;
516 fDiscoveryCallback(fCallbackPtr
, &info
, nullptr);
526 if (helperTool
.isNotEmpty())
527 startPipeServer(helperTool
.toRawUTF8(), fDiscoveryTool
, getPluginTypeAsString(fPluginType
), ":all", -1, 2000);
530 startPipeServer(fDiscoveryTool
, getPluginTypeAsString(fPluginType
), ":all", -1, 2000);
534 const File
file(fBinaries
[fBinaryIndex
]);
535 const String
filename(file
.getFullPathName());
537 if (fCheckCacheCallback
!= nullptr)
539 makeHash(file
, filename
);
541 if (fCheckCacheCallback(fCallbackPtr
, filename
.toRawUTF8(), fNextSha1Sum
))
543 fPluginsFoundInBinary
= true;
544 carla_debug("Skipping \"%s\", using cache", filename
.toRawUTF8());
549 carla_stdout("Scanning \"%s\"...", filename
.toRawUTF8());
552 if (helperTool
.isNotEmpty())
553 startPipeServer(helperTool
.toRawUTF8(), fDiscoveryTool
, getPluginTypeAsString(fPluginType
), filename
.toRawUTF8(), -1, 2000);
556 startPipeServer(fDiscoveryTool
, getPluginTypeAsString(fPluginType
), filename
.toRawUTF8(), -1, 2000);
560 void makeHash(const water::File
& file
, const water::String
& filename
)
564 /* do we want this? it is not exactly needed and makes discovery slow..
565 if (file.existsAsFile() && file.getSize() < 20*1024*1024) // dont bother hashing > 20Mb files
567 water::FileInputStream stream(file);
569 if (stream.openedOk())
572 for (int r; r = stream.read(block, sizeof(block)), r > 0;)
573 sha1.write(block, r);
578 sha1
.write(filename
.toRawUTF8(), filename
.length());
580 const int64_t mtime
= file
.getLastModificationTime();
581 sha1
.write(&mtime
, sizeof(mtime
));
583 fNextSha1Sum
= sha1
.resultAsString();
586 CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CarlaPluginDiscovery
)
589 // --------------------------------------------------------------------------------------------------------------------
591 static bool findDirectories(std::vector
<water::File
>& files
, const char* const pluginPath
, const char* const wildcard
)
593 CARLA_SAFE_ASSERT_RETURN(pluginPath
!= nullptr, true);
595 if (pluginPath
[0] == '\0')
600 using water::StringArray
;
602 const StringArray
splitPaths(StringArray::fromTokens(pluginPath
, CARLA_OS_SPLIT_STR
, ""));
604 if (splitPaths
.size() == 0)
607 for (String
*it
= splitPaths
.begin(), *end
= splitPaths
.end(); it
!= end
; ++it
)
609 const File
dir(it
->toRawUTF8());
610 std::vector
<File
> results
;
612 if (dir
.findChildFiles(results
, File::findDirectories
|File::ignoreHiddenFiles
, true, wildcard
) > 0)
614 files
.reserve(files
.size() + results
.size());
615 files
.insert(files
.end(), results
.begin(), results
.end());
619 return files
.empty();
622 static bool findFiles(std::vector
<water::File
>& files
,
623 const BinaryType btype
, const char* const pluginPath
, const char* const wildcard
)
625 CARLA_SAFE_ASSERT_RETURN(pluginPath
!= nullptr, true);
627 if (pluginPath
[0] == '\0')
632 using water::StringArray
;
634 const StringArray
splitPaths(StringArray::fromTokens(pluginPath
, CARLA_OS_SPLIT_STR
, ""));
636 if (splitPaths
.size() == 0)
639 for (String
*it
= splitPaths
.begin(), *end
= splitPaths
.end(); it
!= end
; ++it
)
641 const File
dir(it
->toRawUTF8());
642 std::vector
<File
> results
;
644 if (dir
.findChildFiles(results
, File::findFiles
|File::ignoreHiddenFiles
, true, wildcard
) > 0)
646 files
.reserve(files
.size() + results
.size());
648 for (std::vector
<File
>::const_iterator cit
= results
.begin(); cit
!= results
.end(); ++cit
)
650 const File
file(*cit
);
652 if (CB::getBinaryTypeFromFile(file
.getFullPathName().toRawUTF8()) == btype
)
653 files
.push_back(file
);
658 return files
.empty();
661 static bool findVST3s(std::vector
<water::File
>& files
,
662 const BinaryType btype
, const char* const pluginPath
)
664 CARLA_SAFE_ASSERT_RETURN(pluginPath
!= nullptr, true);
666 if (pluginPath
[0] == '\0')
671 using water::StringArray
;
673 const StringArray
splitPaths(StringArray::fromTokens(pluginPath
, CARLA_OS_SPLIT_STR
, ""));
675 if (splitPaths
.size() == 0)
678 const uint flags
= btype
== CB::BINARY_WIN32
|| btype
== CB::BINARY_WIN64
679 ? File::findDirectories
|File::findFiles
680 : File::findDirectories
;
682 for (String
*it
= splitPaths
.begin(), *end
= splitPaths
.end(); it
!= end
; ++it
)
684 const File
dir(it
->toRawUTF8());
685 std::vector
<File
> results
;
687 if (dir
.findChildFiles(results
, flags
|File::ignoreHiddenFiles
, true, "*.vst3") > 0)
689 files
.reserve(files
.size() + results
.size());
691 for (std::vector
<File
>::const_iterator cit
= results
.begin(); cit
!= results
.end(); ++cit
)
693 const File
file(*cit
);
695 if (CB::getBinaryTypeFromFile(file
.getFullPathName().toRawUTF8()) == btype
)
696 files
.push_back(file
);
701 return files
.empty();
704 CarlaPluginDiscoveryHandle
carla_plugin_discovery_start(const char* const discoveryTool
,
705 const BinaryType btype
,
706 const PluginType ptype
,
707 const char* const pluginPath
,
708 const CarlaPluginDiscoveryCallback discoveryCb
,
709 const CarlaPluginCheckCacheCallback checkCacheCb
,
710 void* const callbackPtr
)
712 CARLA_SAFE_ASSERT_RETURN(btype
!= CB::BINARY_NONE
, nullptr);
713 CARLA_SAFE_ASSERT_RETURN(ptype
!= CB::PLUGIN_NONE
, nullptr);
714 CARLA_SAFE_ASSERT_RETURN(discoveryTool
!= nullptr && discoveryTool
[0] != '\0', nullptr);
715 CARLA_SAFE_ASSERT_RETURN(discoveryCb
!= nullptr, nullptr);
716 carla_debug("carla_plugin_discovery_start(%s, %d:%s, %d:%s, %s, %p, %p, %p)",
717 discoveryTool
, btype
, BinaryType2Str(btype
), ptype
, PluginType2Str(ptype
), pluginPath
,
718 discoveryCb
, checkCacheCb
, callbackPtr
);
720 bool directories
= false;
721 const char* wildcard
= nullptr;
725 case CB::PLUGIN_INTERNAL
:
728 case CB::PLUGIN_JSFX
:
732 CARLA_SAFE_ASSERT_UINT_RETURN(btype
== CB::BINARY_NATIVE
, btype
, nullptr);
740 case CB::PLUGIN_NONE
:
741 case CB::PLUGIN_JACK
:
742 case CB::PLUGIN_TYPE_COUNT
:
747 case CB::PLUGIN_JSFX
:
749 const CarlaScopedEnvVar
csev("CARLA_DISCOVERY_PATH", pluginPath
);
750 return new CarlaPluginDiscovery(discoveryTool
, btype
, ptype
, discoveryCb
, checkCacheCb
, callbackPtr
, pluginPath
);
753 case CB::PLUGIN_INTERNAL
:
754 return new CarlaPluginDiscovery(discoveryTool
, btype
, ptype
, discoveryCb
, checkCacheCb
, callbackPtr
);
756 case CB::PLUGIN_LADSPA
:
757 case CB::PLUGIN_DSSI
:
761 if (btype
== CB::BINARY_WIN32
|| btype
== CB::BINARY_WIN64
)
768 wildcard
= "*.dylib";
776 case CB::PLUGIN_VST2
:
780 if (btype
== CB::BINARY_WIN32
|| btype
== CB::BINARY_WIN64
)
796 case CB::PLUGIN_VST3
:
803 wildcard
= "*.component";
806 case CB::PLUGIN_CLAP
:
824 CARLA_SAFE_ASSERT_RETURN(wildcard
!= nullptr, nullptr);
826 std::vector
<water::File
> files
;
828 if (ptype
== CB::PLUGIN_VST3
)
830 if (findVST3s(files
, btype
, pluginPath
))
833 else if (directories
)
835 if (findDirectories(files
, pluginPath
, wildcard
))
840 if (findFiles(files
, btype
, pluginPath
, wildcard
))
844 return new CarlaPluginDiscovery(discoveryTool
, btype
, ptype
, std::move(files
),
845 discoveryCb
, checkCacheCb
, callbackPtr
);
848 bool carla_plugin_discovery_idle(const CarlaPluginDiscoveryHandle handle
)
850 return static_cast<CarlaPluginDiscovery
*>(handle
)->idle();
853 void carla_plugin_discovery_skip(const CarlaPluginDiscoveryHandle handle
)
855 static_cast<CarlaPluginDiscovery
*>(handle
)->skip();
858 void carla_plugin_discovery_stop(const CarlaPluginDiscoveryHandle handle
)
860 delete static_cast<CarlaPluginDiscovery
*>(handle
);
863 void carla_plugin_discovery_set_option(const EngineOption option
, const int value
, const char* const valueStr
)
867 #if !defined(BUILD_BRIDGE_ALTERNATIVE_ARCH) && !defined(CARLA_OS_WIN)
868 case CB::ENGINE_OPTION_WINE_EXECUTABLE
:
869 if (valueStr
!= nullptr && valueStr
[0] != '\0')
870 CarlaPluginDiscoveryOptions::getInstance().wine
.executable
= valueStr
;
872 CarlaPluginDiscoveryOptions::getInstance().wine
.executable
.clear();
874 case CB::ENGINE_OPTION_WINE_AUTO_PREFIX
:
875 CARLA_SAFE_ASSERT_RETURN(value
== 0 || value
== 1,);
876 CarlaPluginDiscoveryOptions::getInstance().wine
.autoPrefix
= value
!= 0;
878 case CB::ENGINE_OPTION_WINE_FALLBACK_PREFIX
:
879 if (valueStr
!= nullptr && valueStr
[0] != '\0')
880 CarlaPluginDiscoveryOptions::getInstance().wine
.fallbackPrefix
= valueStr
;
882 CarlaPluginDiscoveryOptions::getInstance().wine
.fallbackPrefix
.clear();
890 // --------------------------------------------------------------------------------------------------------------------