Anklang-0.3.0-1416-gfa8ce8d2 anklang-0.3.0-1416-gfa8ce8d2
ASE — Anklang Sound Engine (C++)

« « « Anklang Documentation
Loading...
Searching...
No Matches
main.cc
Go to the documentation of this file.
1 // This Source Code Form is licensed MPL-2.0: http://mozilla.org/MPL/2.0
2#include "main.hh"
3#include "api.hh"
4#include "path.hh"
5#include "utils.hh"
6#include "jsonapi.hh"
7#include "driver.hh"
8#include "project.hh"
9#include "loft.hh"
10#include "compress.hh"
11#include "webui.hh"
12#include "server.hh"
13#include "internal.hh"
14#include "testing.hh"
15
16#include <limits.h>
17#include <stdlib.h>
18#include <unistd.h>
19#include <signal.h>
20#include <malloc.h>
21#include <unistd.h>
22#include <fcntl.h>
23#include <sys/resource.h>
24#ifdef ASE_WITH_CPPTRACE
25#include <cpptrace/from_current.hpp>
26#endif
27
28#include "trkn.hh"
29
30#undef B0 // undo pollution from termios.h
31
32#define MDEBUG(...) Ase::debug ("memory", __VA_ARGS__)
33
34namespace Ase {
35
37 MainAppImpl ();
38};
39MainAppImpl main_app;
40const MainApp &App = main_app;
41
42MainAppImpl::MainAppImpl()
43{}
44
45LoopP main_loop = Loop::current();
46static String arg_ui_mode;
47static int arg_unauth_port = 0;
48
49// == JobQueue ==
50static void
51call_main_loop (const std::function<void()> &fun)
52{
53 main_loop->add (fun);
54}
55JobQueue main_jobs (call_main_loop);
56
57// == MainConfig and arguments ==
58static void
59print_usage (bool help)
60{
61 if (!help)
62 {
63 printout ("%s %s\n", executable_name(), ase_version());
64 printout ("Build: %s\n", ase_build_id());
65 return;
66 }
67 printout ("Usage: %s [OPTIONS] [project.anklang]\n", executable_name());
68 printout (" --check Run integrity tests\n");
69 printout (" --disable-randomization Test mode for deterministic tests\n");
70 printout (" --fatal-warnings Abort on warnings and failing assertions\n");
71 printout (" --help Print program usage and options\n");
72 printout (" --jsbin Print Javascript IPC & binary messages\n");
73 printout (" --jsipc Print Javascript IPC messages\n");
74 printout (" --jsonts Print TypeScript bindings\n");
75 printout (" --list-drivers Print PCM and MIDI drivers\n");
76 printout (" --list-tests List all test names\n");
77 printout (" --list-ui-tests List all TypeScript UI test function names\n");
78 printout (" --norc Prevent loading of any rc files\n");
79 printout (" --ui-test=test Specify TypeScript UI test(s) to run (comma-separated)\n");
80 printout (" --ui-js=script Run JavaScript after UI startup (headless by default)\n");
81 printout (" The window closes unless window.auto_close(false) is called\n");
82 printout (" --play-autostart Automatically start playback of `project.anklang`\n");
83 printout (" --rand64 Produce 64bit random numbers on stdout\n");
84 printout (" --test[=test] Run specific test(s) (comma-separated)\n");
85 printout (" --unauth-dev=NUM Open an unauthenticated websocket port for testing\n");
86 printout (" --headless[=bool] Run browser headless (default for --ui-test and --ui-js)\n");
87 printout (" --ui <none|chromium|google-chrome|htmlgui>\n");
88 printout (" Open GUI in web browser [htmlgui]\n");
89 printout (" --version Print program version\n");
90 printout (" -M mididriver Force use of <mididriver>\n");
91 printout (" -P pcmdriver Force use of <pcmdriver>\n");
92 printout (" -o wavfile Capture output to OPUS/FLAC/WAV file\n");
93 printout (" -t <time> Automatically play and stop after <time> has passed\n"); // -t <time>[{,|;}tailtime]
94 printout ("Options set via $ASE_DEBUG:\n");
95 printout (" :no-logfile: Disable logging to ~/.cache/anklang/ instead of stderr\n");
96 printout (" :coredump: Allow core dumps of Anklang and its GUI process\n");
97}
98
100static bool
101parse_option_arg (const char *option, char **argv, unsigned *ith, const char **argp)
102{
103 const size_t l = strlen (option);
104 if (strncmp (option, argv[*ith], l) == 0) {
105 *argp = argv[*ith] + l;
106 argv[*ith] = nullptr;
107 if ((*argp)[0] == '=')
108 *argp += 1;
109 else if ((*argp)[0] == 0) {
110 *ith += 1;
111 *argp = argv[*ith] ? argv[*ith] : "";
112 argv[*ith] = nullptr;
113 }
114 return true;
115 }
116 return false;
117}
118
119// 1:ERROR 2:FAILED+REJECT 4:IO 8:MESSAGE 16:GET 256:BINARY
120static constexpr int jsipc_logflags = 1 | 2 | 4 | 8 | 16;
121static constexpr int jsbin_logflags = 1 | 256;
122
123static StringS check_test_names;
124static StringS ui_test_names;
125static String ui_js_script;
126
127static void
128parse_args (int *argcp, char **argv, MainAppImpl &config)
129{
130 if (0) // allow jsipc logging via ASE_DEBUG ?
131 {
132 config.jsonapi_logflags |= debug_key_enabled ("jsbin") ? jsbin_logflags : 0;
133 config.jsonapi_logflags |= debug_key_enabled ("jsipc") ? jsipc_logflags : 0;
134 }
135
136 config.norc = false;
137 bool sep = false; // -- separator
138 bool headless_default = false, headless_option = false;
139 std::string default_ui_mode = "htmlgui";
140 const uint argc = *argcp;
141 for (uint i = 1; i < argc; i++)
142 {
143 const char *optarg = nullptr;
144 if (sep)
145 config.args.push_back (argv[i]);
146 else if (strcmp (argv[i], "--fatal-warnings") == 0 || strcmp (argv[i], "--g-fatal-warnings") == 0)
148 else if (strcmp ("--disable-randomization", argv[i]) == 0)
149 config.allow_randomization = false;
150 else if (strcmp ("--norc", argv[i]) == 0)
151 config.norc = true;
152 else if (strcmp ("--rand64", argv[i]) == 0)
153 {
154 FastRng prng;
155 constexpr int N = 8192;
156 uint64_t buffer[N];
157 while (1)
158 {
159 for (size_t i = 0; i < N; i++)
160 buffer[i] = prng.next();
161 fwrite (buffer, sizeof (buffer[0]), N, stdout);
162 }
163 exit (0);
164 }
165 else if (strcmp ("--check", argv[i]) == 0)
166 {
167 config.mode = MainApp::CHECK_INTEGRITY_TESTS;
169 printerr ("CHECK_INTEGRITY_TESTS…\n");
170 default_ui_mode = "none";
171 }
172 else if (strcmp ("--list-tests", argv[i]) == 0)
173 {
175 for (const auto &t : Test::list_tests())
176 ids.push_back (t.ident);
177 std::sort (ids.begin(), ids.end());
178 for (const auto &t : ids)
179 printout ("%s\n", t);
180 exit (0);
181 }
182 else if (strcmp ("--list-ui-tests", argv[i]) == 0)
183 {
184 const String testfile = anklang_runpath (RPath::INSTALLDIR, "/ui/assets/testcalls-list.txt");
185 if (!Path::check (testfile, "e"))
186 fatal_error ("missing UI test list: %s", testfile);
187 const String content = Path::stringread (testfile);
188 StringS lines = string_split (content, "\n");
189 for (const String &line : lines)
190 if (!line.empty () || string_startswith (line, "#"))
191 printout ("%s\n", line);
192 exit (0);
193 }
194 else if (strcmp ("--ui-test", argv[i]) == 0 || strncmp ("--ui-test=", argv[i], 9) == 0)
195 {
196 const char *eq = strchr (argv[i], '=');
197 const char *arg = eq ? eq + 1 : i+1 < argc ? argv[++i] : nullptr;
198 if (arg) {
199 const auto tests = string_split (arg, ",");
200 for (const auto &t : tests)
201 ui_test_names.push_back (t);
202 } else
203 ui_test_names.push_back ("all");
204 headless_default = true;
205 }
206 else if (strcmp ("--ui-js", argv[i]) == 0 || strncmp ("--ui-js=", argv[i], 8) == 0)
207 {
208 const char *eq = strchr (argv[i], '=');
209 ui_js_script = eq ? eq + 1 : i+1 < argc ? argv[++i] : "";
210 if (ui_js_script.empty())
211 ui_js_script = " "; // execute an empty script so the window can close
212 headless_default = true;
213 }
214 else if (strcmp ("--headless", argv[i]) == 0 || strncmp ("--headless=", argv[i], 10) == 0)
215 {
216 const char *eq = strchr (argv[i], '=');
217 config.headless = eq ? string_to_bool (eq + 1) : true;
218 headless_option = true;
219 }
220 else if (strcmp ("--test", argv[i]) == 0 || strncmp ("--test=", argv[i], 7) == 0)
221 {
222 const char *eq = strchr (argv[i], '=');
223 const char *arg = eq ? eq + 1 : i+1 < argc ? argv[++i] : nullptr;
224 config.mode = MainApp::CHECK_INTEGRITY_TESTS;
226 if (arg) {
227 const auto tests = string_split (arg, ",");
228 for (const auto &t : tests)
229 check_test_names.push_back (t);
230 }
231 default_ui_mode = "none";
232 }
233 else if (argv[i] == String ("--blake3") && i + 1 < size_t (argc))
234 {
235 argv[i++] = nullptr;
236 String hash = blake3_hash_file (argv[i]);
237 if (hash.empty())
238 printerr ("%s: failed to read: %s\n", argv[i], strerror (errno));
239 else
240 printout ("%s\n", string_to_hex (hash));
241 exit (hash == "");
242 }
243 else if (strcmp ("--jsonts", argv[i]) == 0) {
244 if (getenv ("ASE_JSONTS") == nullptr)
245 fatal_error ("%s: environment must contain ASE_JSONTS for --jsonts", argv[0]);
246 printout ("%s\n", Jsonipc::g_binding_printer->finish());
247 exit (0);
248 } else if (strcmp ("--jsipc", argv[i]) == 0)
249 config.jsonapi_logflags |= jsipc_logflags;
250 else if (strcmp ("--jsbin", argv[i]) == 0)
251 config.jsonapi_logflags |= jsbin_logflags;
252 else if (strcmp ("--list-drivers", argv[i]) == 0)
253 config.list_drivers = true;
254 else if (strcmp ("-M", argv[i]) == 0 && i + 1 < size_t (argc))
255 {
256 argv[i++] = nullptr;
257 config.midi_override = argv[i];
258 }
259 else if (strcmp ("-P", argv[i]) == 0 && i + 1 < size_t (argc))
260 {
261 argv[i++] = nullptr;
262 config.pcm_override = argv[i];
263 }
264 else if (strcmp ("--no-devices", argv[i]) == 0)
265 {
266 config.no_devices = true;
267 }
268 else if (strcmp ("-h", argv[i]) == 0 ||
269 strcmp ("--help", argv[i]) == 0)
270 {
271 print_usage (true);
272 exit (0);
273 }
274 else if (strcmp ("--version", argv[i]) == 0)
275 {
276 print_usage (false);
277 exit (0);
278 }
279 else if (argv[i] == String ("-o") && i + 1 < size_t (argc))
280 {
281 argv[i++] = nullptr;
282 config.outputfile = argv[i];
283 }
284 else if (argv[i] == String ("--play-autostart"))
285 {
286 config.play_autostart = true;
287 default_ui_mode = "none";
288 }
289 else if (parse_option_arg ("--unauth-dev", argv, &i, &optarg))
290 {
291 arg_unauth_port = string_to_int (optarg);
292 default_ui_mode = "wait";
293 }
294 else if (argv[i] == String ("-t") && i + 1 < size_t (argc))
295 {
296 config.play_autostart = true;
297 argv[i++] = nullptr;
298 config.play_autostop = string_to_seconds (argv[i]);
299 default_ui_mode = "none";
300 }
301 else if (parse_option_arg ("--ui", argv, &i, &optarg))
302 {
303 arg_ui_mode = optarg;
304 }
305 else if (argv[i] == String ("--") && !sep)
306 sep = true;
307 else if (argv[i][0] == '-' && !sep)
308 fatal_error ("invalid command line argument: %s", argv[i]);
309 else
310 config.args.push_back (argv[i]);
311 argv[i] = nullptr;
312 }
313 if (!headless_option)
314 config.headless = headless_default;
315 if (arg_ui_mode.empty())
316 arg_ui_mode = default_ui_mode;
317 if (*argcp > 1)
318 {
319 uint e = 1;
320 for (uint i = 1; i < argc; i++)
321 if (argv[i])
322 {
323 argv[e++] = argv[i];
324 if (i >= e)
325 argv[i] = nullptr;
326 }
327 *argcp = e;
328 }
329}
330
331static String
332make_auth_string()
333{
334 const char *const c52 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz";
335 /* We use WebScoket subprotocol randomization as authentication, so:
336 * a) Authentication happens *before* message interpretation, so an
337 * unauthenticated sender cannot cause crahses via e.g. rapidjson exceptions.
338 * b) To serve as working authentication measure, the subprotocol random string
339 * must be cryptographically-secure.
340 */
341 KeccakCryptoRng csprng;
342 String auth = "sessC";
343 for (size_t i = 0; i < 23; ++i)
344 auth += c52[csprng.random() % 52]; // each step adds 5.7 bits
345 return auth;
346}
347
348static void
349run_tests_and_quit ()
350{
351 if (check_test_names.empty())
352 Test::run();
353 else
354 Test::run (check_test_names);
355 main_loop->quit (0);
356}
357
358void
360{
361 LoopP loop = main_loop;
362 if (loop)
363 loop->wakeup();
364}
365
366static std::atomic<bool> seen_autostop = false;
367
368// Lock and obstruction-free autostop trigger.
369void
371{
372 if (!seen_autostop)
373 {
374 seen_autostop = true;
376 }
377}
378
379static bool
380handle_autostop (const LoopState &state)
381{
382 switch (state.phase)
383 {
384 case LoopState::PREPARE: return seen_autostop;
385 case LoopState::CHECK: return seen_autostop;
386 case LoopState::DISPATCH:
387 info ("Main: stopping playback (auto)");
388 main_loop->quit (0);
389 return true; // keep alive
390 default: ;
391 }
392 return false;
393}
394
395static void
396init_sigpipe()
397{
398 // don't die if we write() data to a process and that process dies (i.e. jackd)
399 sigset_t signal_mask;
400 sigemptyset (&signal_mask);
401 sigaddset (&signal_mask, SIGPIPE);
402
403 int rc = pthread_sigmask (SIG_BLOCK, &signal_mask, NULL);
404 if (rc != 0)
405 Ase::warning ("Ase: pthread_sigmask for SIGPIPE failed: %s\n", strerror (errno));
406}
407
408static void
409disable_core_dumps()
410{
411 // abort() should be instant, but core dumps of large processes (Electron) piped into
412 // e.g. systemd-coredump can block process exit for minutes, a soft RLIMIT_CORE of 0
413 // prevents that, is inherited by child processes and can be raised again for debugging
414 struct rlimit rl = {};
415 if (getrlimit (RLIMIT_CORE, &rl) == 0 && rl.rlim_cur != 0) {
416 rl.rlim_cur = 0;
417 if (setrlimit (RLIMIT_CORE, &rl) != 0)
418 Ase::warning ("Ase: setrlimit for RLIMIT_CORE failed: %s\n", strerror (errno));
419 }
420}
421
422static std::atomic<bool> loft_needs_preallocation_mt = false;
423
424// handle watermark underrun notifications
425static void
426notify_loft_lowmem ()
427{
428 if (!loft_needs_preallocation_mt)
429 {
430 loft_needs_preallocation_mt = true;
432 }
433}
434
435static size_t last_loft_preallocation = 0;
436
437static void
438preallocate_loft (size_t preallocation)
439{
440 using namespace Ase;
441 last_loft_preallocation = preallocation;
442 LoftConfig loftcfg = {
443 .preallocate = last_loft_preallocation,
444 .watermark = last_loft_preallocation / 2,
445 .flags = Loft::PREFAULT_PAGES,
446 };
447 loft_set_config (loftcfg);
448 loft_set_notifier (notify_loft_lowmem);
449 loft_grow_preallocate();
450}
451
452static bool
453dispatch_loft_lowmem (const Ase::LoopState &lstate)
454{
455 using namespace Ase;
456 const bool keep_alive = lstate.phase == LoopState::DISPATCH;
457 // generally, dispatch logic may only run in LoopState::DISPATCH, but this handler
458 // makes a rare exception, because we try to get ahead of concurrently runnint RT-threads...
459 return_unless (loft_needs_preallocation_mt, keep_alive);
460 loft_needs_preallocation_mt = false;
461 last_loft_preallocation *= 2;
462 const size_t newalloc = loft_grow_preallocate (last_loft_preallocation);
463 LoftConfig config;
464 loft_get_config (config);
465 config.watermark = last_loft_preallocation / 2;
466 loft_set_config (config);
467 if (newalloc > 0)
468 MDEBUG ("Loft preallocation in main thread: %f MB", newalloc / (1024. * 1024));
469 return keep_alive;
470}
471
472static void
473prefault_pages (size_t stacksize, size_t heapsize)
474{
475 const size_t pagesize = sysconf (_SC_PAGESIZE);
476 char *heap = (char*) malloc (heapsize);
477 if (heap)
478 for (size_t i = 0; i < heapsize; i += pagesize)
479 heap[i] = 1;
480 free (heap);
481 char *stack = (char*) alloca (stacksize);
482 if (stack)
483 for (size_t i = 0; i < stacksize; i += pagesize)
484 stack[i] = 1;
485}
486
487static int
488main (int argc, char *argv[])
489{
490 using namespace Ase;
491 using namespace AnsiColors;
492
493 // setup thread identifier
494 TaskRegistry::setup_ase ("AnklangMainProc");
495 // use malloc to serve allocations via sbrk only (avoid mmap)
496 mallopt (M_MMAP_MAX, 0);
497 // avoid releasing sbrk memory back to the system (reduce page faults)
498 mallopt (M_TRIM_THRESHOLD, -1);
499 // reserve large sbrk area and reduce page faults for heap and stack
500 prefault_pages ((1024 + 768) * 1024, 64 * 1024 * 1024);
501 // preallocate memory for lock-free allocator
502 preallocate_loft (64 * 1024 * 1024);
503 // warn if preallocation is not sufficient
504 loft_set_growth_notifier ([] (size_t total, size_t needed)
505 {
506 warning ("Loft.BumpAllocator: growing beyond preallocation: totalmem=%u needed=%d\n", total, needed);
507 });
508
509 // print stack trace for uncaught exceptions
510 logging_handle_terminate();
511
512 // SIGPIPE init: needs to be done before any child thread is created
513 init_sigpipe();
514 // Enable us to reap and kill any grand child processes
515 atquit_make_subreaper();
516 // Avoid slow core dumps on abort() for us and child processes, unless $ASE_DEBUG=coredump
517 if (!debug_key_enabled ("coredump"))
518 disable_core_dumps();
519
520 // apply user locale
521 if (!setlocale (LC_ALL, ""))
522 fatal_error ("setlocale: locale not supported by libc: %s", ::strerror (errno));
523
524 // parse args and config
525 parse_args (&argc, argv, main_app);
526 main_app.ui_tests = ui_test_names;
527 main_app.ui_js = ui_js_script;
528 const int socket_port = arg_unauth_port > 0 ? arg_unauth_port : 0;
529 const char *socket_host = "127.0.0.1";
530 const auto socket_info = WebSocketServer::bind_port (socket_host, socket_port);
531 logging_configure (arg_ui_mode != "none" ? string_format ("%u", socket_info.port) : "");
532
533 // handle loft preallocation needs
534 main_loop->exec_dispatcher (dispatch_loft_lowmem, LoopPriority::SYSALLOC);
535
536 // load preferences unless --norc was given
537 if (!App.norc)
538 Preference::load_preferences (true);
539
540 // Ensure Ase server exists
541 ServerImpl::instancep();
542
543 // tracktion initialisation
544 if (!trkn_init (argc, argv, App.no_devices))
545 fatal_error ("Main: failed to initialize tracktion engine");
546
547 const auto B1 = color (BOLD);
548 const auto B0 = color (BOLD_OFF);
549
550 // load drivers and dump device list
552 if (App.list_drivers)
553 {
554 Ase::Driver::EntryVec entries;
555 printout ("%s", _("Available PCM drivers:\n"));
556 entries = Ase::PcmDriver::list_drivers();
557 std::sort (entries.begin(), entries.end(), [] (auto &a, auto &b) { return a.priority < b.priority; });
558 for (const auto &entry : entries)
559 {
560 printout (" %-30s (%s, %08x)\n\t%s\n%s%s%s%s", entry.devid + ":",
561 entry.readonly ? "Input" : entry.writeonly ? "Output" : "Duplex",
562 entry.priority, entry.device_name,
563 entry.capabilities.empty() ? "" : "\t" + entry.capabilities + "\n",
564 entry.device_info.empty() ? "" : "\t" + entry.device_info + "\n",
565 entry.hints.empty() ? "" : "\t(" + entry.hints + ")\n",
566 entry.notice.empty() ? "" : "\t" + entry.notice + "\n");
567 if (debug_key_enabled ("driver"))
568 printerr (" %08x: %s\n", entry.priority, Driver::priority_string (entry.priority));
569 }
570 printout ("%s", _("Available MIDI drivers:\n"));
571 entries = Ase::MidiDriver::list_drivers();
572 std::sort (entries.begin(), entries.end(), [] (auto &a, auto &b) { return a.priority < b.priority; });
573 for (const auto &entry : entries)
574 {
575 printout (" %-30s (%s, %08x)\n\t%s\n%s%s%s%s", entry.devid + ":",
576 entry.readonly ? "Input" : entry.writeonly ? "Output" : "Duplex",
577 entry.priority, entry.device_name,
578 entry.capabilities.empty() ? "" : "\t" + entry.capabilities + "\n",
579 entry.device_info.empty() ? "" : "\t" + entry.device_info + "\n",
580 entry.hints.empty() ? "" : "\t(" + entry.hints + ")\n",
581 entry.notice.empty() ? "" : "\t" + entry.notice + "\n");
582 if (debug_key_enabled ("driver"))
583 printerr (" %08x: %s\n", entry.priority, Driver::priority_string (entry.priority));
584 }
585 return 0;
586 }
587
588 // load projects
589 ProjectImplP preload_project;
590 for (const auto &filename : App.args)
591 {
592 preload_project = ProjectImpl::create (Path::basename (filename));
593 Error error = Error::NO_MEMORY;
594 if (preload_project)
595 error = preload_project->load_project (filename);
596 diag ("Main: load project: %s: %s", filename, ase_error_blurb (error));
597 if (!!error)
598 warning ("%s: failed to load project: %s", filename, ase_error_blurb (error));
599 }
600
601 // open Jsonapi socket
602 const String auth_token = arg_unauth_port > 0 ? "" : make_auth_string();
603 auto wss = WebSocketServer::create (jsonapi_make_connection, App.jsonapi_logflags, auth_token);
604 main_app.web_socket_server = &*wss;
605 wss->http_dir (anklang_runpath (RPath::INSTALLDIR, "/ui/"));
606 // wss->http_alias ("/User/Controller", anklang_home_dir ("/Controller"));
607 wss->http_alias ("/Builtin/Controller", anklang_runpath (RPath::INSTALLDIR, "/Controller"));
608 // wss->http_alias ("/User/Scripts", anklang_home_dir ("/Scripts"));
609 wss->http_alias ("/Builtin/Scripts", anklang_runpath (RPath::INSTALLDIR,"/Scripts"));
610 const String subprotocol = ""; // make_auth_string()
611 jsonapi_set_subprotocol (subprotocol);
612 if (App.mode == MainApp::SYNTHENGINE && arg_ui_mode != "none") {
613 wss->listen (socket_info, [] () { main_loop->quit (-1); });
614 std::string webui_url = wss->url();
615 if (!socket_port) {
616 String redirecthtml = webui_create_auth_redirect ("anklang", wss->listen_port(), auth_token, arg_ui_mode);
617 if (errno)
618 fatal_error ("%s: failed to create html redirect file in $HOME", redirecthtml);
619 webui_url = "file://" + redirecthtml;
620 wss->see_other (webui_url);
621 }
622 info ("Main: WebUI address: %s", webui_url);
623
624 WebuiFlags webui_flags = main_app.headless ? WebuiFlags::HEADLESS : WebuiFlags::NONE;
625 if (main_app.ui_tests.size())
626 webui_flags = webui_flags | WebuiFlags::CONSOLE_LOGS; // WebuiFlags::STDIO_REDIRECT
627 auto ereason = webui_start_browser (arg_ui_mode, main_loop, webui_url,
628 [] (int exit_code)
629 {
630 main_loop->quit (exit_code);
631 }, webui_flags);
632
633 if (ereason.error)
634 fatal_error ("Main: failed to run WebUI: %s: %s", ereason.what, ::strerror (ereason.error));
635 }
636
637 // run atquit handler on SIGHUP SIGINT
638 for (int sigid : { SIGHUP, SIGINT, SIGQUIT, SIGABRT, SIGTERM, SIGSYS }) {
639 main_loop->exec_usignal (sigid, [] (int8 sig) {
640 info ("Main: got signal %d: terminate", sig);
641 atquit_terminate (-1);
642 return false;
643 });
644 USignalSource::install_sigaction (sigid);
645 }
646
647 // catch SIGUSR2 to close sockets
648 main_loop->exec_usignal (SIGUSR2, [wss] (int8 sig) {
649 info ("Main: got signal %d: reset WebSocket", sig);
650 wss->reset();
651 return true;
652 });
653 USignalSource::install_sigaction (SIGUSR2);
654
655 // start output capturing
656 if (App.outputfile)
657 ; // TODO: implement capturing
658
659 // start auto play
660 if (App.play_autostart && preload_project)
661 main_loop->add ([preload_project] ()
662 {
663 info ("Main: starting playback (auto)");
664 preload_project->start_playback (App.play_autostop);
666 // handle automatic shutdown
667 main_loop->exec_dispatcher (handle_autostop);
668
669 // prune old log files after some time
670 main_loop->add ([] ()
671 {
672 logging_prune_old_logs (3.0 * 24.0 * 60.0 * 60.0);
674
675 // run test suite
676 if (App.mode == MainApp::CHECK_INTEGRITY_TESTS)
677 main_loop->add (run_tests_and_quit);
678
679 // run main event loop and catch SIGUSR2
680 const int exitcode = main_loop->run();
681 assert_return (main_loop, -1); // ptr must be kept around
682 diag ("Main: event loop quit: code=%d", exitcode);
683
684 // cleanup
685 wss->shutdown(); // close socket, allow no more calls
686 main_app.web_socket_server = nullptr;
687 wss = nullptr;
688
689 // deactivate any projects, releases Audio resources
690 ProjectImpl::force_shutdown_all();
691
692 // halt audio engine, join its threads, dispatch cleanups
693 main_loop->iterate_pending();
694
695 // shutdown tracktion *after* main loop stopped
696 trkn_shutdown ();
697
698 diag ("Main: exiting: %d", exitcode);
699 atquit_terminate (exitcode);
700 return exitcode;
701}
702
703} // Ase
704
705int
706main (int argc, char *argv[])
707{
708 int r = -128;
709#ifdef ASE_WITH_CPPTRACE
710 CPPTRACE_TRY { r = Ase::main (argc, argv); }
711 CPPTRACE_CATCH (const std::exception& e) {
712 std::string msg = "Exception: ";
713 msg += e.what();
714 msg += "\n";
715 fflush (stdout);
716 fputs (msg.c_str(), stderr);
717 fflush (stderr);
718 cpptrace::from_current_exception().print();
719 }
720#else
721 r = Ase::main (argc, argv);
722#endif
723 return r;
724}
T c_str(T... args)
static String priority_string(uint priority)
Return string which represents the given priority mask.
Definition driver.cc:31
static LoopP current()
Return the thread-local singleton loop, created on first call.
Definition loop.cc:307
T empty(T... args)
exit
fflush
fputs
free
fwrite
optarg
#define assert_return(expr,...)
Return from the current function if expr is unmet and issue an assertion warning.
Definition internal.hh:28
#define return_unless(cond,...)
Return silently if cond does not evaluate to true with return value ...
Definition internal.hh:72
#define _(...)
Retrieve the translation of a C or C++ string.
Definition internal.hh:17
std::string color(Colors acolor, Colors c1, Colors c2, Colors c3, Colors c4, Colors c5, Colors c6)
Return ANSI code for the specified color if stdout & stderr should be colorized, see colorize_tty().
Definition platform.cc:191
String basename(const String &path)
Strips all directory components from path and returns the resulting file name.
Definition path.cc:68
bool check(const String &file, const String &mode)
Definition path.cc:625
int run(void)
Run all registered tests.
Definition testing.cc:264
The Anklang C++ API namespace.
Definition api.hh:8
std::string string_format(const char *format, const Args &...args) __attribute__((__format__(__printf__
Format a string similar to sprintf(3) with support for std::string and std::ostringstream convertible...
bool trkn_init(int argc, char *argv[], bool nodevs)
Setup tracktion and tracktion::engine.
Definition trkn.cc:99
StringS string_split(const String &string, const String &splitter, size_t maxn)
Split a string, using splitter as delimiter.
Definition strings.cc:343
String string_to_hex(const String &input)
Convert bytes in string input to hexadecimal numbers.
Definition strings.cc:1171
void logging_prune_old_logs(double age_seconds)
Delete log files older than age seconds.
Definition logging.cc:233
int8_t int8
An 8-bit signed integer.
Definition cxxaux.hh:25
std::vector< String > StringS
Convenience alias for a std::vector<std::string>.
Definition cxxaux.hh:35
JobQueue main_jobs(call_main_loop)
Execute a job callback in the event loop.
Definition main.hh:44
Error
Enum representing Error states.
Definition api.hh:21
const char * ase_error_blurb(Error error)
Describe Error condition.
Definition server.cc:276
std::string anklang_runpath(RPath rpath, const String &segment)
Retrieve various resource paths at runtime.
Definition platform.cc:58
double string_to_seconds(const String &string, double fallback)
Parse string into seconds.
Definition strings.cc:773
int64 string_to_int(const String &string, size_t *consumed, uint base)
Parse a string into a 64bit integer, optionally specifying the expected number base.
Definition strings.cc:578
void main_loop_wakeup()
Wake up the event loop.
Definition main.cc:359
bool logging_fatal_warnings
Global flag to cause the program to abort on warnings.
Definition logging.cc:304
@ IDLE
Mildly important, used for background tasks.
@ SYSALLOC
Internal maintenance, don't use.
const char * ase_version()
Provide a string containing the package version.
Definition platform.cc:803
std::string String
Convenience alias for std::string.
Definition cxxaux.hh:34
std::string executable_name()
Retrieve the name part of executable_path().
Definition platform.cc:742
bool string_to_bool(const String &string, bool fallback)
Interpret a string as boolean value.
Definition strings.cc:467
void main_loop_autostop_mt()
Stop the event loop after a timeout.
Definition main.cc:370
bool debug_key_enabled(const char *conditional) noexcept
Check if conditional is enabled by $ASE_DEBUG.
Definition logging.cc:320
uint32_t uint
Provide 'uint' as convenience type.
Definition cxxaux.hh:17
void load_registered_drivers()
Load all registered drivers.
Definition driver.cc:76
size_t preallocate
Amount of preallocated available memory.
Definition loft.hh:44
size_t watermark
Watermark to trigger async preallocation.
Definition loft.hh:45
bool string_startswith(const String &string, const String &fragment)
Returns whether string starts with fragment.
Definition strings.cc:846
const char * ase_build_id()
Provide a string containing the ASE library build id.
Definition platform.cc:809
Configuration for Loft allocations.
Definition loft.hh:43
size_t hash(size_t seed, const T &v)
pthread_sigmask
T push_back(T... args)
sigaddset
sigemptyset
T size(T... args)
T sort(T... args)
typedef uint64_t
strchr
strlen
sysconf
T what(T... args)