Support multiple audio APIs, allowing ALSA for Linux and ASIO for Windows (#2363).
[dcpomatic.git] / src / wx / config_dialog.cc
index 08e2721da35e49f448b3d2ccabd9de93dbea9f22..9e68d8cb3ecbdbad3d3b7551422f81e6d0c1ac57 100644 (file)
 
 */
 
-#include "config_dialog.h"
-#include "static_text.h"
+
+#include "audio_mapping_view.h"
 #include "check_box.h"
-#include "nag_dialog.h"
+#include "config_dialog.h"
 #include "dcpomatic_button.h"
-#include "audio_mapping_view.h"
+#include "dcpomatic_choice.h"
+#include "nag_dialog.h"
+#include "static_text.h"
+#include <dcp/file.h>
 #include <dcp/raw_convert.h>
 
 
@@ -132,9 +135,11 @@ GeneralPage::add_language_controls (wxGridBagSizer* table, int& r)
        languages.push_back (make_pair("Português europeu", "pt_PT"));
        languages.push_back (make_pair("Português do Brasil", "pt_BR"));
        languages.push_back (make_pair("Svenska", "sv_SE"));
+       languages.push_back (make_pair("Slovenščina", "sl_SI"));
        languages.push_back (make_pair("Slovenský jazyk", "sk_SK"));
        // languages.push_back (make_pair("Türkçe", "tr_TR"));
        languages.push_back (make_pair("українська мова", "uk_UA"));
+       languages.push_back (make_pair("Magyar nyelv", "hu_HU"));
        checked_set (_language, languages);
        table->Add (_language, wxGBPosition (r, 1));
        ++r;
@@ -148,7 +153,7 @@ GeneralPage::add_language_controls (wxGridBagSizer* table, int& r)
        restart->SetFont (font);
        ++r;
 
-       _set_language->Bind (wxEVT_CHECKBOX, bind (&GeneralPage::set_language_changed, this));
+       _set_language->bind(&GeneralPage::set_language_changed, this);
        _language->Bind     (wxEVT_CHOICE,   bind (&GeneralPage::language_changed,     this));
 }
 
@@ -163,8 +168,8 @@ GeneralPage::add_update_controls (wxGridBagSizer* table, int& r)
        table->Add (_check_for_test_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
        ++r;
 
-       _check_for_updates->Bind (wxEVT_CHECKBOX, bind (&GeneralPage::check_for_updates_changed, this));
-       _check_for_test_updates->Bind (wxEVT_CHECKBOX, bind (&GeneralPage::check_for_test_updates_changed, this));
+       _check_for_updates->bind(&GeneralPage::check_for_updates_changed, this);
+       _check_for_test_updates->bind(&GeneralPage::check_for_test_updates_changed, this);
 }
 
 void
@@ -462,14 +467,13 @@ CertificateChainEditor::export_certificate ()
                if (path.extension() != ".pem") {
                        path += ".pem";
                }
-               auto f = fopen_boost (path, "w");
+               dcp::File f(path, "w");
                if (!f) {
                        throw OpenFileError (path, errno, OpenFileError::WRITE);
                }
 
                string const s = j->certificate (true);
-               checked_fwrite (s.c_str(), s.length(), f, path);
-               fclose (f);
+               f.checked_write(s.c_str(), s.length());
        }
        d->Destroy ();
 }
@@ -487,14 +491,13 @@ CertificateChainEditor::export_chain ()
                if (path.extension() != ".pem") {
                        path += ".pem";
                }
-               auto f = fopen_boost (path, "w");
+               dcp::File f(path, "w");
                if (!f) {
                        throw OpenFileError (path, errno, OpenFileError::WRITE);
                }
 
                auto const s = _get()->chain();
-               checked_fwrite (s.c_str(), s.length(), f, path);
-               fclose (f);
+               f.checked_write (s.c_str(), s.length());
        }
 
        d->Destroy ();
@@ -616,14 +619,13 @@ CertificateChainEditor::export_private_key ()
                if (path.extension() != ".pem") {
                        path += ".pem";
                }
-               auto f = fopen_boost (path, "w");
+               dcp::File f(path, "w");
                if (!f) {
                        throw OpenFileError (path, errno, OpenFileError::WRITE);
                }
 
                auto const s = _get()->key().get ();
-               checked_fwrite (s.c_str(), s.length(), f, path);
-               fclose (f);
+               f.checked_write(s.c_str(), s.length());
        }
        d->Destroy ();
 }
@@ -733,17 +735,16 @@ KeysPage::export_decryption_chain_and_key ()
 
        if (d->ShowModal () == wxID_OK) {
                boost::filesystem::path path (wx_to_std(d->GetPath()));
-               auto f = fopen_boost (path, "w");
+               dcp::File f(path, "w");
                if (!f) {
                        throw OpenFileError (path, errno, OpenFileError::WRITE);
                }
 
                auto const chain = Config::instance()->decryption_chain()->chain();
-               checked_fwrite (chain.c_str(), chain.length(), f, path);
-               optional<string> const key = Config::instance()->decryption_chain()->key();
+               f.checked_write (chain.c_str(), chain.length());
+               auto const key = Config::instance()->decryption_chain()->key();
                DCPOMATIC_ASSERT (key);
-               checked_fwrite (key->c_str(), key->length(), f, path);
-               fclose (f);
+               f.checked_write(key->c_str(), key->length());
        }
        d->Destroy ();
 
@@ -768,15 +769,15 @@ KeysPage::import_decryption_chain_and_key ()
        if (d->ShowModal () == wxID_OK) {
                auto new_chain = make_shared<dcp::CertificateChain>();
 
-               FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "r");
+               dcp::File f(wx_to_std(d->GetPath()), "r");
                if (!f) {
-                       throw OpenFileError (wx_to_std (d->GetPath ()), errno, OpenFileError::WRITE);
+                       throw OpenFileError (f.path(), errno, OpenFileError::WRITE);
                }
 
                string current;
-               while (!feof (f)) {
+               while (!f.eof()) {
                        char buffer[128];
-                       if (fgets (buffer, 128, f) == 0) {
+                       if (f.gets(buffer, 128) == 0) {
                                break;
                        }
                        current += buffer;
@@ -788,7 +789,6 @@ KeysPage::import_decryption_chain_and_key ()
                                current = "";
                        }
                }
-               fclose (f);
 
                if (new_chain->chain_valid() && new_chain->private_key_valid()) {
                        Config::instance()->set_decryption_chain (new_chain);
@@ -833,14 +833,13 @@ KeysPage::export_decryption_certificate ()
                if (path.extension() != ".pem") {
                        path += ".pem";
                }
-               auto f = fopen_boost (path, "w");
+               dcp::File f(path, "w");
                if (!f) {
                        throw OpenFileError (path, errno, OpenFileError::WRITE);
                }
 
                auto const s = Config::instance()->decryption_chain()->leaf().certificate (true);
-               checked_fwrite (s.c_str(), s.length(), f, path);
-               fclose (f);
+               f.checked_write(s.c_str(), s.length());
        }
 
        d->Destroy ();
@@ -862,9 +861,11 @@ SoundPage::setup ()
 
        _sound = new CheckBox (_panel, _("Play sound via"));
        table->Add (_sound, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
-       wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
+       auto s = new wxBoxSizer (wxHORIZONTAL);
+       _sound_api = new Choice(_panel);
+       s->Add(_sound_api, 0);
        _sound_output = new wxChoice (_panel, wxID_ANY);
-       s->Add (_sound_output, 0);
+       s->Add(_sound_output, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
        _sound_output_details = new wxStaticText (_panel, wxID_ANY, wxT(""));
        s->Add (_sound_output_details, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, DCPOMATIC_SIZER_X_GAP);
        table->Add (s, wxGBPosition(r, 1));
@@ -885,22 +886,40 @@ SoundPage::setup ()
        font.SetPointSize (font.GetPointSize() - 1);
        _sound_output_details->SetFont (font);
 
-       RtAudio audio (DCPOMATIC_RTAUDIO_API);
+       for (auto const& api: audio_apis()) {
+               _sound_api->add(api.name(), new wxStringClientData(std_to_wx(api.id())));
+       }
+
+       update_sound_outputs();
+
+       _sound->bind(&SoundPage::sound_changed, this);
+       _sound_api->Bind(wxEVT_CHOICE, bind(&SoundPage::sound_api_changed, this));
+       _sound_output->Bind(wxEVT_CHOICE, bind(&SoundPage::sound_output_changed, this));
+       _map->Changed.connect(bind(&SoundPage::map_changed, this, _1));
+       _reset_to_default->Bind(wxEVT_BUTTON, bind(&SoundPage::reset_to_default, this));
+}
+
+
+void
+SoundPage::update_sound_outputs()
+{
+       _sound_output->Clear();
+
+       RtAudio audio(id_to_audio_api(Config::instance()->sound_api()).rtaudio_id());
        for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
                try {
-                       auto dev = audio.getDeviceInfo (i);
+                       auto dev = audio.getDeviceInfo(i);
                        if (dev.probed && dev.outputChannels > 0) {
-                               _sound_output->Append (std_to_wx (dev.name));
+                               _sound_output->Append(std_to_wx(dev.name));
                        }
                } catch (RtAudioError&) {
                        /* Something went wrong so let's just ignore that device */
                }
        }
 
-       _sound->Bind        (wxEVT_CHECKBOX, bind(&SoundPage::sound_changed, this));
-       _sound_output->Bind (wxEVT_CHOICE,   bind(&SoundPage::sound_output_changed, this));
-       _map->Changed.connect (bind(&SoundPage::map_changed, this, _1));
-       _reset_to_default->Bind (wxEVT_BUTTON,   bind(&SoundPage::reset_to_default, this));
+       if (!_sound_output->IsEmpty()) {
+               _sound_output->SetSelection(0);
+       }
 }
 
 void
@@ -915,16 +934,28 @@ SoundPage::map_changed (AudioMapping m)
        Config::instance()->set_audio_mapping (m);
 }
 
+
 void
 SoundPage::sound_changed ()
 {
        Config::instance()->set_sound (_sound->GetValue ());
 }
 
+
+void
+SoundPage::sound_api_changed()
+{
+       auto config = Config::instance();
+       config->set_sound_api(index_to_audio_api(_sound_api->get().get_value_or(0)).id());
+       config->unset_sound_output();
+       update_sound_outputs();
+}
+
+
 void
 SoundPage::sound_output_changed ()
 {
-       RtAudio audio (DCPOMATIC_RTAUDIO_API);
+       RtAudio audio(id_to_audio_api(Config::instance()->sound_api()).rtaudio_id());
        auto const so = get_sound_output();
        string default_device;
        try {
@@ -946,6 +977,28 @@ SoundPage::config_changed ()
 
        checked_set (_sound, config->sound ());
 
+       auto const current_api = get_sound_api();
+       optional<string> configured_api;
+
+       if (config->sound_api()) {
+               configured_api = config->sound_api().get();
+       } else {
+               configured_api = audio_apis()[0].id();
+       }
+
+       if (current_api != *configured_api) {
+               unsigned int i = 0;
+               while (i < _sound_api->GetCount()) {
+                       if (string_client_data(_sound_api->GetClientObject(i)) == std_to_wx(*configured_api)) {
+                               _sound_api->SetSelection(i);
+                               break;
+                       }
+                       ++i;
+               }
+       }
+
+       RtAudio audio(id_to_audio_api(configured_api).rtaudio_id());
+
        auto const current_so = get_sound_output ();
        optional<string> configured_so;
 
@@ -953,10 +1006,9 @@ SoundPage::config_changed ()
                configured_so = config->sound_output().get();
        } else {
                /* No configured output means we should use the default */
-               RtAudio audio (DCPOMATIC_RTAUDIO_API);
                try {
                        configured_so = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
-               } catch (RtAudioError&) {
+               } catch (RtAudioError& e) {
                        /* Probably no audio devices at all */
                }
        }
@@ -973,19 +1025,6 @@ SoundPage::config_changed ()
                }
        }
 
-       RtAudio audio (DCPOMATIC_RTAUDIO_API);
-
-       map<int, wxString> apis;
-       apis[RtAudio::MACOSX_CORE]    = _("CoreAudio");
-       apis[RtAudio::WINDOWS_ASIO]   = _("ASIO");
-       apis[RtAudio::WINDOWS_DS]     = _("Direct Sound");
-       apis[RtAudio::WINDOWS_WASAPI] = _("WASAPI");
-       apis[RtAudio::UNIX_JACK]      = _("JACK");
-       apis[RtAudio::LINUX_ALSA]     = _("ALSA");
-       apis[RtAudio::LINUX_PULSE]    = _("PulseAudio");
-       apis[RtAudio::LINUX_OSS]      = _("OSS");
-       apis[RtAudio::RTAUDIO_DUMMY]  = _("Dummy");
-
        int channels = 0;
        if (configured_so) {
                for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
@@ -1000,9 +1039,7 @@ SoundPage::config_changed ()
                }
        }
 
-       _sound_output_details->SetLabel (
-               wxString::Format(_("%d channels on %s"), channels, apis[audio.getCurrentApi()])
-               );
+       _sound_output_details->SetLabel(wxString::Format(_("%d channels"), channels));
 
        _map->set (Config::instance()->audio_mapping(channels));
 
@@ -1027,13 +1064,26 @@ SoundPage::setup_sensitivity ()
        _sound_output->Enable (_sound->GetValue());
 }
 
+
+optional<string>
+SoundPage::get_sound_api()
+{
+       auto const sel = _sound_api->GetSelection();
+       if (sel == wxNOT_FOUND) {
+               return {};
+       }
+
+       return wx_to_std(selection_string_client_data(_sound_api));
+}
+
+
 /** @return Currently-selected preview sound output in the dialogue */
 optional<string>
 SoundPage::get_sound_output ()
 {
        int const sel = _sound_output->GetSelection ();
        if (sel == wxNOT_FOUND) {
-               return optional<string> ();
+               return {};
        }
 
        return wx_to_std (_sound_output->GetString (sel));
@@ -1056,7 +1106,7 @@ LocationsPage::GetName () const
 wxBitmap
 LocationsPage::GetLargeIcon () const
 {
-       return wxBitmap(bitmap_path("locations"), wxBITMAP_TYPE_PNG);
+       return wxBitmap(icon_path("locations"), wxBITMAP_TYPE_PNG);
 }
 #endif