Workaround for the JACK/SCHED_FIFO issue - Alex
[terminatorX.git] / src / tX_engine.cc
1 /*
2     terminatorX - realtime audio scratching software
3     Copyright (C) 1999-2003  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, write to the Free Software
17     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18  
19     File: tX_engine.c
20  
21     Description: Contains the code that does the real "Scratching
22                  business": XInput, DGA, Mouse and Keyboardgrabbing
23                  etc.
24
25     02 Jun 1999: Implemented high-priority/rt-FIFO-Scheduling use for
26                  engine-thread.
27                  
28     04 Jun 1999: Changed warp-feature behaviour: still connected to
29                  mouse-speed (should be changed to maybe) but now
30                  depends on sample size -> you can warp through all
31                  samples with the same mouse-distance.
32                  
33     12 Aug 2002: Complete rewrite - tX_engine is now a class and the thread
34         is created on startup and kept alive until termination
35 */    
36
37 #include <config.h>
38 #include "tX_types.h"
39 #include "tX_engine.h"
40 #include "tX_audiodevice.h"
41 #include "tX_mouse.h"
42 #include "tX_vtt.h"
43 #include <pthread.h>
44 #include <gtk/gtk.h>
45 #include <gdk/gdkprivate.h>
46 #include "tX_mastergui.h"
47 #include "tX_global.h"
48 #include "tX_tape.h"
49 #include "tX_widget.h"
50 #include <config.h>
51 #include "tX_sequencer.h"
52 #include <errno.h>
53 #include <sched.h>
54 #include "tX_capabilities.h"
55
56 #include <sys/time.h>
57 #include <sys/resource.h>
58
59 tX_engine *tX_engine::engine=NULL;
60
61 tX_engine *tX_engine::get_instance() {
62         if (!engine) {
63                 engine=new tX_engine();
64         }
65         
66         return engine;
67 }
68
69 void tX_engine::set_grab_request() {
70         grab_request=true;
71 }
72
73 int16_t* tX_engine::render_cycle() {
74         /* Checking whether to grab or not  */
75         if (grab_request!=grab_active) {
76                 if (grab_request) {
77                         /* Activating grab... */
78                         int result=mouse->grab(); 
79                         if (result!=0) {
80                                 tX_error("tX_engine::render_cycle(): failed to grab mouse - error %i", result);
81                                 grab_active=false;
82                                 /* Reseting grab_request, too - doesn't help keeping it, does it ? ;) */
83                                 grab_request=false;
84                                 mouse->ungrab();
85                                 grab_off();
86                         } else {
87                                 grab_active=true;
88                         }
89                 } else {
90                         /* Deactivating grab... */
91                         mouse->ungrab();
92                         grab_active=false;
93                         grab_off(); // for the mastergui this is...
94                 }
95         }
96
97         /* Handling input events... */
98         if (grab_active) {
99                 if (mouse->check_event()) {
100                         /* If we're here the user pressed ESC */
101                         grab_request=false;
102                 }
103         }
104
105         /* Forward the sequencer... */
106         sequencer.step();
107
108         /* Render the next block... */
109         int16_t *data=vtt_class::render_all_turntables();
110         
111         /* Record the audio if necessary... */
112         if (is_recording()) tape->eat(data);
113         
114         return  data;
115 }
116
117 void tX_engine::loop() {
118         while (!thread_terminate) {
119                 /* Waiting for the trigger */
120                 pthread_mutex_lock(&start);
121 #ifdef USE_SCHEDULER
122                 pid_t pid=getpid();
123                 struct sched_param parm;
124                         
125                 if (globals.use_realtime && (globals.audiodevice_type!=JACK)) {
126                         sched_getparam(pid, &parm);
127                         parm.sched_priority=sched_get_priority_max(SCHED_FIFO);
128                                                 
129                         if (pthread_setschedparam(pthread_self(), SCHED_FIFO, &parm)) {
130                                 tX_error("loop(): failed to set realtime priority.");
131                         } else {
132                                 tX_debug("loop(): set SCHED_FIFO.");
133                         }
134                 } else {
135                         sched_getparam(pid, &parm);
136                         parm.sched_priority=sched_get_priority_max(SCHED_OTHER);
137                                                 
138                         if (pthread_setschedparam(pthread_self(), SCHED_OTHER, &parm)) {
139                                 tX_error("loop(): failed to set non-realtime priority.");
140                         } else {
141                                 tX_debug("loop(): set SCHED_OTHER.");
142                         }                       
143                 }
144 #endif          
145                 loop_is_active=true;
146                 pthread_mutex_unlock(&start);
147
148                 if (!stop_flag) device->start(); // Hand flow control over to the device
149                 
150                 /* Stopping engine... */
151                 loop_is_active=false;
152         }
153 }
154
155 void *engine_thread_entry(void *engine_void) {
156         tX_engine *engine=(tX_engine*) engine_void;
157         int result;
158         
159         /* Dropping root privileges for the engine thread - if running suid. */
160         
161         if ((!geteuid()) && (getuid() != geteuid())) {
162 #ifdef USE_CAPABILITIES
163                 tX_error("engine_thread_entry(): using capabilities but still suid root?");
164                 tX_error("engine_thread_entry(): Please report this.");
165                 exit(-1);
166 #endif
167                 
168 #ifndef ALLOW_SUID_ROOT
169                 tX_error("This binary doesn't support running suid-root.");
170                 tX_error("Reconfigure with --enable-capabilities or --enable-suidroot if you really want that.");
171                 exit(-1);
172 #endif          
173                 tX_debug("engine_thread_entry() - Running suid root - dropping privileges.");
174                 
175                 result=setuid(getuid());
176                 
177                 if (result!=0) {
178                         tX_error("engine_thread_entry() - Failed to drop root privileges.");
179                         exit(2);
180                 }
181         }
182
183 #ifdef USE_SCHEDULER
184         pid_t pid=getpid();
185         struct sched_param parm;
186
187 #ifdef USE_CAPABILITIES
188         if (have_nice_capability()) {
189                 if (globals.use_realtime) {
190                         sched_getparam(pid, &parm);
191                         parm.sched_priority=sched_get_priority_max(SCHED_FIFO);
192                                                 
193                         if (pthread_setschedparam(pthread_self(), SCHED_FIFO, &parm)) {
194                                 tX_error("engine_thread_entry(): failed to set realtime priority.");
195                         } else {
196                                 tX_debug("engine_thread_entry(): set SCHED_FIFO via capabilities.");
197                         }
198                 }
199         } else {
200                 tX_warning("engine_thread_entry(): can't set SCHED_FIFO -> lacking capabilities.");
201         }
202 #endif //USE_CAPABILITIES
203         int policy=0;
204         
205         pthread_getschedparam(pthread_self(), &policy, &parm);
206         if (policy!=SCHED_FIFO) {
207                 tX_warning("engine_thread_entry() - engine has no realtime priority scheduling.");
208         }
209 #endif //USE_SCHEDULER
210                 
211 #ifdef USE_JACK
212         /* Create the client now, so the user has something to connect to. */
213         tX_jack_client::get_instance();
214 #endif  
215
216 #ifdef USE_SCHEDULER
217         tX_debug("engine_thread_entry() engine thread is p: %i, t: %i and has policy %i.", getpid(), (int) pthread_self(), sched_getscheduler(getpid()));
218 #endif
219         
220         engine->loop();
221         
222         tX_debug("engine_thread_entry() engine thread terminating.");
223         
224         pthread_exit(NULL);
225 }
226
227 tX_engine :: tX_engine() {
228         int result;
229         
230         pthread_mutex_init(&start, NULL);
231         pthread_mutex_lock(&start);
232         thread_terminate=false;
233         
234         /* Creating the actual engine thread.. */
235 #ifdef USE_SCHEDULER    
236         if (!geteuid() /* && globals.use_realtime */) {
237
238 #ifndef ALLOW_SUID_ROOT
239                 tX_error("This binary doesn't support running suid-root.");
240                 tX_error("Reconfigure with --enable-capabilities or --enable-suidroot if you really want that.");
241                 exit(-1);
242 #endif          
243                 
244                 pthread_attr_t pattr;
245                 struct sched_param sparm;
246                 
247                 tX_debug("tX_engine() - setting SCHED_FIFO.");
248                 
249                 pthread_attr_init(&pattr);
250                 pthread_attr_setdetachstate(&pattr, PTHREAD_CREATE_JOINABLE);
251                 pthread_attr_setschedpolicy(&pattr, SCHED_FIFO);
252         
253                 sched_getparam(getpid(), &sparm);
254                 sparm.sched_priority=sched_get_priority_max(SCHED_FIFO);
255         
256                 pthread_attr_setschedparam(&pattr, &sparm);
257                 pthread_attr_setinheritsched(&pattr, PTHREAD_EXPLICIT_SCHED);
258                 pthread_attr_setscope(&pattr, PTHREAD_SCOPE_SYSTEM);
259                 
260                 result=pthread_create(&thread, &pattr, engine_thread_entry, (void *) this);
261         } else {
262 #endif // USE_SCHEDULER
263                 //tX_debug("tX_engine() - can't set SCHED_FIFO euid: %i.", geteuid());
264                 
265                 result=pthread_create(&thread, NULL, engine_thread_entry, (void *) this);
266 #ifdef USE_SCHEDULER            
267         }
268 #endif
269         
270         if (result!=0) {
271                 tX_error("tX_engine() - Failed to create engine thread. Errno is %i.", errno);
272                 exit(1);
273         }
274         
275         /* Dropping root privileges for the main thread - if running suid. */
276         
277         if ((!geteuid()) && (getuid() != geteuid())) {
278                 tX_debug("tX_engine() - Running suid root - dropping privileges.");
279                 
280                 result=setuid(getuid());
281                 
282                 if (result!=0) {
283                         tX_error("tX_engine() - Failed to drop root privileges.");
284                         exit(2);
285                 }
286         }
287         
288         mouse=new tx_mouse();
289 #ifdef USE_ALSA_MIDI_IN 
290         midi=new tX_midiin();
291 #endif  
292         tape=new tx_tapedeck();
293
294         device=NULL;
295         recording=false;
296         recording_request=false;
297         loop_is_active=false;
298         grab_request=false;
299         grab_active=false;
300 }
301
302 void tX_engine :: set_recording_request (bool recording) {
303         this->recording_request=recording;
304 }
305
306 tX_engine_error tX_engine :: run() {
307         list <vtt_class *> :: iterator vtt;
308         
309         if (loop_is_active) return ERROR_BUSY;
310         
311         switch (globals.audiodevice_type) {
312 #ifdef USE_OSS  
313                 case OSS:
314                         device=new tX_audiodevice_oss(); 
315                 break;
316 #endif                  
317
318 #ifdef USE_ALSA                 
319                 case ALSA:
320                         device=new tX_audiodevice_alsa(); 
321                 break;
322 #endif
323
324 #ifdef USE_JACK
325                 case JACK:
326                         device=new tX_audiodevice_jack();
327                 break;
328 #endif
329                 
330                 default:
331                         device=NULL; return ERROR_AUDIO;
332         }
333         
334         if (device->open()) {
335                 if (device->get_is_open()) device->close();
336                 delete device;
337                 device=NULL;            
338                 return ERROR_AUDIO;
339         }       
340
341         vtt_class::set_sample_rate(device->get_sample_rate());
342         
343         if (recording_request) {
344                 if (tape->start_record(globals.record_filename, vtt_class::get_mix_buffer_size(), device->get_sample_rate())) {
345                         device->close();
346                         delete device;
347                         device=NULL;                    
348                         return ERROR_TAPE;                      
349                 } else {
350                         recording=true;
351                 }
352         }
353         
354         for (vtt=vtt_class::main_list.begin(); vtt!=vtt_class::main_list.end(); vtt++) {
355                 (*vtt)->sync_countdown=0;
356                 if ((*vtt)->autotrigger) (*vtt)->trigger();
357         }
358
359         sequencer.forward_to_start_timestamp(1);        
360         stop_flag=false;
361         /* Trigger the engine thread... */
362         pthread_mutex_unlock(&start);
363         
364         return NO_ERROR;
365 }
366
367 void tX_engine :: stop() {
368         list <vtt_class *> :: iterator vtt;
369         
370         if (!loop_is_active) {
371                 tX_error("tX_engine::stop() - but loop's not running?");
372         }
373         
374         pthread_mutex_lock(&start);
375         stop_flag=true;
376         
377         tX_debug("tX_engine::stop() - waiting for loop to stop.");
378         
379         while (loop_is_active) {
380                 /* Due to gtk+ signal handling this can cause a deadlock
381                    on the seqpars' update list. So we need to handle events
382                    while waiting...                     
383                 */
384                 while (gtk_events_pending()) gtk_main_iteration();
385                 usleep(50);
386         }
387         
388         tX_debug("tX_engine::stop() - loop has stopped.");
389
390         if (device->get_is_open()) device->close();
391         delete device;
392         device=NULL;
393         
394         for (vtt=vtt_class::main_list.begin(); vtt!=vtt_class::main_list.end(); vtt++) {
395                 (*vtt)->stop();
396                 (*vtt)->ec_clear_buffer();
397         }
398         
399         if (is_recording()) tape->stop_record();
400         recording=false;
401 }
402
403 tX_engine :: ~tX_engine() {
404         void *dummy;
405                 
406         thread_terminate=true;
407         stop_flag=true;
408         pthread_mutex_unlock(&start);
409         tX_debug("~tX_engine() - Waiting for engine thread to terminate.");
410         pthread_join(thread, &dummy);   
411         
412         delete mouse;
413 #ifdef USE_ALSA_MIDI_IN         
414         delete midi;
415 #endif  
416         delete tape;    
417 }