2 Copyright (C) 2012-2021 Carl Hetherington <cth@carlh.net>
4 This file is part of DCP-o-matic.
6 DCP-o-matic is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 DCP-o-matic is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
21 #include "config_dialog.h"
22 #include "static_text.h"
23 #include "check_box.h"
24 #include "nag_dialog.h"
25 #include "dcpomatic_button.h"
26 #include "audio_mapping_view.h"
27 #include <dcp/raw_convert.h>
35 using std::make_shared;
37 using boost::optional;
38 using std::shared_ptr;
40 #if BOOST_VERSION >= 106100
41 using namespace boost::placeholders;
51 Page::Page (wxSize panel_size, int border)
54 , _panel_size (panel_size)
55 , _window_exists (false)
57 _config_connection = Config::instance()->Changed.connect (bind (&Page::config_changed_wrapper, this));
62 Page::CreateWindow (wxWindow* parent)
64 return create_window (parent);
69 Page::create_window (wxWindow* parent)
71 _panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size);
72 auto s = new wxBoxSizer (wxVERTICAL);
76 _window_exists = true;
79 _panel->Bind (wxEVT_DESTROY, bind (&Page::window_destroyed, this));
85 Page::config_changed_wrapper ()
93 Page::window_destroyed ()
95 _window_exists = false;
99 GeneralPage::GeneralPage (wxSize panel_size, int border)
100 : Page (panel_size, border)
107 GeneralPage::GetName () const
114 GeneralPage::add_language_controls (wxGridBagSizer* table, int& r)
116 _set_language = new CheckBox (_panel, _("Set language"));
117 table->Add (_set_language, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
118 _language = new wxChoice (_panel, wxID_ANY);
119 vector<pair<string, string>> languages;
120 languages.push_back (make_pair("Čeština", "cs_CZ"));
121 languages.push_back (make_pair("汉语/漢語", "zh_CN"));
122 languages.push_back (make_pair("Dansk", "da_DK"));
123 languages.push_back (make_pair("Deutsch", "de_DE"));
124 languages.push_back (make_pair("English", "en_GB"));
125 languages.push_back (make_pair("Español", "es_ES"));
126 languages.push_back (make_pair("Français", "fr_FR"));
127 languages.push_back (make_pair("Italiano", "it_IT"));
128 languages.push_back (make_pair("Nederlands", "nl_NL"));
129 languages.push_back (make_pair("Русский", "ru_RU"));
130 languages.push_back (make_pair("Polski", "pl_PL"));
131 languages.push_back (make_pair("Português europeu", "pt_PT"));
132 languages.push_back (make_pair("Português do Brasil", "pt_BR"));
133 languages.push_back (make_pair("Svenska", "sv_SE"));
134 languages.push_back (make_pair("Slovenský jazyk", "sk_SK"));
135 // languages.push_back (make_pair("Türkçe", "tr_TR"));
136 languages.push_back (make_pair("українська мова", "uk_UA"));
137 checked_set (_language, languages);
138 table->Add (_language, wxGBPosition (r, 1));
141 auto restart = add_label_to_sizer (
142 table, _panel, _("(restart DCP-o-matic to see language changes)"), false, wxGBPosition (r, 0), wxGBSpan (1, 2)
144 wxFont font = restart->GetFont();
145 font.SetStyle (wxFONTSTYLE_ITALIC);
146 font.SetPointSize (font.GetPointSize() - 1);
147 restart->SetFont (font);
150 _set_language->Bind (wxEVT_CHECKBOX, bind (&GeneralPage::set_language_changed, this));
151 _language->Bind (wxEVT_CHOICE, bind (&GeneralPage::language_changed, this));
155 GeneralPage::add_update_controls (wxGridBagSizer* table, int& r)
157 _check_for_updates = new CheckBox (_panel, _("Check for updates on startup"));
158 table->Add (_check_for_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
161 _check_for_test_updates = new CheckBox (_panel, _("Check for testing updates on startup"));
162 table->Add (_check_for_test_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
165 _check_for_updates->Bind (wxEVT_CHECKBOX, bind (&GeneralPage::check_for_updates_changed, this));
166 _check_for_test_updates->Bind (wxEVT_CHECKBOX, bind (&GeneralPage::check_for_test_updates_changed, this));
170 GeneralPage::config_changed ()
172 auto config = Config::instance ();
174 checked_set (_set_language, static_cast<bool>(config->language()));
176 /* Backwards compatibility of config file */
178 map<string, string> compat_map;
179 compat_map["fr"] = "fr_FR";
180 compat_map["it"] = "it_IT";
181 compat_map["es"] = "es_ES";
182 compat_map["sv"] = "sv_SE";
183 compat_map["de"] = "de_DE";
184 compat_map["nl"] = "nl_NL";
185 compat_map["ru"] = "ru_RU";
186 compat_map["pl"] = "pl_PL";
187 compat_map["da"] = "da_DK";
188 compat_map["pt"] = "pt_PT";
189 compat_map["sk"] = "sk_SK";
190 compat_map["cs"] = "cs_CZ";
191 compat_map["uk"] = "uk_UA";
193 auto lang = config->language().get_value_or("en_GB");
194 if (compat_map.find(lang) != compat_map.end ()) {
195 lang = compat_map[lang];
198 checked_set (_language, lang);
200 checked_set (_check_for_updates, config->check_for_updates ());
201 checked_set (_check_for_test_updates, config->check_for_test_updates ());
203 setup_sensitivity ();
207 GeneralPage::setup_sensitivity ()
209 _language->Enable (_set_language->GetValue ());
210 _check_for_test_updates->Enable (_check_for_updates->GetValue ());
214 GeneralPage::set_language_changed ()
216 setup_sensitivity ();
217 if (_set_language->GetValue ()) {
220 Config::instance()->unset_language ();
225 GeneralPage::language_changed ()
227 int const sel = _language->GetSelection ();
229 Config::instance()->set_language (string_client_data (_language->GetClientObject (sel)));
231 Config::instance()->unset_language ();
236 GeneralPage::check_for_updates_changed ()
238 Config::instance()->set_check_for_updates (_check_for_updates->GetValue ());
242 GeneralPage::check_for_test_updates_changed ()
244 Config::instance()->set_check_for_test_updates (_check_for_test_updates->GetValue ());
247 CertificateChainEditor::CertificateChainEditor (
251 function<void (shared_ptr<dcp::CertificateChain>)> set,
252 function<shared_ptr<const dcp::CertificateChain> (void)> get,
253 function<bool (void)> nag_alter
255 : wxDialog (parent, wxID_ANY, title)
258 , _nag_alter (nag_alter)
260 _sizer = new wxBoxSizer (wxVERTICAL);
262 auto certificates_sizer = new wxBoxSizer (wxHORIZONTAL);
263 _sizer->Add (certificates_sizer, 0, wxALL, border);
265 _certificates = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (440, 150), wxLC_REPORT | wxLC_SINGLE_SEL);
270 ip.SetText (_("Type"));
272 _certificates->InsertColumn (0, ip);
278 ip.SetText (_("Thumbprint"));
281 wxFont font = ip.GetFont ();
282 font.SetFamily (wxFONTFAMILY_TELETYPE);
285 _certificates->InsertColumn (1, ip);
288 certificates_sizer->Add (_certificates, 1, wxEXPAND);
291 auto s = new wxBoxSizer (wxVERTICAL);
292 _add_certificate = new Button (this, _("Add..."));
293 s->Add (_add_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
294 _remove_certificate = new Button (this, _("Remove"));
295 s->Add (_remove_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
296 _export_certificate = new Button (this, _("Export certificate..."));
297 s->Add (_export_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
298 _export_chain = new Button (this, _("Export chain..."));
299 s->Add (_export_chain, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
300 certificates_sizer->Add (s, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
303 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
304 _sizer->Add (table, 1, wxALL | wxEXPAND, border);
307 add_label_to_sizer (table, this, _("Leaf private key"), true, wxGBPosition (r, 0));
308 _private_key = new StaticText (this, wxT(""));
309 wxFont font = _private_key->GetFont ();
310 font.SetFamily (wxFONTFAMILY_TELETYPE);
311 _private_key->SetFont (font);
312 table->Add (_private_key, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
313 _import_private_key = new Button (this, _("Import..."));
314 table->Add (_import_private_key, wxGBPosition (r, 2));
315 _export_private_key = new Button (this, _("Export..."));
316 table->Add (_export_private_key, wxGBPosition (r, 3));
319 _button_sizer = new wxBoxSizer (wxHORIZONTAL);
320 _remake_certificates = new Button (this, _("Re-make certificates and key..."));
321 _button_sizer->Add (_remake_certificates, 1, wxRIGHT, border);
322 table->Add (_button_sizer, wxGBPosition (r, 0), wxGBSpan (1, 4));
325 _private_key_bad = new StaticText (this, _("Leaf private key does not match leaf certificate!"));
326 font = *wxSMALL_FONT;
327 font.SetWeight (wxFONTWEIGHT_BOLD);
328 _private_key_bad->SetFont (font);
329 table->Add (_private_key_bad, wxGBPosition (r, 0), wxGBSpan (1, 3));
332 _add_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::add_certificate, this));
333 _remove_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::remove_certificate, this));
334 _export_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_certificate, this));
335 _certificates->Bind (wxEVT_LIST_ITEM_SELECTED, bind (&CertificateChainEditor::update_sensitivity, this));
336 _certificates->Bind (wxEVT_LIST_ITEM_DESELECTED, bind (&CertificateChainEditor::update_sensitivity, this));
337 _remake_certificates->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::remake_certificates, this));
338 _export_chain->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_chain, this));
339 _import_private_key->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::import_private_key, this));
340 _export_private_key->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_private_key, this));
342 auto buttons = CreateSeparatedButtonSizer (wxCLOSE);
344 _sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
347 SetSizerAndFit (_sizer);
349 update_certificate_list ();
350 update_private_key ();
351 update_sensitivity ();
355 CertificateChainEditor::add_button (wxWindow* button)
357 _button_sizer->Add (button, 0, wxLEFT | wxRIGHT, DCPOMATIC_SIZER_X_GAP);
362 CertificateChainEditor::add_certificate ()
364 auto d = new wxFileDialog (this, _("Select Certificate File"));
366 if (d->ShowModal() == wxID_OK) {
371 extra = c.read_string (dcp::file_to_string (wx_to_std (d->GetPath ())));
372 } catch (boost::filesystem::filesystem_error& e) {
373 error_dialog (this, _("Could not import certificate (%s)"), d->GetPath());
378 if (!extra.empty ()) {
381 _("This file contains other certificates (or other data) after its first certificate. "
382 "Only the first certificate will be used.")
385 auto chain = make_shared<dcp::CertificateChain>(*_get().get());
387 if (!chain->chain_valid ()) {
390 _("Adding this certificate would make the chain inconsistent, so it will not be added. "
391 "Add certificates in order from root to intermediate to leaf.")
396 update_certificate_list ();
398 } catch (dcp::MiscError& e) {
399 error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
405 update_sensitivity ();
409 CertificateChainEditor::remove_certificate ()
412 /* Cancel was clicked */
416 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
421 _certificates->DeleteItem (i);
422 auto chain = make_shared<dcp::CertificateChain>(*_get().get());
426 update_sensitivity ();
427 update_certificate_list ();
431 CertificateChainEditor::export_certificate ()
433 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
438 auto all = _get()->root_to_leaf();
440 wxString default_name;
442 default_name = "root.pem";
443 } else if (i == static_cast<int>(all.size() - 1)) {
444 default_name = "leaf.pem";
446 default_name = "intermediate.pem";
449 auto d = new wxFileDialog(
450 this, _("Select Certificate File"), wxEmptyString, default_name, wxT ("PEM files (*.pem)|*.pem"),
451 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
454 auto j = all.begin ();
455 for (int k = 0; k < i; ++k) {
459 if (d->ShowModal () == wxID_OK) {
460 boost::filesystem::path path (wx_to_std(d->GetPath()));
461 if (path.extension() != ".pem") {
464 auto f = fopen_boost (path, "w");
466 throw OpenFileError (path, errno, OpenFileError::WRITE);
469 string const s = j->certificate (true);
470 checked_fwrite (s.c_str(), s.length(), f, path);
477 CertificateChainEditor::export_chain ()
479 auto d = new wxFileDialog (
480 this, _("Select Chain File"), wxEmptyString, wxT("certificate_chain.pem"), wxT("PEM files (*.pem)|*.pem"),
481 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
484 if (d->ShowModal () == wxID_OK) {
485 boost::filesystem::path path (wx_to_std(d->GetPath()));
486 if (path.extension() != ".pem") {
489 auto f = fopen_boost (path, "w");
491 throw OpenFileError (path, errno, OpenFileError::WRITE);
494 auto const s = _get()->chain();
495 checked_fwrite (s.c_str(), s.length(), f, path);
503 CertificateChainEditor::update_certificate_list ()
505 _certificates->DeleteAllItems ();
507 auto certs = _get()->root_to_leaf();
508 for (auto const& i: certs) {
511 _certificates->InsertItem (item);
512 _certificates->SetItem (n, 1, std_to_wx (i.thumbprint ()));
515 _certificates->SetItem (n, 0, _("Root"));
516 } else if (n == (certs.size() - 1)) {
517 _certificates->SetItem (n, 0, _("Leaf"));
519 _certificates->SetItem (n, 0, _("Intermediate"));
525 static wxColour normal = _private_key_bad->GetForegroundColour ();
527 if (_get()->private_key_valid()) {
528 _private_key_bad->Hide ();
529 _private_key_bad->SetForegroundColour (normal);
531 _private_key_bad->Show ();
532 _private_key_bad->SetForegroundColour (wxColour (255, 0, 0));
537 CertificateChainEditor::remake_certificates ()
540 /* Cancel was clicked */
544 auto d = new MakeChainDialog (this, _get());
546 if (d->ShowModal () == wxID_OK) {
548 update_certificate_list ();
549 update_private_key ();
556 CertificateChainEditor::update_sensitivity ()
558 /* We can only remove the leaf certificate */
559 _remove_certificate->Enable (_certificates->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) == (_certificates->GetItemCount() - 1));
560 _export_certificate->Enable (_certificates->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
564 CertificateChainEditor::update_private_key ()
566 checked_set (_private_key, dcp::private_key_fingerprint (_get()->key().get()));
571 CertificateChainEditor::import_private_key ()
573 auto d = new wxFileDialog (this, _("Select Key File"));
575 if (d->ShowModal() == wxID_OK) {
577 boost::filesystem::path p (wx_to_std (d->GetPath ()));
578 if (boost::filesystem::file_size (p) > 8192) {
581 wxString::Format (_("Could not read key file; file is too long (%s)"), std_to_wx (p.string ()))
586 auto chain = make_shared<dcp::CertificateChain>(*_get().get());
587 chain->set_key (dcp::file_to_string (p));
589 update_private_key ();
590 } catch (std::exception& e) {
591 error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
597 update_sensitivity ();
601 CertificateChainEditor::export_private_key ()
603 auto key = _get()->key();
608 auto d = new wxFileDialog (
609 this, _("Select Key File"), wxEmptyString, wxT("private_key.pem"), wxT("PEM files (*.pem)|*.pem"),
610 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
613 if (d->ShowModal () == wxID_OK) {
614 boost::filesystem::path path (wx_to_std(d->GetPath()));
615 if (path.extension() != ".pem") {
618 auto f = fopen_boost (path, "w");
620 throw OpenFileError (path, errno, OpenFileError::WRITE);
623 auto const s = _get()->key().get ();
624 checked_fwrite (s.c_str(), s.length(), f, path);
631 KeysPage::GetName () const
639 wxFont subheading_font (*wxNORMAL_FONT);
640 subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
642 auto sizer = _panel->GetSizer();
645 auto m = new StaticText (_panel, _("Decrypting KDMs"));
646 m->SetFont (subheading_font);
647 sizer->Add (m, 0, wxALL | wxEXPAND, _border);
650 auto buttons = new wxBoxSizer (wxVERTICAL);
652 auto export_decryption_certificate = new Button (_panel, _("Export KDM decryption leaf certificate..."));
653 buttons->Add (export_decryption_certificate, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
654 auto export_settings = new Button (_panel, _("Export all KDM decryption settings..."));
655 buttons->Add (export_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
656 auto import_settings = new Button (_panel, _("Import all KDM decryption settings..."));
657 buttons->Add (import_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
658 auto decryption_advanced = new Button (_panel, _("Advanced..."));
659 buttons->Add (decryption_advanced, 0);
661 sizer->Add (buttons, 0, wxLEFT, _border);
663 export_decryption_certificate->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_certificate, this));
664 export_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_chain_and_key, this));
665 import_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::import_decryption_chain_and_key, this));
666 decryption_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::decryption_advanced, this));
669 auto m = new StaticText (_panel, _("Signing DCPs and KDMs"));
670 m->SetFont (subheading_font);
671 sizer->Add (m, 0, wxALL | wxEXPAND, _border);
674 auto signing_advanced = new Button (_panel, _("Advanced..."));
675 sizer->Add (signing_advanced, 0, wxLEFT | wxBOTTOM, _border);
676 signing_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::signing_advanced, this));
680 KeysPage::decryption_advanced ()
682 auto c = new CertificateChainEditor (
683 _panel, _("Decrypting KDMs"), _border,
684 bind(&Config::set_decryption_chain, Config::instance(), _1),
685 bind(&Config::decryption_chain, Config::instance()),
686 bind(&KeysPage::nag_alter_decryption_chain, this)
693 KeysPage::signing_advanced ()
695 auto c = new CertificateChainEditor (
696 _panel, _("Signing DCPs and KDMs"), _border,
697 bind(&Config::set_signer_chain, Config::instance(), _1),
698 bind(&Config::signer_chain, Config::instance()),
706 KeysPage::export_decryption_chain_and_key ()
708 auto d = new wxFileDialog (
709 _panel, _("Select Export File"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom"),
710 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
713 if (d->ShowModal () == wxID_OK) {
714 boost::filesystem::path path (wx_to_std(d->GetPath()));
715 auto f = fopen_boost (path, "w");
717 throw OpenFileError (path, errno, OpenFileError::WRITE);
720 auto const chain = Config::instance()->decryption_chain()->chain();
721 checked_fwrite (chain.c_str(), chain.length(), f, path);
722 optional<string> const key = Config::instance()->decryption_chain()->key();
723 DCPOMATIC_ASSERT (key);
724 checked_fwrite (key->c_str(), key->length(), f, path);
732 KeysPage::import_decryption_chain_and_key ()
734 if (NagDialog::maybe_nag (
736 Config::NAG_IMPORT_DECRYPTION_CHAIN,
737 _("If you continue with this operation you will no longer be able to use any DKDMs that you have created with the current certificates and key. Also, any KDMs that have been sent to you for those certificates will become useless. Proceed with caution!"),
743 auto d = new wxFileDialog (
744 _panel, _("Select File To Import"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom")
747 if (d->ShowModal () == wxID_OK) {
748 auto new_chain = make_shared<dcp::CertificateChain>();
750 FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "r");
752 throw OpenFileError (wx_to_std (d->GetPath ()), errno, OpenFileError::WRITE);
758 if (fgets (buffer, 128, f) == 0) {
762 if (strncmp (buffer, "-----END CERTIFICATE-----", 25) == 0) {
763 new_chain->add (dcp::Certificate (current));
765 } else if (strncmp (buffer, "-----END RSA PRIVATE KEY-----", 29) == 0) {
766 new_chain->set_key (current);
772 if (new_chain->chain_valid() && new_chain->private_key_valid()) {
773 Config::instance()->set_decryption_chain (new_chain);
775 error_dialog (_panel, _("Invalid DCP-o-matic export file"));
782 KeysPage::nag_alter_decryption_chain ()
784 return NagDialog::maybe_nag (
786 Config::NAG_ALTER_DECRYPTION_CHAIN,
787 _("If you continue with this operation you will no longer be able to use any DKDMs that you have created. Also, any KDMs that have been sent to you will become useless. Proceed with caution!"),
793 KeysPage::export_decryption_certificate ()
795 auto config = Config::instance();
796 wxString default_name = "dcpomatic";
797 if (!config->dcp_creator().empty()) {
798 default_name += "_" + std_to_wx(careful_string_filter(config->dcp_creator()));
800 if (!config->dcp_issuer().empty()) {
801 default_name += "_" + std_to_wx(careful_string_filter(config->dcp_issuer()));
803 default_name += wxT("_kdm_decryption_cert.pem");
805 auto d = new wxFileDialog (
806 _panel, _("Select Certificate File"), wxEmptyString, default_name, wxT("PEM files (*.pem)|*.pem"),
807 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
810 if (d->ShowModal () == wxID_OK) {
811 boost::filesystem::path path (wx_to_std(d->GetPath()));
812 if (path.extension() != ".pem") {
815 auto f = fopen_boost (path, "w");
817 throw OpenFileError (path, errno, OpenFileError::WRITE);
820 auto const s = Config::instance()->decryption_chain()->leaf().certificate (true);
821 checked_fwrite (s.c_str(), s.length(), f, path);
829 SoundPage::GetName () const
837 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
838 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
842 _sound = new CheckBox (_panel, _("Play sound via"));
843 table->Add (_sound, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
844 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
845 _sound_output = new wxChoice (_panel, wxID_ANY);
846 s->Add (_sound_output, 0);
847 _sound_output_details = new wxStaticText (_panel, wxID_ANY, wxT(""));
848 s->Add (_sound_output_details, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, DCPOMATIC_SIZER_X_GAP);
849 table->Add (s, wxGBPosition(r, 1));
852 add_label_to_sizer (table, _panel, _("Mapping"), true, wxGBPosition(r, 0));
853 _map = new AudioMappingView (_panel, _("DCP"), _("DCP"), _("Output"), _("output"));
854 _map->SetSize (-1, 400);
855 table->Add (_map, wxGBPosition(r, 1), wxDefaultSpan, wxEXPAND);
858 _reset_to_default = new Button (_panel, _("Reset to default"));
859 table->Add (_reset_to_default, wxGBPosition(r, 1));
862 wxFont font = _sound_output_details->GetFont();
863 font.SetStyle (wxFONTSTYLE_ITALIC);
864 font.SetPointSize (font.GetPointSize() - 1);
865 _sound_output_details->SetFont (font);
867 RtAudio audio (DCPOMATIC_RTAUDIO_API);
868 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
870 auto dev = audio.getDeviceInfo (i);
871 if (dev.probed && dev.outputChannels > 0) {
872 _sound_output->Append (std_to_wx (dev.name));
874 } catch (RtAudioError&) {
875 /* Something went wrong so let's just ignore that device */
879 _sound->Bind (wxEVT_CHECKBOX, bind(&SoundPage::sound_changed, this));
880 _sound_output->Bind (wxEVT_CHOICE, bind(&SoundPage::sound_output_changed, this));
881 _map->Changed.connect (bind(&SoundPage::map_changed, this, _1));
882 _reset_to_default->Bind (wxEVT_BUTTON, bind(&SoundPage::reset_to_default, this));
886 SoundPage::reset_to_default ()
888 Config::instance()->set_audio_mapping_to_default ();
892 SoundPage::map_changed (AudioMapping m)
894 Config::instance()->set_audio_mapping (m);
898 SoundPage::sound_changed ()
900 Config::instance()->set_sound (_sound->GetValue ());
904 SoundPage::sound_output_changed ()
906 RtAudio audio (DCPOMATIC_RTAUDIO_API);
907 auto const so = get_sound_output();
908 string default_device;
910 default_device = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
911 } catch (RtAudioError&) {
914 if (!so || *so == default_device) {
915 Config::instance()->unset_sound_output ();
917 Config::instance()->set_sound_output (*so);
922 SoundPage::config_changed ()
924 auto config = Config::instance ();
926 checked_set (_sound, config->sound ());
928 auto const current_so = get_sound_output ();
929 optional<string> configured_so;
931 if (config->sound_output()) {
932 configured_so = config->sound_output().get();
934 /* No configured output means we should use the default */
935 RtAudio audio (DCPOMATIC_RTAUDIO_API);
937 configured_so = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
938 } catch (RtAudioError&) {
939 /* Probably no audio devices at all */
943 if (configured_so && current_so != configured_so) {
944 /* Update _sound_output with the configured value */
946 while (i < _sound_output->GetCount()) {
947 if (_sound_output->GetString(i) == std_to_wx(*configured_so)) {
948 _sound_output->SetSelection (i);
955 RtAudio audio (DCPOMATIC_RTAUDIO_API);
957 map<int, wxString> apis;
958 apis[RtAudio::MACOSX_CORE] = _("CoreAudio");
959 apis[RtAudio::WINDOWS_ASIO] = _("ASIO");
960 apis[RtAudio::WINDOWS_DS] = _("Direct Sound");
961 apis[RtAudio::WINDOWS_WASAPI] = _("WASAPI");
962 apis[RtAudio::UNIX_JACK] = _("JACK");
963 apis[RtAudio::LINUX_ALSA] = _("ALSA");
964 apis[RtAudio::LINUX_PULSE] = _("PulseAudio");
965 apis[RtAudio::LINUX_OSS] = _("OSS");
966 apis[RtAudio::RTAUDIO_DUMMY] = _("Dummy");
970 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
972 auto info = audio.getDeviceInfo(i);
973 if (info.name == *configured_so && info.outputChannels > 0) {
974 channels = info.outputChannels;
976 } catch (RtAudioError&) {
982 _sound_output_details->SetLabel (
983 wxString::Format(_("%d channels on %s"), channels, apis[audio.getCurrentApi()])
986 _map->set (Config::instance()->audio_mapping(channels));
988 vector<NamedChannel> input;
989 for (int i = 0; i < MAX_DCP_AUDIO_CHANNELS; ++i) {
990 input.push_back (NamedChannel(short_audio_channel_name(i), i));
992 _map->set_input_channels (input);
994 vector<NamedChannel> output;
995 for (int i = 0; i < channels; ++i) {
996 output.push_back (NamedChannel(dcp::raw_convert<string>(i), i));
998 _map->set_output_channels (output);
1000 setup_sensitivity ();
1004 SoundPage::setup_sensitivity ()
1006 _sound_output->Enable (_sound->GetValue());
1009 /** @return Currently-selected preview sound output in the dialogue */
1011 SoundPage::get_sound_output ()
1013 int const sel = _sound_output->GetSelection ();
1014 if (sel == wxNOT_FOUND) {
1015 return optional<string> ();
1018 return wx_to_std (_sound_output->GetString (sel));
1022 LocationsPage::LocationsPage (wxSize panel_size, int border)
1023 : Page (panel_size, border)
1029 LocationsPage::GetName () const
1031 return _("Locations");
1034 #ifdef DCPOMATIC_OSX
1036 LocationsPage::GetLargeIcon () const
1038 return wxBitmap(bitmap_path("locations"), wxBITMAP_TYPE_PNG);
1043 LocationsPage::setup ()
1047 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1048 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
1050 add_label_to_sizer (table, _panel, _("Content directory"), true, wxGBPosition (r, 0));
1051 _content_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1052 table->Add (_content_directory, wxGBPosition (r, 1));
1055 add_label_to_sizer (table, _panel, _("Playlist directory"), true, wxGBPosition (r, 0));
1056 _playlist_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1057 table->Add (_playlist_directory, wxGBPosition (r, 1));
1060 add_label_to_sizer (table, _panel, _("KDM directory"), true, wxGBPosition (r, 0));
1061 _kdm_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1062 table->Add (_kdm_directory, wxGBPosition (r, 1));
1065 _content_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::content_directory_changed, this));
1066 _playlist_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::playlist_directory_changed, this));
1067 _kdm_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::kdm_directory_changed, this));
1071 LocationsPage::config_changed ()
1073 auto config = Config::instance ();
1075 if (config->player_content_directory()) {
1076 checked_set (_content_directory, *config->player_content_directory());
1078 if (config->player_playlist_directory()) {
1079 checked_set (_playlist_directory, *config->player_playlist_directory());
1081 if (config->player_kdm_directory()) {
1082 checked_set (_kdm_directory, *config->player_kdm_directory());
1087 LocationsPage::content_directory_changed ()
1089 Config::instance()->set_player_content_directory(wx_to_std(_content_directory->GetPath()));
1093 LocationsPage::playlist_directory_changed ()
1095 Config::instance()->set_player_playlist_directory(wx_to_std(_playlist_directory->GetPath()));
1099 LocationsPage::kdm_directory_changed ()
1101 Config::instance()->set_player_kdm_directory(wx_to_std(_kdm_directory->GetPath()));