It's 2020...
[terminatorX.git] / src / main.cc
1 /*
2     terminatorX - realtime audio scratching software
3     Copyright (C) 1999-2020  Alexander König
4
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License
16     along with this program.  If not, see <http://www.gnu.org/licenses/>.
17     
18     File: main.c
19     
20     Description: This contains the main() function. All the initializing
21                  happens here.
22 */
23
24 #define TX_GTKRC "/usr/share/themes/terminatorX/gtk/gtkrc"
25
26 #define BENCH_CYCLES 100000
27
28 #include <stdio.h>
29 #include "tX_mastergui.h"
30 #include <malloc.h>
31 #include <math.h>
32 #include <stdio.h>
33 #ifndef WIN32
34 #include <unistd.h>
35 #endif
36
37 #ifdef HAVE_CONFIG_H
38 #include <config.h>
39 #endif
40
41 #include "tX_endian.h"
42 #include "tX_types.h"
43 #include "tX_global.h"
44 #include "tX_audiodevice.h"
45 #include "version.h"
46 #include "tX_dialog.h"
47 #include <gtk/gtk.h>
48 #include <glib.h>
49 #include <string.h>
50
51 #include "tX_ladspa.h"
52 #include "tX_ladspa_class.h"
53 #include "tX_engine.h"
54 #include "tX_capabilities.h"
55 #include "tX_pbutton.h"
56
57 #ifdef CREATE_BENCHMARK 
58 #include "tX_vtt.h"
59 #endif
60
61 #ifdef USE_SCHEDULER
62 #include <sched.h>
63 #include <sys/types.h>
64 #include <unistd.h>
65 #endif
66
67 #ifdef USE_JACK 
68 void jack_check()
69 {
70         if ((!tX_jack_client::get_instance()) && (globals.audiodevice_type==JACK)) {
71                 tx_note("Couldn't connect to JACK server - JACK output not available.\n\nIf you want to use JACK, ensure the JACK daemon is running before you start terminatorX.", true);
72         }
73 }
74 #endif // USE_JACK
75
76 static bool timesup=false;
77
78 gboolean timeout(void *)
79 {
80         timesup=true;
81         return FALSE;
82 }
83
84 void show_help()
85 {
86                         
87         fprintf(stderr, "\
88 usage: terminatorX [options]\n\
89 \n\
90   -h, --help                    Display help info\n\
91   -f, --file                    Load saved terminatorX set file\n\
92   -r, --rc-file [file]          Load alternate rc file\n\
93   -d, --dont-save               Do not save settings at exit\n\
94   -s, --std-out                 Use stdout for sound output\n\
95   --device=[output device]      Use alternate device for sound output\n\
96 \n");
97 }
98
99 int parse_args(int *argc, char **argv)
100 {
101         // pass over argv once to see if we need to load an alternate_rc file
102         for (int i = 1 ; i != *argc ; ++i ) {
103                 if ((strcmp(argv[i], "-r") == 0) || (strcmp(argv[i], "--rc-file") == 0)) {
104                         if (argv[i+1] ) {       
105                                 ++i;
106                                 fprintf(stderr, "tX: Loading alternate rc file %s\n", argv[i]);
107                                 globals.alternate_rc = argv[i];
108                         } else {
109                                 show_help();    
110                                 exit(1);
111                         }
112                         break;
113                 }
114         }
115         
116         // load up the global values
117         load_globals();
118
119         // default the flag options, or they'll be set from last execution... (think globals.no_gui ;)
120         globals.no_gui = 0;
121         globals.alternate_rc = 0;
122         globals.store_globals = 1;
123         globals.startup_set = 0;
124                 
125         // then pass over again, this time setting passed values
126         for (int i = 1 ; i < *argc ; ++i ) {
127                 if ((strcmp(argv[i], "-f") == 0) || (strcmp(argv[i], "--file") == 0)) {
128                         ++i;
129                         globals.startup_set = argv[i];
130                 } else if (((strcmp(argv[i], "-r") == 0) || (strcmp(argv[i], "--rc-file") == 0)) && (argv[i+1])) {
131                         ++i;
132                         globals.alternate_rc = argv[i];
133                 } else if ((strcmp(argv[i], "-d") == 0) || (strcmp(argv[i], "--dont-save") == 0)) {
134                         fprintf(stderr, "tX: Do not save settings on exit\n");
135                         globals.store_globals = 0;
136
137                 } else if ((strcmp(argv[i], "-s") == 0) || (strcmp(argv[i], "--std-out") == 0)) {
138                         globals.use_stdout_cmdline = 1;
139                         globals.use_stdout = 1;
140                 } else if ((strncmp(argv[i], "--device",8) == 0)) {
141                         if (strlen(argv[i]+9)<=PATH_MAX)
142                                 strcpy(globals.oss_device,argv[i]+9);
143                         else {
144                                 show_help();
145                                 exit(1);
146                         }
147                 } else {
148                         show_help();
149                         exit(1);
150                 }
151         }
152         return 1;
153 }
154
155 void checkenv(const char *name)
156 {
157         char *value;
158         int length;
159         
160         value=getenv(name);
161         if (value) {
162                 length=strlen(value);
163                 /*
164                  strnlen requires extra macros...
165                  length=strnlen(value, PATH_MAX+1);
166                 */
167                 
168                 if (length>=PATH_MAX) {
169                         tX_error("Your \"%s\" environment variable seems malicious (%i chars).", name, length);
170                         tX_error("Please correct that and restart terminatorX.");
171                         exit(-1);
172                 }
173         }
174 }
175
176 int main(int argc, char **argv)
177 {
178         bool keep_caps_failed = false;
179         bool root_dropped = false;
180
181 #ifdef USE_CAPABILITIES 
182         if (!geteuid()) {
183                 if (prctl(PR_SET_KEEPCAPS, 1, -1, -1, -1)) {
184                         keep_caps_failed = true;
185                 }
186                 set_nice_capability(CAP_PERMITTED);
187         }
188 #endif
189
190         GError *mouse_error = mouse.open_channel();
191
192         if ((!geteuid()) && (getuid() != geteuid())) {
193                 int result=setuid(getuid());
194                 root_dropped = true;
195                 
196                 if (result) {
197                         tX_error("main() panic: can't drop root privileges.");
198                         exit(2);
199                 }
200         }
201         
202         /* No suidroot below this comment. */
203
204         fprintf(stderr, "%s - Copyright (C) 1999-2020 by Alexander König\n", VERSIONSTRING);
205         fprintf(stderr, "terminatorX comes with ABSOLUTELY NO WARRANTY - for details read the license.\n");
206
207         if (keep_caps_failed) {
208                 tX_error("failed to keep capabilities.");
209         }
210
211         if (root_dropped) {
212                 tX_msg("started suid-root - root privileges dropped.");
213         }
214
215         
216 #ifdef USE_CAPABILITIES         
217         set_nice_capability(CAP_EFFECTIVE);     
218 #endif
219         
220         /* Theses checks are now sort of unecessary... Anyway... */
221         checkenv("HOME");
222         checkenv("XLOCALEDIR"); 
223
224         gtk_init (&argc, &argv);
225         
226 #ifdef USE_STARTUP_NOTIFICATION
227         // startup isn't really finished with the nagbox alone...
228         gtk_window_set_auto_startup_notification(FALSE);
229 #endif  
230         
231         parse_args(&argc, argv); // loads settings
232
233         if (globals.show_nag) { 
234                 show_about(1);
235                 g_timeout_add(2000, (GSourceFunc) timeout, NULL);
236         }
237         
238         tX_engine *engine=tX_engine::get_instance();
239         LADSPA_Class::init();
240         LADSPA_Plugin::init();
241
242 #ifdef USE_JACK 
243         if (globals.audiodevice_type == JACK) {
244                 // only init jack interface when chosen via configuration
245                 // to allow wiring of jack in-/outputs
246                 tX_jack_client::get_instance()->init();
247         }
248 #endif  
249
250 #ifdef USE_SCHEDULER
251         tX_debug("main() GUI thread is p:%i, t:%i and has policy %i.", getpid(), (int) pthread_self(), sched_getscheduler(getpid()));
252 #endif  
253         create_mastergui(globals.width, globals.height);
254         
255         if (globals.show_nag) {
256                 while (!timesup) {
257                         while (gtk_events_pending()) { gtk_main_iteration(); }
258                         gdk_flush();                            
259                         usleep(250);
260                 }
261                 destroy_about();
262         }
263         
264 #ifdef USE_JACK
265         jack_check();
266 #endif
267         display_mastergui();
268         
269         if (globals.startup_set) {
270                 while (gtk_events_pending()) { gtk_main_iteration(); }
271                 gdk_flush();    
272                 tX_cursor::set_cursor(tX_cursor::WAIT_CURSOR);
273                 load_tt_part(globals.startup_set);
274                 tX_cursor::reset_cursor();
275         } else {
276 #ifdef USE_ALSA_MIDI_IN
277                 if (globals.auto_assign_midi) tX_midiin::auto_assign_midi_mappings(NULL, NULL);
278 #endif          
279         }
280
281         if (mouse_error) {
282                 char buffer[4096];
283                 const char *errorFmt = "<span size=\"larger\" weight=\"bold\">Failed to access input hardware</span>\n\n"
284                         "terminatorX failed to get direct access to the Linux input interface and "
285                         "will now fall back to the standard \"pointer warp\" mode, which may result in "
286                         "<span weight=\"bold\">significantly reduced scratching precision</span>.\n\nTo achieve "
287                         "high precision scratching either\n - <span style=\"italic\">install terminatorX suid-root</span>, or\n"
288                         " - <span style=\"italic\">add the users running terminatorX to a group that can access the special "
289                         "file \"/dev/input/mice\"</span>\nand restart terminatorX.\n\n"
290                         "The reported error was: <span weight=\"bold\">%s</span>";
291
292
293                 if (globals.input_fallback_warning) {
294                         snprintf(buffer, 4096, errorFmt, mouse_error->message);
295                         GtkWidget *dialog=gtk_message_dialog_new_with_markup(GTK_WINDOW(main_window), GTK_DIALOG_DESTROY_WITH_PARENT,
296                                         GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "%s", "");
297                         gtk_message_dialog_set_markup(GTK_MESSAGE_DIALOG(dialog), buffer);
298                         GtkWidget *message_area = gtk_message_dialog_get_message_area(GTK_MESSAGE_DIALOG(dialog));
299                         GtkWidget *check_button = gtk_check_button_new_with_mnemonic ("Show this warning next time");
300                         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check_button), 1);
301                         gtk_widget_show(check_button);
302                         gtk_box_pack_start(GTK_BOX(message_area), check_button, FALSE, FALSE, 0);
303                         gtk_widget_grab_focus(gtk_dialog_get_widget_for_response(GTK_DIALOG(dialog), GTK_RESPONSE_CLOSE));
304                         gtk_dialog_run(GTK_DIALOG(dialog));
305                         globals.input_fallback_warning=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(check_button));
306                         gtk_widget_destroy(dialog);
307                 } else {
308                         tX_warning("Failed t access input hardware: %s", mouse_error->message);
309                 }
310
311                 g_error_free(mouse_error);
312         }
313                 
314 #ifdef USE_STARTUP_NOTIFICATION
315         gdk_notify_startup_complete();
316 #endif  
317         
318 #ifndef CREATE_BENCHMARK
319         gtk_main();
320
321         store_globals();
322
323         mouse.close_channel();
324
325         delete engine;
326         
327         fprintf(stderr, "Have a nice life.\n");
328 #else // CREATE_BENCHMARK
329         gtk_widget_hide(main_window);
330         while (gtk_events_pending()) gtk_main_iteration(); gdk_flush(); 
331         gdk_flush();
332         
333         vtt_class::set_sample_rate(48000);
334         
335         printf("\n* BENCHMARKING *\n");
336         
337         GTimer *bench_time = g_timer_new();
338         gulong micros;
339         double ratio;
340         double res;
341         list <vtt_class *> :: iterator vtt;
342         
343         for (vtt=vtt_class::main_list.begin(); vtt!=vtt_class::main_list.end(); vtt++) {
344                 if ((*vtt)->autotrigger) (*vtt)->trigger();
345         }
346         
347         sleep(3);       
348         g_timer_start(bench_time);
349         
350         for (int i=0; i<BENCH_CYCLES; i++) {
351                 vtt_class::render_all_turntables();
352         }
353         g_timer_stop(bench_time);
354         res=g_timer_elapsed(bench_time, &micros);
355         
356         ratio=((double) BENCH_CYCLES)/res;
357         printf ("Rendered %i blocks in %f secons,\n=> %f blocks per second.\n\n", (long) BENCH_CYCLES, res, ratio);
358 #endif // CREATE_BENCHMARK
359         return (0);
360 }