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.

ovd_main.cpp 37KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874
  1. #include <stack>
  2. #include <vector>
  3. #include <map>
  4. #include <tuple>
  5. #include <string>
  6. #include <iostream>
  7. #include <algorithm>
  8. #include <json/json.h>
  9. #include <system/ovCTime.h>
  10. #include <system/ovCMath.h>
  11. #include <openvibe/kernel/metabox/ovIMetaboxManager.h>
  12. #include "ovd_base.h"
  13. #include "ovdCInterfacedObject.h"
  14. #include "ovdCInterfacedScenario.h"
  15. #include "ovdCApplication.h"
  16. #include "ovdAssert.h"
  17. #if defined TARGET_OS_Windows
  18. #include "Windows.h"
  19. #include "shellapi.h"
  20. #endif
  21. namespace OpenViBE {
  22. namespace Designer {
  23. std::map<size_t, GdkColor> gColors;
  24. class CPluginObjectDescEnum
  25. {
  26. public:
  27. explicit CPluginObjectDescEnum(const Kernel::IKernelContext& ctx) : m_kernelCtx(ctx) { }
  28. virtual ~CPluginObjectDescEnum() = default;
  29. virtual bool enumeratePluginObjectDesc()
  30. {
  31. CIdentifier id;
  32. while ((id = m_kernelCtx.getPluginManager().getNextPluginObjectDescIdentifier(id)) != CIdentifier::undefined())
  33. {
  34. this->callback(*m_kernelCtx.getPluginManager().getPluginObjectDesc(id));
  35. }
  36. return true;
  37. }
  38. virtual bool enumeratePluginObjectDesc(const CIdentifier& parentClassID)
  39. {
  40. CIdentifier id;
  41. while ((id = m_kernelCtx.getPluginManager().getNextPluginObjectDescIdentifier(id, parentClassID)) != CIdentifier::undefined())
  42. {
  43. this->callback(*m_kernelCtx.getPluginManager().getPluginObjectDesc(id));
  44. }
  45. return true;
  46. }
  47. virtual bool callback(const Plugins::IPluginObjectDesc& pod) = 0;
  48. protected:
  49. const Kernel::IKernelContext& m_kernelCtx;
  50. };
  51. // ------------------------------------------------------------------------------------------------------------------------------------
  52. // ------------------------------------------------------------------------------------------------------------------------------------
  53. // ------------------------------------------------------------------------------------------------------------------------------------
  54. class CPluginObjectDescCollector final : public CPluginObjectDescEnum
  55. {
  56. public:
  57. explicit CPluginObjectDescCollector(const Kernel::IKernelContext& ctx) : CPluginObjectDescEnum(ctx) { }
  58. bool callback(const Plugins::IPluginObjectDesc& pod) override
  59. {
  60. const std::string name = std::string(pod.getCategory()) + "/" + std::string(pod.getName());
  61. const auto it = m_pods.find(name);
  62. if (it != m_pods.end())
  63. {
  64. m_kernelCtx.getLogManager() << Kernel::LogLevel_ImportantWarning << "Duplicate plugin object name " << name << " "
  65. << it->second->getCreatedClass() << " and " << pod.getCreatedClass() << "\n";
  66. }
  67. m_pods[name] = &pod;
  68. return true;
  69. }
  70. std::map<std::string, const Plugins::IPluginObjectDesc*>& getPluginObjectDescMap() { return m_pods; }
  71. private:
  72. std::map<std::string, const Plugins::IPluginObjectDesc*> m_pods;
  73. };
  74. // ------------------------------------------------------------------------------------------------------------------------------------
  75. // ------------------------------------------------------------------------------------------------------------------------------------
  76. // ------------------------------------------------------------------------------------------------------------------------------------
  77. class CPluginObjectDescLogger final : public CPluginObjectDescEnum
  78. {
  79. public:
  80. explicit CPluginObjectDescLogger(const Kernel::IKernelContext& ctx) : CPluginObjectDescEnum(ctx) { }
  81. bool callback(const Plugins::IPluginObjectDesc& pod) override
  82. {
  83. // Outputs plugin info to console
  84. m_kernelCtx.getLogManager() << Kernel::LogLevel_Trace << "Plugin <" << pod.getName() << ">\n";
  85. m_kernelCtx.getLogManager() << Kernel::LogLevel_Debug << " | Plugin category : " << pod.getCategory() << "\n";
  86. m_kernelCtx.getLogManager() << Kernel::LogLevel_Debug << " | Class identifier : " << pod.getCreatedClass() << "\n";
  87. m_kernelCtx.getLogManager() << Kernel::LogLevel_Debug << " | Author name : " << pod.getAuthorName() << "\n";
  88. m_kernelCtx.getLogManager() << Kernel::LogLevel_Debug << " | Author company name : " << pod.getAuthorCompanyName() << "\n";
  89. m_kernelCtx.getLogManager() << Kernel::LogLevel_Debug << " | Short description : " << pod.getShortDescription() << "\n";
  90. m_kernelCtx.getLogManager() << Kernel::LogLevel_Debug << " | Detailed description : " << pod.getDetailedDescription() << "\n";
  91. return true;
  92. }
  93. };
  94. // ------------------------------------------------------------------------------------------------------------------------------------
  95. // ------------------------------------------------------------------------------------------------------------------------------------
  96. // ------------------------------------------------------------------------------------------------------------------------------------
  97. namespace {
  98. typedef std::map<std::string, std::tuple<int, int, int>> components_map_t;
  99. // Parses a JSON encoded list of components with their versions
  100. // We use an output variable because we want to be able to "enhance" an already existing list if necessary
  101. void getVersionComponentsFromConfigToken(const Kernel::IKernelContext& ctx, const char* configToken, components_map_t& componentVersions)
  102. {
  103. json::Object versionsObject;
  104. // We use a lookup instead of expansion as JSON can contain { } characters
  105. const CString versionsJSON = ctx.getConfigurationManager().expand(CString("${") + configToken + "}");
  106. if (versionsJSON.length() != 0)
  107. {
  108. // This check is necessary because the asignemt operator would fail with an assert
  109. if (json::Deserialize(versionsJSON.toASCIIString()).GetType() == json::ObjectVal) { versionsObject = json::Deserialize(versionsJSON.toASCIIString()); }
  110. for (const auto& component : versionsObject)
  111. {
  112. int versionMajor, versionMinor, versionPatch;
  113. sscanf(component.second, "%d.%d.%d", &versionMajor, &versionMinor, &versionPatch);
  114. componentVersions[component.first] = std::make_tuple(versionMajor, versionMinor, versionPatch);
  115. }
  116. }
  117. }
  118. } // namespace
  119. static void InsertPluginObjectDescToGtkTreeStore(const Kernel::IKernelContext& ctx, std::map<std::string, const Plugins::IPluginObjectDesc*>& pods,
  120. GtkTreeStore* treeStore, std::vector<const Plugins::IPluginObjectDesc*>& newBoxes,
  121. std::vector<const Plugins::IPluginObjectDesc*>& updatedBoxes, bool isNewVersion = false)
  122. {
  123. typedef std::map<std::string, std::tuple<int, int, int>> components_map_t;
  124. components_map_t currentVersions;
  125. getVersionComponentsFromConfigToken(ctx, "ProjectVersion_Components", currentVersions);
  126. // By default, fix version to current version - to display the new/update boxes available since current version only
  127. components_map_t lastUsedVersions = currentVersions;
  128. getVersionComponentsFromConfigToken(ctx, "Designer_LastComponentVersionsUsed", lastUsedVersions);
  129. for (const auto& pod : pods)
  130. {
  131. const Plugins::IPluginObjectDesc* p = pod.second;
  132. CString stockItemName;
  133. const auto* desc = dynamic_cast<const Plugins::IBoxAlgorithmDesc*>(p);
  134. if (desc != nullptr) { stockItemName = desc->getStockItemName(); }
  135. bool shouldShow = true;
  136. if (ctx.getPluginManager().isPluginObjectFlaggedAsDeprecated(p->getCreatedClass())
  137. && !ctx.getConfigurationManager().expandAsBoolean("${Designer_ShowDeprecated}", false)) { shouldShow = false; }
  138. /*
  139. if (ctx.getPluginManager().isPluginObjectFlaggedAsUnstable(desc->getCreatedClass())
  140. && !ctx.getConfigurationManager().expandAsBoolean("${Designer_ShowUnstable}", false)) { shouldShow = false; }
  141. */
  142. if (shouldShow)
  143. {
  144. GtkStockItem stockItem;
  145. if (gtk_stock_lookup(stockItemName, &stockItem) == 0) { stockItemName = GTK_STOCK_NEW; }
  146. // Splits the plugin category
  147. std::vector<std::string> categories;
  148. std::string str = std::string(p->getCategory());
  149. size_t j, i = size_t(-1);
  150. while ((j = str.find('/', i + 1)) != std::string::npos)
  151. {
  152. std::string subCategory = std::string(str, i + 1, j - i - 1);
  153. if (subCategory != std::string("")) { categories.push_back(subCategory); }
  154. i = j;
  155. }
  156. if (i + 1 != str.length()) { categories.emplace_back(str, i + 1, str.length() - i - 1); }
  157. // Fills plugin in the tree
  158. GtkTreeIter iter1;
  159. GtkTreeIter iter2;
  160. GtkTreeIter* iterParent = nullptr;
  161. GtkTreeIter* iterChild = &iter1;
  162. for (const std::string& category : categories)
  163. {
  164. bool found = false;
  165. bool valid = gtk_tree_model_iter_children(GTK_TREE_MODEL(treeStore), iterChild, iterParent) != 0;
  166. while (valid && !found)
  167. {
  168. gchar* name = nullptr;
  169. gboolean isPlugin;
  170. gtk_tree_model_get(GTK_TREE_MODEL(treeStore), iterChild, Resource_StringName, &name, Resource_BooleanIsPlugin, &isPlugin, -1);
  171. if ((isPlugin == 0) && name == category) { found = true; }
  172. else { valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(treeStore), iterChild) != 0; }
  173. }
  174. if (!found)
  175. {
  176. gtk_tree_store_append(GTK_TREE_STORE(treeStore), iterChild, iterParent);
  177. gtk_tree_store_set(GTK_TREE_STORE(treeStore), iterChild, Resource_StringName, category.c_str(),
  178. Resource_StringShortDescription, "", Resource_StringStockIcon, "gtk-directory", Resource_StringColor, "#000000",
  179. Resource_StringFont, "", Resource_BooleanIsPlugin, gboolean(FALSE), -1);
  180. }
  181. if (iterParent == nullptr) { iterParent = &iter2; }
  182. GtkTreeIter* iterSwap = iterChild;
  183. iterChild = iterParent;
  184. iterParent = iterSwap;
  185. }
  186. gtk_tree_store_append(GTK_TREE_STORE(treeStore), iterChild, iterParent);
  187. // define color of the text of the box
  188. std::string textColor = "black";
  189. std::string bgColor = "white";
  190. std::string textFont;
  191. str = p->getName().toASCIIString();
  192. if (ctx.getPluginManager().isPluginObjectFlaggedAsDeprecated(p->getCreatedClass())) { textColor = "#3f7f7f"; }
  193. // If the software is launched for the first time after update, highlight new/updated boxes in tree-view
  194. std::string boxSoftwareComponent = p->getSoftwareComponent().toASCIIString();
  195. if (boxSoftwareComponent != "unknown")
  196. {
  197. int currentVMajor = std::get<0>(currentVersions[boxSoftwareComponent]);
  198. int currentVMinor = std::get<1>(currentVersions[boxSoftwareComponent]);
  199. int currentVPatch = std::get<2>(currentVersions[boxSoftwareComponent]);
  200. int lastUsedVMajor = std::get<0>(lastUsedVersions[boxSoftwareComponent]);
  201. int lastUsedVMinor = std::get<1>(lastUsedVersions[boxSoftwareComponent]);
  202. int lastUsedVPatch = std::get<2>(lastUsedVersions[boxSoftwareComponent]);
  203. int boxCompoVMajor = 0;
  204. int boxCompoVMinor = 0;
  205. int boxCompoVPatch = 0;
  206. sscanf(p->getAddedSoftwareVersion().toASCIIString(), "%d.%d.%d", &boxCompoVMajor, &boxCompoVMinor, &boxCompoVPatch);
  207. // If this is a new version, then add in list all the updated/new boxes since last version opened
  208. if (isNewVersion
  209. && ((lastUsedVMajor < boxCompoVMajor && boxCompoVMajor <= currentVMajor)
  210. || (boxCompoVMajor == currentVMajor && lastUsedVMinor < boxCompoVMinor && boxCompoVMinor <= currentVMinor)
  211. || (boxCompoVMinor == currentVMinor && lastUsedVPatch < boxCompoVPatch && boxCompoVPatch <= currentVPatch)
  212. // As default value for lastUsedVMinor and lastUsedVMajor are the current software version
  213. || (boxCompoVMajor == currentVMajor && boxCompoVMinor == currentVMinor && boxCompoVPatch == currentVPatch)))
  214. {
  215. str += " (New)";
  216. bgColor = "#FFFFC4";
  217. newBoxes.push_back(p);
  218. }
  219. // Otherwise
  220. else if (boxCompoVMajor == currentVMajor && boxCompoVMinor == currentVMinor && boxCompoVPatch ==
  221. currentVPatch) { newBoxes.push_back(p); }
  222. else
  223. {
  224. int boxCompoUpdatedVMajor = 0;
  225. int boxCompoUpdatedVMinor = 0;
  226. int boxCompoUpdatedVPatch = 0;
  227. sscanf(p->getUpdatedSoftwareVersion().toASCIIString(), "%d.%d.%d", &boxCompoUpdatedVMajor,
  228. &boxCompoUpdatedVMinor, &boxCompoUpdatedVPatch);
  229. // If this is a new version, then add in list all the updated/new boxes since last version opened
  230. if (isNewVersion
  231. && ((lastUsedVMajor < boxCompoUpdatedVMajor && boxCompoUpdatedVMajor <= currentVMajor)
  232. || (boxCompoUpdatedVMajor == currentVMajor && lastUsedVMinor < boxCompoUpdatedVMinor && boxCompoUpdatedVMinor <= currentVMinor)
  233. || (boxCompoUpdatedVMinor == currentVMinor && lastUsedVPatch < boxCompoUpdatedVPatch && boxCompoUpdatedVPatch <= currentVPatch)
  234. // If this is a new version Designer, and last version opened was set to default value i.e. version of current software
  235. || (boxCompoUpdatedVMajor == currentVMajor && boxCompoUpdatedVMinor == currentVMinor && boxCompoUpdatedVPatch == currentVPatch)))
  236. {
  237. str += " (New)";
  238. bgColor = "#FFFFC4";
  239. updatedBoxes.push_back(p);
  240. }
  241. // Otherwise
  242. else if (!isNewVersion && (boxCompoUpdatedVMajor == currentVMajor && boxCompoUpdatedVMinor == currentVMinor
  243. && boxCompoUpdatedVPatch == currentVPatch)) { updatedBoxes.push_back(p); }
  244. }
  245. }
  246. // Construct a string containing the BoxAlgorithmIdentifier concatenated with a metabox identifier if necessary
  247. std::string boxAlgorithmDesc = p->getCreatedClass().str();
  248. if (p->getCreatedClass() == OVP_ClassId_BoxAlgorithm_Metabox)
  249. {
  250. boxAlgorithmDesc += dynamic_cast<const Metabox::IMetaboxObjectDesc*>(p)->getMetaboxDescriptor();
  251. textColor = "#007020";
  252. }
  253. gtk_tree_store_set(GTK_TREE_STORE(treeStore), iterChild, Resource_StringName, str.c_str(),
  254. Resource_StringShortDescription, static_cast<const char*>(p->getShortDescription()),
  255. Resource_StringIdentifier, static_cast<const char*>(boxAlgorithmDesc.c_str()),
  256. Resource_StringStockIcon, static_cast<const char*>(stockItemName), Resource_StringColor, textColor.c_str(),
  257. Resource_StringFont, textFont.c_str(), Resource_BooleanIsPlugin, gboolean(TRUE),
  258. Resource_BackGroundColor, static_cast<const char*>(bgColor.c_str()), -1);
  259. }
  260. }
  261. }
  262. // ------------------------------------------------------------------------------------------------------------------------------------
  263. // ------------------------------------------------------------------------------------------------------------------------------------
  264. // ------------------------------------------------------------------------------------------------------------------------------------
  265. typedef struct SConfig
  266. {
  267. SConfig() = default;
  268. ECommandLineFlag getFlags() const { return ECommandLineFlag(noGui | noCheckColorDepth | noManageSession | noVisualization | define | randomSeed | config); }
  269. std::vector<std::pair<ECommandLineFlag, std::string>> flags;
  270. ECommandLineFlag noGui = CommandLineFlag_None;
  271. ECommandLineFlag noCheckColorDepth = CommandLineFlag_None;
  272. ECommandLineFlag noManageSession = CommandLineFlag_None;
  273. ECommandLineFlag noVisualization = CommandLineFlag_None;
  274. ECommandLineFlag define = CommandLineFlag_None;
  275. ECommandLineFlag randomSeed = CommandLineFlag_None;
  276. ECommandLineFlag config = CommandLineFlag_None;
  277. bool help = false;
  278. // to resolve warning: padding struct 'SConfig' with 4 bytes to align 'tokens
  279. int structPadding = 0;
  280. std::map<std::string, std::string> tokens;
  281. } config_t;
  282. static char backslash_to_slash(const char c) { return c == '\\' ? '/' : c; }
  283. /** ------------------------------------------------------------------------------------------------------------------------------------
  284. * Use Mutex to ensure that only one instance with GUI of Designer runs at the same time
  285. * if another instance exists, sends a message to it so that it opens a scenario or get the focus back
  286. * \param config: play, play-fast or open
  287. * \param logMgr: name of the scenario to open
  288. ------------------------------------------------------------------------------------------------------------------------------------**/
  289. #if defined NDEBUG
  290. static bool ensureOneInstanceOfDesigner(config_t& config, Kernel::ILogManager& logMgr)
  291. {
  292. try
  293. {
  294. // If the mutex cannot be opened, it's the first instance of Designer, go to catch
  295. boost::interprocess::named_mutex mutex(boost::interprocess::open_only, MUTEX_NAME);
  296. // If the mutex was opened, then an instance of designer is already running, we send it a message before dying
  297. // The message contains the command to send: sMode: open, play, play-fast a scenario, sScenarioPath: path of the scenario
  298. boost::interprocess::scoped_lock<boost::interprocess::named_mutex> lock(mutex);
  299. std::string msg;
  300. if (config.flags.empty()) { msg = std::to_string(int(CommandLineFlag_None)) + ": ;"; }
  301. for (auto& flag : config.flags)
  302. {
  303. std::string fileName = flag.second;
  304. std::transform(fileName.begin(), fileName.end(), fileName.begin(), backslash_to_slash);
  305. msg += std::to_string(int(flag.first)) + ": <" + fileName + "> ; ";
  306. }
  307. const size_t msgSize = strlen(msg.c_str()) * sizeof(char);
  308. boost::interprocess::message_queue messageToFirstInstance(boost::interprocess::open_or_create, MESSAGE_NAME, msgSize, msgSize);
  309. messageToFirstInstance.send(msg.c_str(), msgSize, 0);
  310. return false;
  311. }
  312. catch (boost::interprocess::interprocess_exception&)
  313. {
  314. //Create the named mutex to catch the potential next instance of Designer that could open
  315. boost::interprocess::named_mutex mutex(boost::interprocess::create_only, MUTEX_NAME);
  316. return true;
  317. }
  318. }
  319. #else
  320. static bool ensureOneInstanceOfDesigner(config_t& /*config*/, Kernel::ILogManager& /*logMgr*/) { return true; }
  321. #endif
  322. bool parse_arguments(int argc, char** argv, config_t& config)
  323. {
  324. config_t tmp;
  325. std::vector<std::string> args;
  326. #if defined TARGET_OS_Windows
  327. int nArg;
  328. LPWSTR* argListUtf16 = CommandLineToArgvW(GetCommandLineW(), &nArg);
  329. for (int i = 1; i < nArg; ++i)
  330. {
  331. GError* error = nullptr;
  332. glong itemsRead, itemsWritten;
  333. char* argUtf8 = g_utf16_to_utf8(reinterpret_cast<gunichar2*>(argListUtf16[i]), glong(wcslen(argListUtf16[i])), &itemsRead, &itemsWritten, &error);
  334. args.emplace_back(argUtf8);
  335. if (error != nullptr)
  336. {
  337. g_error_free(error);
  338. return false;
  339. }
  340. }
  341. #else
  342. args = std::vector<std::string>(argv + 1, argv + argc);
  343. #endif
  344. args.emplace_back("");
  345. for (auto it = args.cbegin(); it != args.cend(); ++it)
  346. {
  347. if (*it == "") {}
  348. else if (*it == "-h" || *it == "--help")
  349. {
  350. tmp.help = true;
  351. config = tmp;
  352. return false;
  353. }
  354. else if (*it == "-o" || *it == "--open") { tmp.flags.emplace_back(CommandLineFlag_Open, *++it); }
  355. else if (*it == "-p" || *it == "--play") { tmp.flags.emplace_back(CommandLineFlag_Play, *++it); }
  356. else if (*it == "-pf" || *it == "--play-fast") { tmp.flags.emplace_back(CommandLineFlag_PlayFast, *++it); }
  357. else if (*it == "--no-gui")
  358. {
  359. tmp.noGui = CommandLineFlag_NoGui;
  360. tmp.noCheckColorDepth = CommandLineFlag_NoCheckColorDepth;
  361. tmp.noManageSession = CommandLineFlag_NoManageSession;
  362. }
  363. else if (*it == "--no-visualization") { tmp.noVisualization = CommandLineFlag_NoVisualization; }
  364. else if (*it == "--invisible")
  365. {
  366. // no-gui + no-visualization
  367. tmp.noVisualization = CommandLineFlag_NoVisualization;
  368. tmp.noGui = CommandLineFlag_NoGui;
  369. tmp.noCheckColorDepth = CommandLineFlag_NoCheckColorDepth;
  370. tmp.noManageSession = CommandLineFlag_NoManageSession;
  371. }
  372. else if (*it == "--no-check-color-depth") { tmp.noCheckColorDepth = CommandLineFlag_NoCheckColorDepth; }
  373. else if (*it == "--no-session-management") { tmp.noManageSession = CommandLineFlag_NoManageSession; }
  374. else if (*it == "-c" || *it == "--config")
  375. {
  376. if (*++it == "")
  377. {
  378. std::cout << "Error: Switch --config needs an argument\n";
  379. return false;
  380. }
  381. tmp.flags.emplace_back(CommandLineFlag_Config, *it);
  382. }
  383. else if (*it == "-d" || *it == "--define")
  384. {
  385. if (*++it == "")
  386. {
  387. std::cout << "Error: Need two arguments after -d / --define.\n";
  388. return false;
  389. }
  390. // Were not using = as a separator for token/value, as on Windows its a problem passing = to the cmd interpreter
  391. // which is used to launch the actual designer exe.
  392. const std::string& token = *it;
  393. if (*++it == "")
  394. {
  395. std::cout << "Error: Need two arguments after -d / --define.\n";
  396. return false;
  397. }
  398. const std::string& value = *it; // iterator will increment later
  399. tmp.tokens[token] = value;
  400. }
  401. else if (*it == "--random-seed")
  402. {
  403. if (*++it == "")
  404. {
  405. std::cout << "Error: Switch --random-seed needs an argument\n";
  406. return false;
  407. }
  408. tmp.flags.emplace_back(CommandLineFlag_RandomSeed, *it);
  409. }
  410. else if (*it == "--g-fatal-warnings")
  411. {
  412. // Do nothing here but accept this gtk flag
  413. }
  414. else
  415. {
  416. #if 0
  417. // Assumes we just open a scenario - this is for retro compatibility and should not be supported in the future
  418. config.flags.push_back(std::make_pair(CommandLineFlag_Open, *++it));
  419. #endif
  420. return false;
  421. }
  422. }
  423. #if 0
  424. config.flags = config.flags;
  425. config.checkColorDepth = config.m_bCheckColorDepth;
  426. config.showGui = config.m_bShowGui;
  427. #else
  428. config = tmp;
  429. #endif
  430. return true;
  431. }
  432. #if defined OPENVIBE_SPLASHSCREEN
  433. gboolean cb_remove_splashscreen(gpointer data)
  434. {
  435. gtk_widget_hide(GTK_WIDGET(data));
  436. return false;
  437. }
  438. #endif
  439. // ------------------------------------------------------------------------------------------------------------------------------------
  440. // ------------------------------------------------------------------------------------------------------------------------------------
  441. // ------------------------------------------------------------------------------------------------------------------------------------
  442. void message(const char* title, const char* msg, const GtkMessageType type)
  443. {
  444. GtkWidget* dialog = gtk_message_dialog_new(nullptr, GtkDialogFlags(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT), type, GTK_BUTTONS_OK, "%s", title);
  445. gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), "%s", msg);
  446. ::gtk_window_set_icon_from_file(GTK_WINDOW(dialog), (Directories::getDataDir() + CString("/applications/designer/designer.ico")).toASCIIString(),
  447. nullptr);
  448. gtk_window_set_title(GTK_WINDOW(dialog), title);
  449. gtk_dialog_run(GTK_DIALOG(dialog));
  450. gtk_widget_destroy(dialog);
  451. }
  452. void user_info(char** argv, Kernel::ILogManager* logManager)
  453. {
  454. const std::vector<std::string> messages =
  455. {
  456. "Syntax : " + std::string(argv[0]) + " [ switches ]\n",
  457. "Possible switches :\n",
  458. " --help : displays this help message and exits\n",
  459. " --config filename : path to config file\n",
  460. " --define token value : specify configuration token with a given value\n",
  461. " --open filename : opens a scenario (see also --no-session-management)\n",
  462. " --play filename : plays the opened scenario (see also --no-session-management)\n",
  463. " --play-fast filename : plays fast forward the opened scenario (see also --no-session-management)\n",
  464. " --no-gui : hides the " DESIGNER_NAME " graphical user interface (assumes --no-color-depth-test)\n",
  465. " --no-visualization : hides the visualisation widgets\n",
  466. " --invisible : hides the designer and the visualisation widgets (assumes --no-check-color-depth and --no-session-management)\n",
  467. " --no-check-color-depth : does not check 24/32 bits color depth\n",
  468. " --no-session-management : neither restore last used scenarios nor saves them at exit\n",
  469. " --random-seed uint : initialize random number generator with value, default=time(nullptr)\n"
  470. };
  471. if (logManager != nullptr) { for (const auto& m : messages) { (*logManager) << Kernel::LogLevel_Info << m; } }
  472. else { for (const auto& m : messages) { std::cout << m; } }
  473. }
  474. int go(int argc, char** argv)
  475. {
  476. bool errorWhileLoadingScenario = false, playRequested = false;
  477. /*
  478. { 0, 0, 0, 0 },
  479. { 0, 16383, 16383, 16383 },
  480. { 0, 32767, 32767, 32767 },
  481. { 0, 49151, 49151, 49151 },
  482. { 0, 65535, 65535, 65535 },
  483. */
  484. #define GDK_COLOR_SET(c, r, g, b) { (c).pixel=0; (c).red=r; (c).green=g; (c).blue=b; }
  485. GDK_COLOR_SET(gColors[Color_BackgroundPlayerStarted], 32767, 32767, 32767);
  486. GDK_COLOR_SET(gColors[Color_BoxBackgroundSelected], 65535, 65535, 49151);
  487. GDK_COLOR_SET(gColors[Color_BoxBackgroundMissing], 49151, 32767, 32767);
  488. GDK_COLOR_SET(gColors[Color_BoxBackgroundDisabled], 46767, 46767, 59151);
  489. GDK_COLOR_SET(gColors[Color_BoxBackgroundDeprecated], 65535, 50000, 32767);
  490. GDK_COLOR_SET(gColors[Color_BoxBackgroundOutdated], 57343, 57343, 57343);
  491. GDK_COLOR_SET(gColors[Color_BoxBackgroundMetabox], 58343, 65535, 62343);
  492. GDK_COLOR_SET(gColors[Color_BoxBackgroundUnstable], 49151, 49151, 49151);
  493. GDK_COLOR_SET(gColors[Color_BoxBackground], 65535, 65535, 65535);
  494. GDK_COLOR_SET(gColors[Color_BoxBorderSelected], 0, 0, 0);
  495. GDK_COLOR_SET(gColors[Color_BoxBorder], 0, 0, 0);
  496. GDK_COLOR_SET(gColors[Color_BoxInputBackground], 65535, 49151, 32767);
  497. GDK_COLOR_SET(gColors[Color_BoxInputBorder], 16383, 16383, 16383);
  498. GDK_COLOR_SET(gColors[Color_BoxOutputBackground], 32767, 65535, 49151);
  499. GDK_COLOR_SET(gColors[Color_BoxOutputBorder], 16383, 16383, 16383);
  500. GDK_COLOR_SET(gColors[Color_BoxSettingBackground], 49151, 32767, 65535);
  501. GDK_COLOR_SET(gColors[Color_BoxSettingBorder], 16383, 16383, 16383);
  502. GDK_COLOR_SET(gColors[Color_CommentBackground], 65535, 65535, 57343);
  503. GDK_COLOR_SET(gColors[Color_CommentBackgroundSelected], 65535, 65535, 49151);
  504. GDK_COLOR_SET(gColors[Color_CommentBorder], 32767, 32767, 32767);
  505. GDK_COLOR_SET(gColors[Color_CommentBorderSelected], 32767, 32767, 32767);
  506. GDK_COLOR_SET(gColors[Color_Link], 0, 0, 0);
  507. GDK_COLOR_SET(gColors[Color_LinkSelected], 49151, 49151, 16383);
  508. GDK_COLOR_SET(gColors[Color_LinkUpCast], 32767, 16383, 16383);
  509. GDK_COLOR_SET(gColors[Color_LinkDownCast], 16383, 32767, 16383);
  510. GDK_COLOR_SET(gColors[Color_LinkInvalid], 49151, 16383, 16383);
  511. GDK_COLOR_SET(gColors[Color_SelectionArea], 0x3f00, 0x3f00, 0x3f00);
  512. GDK_COLOR_SET(gColors[Color_SelectionAreaBorder], 0, 0, 0);
  513. #undef GDK_COLOR_SET
  514. //___________________________________________________________________//
  515. // //
  516. config_t config;
  517. bool bArgParseResult = parse_arguments(argc, argv, config);
  518. if (!bArgParseResult)
  519. {
  520. if (config.help)
  521. {
  522. user_info(argv, nullptr);
  523. return 0;
  524. }
  525. }
  526. CKernelLoader loader;
  527. std::cout << "[ INF ] Created kernel loader, trying to load kernel module" << "\n";
  528. CString errorMsg;
  529. #if defined TARGET_OS_Windows
  530. CString file = Directories::getLibDir() + "/openvibe-kernel.dll";
  531. #elif defined TARGET_OS_Linux
  532. CString file = Directories::getLibDir() + "/libopenvibe-kernel.so";
  533. #elif defined TARGET_OS_MacOS
  534. CString file = Directories::getLibDir() + "/libopenvibe-kernel.dylib";
  535. #endif
  536. if (!loader.load(file, &errorMsg)) { std::cout << "[ FAILED ] Error loading kernel (" << errorMsg << ")" << " from [" << file << "]\n"; }
  537. else
  538. {
  539. std::cout << "[ INF ] Kernel module loaded, trying to get kernel descriptor" << "\n";
  540. Kernel::IKernelDesc* desc = nullptr;
  541. Kernel::IKernelContext* context = nullptr;
  542. loader.initialize();
  543. loader.getKernelDesc(desc);
  544. if (desc == nullptr) { std::cout << "[ FAILED ] No kernel descriptor" << "\n"; }
  545. else
  546. {
  547. std::cout << "[ INF ] Got kernel descriptor, trying to create kernel" << "\n";
  548. context = desc->createKernel("designer", Directories::getDataDir() + "/kernel/openvibe.conf");
  549. context->initialize();
  550. context->getConfigurationManager().addConfigurationFromFile(Directories::getDataDir() + "/applications/designer/designer.conf");
  551. CString appConfigFile = context->getConfigurationManager().expand("${Designer_CustomConfigurationFile}");
  552. context->getConfigurationManager().addConfigurationFromFile(appConfigFile);
  553. // add other configuration file if --config option
  554. auto it = config.flags.begin();
  555. // initialize random number generator with nullptr by default
  556. System::Math::initializeRandomMachine(time(nullptr));
  557. while (it != config.flags.end())
  558. {
  559. if (it->first == CommandLineFlag_Config)
  560. {
  561. appConfigFile = CString(it->second.c_str());
  562. context->getConfigurationManager().addConfigurationFromFile(appConfigFile);
  563. }
  564. else if (it->first == CommandLineFlag_RandomSeed)
  565. {
  566. const size_t seed = size_t(strtol(it->second.c_str(), nullptr, 10));
  567. System::Math::initializeRandomMachine(seed);
  568. }
  569. ++it;
  570. }
  571. if (context == nullptr) { std::cout << "[ FAILED ] No kernel created by kernel descriptor" << "\n"; }
  572. else
  573. {
  574. Toolkit::initialize(*context);
  575. VisualizationToolkit::initialize(*context);
  576. //initialise Gtk before 3D context
  577. gtk_init(&argc, &argv);
  578. // gtk_rc_parse(Directories::getDataDir() + "/applications/designer/interface.gtkrc");
  579. #if defined OPENVIBE_SPLASHSCREEN
  580. GtkWidget* splashScreenWindow = gtk_window_new(GTK_WINDOW_POPUP);
  581. gtk_window_set_position(GTK_WINDOW(splashScreenWindow), GTK_WIN_POS_CENTER);
  582. gtk_window_set_type_hint(GTK_WINDOW(splashScreenWindow), GDK_WINDOW_TYPE_HINT_SPLASHSCREEN);
  583. gtk_window_set_default_size(GTK_WINDOW(splashScreenWindow), 600, 400);
  584. GtkWidget* splashScreenImage = gtk_image_new_from_file(Directories::getDataDir() + "/applications/designer/splashscreen.png");
  585. gtk_container_add(GTK_CONTAINER(splashScreenWindow), (splashScreenImage));
  586. gtk_widget_show(splashScreenImage);
  587. gtk_widget_show(splashScreenWindow);
  588. g_timeout_add(500, cb_remove_splashscreen, splashScreenWindow);
  589. while (gtk_events_pending()) { gtk_main_iteration(); }
  590. #endif
  591. Kernel::IConfigurationManager& configMgr = context->getConfigurationManager();
  592. Kernel::ILogManager& logMgr = context->getLogManager();
  593. bArgParseResult = parse_arguments(argc, argv, config);
  594. context->getPluginManager().addPluginsFromFiles(configMgr.expand("${Kernel_Plugins}"));
  595. //FIXME : set locale only when needed
  596. CString locale = configMgr.expand("${Designer_Locale}");
  597. if (locale == CString("")) { locale = "C"; }
  598. setlocale(LC_ALL, locale.toASCIIString());
  599. if (!(bArgParseResult || config.help)) { user_info(argv, &logMgr); }
  600. else
  601. {
  602. if ((!configMgr.expandAsBoolean("${Kernel_WithGUI}", true)) && ((config.getFlags() & CommandLineFlag_NoGui) == 0))
  603. {
  604. logMgr << Kernel::LogLevel_ImportantWarning <<
  605. "${Kernel_WithGUI} is set to false and --no-gui flag not set. Forcing the --no-gui flag\n";
  606. config.noGui = CommandLineFlag_NoGui;
  607. config.noCheckColorDepth = CommandLineFlag_NoCheckColorDepth;
  608. config.noManageSession = CommandLineFlag_NoManageSession;
  609. }
  610. if (config.noGui != CommandLineFlag_NoGui && !ensureOneInstanceOfDesigner(config, logMgr))
  611. {
  612. logMgr << Kernel::LogLevel_Trace << "An instance of Designer is already running.\n";
  613. return 0;
  614. }
  615. {
  616. CApplication app(*context);
  617. app.initialize(config.getFlags());
  618. // FIXME is it necessary to keep next line uncomment ?
  619. //bool isScreenValid=true;
  620. if (config.noCheckColorDepth == 0)
  621. {
  622. if (GDK_IS_DRAWABLE(GTK_WIDGET(app.m_MainWindow)->window))
  623. {
  624. // FIXME is it necessary to keep next line uncomment ?
  625. //isScreenValid=false;
  626. switch (gdk_drawable_get_depth(GTK_WIDGET(app.m_MainWindow)->window))
  627. {
  628. case 24:
  629. case 32:
  630. // FIXME is it necessary to keep next line uncomment ?
  631. //isScreenValid=true;
  632. break;
  633. default:
  634. logMgr << Kernel::LogLevel_Error << "Please change the color depth of your screen to either 24 or 32 bits\n";
  635. // TODO find a way to break
  636. break;
  637. }
  638. }
  639. }
  640. // Add or replace a configuration token if required in command line
  641. for (const auto& t : config.tokens)
  642. {
  643. logMgr << Kernel::LogLevel_Trace << "Adding command line configuration token [" << t.first << " = " << t.second << "]\n";
  644. configMgr.addOrReplaceConfigurationToken(t.first.c_str(), t.second.c_str());
  645. }
  646. for (const auto& f : config.flags)
  647. {
  648. std::string fileName = f.second;
  649. std::transform(fileName.begin(), fileName.end(), fileName.begin(), backslash_to_slash);
  650. bool error;
  651. switch (f.first)
  652. {
  653. case CommandLineFlag_Open:
  654. logMgr << Kernel::LogLevel_Info << "Opening scenario [" << fileName << "]\n";
  655. if (!app.openScenario(fileName.c_str()))
  656. {
  657. logMgr << Kernel::LogLevel_Error << "Could not open scenario " << fileName << "\n";
  658. errorWhileLoadingScenario = config.noGui == CommandLineFlag_NoGui;
  659. }
  660. break;
  661. case CommandLineFlag_Play:
  662. logMgr << Kernel::LogLevel_Info << "Opening and playing scenario [" << fileName << "]\n";
  663. error = !app.openScenario(fileName.c_str());
  664. if (!error)
  665. {
  666. app.playScenarioCB();
  667. error = app.getCurrentInterfacedScenario()->m_PlayerStatus != Kernel::EPlayerStatus::Play;
  668. }
  669. if (error)
  670. {
  671. logMgr << Kernel::LogLevel_Error << "Scenario open or load error with --play.\n";
  672. errorWhileLoadingScenario = config.noGui == CommandLineFlag_NoGui;
  673. }
  674. break;
  675. case CommandLineFlag_PlayFast:
  676. logMgr << Kernel::LogLevel_Info << "Opening and fast playing scenario [" << fileName << "]\n";
  677. error = !app.openScenario(fileName.c_str());
  678. if (!error)
  679. {
  680. app.forwardScenarioCB();
  681. error = app.getCurrentInterfacedScenario()->m_PlayerStatus != Kernel::EPlayerStatus::Forward;
  682. }
  683. if (error)
  684. {
  685. logMgr << Kernel::LogLevel_Error << "Scenario open or load error with --play-fast.\n";
  686. errorWhileLoadingScenario = config.noGui == CommandLineFlag_NoGui;
  687. }
  688. playRequested = true;
  689. break;
  690. //case CommandLineFlag_Define:
  691. //break;
  692. default:
  693. break;
  694. }
  695. }
  696. if (!playRequested && config.noGui == CommandLineFlag_NoGui)
  697. {
  698. logMgr << Kernel::LogLevel_Info
  699. << "Switch --no-gui is enabled but no play operation was requested. Designer will exit automatically.\n";
  700. }
  701. if (app.m_Scenarios.empty() && config.noGui != CommandLineFlag_NoGui) { app.newScenarioCB(); }
  702. if (!app.m_Scenarios.empty())
  703. {
  704. CPluginObjectDescCollector cbCollector1(*context);
  705. CPluginObjectDescCollector cbCollector2(*context);
  706. CPluginObjectDescLogger cbLogger(*context);
  707. cbLogger.enumeratePluginObjectDesc();
  708. cbCollector1.enumeratePluginObjectDesc(OV_ClassId_Plugins_BoxAlgorithmDesc);
  709. cbCollector2.enumeratePluginObjectDesc(OV_ClassId_Plugins_AlgorithmDesc);
  710. InsertPluginObjectDescToGtkTreeStore(*context, cbCollector1.getPluginObjectDescMap(), app.m_BoxAlgorithmTreeModel, app.m_NewBoxes,
  711. app.m_UpdatedBoxes, app.m_IsNewVersion);
  712. InsertPluginObjectDescToGtkTreeStore(*context, cbCollector2.getPluginObjectDescMap(), app.m_AlgorithmTreeModel, app.m_NewBoxes,
  713. app.m_UpdatedBoxes);
  714. std::map<std::string, const Plugins::IPluginObjectDesc*> metaboxDescMap;
  715. CIdentifier id;
  716. while ((id = context->getMetaboxManager().getNextMetaboxObjectDescIdentifier(id)) != CIdentifier::undefined()
  717. ) { metaboxDescMap[id.str()] = context->getMetaboxManager().getMetaboxObjectDesc(id); }
  718. InsertPluginObjectDescToGtkTreeStore(*context, metaboxDescMap, app.m_BoxAlgorithmTreeModel, app.m_NewBoxes, app.m_UpdatedBoxes,
  719. app.m_IsNewVersion);
  720. context->getLogManager() << Kernel::LogLevel_Info << "Initialization took "
  721. << context->getConfigurationManager().expand("$Core{real-time}") << " ms\n";
  722. // If the application is a newly launched version, and not launched without GUI -> display changelog
  723. if (app.m_IsNewVersion && config.noGui != CommandLineFlag_NoGui) { app.displayChangelogWhenAvailable(); }
  724. try { gtk_main(); }
  725. catch (DesignerException& ex)
  726. {
  727. std::cerr << "Caught designer exception" << std::endl;
  728. GtkWidget* errorDialog = gtk_message_dialog_new(nullptr, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
  729. "%s", ex.getErrorString().c_str());
  730. gtk_window_set_title(GTK_WINDOW(errorDialog), (std::string(BRAND_NAME) + " has stopped functioning").c_str());
  731. gtk_dialog_run(GTK_DIALOG(errorDialog));
  732. }
  733. catch (...) { std::cerr << "Caught top level exception" << std::endl; }
  734. }
  735. }
  736. }
  737. logMgr << Kernel::LogLevel_Info << "Application terminated, releasing allocated objects\n";
  738. VisualizationToolkit::uninitialize(*context);
  739. Toolkit::uninitialize(*context);
  740. desc->releaseKernel(context);
  741. // Remove the mutex only if the application was run with a gui
  742. if (config.noGui != CommandLineFlag_NoGui) { boost::interprocess::named_mutex::remove(MUTEX_NAME); }
  743. }
  744. }
  745. loader.uninitialize();
  746. loader.unload();
  747. }
  748. return errorWhileLoadingScenario ? -1 : 0;
  749. }
  750. } // namespace Designer
  751. } // namespace OpenViBE
  752. int main(const int argc, char** argv)
  753. {
  754. // Remove mutex at startup, as the main loop regenerates frequently this mutex,
  755. // if another instance is running, it should have the time to regenerate it
  756. // Avoids that after crashing, a mutex stays blocking
  757. boost::interprocess::named_mutex::remove(MUTEX_NAME);
  758. try { OpenViBE::Designer::go(argc, argv); }
  759. catch (...) { std::cout << "Caught an exception at the very top...\nLeaving application!\n"; }
  760. //return go(argc, argv);
  761. }
  762. #if defined TARGET_OS_Windows
  763. // Should be used once we get rid of the .cmd launchers
  764. int WINAPI WinMain(HINSTANCE /*instance*/, HINSTANCE /*prevInstance*/, LPSTR /*cmdLine*/, int /*windowStyle*/) { return main(__argc, __argv); }
  765. #endif //defined TARGET_OS_Windows