save and restore the selected engine state at startup
[ardour.git] / gtk2_ardour / engine_dialog.cc
1 /*
2     Copyright (C) 2010 Paul Davis
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include <exception>
21 #include <vector>
22 #include <cmath>
23 #include <fstream>
24 #include <map>
25
26 #include <boost/scoped_ptr.hpp>
27
28 #include <glibmm.h>
29 #include <gtkmm/messagedialog.h>
30
31 #include "pbd/error.h"
32 #include "pbd/xml++.h"
33
34 #include <gtkmm/stock.h>
35 #include <gtkmm/notebook.h>
36 #include <gtkmm2ext/utils.h>
37
38 #include "ardour/audio_backend.h"
39 #include "ardour/audioengine.h"
40 #include "ardour/rc_configuration.h"
41
42 #include "pbd/convert.h"
43 #include "pbd/error.h"
44
45 #include "engine_dialog.h"
46 #include "i18n.h"
47
48 using namespace std;
49 using namespace Gtk;
50 using namespace Gtkmm2ext;
51 using namespace PBD;
52 using namespace Glib;
53
54 EngineControl::EngineControl ()
55         : input_latency_adjustment (0, 0, 99999, 1)
56         , input_latency (input_latency_adjustment)
57         , output_latency_adjustment (0, 0, 99999, 1)
58         , output_latency (output_latency_adjustment)
59         , input_channels_adjustment (2, 0, 256, 1)
60         , input_channels (input_channels_adjustment)
61         , output_channels_adjustment (2, 0, 256, 1)
62         , output_channels (output_channels_adjustment)
63         , ports_adjustment (128, 8, 1024, 1, 16)
64         , ports_spinner (ports_adjustment)
65         , realtime_button (_("Realtime"))
66 #ifdef __APPLE___
67         , basic_packer (6, 2)
68 #else
69         , basic_packer (9, 2)
70 #endif
71         , _used (false)
72 {
73         using namespace Notebook_Helpers;
74         Label* label;
75         vector<string> strings;
76         int row = 0;
77
78
79         /* basic parameters */
80
81         basic_packer.set_spacings (6);
82
83         strings.clear ();
84
85         vector<const ARDOUR::AudioBackendInfo*> backends = ARDOUR::AudioEngine::instance()->available_backends();
86         for (vector<const ARDOUR::AudioBackendInfo*>::const_iterator b = backends.begin(); b != backends.end(); ++b) {
87                 strings.push_back ((*b)->name);
88         }
89
90         set_popdown_strings (backend_combo, strings);
91         backend_combo.set_active_text (strings.front());
92
93         backend_combo.signal_changed().connect (sigc::mem_fun (*this, &EngineControl::backend_changed));
94         backend_changed ();
95
96         driver_combo.signal_changed().connect (sigc::mem_fun (*this, &EngineControl::driver_changed));
97
98         strings.clear ();
99         strings.push_back (_("None"));
100 #ifdef __APPLE__
101         strings.push_back (_("coremidi"));
102 #else
103         strings.push_back (_("seq"));
104         strings.push_back (_("raw"));
105 #endif
106         set_popdown_strings (midi_driver_combo, strings);
107         midi_driver_combo.set_active_text (strings.front ());
108
109         row = 0;
110
111         label = manage (left_aligned_label (_("Audio System:")));
112         basic_packer.attach (*label, 0, 1, row, row + 1, FILL|EXPAND, (AttachOptions) 0);
113         basic_packer.attach (backend_combo, 1, 2, row, row + 1, FILL|EXPAND, (AttachOptions) 0);
114         row++;
115
116         label = manage (left_aligned_label (_("Driver:")));
117         basic_packer.attach (*label, 0, 1, row, row + 1, FILL|EXPAND, (AttachOptions) 0);
118         basic_packer.attach (driver_combo, 1, 2, row, row + 1, FILL|EXPAND, (AttachOptions) 0);
119         row++;
120
121         label = manage (left_aligned_label (_("Device:")));
122         basic_packer.attach (*label, 0, 1, row, row + 1, FILL|EXPAND, (AttachOptions) 0);
123         basic_packer.attach (device_combo, 1, 2, row, row + 1, FILL|EXPAND, (AttachOptions) 0);
124         row++;
125
126         label = manage (left_aligned_label (_("Sample rate:")));
127         basic_packer.attach (*label, 0, 1, row, row + 1, FILL|EXPAND, (AttachOptions) 0);
128         basic_packer.attach (sample_rate_combo, 1, 2, row, row + 1, FILL|EXPAND, (AttachOptions) 0);
129         row++;
130
131         sr_connection = sample_rate_combo.signal_changed().connect (sigc::mem_fun (*this, &EngineControl::sample_rate_changed));
132
133         label = manage (left_aligned_label (_("Buffer size:")));
134         basic_packer.attach (*label, 0, 1, row, row + 1, FILL|EXPAND, (AttachOptions) 0);
135         basic_packer.attach (buffer_size_combo, 1, 2, row, row + 1, FILL|EXPAND, (AttachOptions) 0);
136         row++;
137
138         bs_connection = buffer_size_combo.signal_changed().connect (sigc::mem_fun (*this, &EngineControl::buffer_size_changed));
139
140         label = manage (left_aligned_label (_("Hardware input latency:")));
141         basic_packer.attach (*label, 0, 1, row, row+1, FILL|EXPAND, (AttachOptions) 0);
142         basic_packer.attach (input_latency, 1, 2, row, row+1, FILL|EXPAND, (AttachOptions) 0);
143         label = manage (left_aligned_label (_("samples")));
144         basic_packer.attach (*label, 2, 3, row, row+1, FILL|EXPAND, (AttachOptions) 0);
145         ++row;
146
147         label = manage (left_aligned_label (_("Hardware output latency:")));
148         basic_packer.attach (*label, 0, 1, row, row+1, FILL|EXPAND, (AttachOptions) 0);
149         basic_packer.attach (output_latency, 1, 2, row, row+1, FILL|EXPAND, (AttachOptions) 0);
150         label = manage (left_aligned_label (_("samples")));
151         basic_packer.attach (*label, 2, 3, row, row+1, FILL|EXPAND, (AttachOptions) 0);
152         ++row;
153
154         device_combo.set_size_request (250, -1);
155         input_device_combo.set_size_request (250, -1);
156         output_device_combo.set_size_request (250, -1);
157
158         device_combo.signal_changed().connect (sigc::mem_fun (*this, &EngineControl::device_changed));
159
160         basic_hbox.pack_start (basic_packer, false, false);
161
162         basic_packer.set_border_width (12);
163         midi_packer.set_border_width (12);
164
165         notebook.pages().push_back (TabElem (basic_hbox, _("Audio System Settings")));
166         notebook.pages().push_back (TabElem (midi_hbox, _("MIDI Settings")));
167         notebook.set_border_width (12);
168
169         notebook.set_tab_pos (POS_RIGHT);
170         notebook.show_all ();
171
172         notebook.set_name ("SettingsNotebook");
173
174         set_border_width (12);
175         pack_start (notebook);
176
177         /* Pick up any existing audio setup configuration, if appropriate */
178
179         XMLNode* audio_setup = ARDOUR::Config->extra_xml ("AudioMIDISetup");
180         
181         if (audio_setup) {
182                 set_state (*audio_setup);
183         }
184 }
185
186 EngineControl::~EngineControl ()
187 {
188
189 }
190
191 void
192 EngineControl::backend_changed ()
193 {
194         string backend_name = backend_combo.get_active_text();
195         boost::shared_ptr<ARDOUR::AudioBackend> backend;
196
197         if (!(backend = ARDOUR::AudioEngine::instance()->set_backend (backend_name, "ardour", ""))) {
198                 /* eh? */
199                 return;
200         }
201
202         if (backend->requires_driver_selection()) {
203                 vector<string> drivers = backend->enumerate_drivers();
204                 driver_combo.set_sensitive (true);
205                 set_popdown_strings (driver_combo, drivers);
206                 driver_combo.set_active_text (drivers.front());
207                 driver_changed ();
208         } else {
209                 driver_combo.set_sensitive (false);
210                 list_devices ();
211         }
212         
213         maybe_set_state ();
214 }
215
216 void
217 EngineControl::list_devices ()
218 {
219         boost::shared_ptr<ARDOUR::AudioBackend> backend = ARDOUR::AudioEngine::instance()->current_backend();
220         assert (backend);
221
222         /* now fill out devices, mark sample rates, buffer sizes insensitive */
223             
224         vector<ARDOUR::AudioBackend::DeviceStatus> all_devices = backend->enumerate_devices ();
225         
226         /* NOTE: Ardour currently does not display the "available" field of the
227          * returned devices.
228          *
229          * Doing so would require a different GUI widget than the combo
230          * box/popdown that we currently use, since it has no way to list
231          * items that are not selectable. Something more like a popup menu,
232          * which could have unselectable items, would be appropriate.
233          */
234
235         vector<string> available_devices;
236
237         for (vector<ARDOUR::AudioBackend::DeviceStatus>::const_iterator i = all_devices.begin(); i != all_devices.end(); ++i) {
238                 available_devices.push_back (i->name);
239         }
240
241         set_popdown_strings (device_combo, available_devices);
242         set_popdown_strings (input_device_combo, available_devices);
243         set_popdown_strings (output_device_combo, available_devices);
244         
245         if (!available_devices.empty()) {
246                 device_combo.set_active_text (available_devices.front());
247                 input_device_combo.set_active_text (available_devices.front());
248                 output_device_combo.set_active_text (available_devices.front());
249         }
250
251         device_changed ();
252 }
253         
254 void
255 EngineControl::driver_changed ()
256 {
257         boost::shared_ptr<ARDOUR::AudioBackend> backend = ARDOUR::AudioEngine::instance()->current_backend();
258         assert (backend);
259
260         backend->set_driver (driver_combo.get_active_text());
261         list_devices ();
262
263         maybe_set_state ();
264 }
265
266 void
267 EngineControl::device_changed ()
268 {
269         boost::shared_ptr<ARDOUR::AudioBackend> backend = ARDOUR::AudioEngine::instance()->current_backend();
270         assert (backend);
271         string device_name = device_combo.get_active_text ();
272         vector<string> s;
273
274         /* don't allow programmatic change to sample_rate_combo to cause a
275            recursive call to this method.
276         */
277            
278         sr_connection.block ();
279
280         /* sample rates */
281
282         vector<float> sr = backend->available_sample_rates (device_name);
283         for (vector<float>::const_iterator x = sr.begin(); x != sr.end(); ++x) {
284                 char buf[32];
285                 if (fmod (*x, 1000.0f)) {
286                         snprintf (buf, sizeof (buf), "%.1f kHz", (*x)/1000.0);
287                 } else {
288                         snprintf (buf, sizeof (buf), "%.0f kHz", (*x)/1000.0);
289                 }
290                 s.push_back (buf);
291         }
292
293         set_popdown_strings (sample_rate_combo, s);
294         sample_rate_combo.set_active_text (s.front());
295
296         reshow_buffer_sizes (true);
297
298         sr_connection.unblock ();
299         
300         maybe_set_state ();
301 }       
302
303 void 
304 EngineControl::sample_rate_changed ()
305 {
306         /* reset the strings for buffer size to show the correct msec value
307            (reflecting the new sample rate
308         */
309
310         reshow_buffer_sizes (false);
311         save_state ();
312
313 }
314
315 void 
316 EngineControl::buffer_size_changed ()
317 {
318         save_state ();
319 }
320
321 void
322 EngineControl::reshow_buffer_sizes (bool size_choice_changed)
323 {
324         boost::shared_ptr<ARDOUR::AudioBackend> backend = ARDOUR::AudioEngine::instance()->current_backend();
325         assert (backend);
326         string device_name = device_combo.get_active_text ();
327         vector<string> s;
328         uint32_t existing_size_choice = 0;
329         string new_target_string;
330
331         /* buffer sizes  - convert from just samples to samples + msecs for
332          * the displayed string
333          */
334
335         bs_connection.block ();
336
337         if (!size_choice_changed) {
338                 sscanf (buffer_size_combo.get_active_text().c_str(), "%" PRIu32, &existing_size_choice);
339         }
340
341         s.clear ();
342         vector<uint32_t> bs = backend->available_buffer_sizes(device_name);
343         uint32_t rate = get_rate();
344
345         for (vector<uint32_t>::const_iterator x = bs.begin(); x != bs.end(); ++x) {
346                 char buf[32];
347
348                 /* Translators: "samples" is ALWAYS plural here, so we do not
349                    need singular form as well. Same for msecs.
350                 */
351                 snprintf (buf, sizeof (buf), _("%u samples (%.1f msecs)"), *x, (2 * (*x)) / (rate/1000.0));
352                 s.push_back (buf);
353
354                 /* if this is the size previously chosen, this is the string we
355                  * will want to be active in the combo.
356                  */
357
358                 if (existing_size_choice == *x) {
359                         new_target_string = buf;
360                 }
361                         
362         }
363
364         set_popdown_strings (buffer_size_combo, s);
365
366         if (!new_target_string.empty()) {
367                 buffer_size_combo.set_active_text (new_target_string);
368         } else {
369                 buffer_size_combo.set_active_text (s.front());
370         }
371
372         bs_connection.unblock ();
373 }
374
375 void
376 EngineControl::audio_mode_changed ()
377 {
378         std::string str = audio_mode_combo.get_active_text();
379
380         if (str == _("Playback/recording on 1 device")) {
381                 input_device_combo.set_sensitive (false);
382                 output_device_combo.set_sensitive (false);
383         } else if (str == _("Playback/recording on 2 devices")) {
384                 input_device_combo.set_sensitive (true);
385                 output_device_combo.set_sensitive (true);
386         } else if (str == _("Playback only")) {
387                 output_device_combo.set_sensitive (true);
388                 input_device_combo.set_sensitive (false);
389         } else if (str == _("Recording only")) {
390                 input_device_combo.set_sensitive (true);
391                 output_device_combo.set_sensitive (false);
392         }
393 }
394
395 EngineControl::State*
396 EngineControl::get_matching_state (const string& backend,
397                                    const string& driver,
398                                    const string& device)
399 {
400         for (StateList::iterator i = states.begin(); i != states.end(); ++i) {
401                 if ((*i).backend == backend &&
402                     (*i).driver == driver &&
403                     (*i).device == device) {
404                         return &(*i);
405                 }
406         }
407         return 0;
408 }
409
410 EngineControl::State*
411 EngineControl::get_current_state ()
412 {
413         boost::shared_ptr<ARDOUR::AudioBackend> backend = ARDOUR::AudioEngine::instance()->current_backend();
414
415         if (backend) {
416                 return get_matching_state (backend_combo.get_active_text(),
417                                             (backend->requires_driver_selection() ? (std::string) driver_combo.get_active_text() : string()),
418                                             device_combo.get_active_text());
419         }
420
421
422         return get_matching_state (backend_combo.get_active_text(),
423                                    string(),
424                                    device_combo.get_active_text());
425 }
426
427 void
428 EngineControl::save_state ()
429 {
430         bool existing = true;
431         State* state = get_current_state ();
432
433         if (!state) {
434                 existing = false;
435                 state = new State;
436         }
437         
438         state->backend = backend_combo.get_active_text ();
439         state->driver = driver_combo.get_active_text ();
440         state->device = device_combo.get_active_text ();
441         state->buffer_size = buffer_size_combo.get_active_text ();
442         state->sample_rate = sample_rate_combo.get_active_text ();
443
444         if (!existing) {
445                 states.push_back (*state);
446         }
447 }
448
449 void
450 EngineControl::maybe_set_state ()
451 {
452         State* state = get_current_state ();
453
454         if (state) {
455                 sr_connection.block ();
456                 bs_connection.block ();
457                 sample_rate_combo.set_active_text (state->sample_rate);
458                 /* need to reset possible strings for buffer size before we do
459                    this
460                 */
461                 reshow_buffer_sizes (false);
462                 buffer_size_combo.set_active_text (state->buffer_size);
463                 bs_connection.unblock ();
464                 sr_connection.unblock ();
465         }
466 }
467         
468 XMLNode&
469 EngineControl::get_state ()
470 {
471         XMLNode* root = new XMLNode ("AudioMIDISetup");
472         std::string path;
473
474         if (_used) {
475
476                 if (!states.empty()) {
477                         XMLNode* state_nodes = new XMLNode ("EngineStates");
478                         
479                         for (StateList::const_iterator i = states.begin(); i != states.end(); ++i) {
480
481                                 XMLNode* node = new XMLNode ("State");
482
483                                 node->add_property ("backend", (*i).backend);
484                                 node->add_property ("driver", (*i).driver);
485                                 node->add_property ("device", (*i).device);
486                                 node->add_property ("sample-rate", (*i).sample_rate);
487                                 node->add_property ("buffer-size", (*i).buffer_size);
488                                 node->add_property ("input-latency", (*i).input_latency);
489                                 node->add_property ("output-latency", (*i).output_latency);
490                                 node->add_property ("input-channels", (*i).input_channels);
491                                 node->add_property ("output-channels", (*i).output_channels);
492                                 node->add_property ("active", (*i).active ? "yes" : "no");
493         
494                                 state_nodes->add_child_nocopy (*node);
495                         }
496
497                         root->add_child_nocopy (*state_nodes);
498                 }
499         }
500
501         return *root;
502 }
503
504 void
505 EngineControl::set_state (const XMLNode& root)
506 {
507         XMLNodeList          clist, cclist;
508         XMLNodeConstIterator citer, cciter;
509         XMLNode* child;
510         XMLNode* grandchild;
511         XMLProperty* prop = NULL;
512
513         if (root.name() != "AudioMIDISetup") {
514                 return;
515         }
516
517         clist = root.children();
518
519         states.clear ();
520
521         for (citer = clist.begin(); citer != clist.end(); ++citer) {
522
523                 child = *citer;
524                 
525                 if (child->name() != "EngineStates") {
526                         continue;
527                 }
528
529                 cclist = child->children();
530
531                 for (cciter = cclist.begin(); cciter != cclist.end(); ++cciter) {
532                         State state;
533                         
534                         grandchild = *cciter;
535
536                         if (grandchild->name() != "State") {
537                                 continue;
538                         }
539                         
540                         if ((prop = grandchild->property ("backend")) == 0) {
541                                 continue;
542                         }
543                         state.backend = prop->value ();
544                         
545                         if ((prop = grandchild->property ("driver")) == 0) {
546                                 continue;
547                         }
548                         state.driver = prop->value ();
549                         
550                         if ((prop = grandchild->property ("device")) == 0) {
551                                 continue;
552                         }
553                         state.device = prop->value ();
554                         
555                         if ((prop = grandchild->property ("sample-rate")) == 0) {
556                                 continue;
557                         }
558                         state.sample_rate = prop->value ();
559                         
560                         if ((prop = grandchild->property ("buffer-size")) == 0) {
561                                 continue;
562                         }
563                         state.buffer_size = prop->value ();
564                         
565                         if ((prop = grandchild->property ("input-latency")) == 0) {
566                                 continue;
567                         }
568                         state.input_latency = prop->value ();
569                         
570                         if ((prop = grandchild->property ("output-latency")) == 0) {
571                                 continue;
572                         }
573                         state.output_latency = prop->value ();
574                         
575                         if ((prop = grandchild->property ("input-channels")) == 0) {
576                                 continue;
577                         }
578                         state.input_channels = prop->value ();
579                         
580                         if ((prop = grandchild->property ("output-channels")) == 0) {
581                                 continue;
582                         }
583                         state.output_channels = prop->value ();
584
585                         if ((prop = grandchild->property ("active")) == 0) {
586                                 continue;
587                         }
588                         state.active = string_is_affirmative (prop->value ());
589                         
590                         states.push_back (state);
591                 }
592         }
593
594         /* now see if there was an active state and switch the setup to it */
595         
596         for (StateList::const_iterator i = states.begin(); i != states.end(); ++i) {
597                 if ((*i).active) {
598                         sr_connection.block ();
599                         bs_connection.block ();
600                         backend_combo.set_active_text ((*i).backend);
601                         driver_combo.set_active_text ((*i).driver);
602                         device_combo.set_active_text ((*i).device);
603                         sample_rate_combo.set_active_text ((*i).sample_rate);
604                         buffer_size_combo.set_active_text ((*i).buffer_size);
605                         sr_connection.unblock ();
606                         bs_connection.unblock ();
607                         break;
608                 }
609         }
610 }
611
612 int
613 EngineControl::setup_engine (bool start)
614 {
615         boost::shared_ptr<ARDOUR::AudioBackend> backend = ARDOUR::AudioEngine::instance()->current_backend();
616         assert (backend);
617
618         /* grab the parameters from the GUI and apply them */
619
620         try {
621                 if (backend->requires_driver_selection()) {
622                         if (backend->set_driver (get_driver())) {
623                                 return -1;
624                         }
625                 }
626
627                 if (backend->set_device_name (get_device_name())) {
628                         return -1;
629                 }
630
631                 if (backend->set_sample_rate (get_rate())) {
632                         error << string_compose (_("Cannot set sample rate to %1"), get_rate()) << endmsg;
633                         return -1;
634                 }
635                 if (backend->set_buffer_size (get_buffer_size())) {
636                         error << string_compose (_("Cannot set buffer size to %1"), get_buffer_size()) << endmsg;
637                         return -1;
638                 }
639                 if (backend->set_input_channels (get_input_channels())) {
640                         error << string_compose (_("Cannot set input channels to %1"), get_input_channels()) << endmsg;
641                         return -1;
642                 }
643                 if (backend->set_output_channels (get_output_channels())) {
644                         error << string_compose (_("Cannot set output channels to %1"), get_output_channels()) << endmsg;
645                         return -1;
646                 }
647                 if (backend->set_systemic_input_latency (get_input_latency())) {
648                         error << string_compose (_("Cannot set input latency to %1"), get_input_latency()) << endmsg;
649                         return -1;
650                 }
651                 if (backend->set_systemic_output_latency (get_output_latency())) {
652                         error << string_compose (_("Cannot set output latency to %1"), get_output_latency()) << endmsg;
653                         return -1;
654                 }
655
656                 /* we've used this dialog to configure the engine, which means
657                  * that our state becomes relevant for saving (and thus
658                  * implicitly, restoring.
659                  */
660
661                 _used = true;
662
663                 /* get a pointer to the current state object, creating one if
664                  * necessary
665                  */
666
667                 State* state = get_current_state ();
668
669                 if (!state) {
670                         save_state ();
671                         state = get_current_state ();
672                         assert (state);
673                 }
674
675                 /* all off */
676
677                 for (StateList::iterator i = states.begin(); i != states.end(); ++i) {
678                         (*i).active = false;
679                 }
680
681                 /* mark this one active (to be used next time the dialog is
682                  * shown)
683                  */
684
685                 state->active = true;
686                 
687                 if (start) {
688                         return ARDOUR::AudioEngine::instance()->start();
689                 }
690
691                 return 0;
692
693         } catch (...) {
694                 cerr << "exception thrown...\n";
695                 return -1;
696         }
697 }
698
699 uint32_t
700 EngineControl::get_rate () const
701 {
702         double r = atof (sample_rate_combo.get_active_text ());
703         /* the string may have been translated with an abbreviation for
704          * thousands, so use a crude heuristic to fix this.
705          */
706         if (r < 1000.0) {
707                 r *= 1000.0;
708         }
709         return lrint (r);
710 }
711
712 uint32_t
713 EngineControl::get_buffer_size () const
714 {
715         string txt = buffer_size_combo.get_active_text ();
716         uint32_t samples;
717
718         if (sscanf (txt.c_str(), "%d", &samples) != 1) {
719                 throw exception ();
720         }
721
722         return samples;
723 }
724
725 uint32_t
726 EngineControl::get_input_channels() const
727 {
728         return (uint32_t) input_channels_adjustment.get_value();
729 }
730
731 uint32_t
732 EngineControl::get_output_channels() const
733 {
734         return (uint32_t) output_channels_adjustment.get_value();
735 }
736
737 uint32_t
738 EngineControl::get_input_latency() const
739 {
740         return (uint32_t) input_latency_adjustment.get_value();
741 }
742
743 uint32_t
744 EngineControl::get_output_latency() const
745 {
746         return (uint32_t) output_latency_adjustment.get_value();
747 }
748
749 string
750 EngineControl::get_driver () const
751 {
752         return driver_combo.get_active_text ();
753 }
754
755 string
756 EngineControl::get_device_name () const
757 {
758         return device_combo.get_active_text ();
759 }
760