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>
32 using std::make_shared;
35 using std::shared_ptr;
39 using boost::optional;
40 #if BOOST_VERSION >= 106100
41 using namespace boost::placeholders;
52 Page::Page (wxSize panel_size, int border)
55 , _panel_size (panel_size)
56 , _window_exists (false)
58 _config_connection = Config::instance()->Changed.connect (bind (&Page::config_changed_wrapper, this));
63 Page::CreateWindow (wxWindow* parent)
65 return create_window (parent);
70 Page::create_window (wxWindow* parent)
72 _panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size);
73 auto s = new wxBoxSizer (wxVERTICAL);
77 _window_exists = true;
80 _panel->Bind (wxEVT_DESTROY, bind (&Page::window_destroyed, this));
86 Page::config_changed_wrapper ()
94 Page::window_destroyed ()
96 _window_exists = false;
100 GeneralPage::GeneralPage (wxSize panel_size, int border)
101 : Page (panel_size, border)
108 GeneralPage::GetName () const
115 GeneralPage::add_language_controls (wxGridBagSizer* table, int& r)
117 _set_language = new CheckBox (_panel, _("Set language"));
118 table->Add (_set_language, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
119 _language = new wxChoice (_panel, wxID_ANY);
120 vector<pair<string, string>> languages;
121 languages.push_back (make_pair("Čeština", "cs_CZ"));
122 languages.push_back (make_pair("汉语/漢語", "zh_CN"));
123 languages.push_back (make_pair("Dansk", "da_DK"));
124 languages.push_back (make_pair("Deutsch", "de_DE"));
125 languages.push_back (make_pair("English", "en_GB"));
126 languages.push_back (make_pair("Español", "es_ES"));
127 languages.push_back (make_pair("Français", "fr_FR"));
128 languages.push_back (make_pair("Italiano", "it_IT"));
129 languages.push_back (make_pair("Nederlands", "nl_NL"));
130 languages.push_back (make_pair("Русский", "ru_RU"));
131 languages.push_back (make_pair("Polski", "pl_PL"));
132 languages.push_back (make_pair("Português europeu", "pt_PT"));
133 languages.push_back (make_pair("Português do Brasil", "pt_BR"));
134 languages.push_back (make_pair("Svenska", "sv_SE"));
135 languages.push_back (make_pair("Slovenský jazyk", "sk_SK"));
136 // languages.push_back (make_pair("Türkçe", "tr_TR"));
137 languages.push_back (make_pair("українська мова", "uk_UA"));
138 checked_set (_language, languages);
139 table->Add (_language, wxGBPosition (r, 1));
142 auto restart = add_label_to_sizer (
143 table, _panel, _("(restart DCP-o-matic to see language changes)"), false, wxGBPosition (r, 0), wxGBSpan (1, 2)
145 wxFont font = restart->GetFont();
146 font.SetStyle (wxFONTSTYLE_ITALIC);
147 font.SetPointSize (font.GetPointSize() - 1);
148 restart->SetFont (font);
151 _set_language->Bind (wxEVT_CHECKBOX, bind (&GeneralPage::set_language_changed, this));
152 _language->Bind (wxEVT_CHOICE, bind (&GeneralPage::language_changed, this));
156 GeneralPage::add_update_controls (wxGridBagSizer* table, int& r)
158 _check_for_updates = new CheckBox (_panel, _("Check for updates on startup"));
159 table->Add (_check_for_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
162 _check_for_test_updates = new CheckBox (_panel, _("Check for testing updates on startup"));
163 table->Add (_check_for_test_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
166 _check_for_updates->Bind (wxEVT_CHECKBOX, bind (&GeneralPage::check_for_updates_changed, this));
167 _check_for_test_updates->Bind (wxEVT_CHECKBOX, bind (&GeneralPage::check_for_test_updates_changed, this));
171 GeneralPage::config_changed ()
173 auto config = Config::instance ();
175 checked_set (_set_language, static_cast<bool>(config->language()));
177 /* Backwards compatibility of config file */
179 map<string, string> compat_map;
180 compat_map["fr"] = "fr_FR";
181 compat_map["it"] = "it_IT";
182 compat_map["es"] = "es_ES";
183 compat_map["sv"] = "sv_SE";
184 compat_map["de"] = "de_DE";
185 compat_map["nl"] = "nl_NL";
186 compat_map["ru"] = "ru_RU";
187 compat_map["pl"] = "pl_PL";
188 compat_map["da"] = "da_DK";
189 compat_map["pt"] = "pt_PT";
190 compat_map["sk"] = "sk_SK";
191 compat_map["cs"] = "cs_CZ";
192 compat_map["uk"] = "uk_UA";
194 auto lang = config->language().get_value_or("en_GB");
195 if (compat_map.find(lang) != compat_map.end ()) {
196 lang = compat_map[lang];
199 checked_set (_language, lang);
201 checked_set (_check_for_updates, config->check_for_updates ());
202 checked_set (_check_for_test_updates, config->check_for_test_updates ());
204 setup_sensitivity ();
208 GeneralPage::setup_sensitivity ()
210 _language->Enable (_set_language->GetValue ());
211 _check_for_test_updates->Enable (_check_for_updates->GetValue ());
215 GeneralPage::set_language_changed ()
217 setup_sensitivity ();
218 if (_set_language->GetValue ()) {
221 Config::instance()->unset_language ();
226 GeneralPage::language_changed ()
228 int const sel = _language->GetSelection ();
230 Config::instance()->set_language (string_client_data (_language->GetClientObject (sel)));
232 Config::instance()->unset_language ();
237 GeneralPage::check_for_updates_changed ()
239 Config::instance()->set_check_for_updates (_check_for_updates->GetValue ());
243 GeneralPage::check_for_test_updates_changed ()
245 Config::instance()->set_check_for_test_updates (_check_for_test_updates->GetValue ());
248 CertificateChainEditor::CertificateChainEditor (
252 function<void (shared_ptr<dcp::CertificateChain>)> set,
253 function<shared_ptr<const dcp::CertificateChain> (void)> get,
254 function<bool (void)> nag_alter
256 : wxDialog (parent, wxID_ANY, title)
259 , _nag_alter (nag_alter)
261 _sizer = new wxBoxSizer (wxVERTICAL);
263 auto certificates_sizer = new wxBoxSizer (wxHORIZONTAL);
264 _sizer->Add (certificates_sizer, 0, wxALL, border);
266 _certificates = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (440, 150), wxLC_REPORT | wxLC_SINGLE_SEL);
271 ip.SetText (_("Type"));
273 _certificates->InsertColumn (0, ip);
279 ip.SetText (_("Thumbprint"));
282 wxFont font = ip.GetFont ();
283 font.SetFamily (wxFONTFAMILY_TELETYPE);
286 _certificates->InsertColumn (1, ip);
289 certificates_sizer->Add (_certificates, 1, wxEXPAND);
292 auto s = new wxBoxSizer (wxVERTICAL);
293 _add_certificate = new Button (this, _("Add..."));
294 s->Add (_add_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
295 _remove_certificate = new Button (this, _("Remove"));
296 s->Add (_remove_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
297 _export_certificate = new Button (this, _("Export certificate..."));
298 s->Add (_export_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
299 _export_chain = new Button (this, _("Export chain..."));
300 s->Add (_export_chain, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
301 certificates_sizer->Add (s, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
304 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
305 _sizer->Add (table, 1, wxALL | wxEXPAND, border);
308 add_label_to_sizer (table, this, _("Leaf private key"), true, wxGBPosition (r, 0));
309 _private_key = new StaticText (this, wxT(""));
310 wxFont font = _private_key->GetFont ();
311 font.SetFamily (wxFONTFAMILY_TELETYPE);
312 _private_key->SetFont (font);
313 table->Add (_private_key, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
314 _import_private_key = new Button (this, _("Import..."));
315 table->Add (_import_private_key, wxGBPosition (r, 2));
316 _export_private_key = new Button (this, _("Export..."));
317 table->Add (_export_private_key, wxGBPosition (r, 3));
320 _button_sizer = new wxBoxSizer (wxHORIZONTAL);
321 _remake_certificates = new Button (this, _("Re-make certificates and key..."));
322 _button_sizer->Add (_remake_certificates, 1, wxRIGHT, border);
323 table->Add (_button_sizer, wxGBPosition (r, 0), wxGBSpan (1, 4));
326 _private_key_bad = new StaticText (this, _("Leaf private key does not match leaf certificate!"));
327 font = *wxSMALL_FONT;
328 font.SetWeight (wxFONTWEIGHT_BOLD);
329 _private_key_bad->SetFont (font);
330 table->Add (_private_key_bad, wxGBPosition (r, 0), wxGBSpan (1, 3));
333 _add_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::add_certificate, this));
334 _remove_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::remove_certificate, this));
335 _export_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_certificate, this));
336 _certificates->Bind (wxEVT_LIST_ITEM_SELECTED, bind (&CertificateChainEditor::update_sensitivity, this));
337 _certificates->Bind (wxEVT_LIST_ITEM_DESELECTED, bind (&CertificateChainEditor::update_sensitivity, this));
338 _remake_certificates->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::remake_certificates, this));
339 _export_chain->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_chain, this));
340 _import_private_key->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::import_private_key, this));
341 _export_private_key->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_private_key, this));
343 auto buttons = CreateSeparatedButtonSizer (wxCLOSE);
345 _sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
348 SetSizerAndFit (_sizer);
350 update_certificate_list ();
351 update_private_key ();
352 update_sensitivity ();
356 CertificateChainEditor::add_button (wxWindow* button)
358 _button_sizer->Add (button, 0, wxLEFT | wxRIGHT, DCPOMATIC_SIZER_X_GAP);
363 CertificateChainEditor::add_certificate ()
365 auto d = new wxFileDialog (this, _("Select Certificate File"));
367 if (d->ShowModal() == wxID_OK) {
372 extra = c.read_string (dcp::file_to_string (wx_to_std (d->GetPath ())));
373 } catch (boost::filesystem::filesystem_error& e) {
374 error_dialog (this, _("Could not import certificate (%s)"), d->GetPath());
379 if (!extra.empty ()) {
382 _("This file contains other certificates (or other data) after its first certificate. "
383 "Only the first certificate will be used.")
386 auto chain = make_shared<dcp::CertificateChain>(*_get().get());
388 if (!chain->chain_valid ()) {
391 _("Adding this certificate would make the chain inconsistent, so it will not be added. "
392 "Add certificates in order from root to intermediate to leaf.")
397 update_certificate_list ();
399 } catch (dcp::MiscError& e) {
400 error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
406 update_sensitivity ();
410 CertificateChainEditor::remove_certificate ()
413 /* Cancel was clicked */
417 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
422 _certificates->DeleteItem (i);
423 auto chain = make_shared<dcp::CertificateChain>(*_get().get());
427 update_sensitivity ();
428 update_certificate_list ();
432 CertificateChainEditor::export_certificate ()
434 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
439 auto all = _get()->root_to_leaf();
441 wxString default_name;
443 default_name = "root.pem";
444 } else if (i == static_cast<int>(all.size() - 1)) {
445 default_name = "leaf.pem";
447 default_name = "intermediate.pem";
450 auto d = new wxFileDialog(
451 this, _("Select Certificate File"), wxEmptyString, default_name, wxT ("PEM files (*.pem)|*.pem"),
452 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
455 auto j = all.begin ();
456 for (int k = 0; k < i; ++k) {
460 if (d->ShowModal () == wxID_OK) {
461 boost::filesystem::path path (wx_to_std(d->GetPath()));
462 if (path.extension() != ".pem") {
465 auto f = fopen_boost (path, "w");
467 throw OpenFileError (path, errno, OpenFileError::WRITE);
470 string const s = j->certificate (true);
471 checked_fwrite (s.c_str(), s.length(), f, path);
478 CertificateChainEditor::export_chain ()
480 auto d = new wxFileDialog (
481 this, _("Select Chain File"), wxEmptyString, wxT("certificate_chain.pem"), wxT("PEM files (*.pem)|*.pem"),
482 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
485 if (d->ShowModal () == wxID_OK) {
486 boost::filesystem::path path (wx_to_std(d->GetPath()));
487 if (path.extension() != ".pem") {
490 auto f = fopen_boost (path, "w");
492 throw OpenFileError (path, errno, OpenFileError::WRITE);
495 auto const s = _get()->chain();
496 checked_fwrite (s.c_str(), s.length(), f, path);
504 CertificateChainEditor::update_certificate_list ()
506 _certificates->DeleteAllItems ();
508 auto certs = _get()->root_to_leaf();
509 for (auto const& i: certs) {
512 _certificates->InsertItem (item);
513 _certificates->SetItem (n, 1, std_to_wx (i.thumbprint ()));
516 _certificates->SetItem (n, 0, _("Root"));
517 } else if (n == (certs.size() - 1)) {
518 _certificates->SetItem (n, 0, _("Leaf"));
520 _certificates->SetItem (n, 0, _("Intermediate"));
526 static wxColour normal = _private_key_bad->GetForegroundColour ();
528 if (_get()->private_key_valid()) {
529 _private_key_bad->Hide ();
530 _private_key_bad->SetForegroundColour (normal);
532 _private_key_bad->Show ();
533 _private_key_bad->SetForegroundColour (wxColour (255, 0, 0));
538 CertificateChainEditor::remake_certificates ()
541 /* Cancel was clicked */
545 auto d = new MakeChainDialog (this, _get());
547 if (d->ShowModal () == wxID_OK) {
549 update_certificate_list ();
550 update_private_key ();
557 CertificateChainEditor::update_sensitivity ()
559 /* We can only remove the leaf certificate */
560 _remove_certificate->Enable (_certificates->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) == (_certificates->GetItemCount() - 1));
561 _export_certificate->Enable (_certificates->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
565 CertificateChainEditor::update_private_key ()
567 checked_set (_private_key, dcp::private_key_fingerprint (_get()->key().get()));
572 CertificateChainEditor::import_private_key ()
574 auto d = new wxFileDialog (this, _("Select Key File"));
576 if (d->ShowModal() == wxID_OK) {
578 boost::filesystem::path p (wx_to_std (d->GetPath ()));
579 if (boost::filesystem::file_size (p) > 8192) {
582 wxString::Format (_("Could not read key file; file is too long (%s)"), std_to_wx (p.string ()))
587 auto chain = make_shared<dcp::CertificateChain>(*_get().get());
588 chain->set_key (dcp::file_to_string (p));
590 update_private_key ();
591 } catch (std::exception& e) {
592 error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
598 update_sensitivity ();
602 CertificateChainEditor::export_private_key ()
604 auto key = _get()->key();
609 auto d = new wxFileDialog (
610 this, _("Select Key File"), wxEmptyString, wxT("private_key.pem"), wxT("PEM files (*.pem)|*.pem"),
611 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
614 if (d->ShowModal () == wxID_OK) {
615 boost::filesystem::path path (wx_to_std(d->GetPath()));
616 if (path.extension() != ".pem") {
619 auto f = fopen_boost (path, "w");
621 throw OpenFileError (path, errno, OpenFileError::WRITE);
624 auto const s = _get()->key().get ();
625 checked_fwrite (s.c_str(), s.length(), f, path);
632 KeysPage::GetName () const
640 wxFont subheading_font (*wxNORMAL_FONT);
641 subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
643 auto sizer = _panel->GetSizer();
646 auto m = new StaticText (_panel, _("Decrypting KDMs"));
647 m->SetFont (subheading_font);
648 sizer->Add (m, 0, wxALL | wxEXPAND, _border);
651 auto kdm_buttons = new wxBoxSizer (wxVERTICAL);
653 auto export_decryption_certificate = new Button (_panel, _("Export KDM decryption leaf certificate..."));
654 kdm_buttons->Add (export_decryption_certificate, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
655 auto export_settings = new Button (_panel, _("Export all KDM decryption settings..."));
656 kdm_buttons->Add (export_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
657 auto import_settings = new Button (_panel, _("Import all KDM decryption settings..."));
658 kdm_buttons->Add (import_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
659 auto decryption_advanced = new Button (_panel, _("Advanced..."));
660 kdm_buttons->Add (decryption_advanced, 0);
662 sizer->Add (kdm_buttons, 0, wxLEFT, _border);
664 export_decryption_certificate->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_certificate, this));
665 export_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_chain_and_key, this));
666 import_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::import_decryption_chain_and_key, this));
667 decryption_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::decryption_advanced, this));
670 auto m = new StaticText (_panel, _("Signing DCPs and KDMs"));
671 m->SetFont (subheading_font);
672 sizer->Add (m, 0, wxALL | wxEXPAND, _border);
675 auto signing_buttons = new wxBoxSizer (wxVERTICAL);
677 auto signing_advanced = new Button (_panel, _("Advanced..."));
678 signing_buttons->Add (signing_advanced, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
679 auto remake_signing = new Button (_panel, _("Re-make certificates and key..."));
680 signing_buttons->Add (remake_signing, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
682 sizer->Add (signing_buttons, 0, wxLEFT, _border);
684 signing_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::signing_advanced, this));
685 remake_signing->Bind (wxEVT_BUTTON, bind(&KeysPage::remake_signing, this));
690 KeysPage::remake_signing ()
692 auto d = new MakeChainDialog (_panel, Config::instance()->signer_chain());
694 if (d->ShowModal () == wxID_OK) {
695 Config::instance()->set_signer_chain(d->get());
701 KeysPage::decryption_advanced ()
703 auto c = new CertificateChainEditor (
704 _panel, _("Decrypting KDMs"), _border,
705 bind(&Config::set_decryption_chain, Config::instance(), _1),
706 bind(&Config::decryption_chain, Config::instance()),
707 bind(&KeysPage::nag_alter_decryption_chain, this)
714 KeysPage::signing_advanced ()
716 auto c = new CertificateChainEditor (
717 _panel, _("Signing DCPs and KDMs"), _border,
718 bind(&Config::set_signer_chain, Config::instance(), _1),
719 bind(&Config::signer_chain, Config::instance()),
727 KeysPage::export_decryption_chain_and_key ()
729 auto d = new wxFileDialog (
730 _panel, _("Select Export File"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom"),
731 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
734 if (d->ShowModal () == wxID_OK) {
735 boost::filesystem::path path (wx_to_std(d->GetPath()));
736 auto f = fopen_boost (path, "w");
738 throw OpenFileError (path, errno, OpenFileError::WRITE);
741 auto const chain = Config::instance()->decryption_chain()->chain();
742 checked_fwrite (chain.c_str(), chain.length(), f, path);
743 optional<string> const key = Config::instance()->decryption_chain()->key();
744 DCPOMATIC_ASSERT (key);
745 checked_fwrite (key->c_str(), key->length(), f, path);
753 KeysPage::import_decryption_chain_and_key ()
755 if (NagDialog::maybe_nag (
757 Config::NAG_IMPORT_DECRYPTION_CHAIN,
758 _("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!"),
764 auto d = new wxFileDialog (
765 _panel, _("Select File To Import"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom")
768 if (d->ShowModal () == wxID_OK) {
769 auto new_chain = make_shared<dcp::CertificateChain>();
771 FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "r");
773 throw OpenFileError (wx_to_std (d->GetPath ()), errno, OpenFileError::WRITE);
779 if (fgets (buffer, 128, f) == 0) {
783 if (strncmp (buffer, "-----END CERTIFICATE-----", 25) == 0) {
784 new_chain->add (dcp::Certificate (current));
786 } else if (strncmp (buffer, "-----END RSA PRIVATE KEY-----", 29) == 0) {
787 new_chain->set_key (current);
793 if (new_chain->chain_valid() && new_chain->private_key_valid()) {
794 Config::instance()->set_decryption_chain (new_chain);
796 error_dialog (_panel, _("Invalid DCP-o-matic export file"));
803 KeysPage::nag_alter_decryption_chain ()
805 return NagDialog::maybe_nag (
807 Config::NAG_ALTER_DECRYPTION_CHAIN,
808 _("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!"),
814 KeysPage::export_decryption_certificate ()
816 auto config = Config::instance();
817 wxString default_name = "dcpomatic";
818 if (!config->dcp_creator().empty()) {
819 default_name += "_" + std_to_wx(careful_string_filter(config->dcp_creator()));
821 if (!config->dcp_issuer().empty()) {
822 default_name += "_" + std_to_wx(careful_string_filter(config->dcp_issuer()));
824 default_name += wxT("_kdm_decryption_cert.pem");
826 auto d = new wxFileDialog (
827 _panel, _("Select Certificate File"), wxEmptyString, default_name, wxT("PEM files (*.pem)|*.pem"),
828 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
831 if (d->ShowModal () == wxID_OK) {
832 boost::filesystem::path path (wx_to_std(d->GetPath()));
833 if (path.extension() != ".pem") {
836 auto f = fopen_boost (path, "w");
838 throw OpenFileError (path, errno, OpenFileError::WRITE);
841 auto const s = Config::instance()->decryption_chain()->leaf().certificate (true);
842 checked_fwrite (s.c_str(), s.length(), f, path);
850 SoundPage::GetName () const
858 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
859 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
863 _sound = new CheckBox (_panel, _("Play sound via"));
864 table->Add (_sound, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
865 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
866 _sound_output = new wxChoice (_panel, wxID_ANY);
867 s->Add (_sound_output, 0);
868 _sound_output_details = new wxStaticText (_panel, wxID_ANY, wxT(""));
869 s->Add (_sound_output_details, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, DCPOMATIC_SIZER_X_GAP);
870 table->Add (s, wxGBPosition(r, 1));
873 add_label_to_sizer (table, _panel, _("Mapping"), true, wxGBPosition(r, 0));
874 _map = new AudioMappingView (_panel, _("DCP"), _("DCP"), _("Output"), _("output"));
875 _map->SetSize (-1, 400);
876 table->Add (_map, wxGBPosition(r, 1), wxDefaultSpan, wxEXPAND);
879 _reset_to_default = new Button (_panel, _("Reset to default"));
880 table->Add (_reset_to_default, wxGBPosition(r, 1));
883 wxFont font = _sound_output_details->GetFont();
884 font.SetStyle (wxFONTSTYLE_ITALIC);
885 font.SetPointSize (font.GetPointSize() - 1);
886 _sound_output_details->SetFont (font);
888 RtAudio audio (DCPOMATIC_RTAUDIO_API);
889 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
891 auto dev = audio.getDeviceInfo (i);
892 if (dev.probed && dev.outputChannels > 0) {
893 _sound_output->Append (std_to_wx (dev.name));
895 } catch (RtAudioError&) {
896 /* Something went wrong so let's just ignore that device */
900 _sound->Bind (wxEVT_CHECKBOX, bind(&SoundPage::sound_changed, this));
901 _sound_output->Bind (wxEVT_CHOICE, bind(&SoundPage::sound_output_changed, this));
902 _map->Changed.connect (bind(&SoundPage::map_changed, this, _1));
903 _reset_to_default->Bind (wxEVT_BUTTON, bind(&SoundPage::reset_to_default, this));
907 SoundPage::reset_to_default ()
909 Config::instance()->set_audio_mapping_to_default ();
913 SoundPage::map_changed (AudioMapping m)
915 Config::instance()->set_audio_mapping (m);
919 SoundPage::sound_changed ()
921 Config::instance()->set_sound (_sound->GetValue ());
925 SoundPage::sound_output_changed ()
927 RtAudio audio (DCPOMATIC_RTAUDIO_API);
928 auto const so = get_sound_output();
929 string default_device;
931 default_device = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
932 } catch (RtAudioError&) {
935 if (!so || *so == default_device) {
936 Config::instance()->unset_sound_output ();
938 Config::instance()->set_sound_output (*so);
943 SoundPage::config_changed ()
945 auto config = Config::instance ();
947 checked_set (_sound, config->sound ());
949 auto const current_so = get_sound_output ();
950 optional<string> configured_so;
952 if (config->sound_output()) {
953 configured_so = config->sound_output().get();
955 /* No configured output means we should use the default */
956 RtAudio audio (DCPOMATIC_RTAUDIO_API);
958 configured_so = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
959 } catch (RtAudioError&) {
960 /* Probably no audio devices at all */
964 if (configured_so && current_so != configured_so) {
965 /* Update _sound_output with the configured value */
967 while (i < _sound_output->GetCount()) {
968 if (_sound_output->GetString(i) == std_to_wx(*configured_so)) {
969 _sound_output->SetSelection (i);
976 RtAudio audio (DCPOMATIC_RTAUDIO_API);
978 map<int, wxString> apis;
979 apis[RtAudio::MACOSX_CORE] = _("CoreAudio");
980 apis[RtAudio::WINDOWS_ASIO] = _("ASIO");
981 apis[RtAudio::WINDOWS_DS] = _("Direct Sound");
982 apis[RtAudio::WINDOWS_WASAPI] = _("WASAPI");
983 apis[RtAudio::UNIX_JACK] = _("JACK");
984 apis[RtAudio::LINUX_ALSA] = _("ALSA");
985 apis[RtAudio::LINUX_PULSE] = _("PulseAudio");
986 apis[RtAudio::LINUX_OSS] = _("OSS");
987 apis[RtAudio::RTAUDIO_DUMMY] = _("Dummy");
991 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
993 auto info = audio.getDeviceInfo(i);
994 if (info.name == *configured_so && info.outputChannels > 0) {
995 channels = info.outputChannels;
997 } catch (RtAudioError&) {
1003 _sound_output_details->SetLabel (
1004 wxString::Format(_("%d channels on %s"), channels, apis[audio.getCurrentApi()])
1007 _map->set (Config::instance()->audio_mapping(channels));
1009 vector<NamedChannel> input;
1010 for (int i = 0; i < MAX_DCP_AUDIO_CHANNELS; ++i) {
1011 input.push_back (NamedChannel(short_audio_channel_name(i), i));
1013 _map->set_input_channels (input);
1015 vector<NamedChannel> output;
1016 for (int i = 0; i < channels; ++i) {
1017 output.push_back (NamedChannel(dcp::raw_convert<string>(i), i));
1019 _map->set_output_channels (output);
1021 setup_sensitivity ();
1025 SoundPage::setup_sensitivity ()
1027 _sound_output->Enable (_sound->GetValue());
1030 /** @return Currently-selected preview sound output in the dialogue */
1032 SoundPage::get_sound_output ()
1034 int const sel = _sound_output->GetSelection ();
1035 if (sel == wxNOT_FOUND) {
1036 return optional<string> ();
1039 return wx_to_std (_sound_output->GetString (sel));
1043 LocationsPage::LocationsPage (wxSize panel_size, int border)
1044 : Page (panel_size, border)
1050 LocationsPage::GetName () const
1052 return _("Locations");
1055 #ifdef DCPOMATIC_OSX
1057 LocationsPage::GetLargeIcon () const
1059 return wxBitmap(bitmap_path("locations"), wxBITMAP_TYPE_PNG);
1064 LocationsPage::setup ()
1068 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1069 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
1071 add_label_to_sizer (table, _panel, _("Content directory"), true, wxGBPosition (r, 0));
1072 _content_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1073 table->Add (_content_directory, wxGBPosition (r, 1));
1076 add_label_to_sizer (table, _panel, _("Playlist directory"), true, wxGBPosition (r, 0));
1077 _playlist_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1078 table->Add (_playlist_directory, wxGBPosition (r, 1));
1081 add_label_to_sizer (table, _panel, _("KDM directory"), true, wxGBPosition (r, 0));
1082 _kdm_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1083 table->Add (_kdm_directory, wxGBPosition (r, 1));
1086 _content_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::content_directory_changed, this));
1087 _playlist_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::playlist_directory_changed, this));
1088 _kdm_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::kdm_directory_changed, this));
1092 LocationsPage::config_changed ()
1094 auto config = Config::instance ();
1096 if (config->player_content_directory()) {
1097 checked_set (_content_directory, *config->player_content_directory());
1099 if (config->player_playlist_directory()) {
1100 checked_set (_playlist_directory, *config->player_playlist_directory());
1102 if (config->player_kdm_directory()) {
1103 checked_set (_kdm_directory, *config->player_kdm_directory());
1108 LocationsPage::content_directory_changed ()
1110 Config::instance()->set_player_content_directory(wx_to_std(_content_directory->GetPath()));
1114 LocationsPage::playlist_directory_changed ()
1116 Config::instance()->set_player_playlist_directory(wx_to_std(_playlist_directory->GetPath()));
1120 LocationsPage::kdm_directory_changed ()
1122 Config::instance()->set_player_kdm_directory(wx_to_std(_kdm_directory->GetPath()));