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/>.
22 #include "audio_mapping_view.h"
23 #include "check_box.h"
24 #include "config_dialog.h"
25 #include "dcpomatic_button.h"
26 #include "nag_dialog.h"
27 #include "static_text.h"
29 #include <dcp/raw_convert.h>
34 using std::make_shared;
37 using std::shared_ptr;
41 using boost::optional;
42 #if BOOST_VERSION >= 106100
43 using namespace boost::placeholders;
54 Page::Page (wxSize panel_size, int border)
57 , _panel_size (panel_size)
58 , _window_exists (false)
60 _config_connection = Config::instance()->Changed.connect (bind (&Page::config_changed_wrapper, this));
65 Page::CreateWindow (wxWindow* parent)
67 return create_window (parent);
72 Page::create_window (wxWindow* parent)
74 _panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size);
75 auto s = new wxBoxSizer (wxVERTICAL);
79 _window_exists = true;
82 _panel->Bind (wxEVT_DESTROY, bind (&Page::window_destroyed, this));
88 Page::config_changed_wrapper ()
96 Page::window_destroyed ()
98 _window_exists = false;
102 GeneralPage::GeneralPage (wxSize panel_size, int border)
103 : Page (panel_size, border)
110 GeneralPage::GetName () const
117 GeneralPage::add_language_controls (wxGridBagSizer* table, int& r)
119 _set_language = new CheckBox (_panel, _("Set language"));
120 table->Add (_set_language, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
121 _language = new wxChoice (_panel, wxID_ANY);
122 vector<pair<string, string>> languages;
123 languages.push_back (make_pair("Čeština", "cs_CZ"));
124 languages.push_back (make_pair("汉语/漢語", "zh_CN"));
125 languages.push_back (make_pair("Dansk", "da_DK"));
126 languages.push_back (make_pair("Deutsch", "de_DE"));
127 languages.push_back (make_pair("English", "en_GB"));
128 languages.push_back (make_pair("Español", "es_ES"));
129 languages.push_back (make_pair("Français", "fr_FR"));
130 languages.push_back (make_pair("Italiano", "it_IT"));
131 languages.push_back (make_pair("Nederlands", "nl_NL"));
132 languages.push_back (make_pair("Русский", "ru_RU"));
133 languages.push_back (make_pair("Polski", "pl_PL"));
134 languages.push_back (make_pair("Português europeu", "pt_PT"));
135 languages.push_back (make_pair("Português do Brasil", "pt_BR"));
136 languages.push_back (make_pair("Svenska", "sv_SE"));
137 languages.push_back (make_pair("Slovenský jazyk", "sk_SK"));
138 // languages.push_back (make_pair("Türkçe", "tr_TR"));
139 languages.push_back (make_pair("українська мова", "uk_UA"));
140 checked_set (_language, languages);
141 table->Add (_language, wxGBPosition (r, 1));
144 auto restart = add_label_to_sizer (
145 table, _panel, _("(restart DCP-o-matic to see language changes)"), false, wxGBPosition (r, 0), wxGBSpan (1, 2)
147 wxFont font = restart->GetFont();
148 font.SetStyle (wxFONTSTYLE_ITALIC);
149 font.SetPointSize (font.GetPointSize() - 1);
150 restart->SetFont (font);
153 _set_language->Bind (wxEVT_CHECKBOX, bind (&GeneralPage::set_language_changed, this));
154 _language->Bind (wxEVT_CHOICE, bind (&GeneralPage::language_changed, this));
158 GeneralPage::add_update_controls (wxGridBagSizer* table, int& r)
160 _check_for_updates = new CheckBox (_panel, _("Check for updates on startup"));
161 table->Add (_check_for_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
164 _check_for_test_updates = new CheckBox (_panel, _("Check for testing updates on startup"));
165 table->Add (_check_for_test_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
168 _check_for_updates->Bind (wxEVT_CHECKBOX, bind (&GeneralPage::check_for_updates_changed, this));
169 _check_for_test_updates->Bind (wxEVT_CHECKBOX, bind (&GeneralPage::check_for_test_updates_changed, this));
173 GeneralPage::config_changed ()
175 auto config = Config::instance ();
177 checked_set (_set_language, static_cast<bool>(config->language()));
179 /* Backwards compatibility of config file */
181 map<string, string> compat_map;
182 compat_map["fr"] = "fr_FR";
183 compat_map["it"] = "it_IT";
184 compat_map["es"] = "es_ES";
185 compat_map["sv"] = "sv_SE";
186 compat_map["de"] = "de_DE";
187 compat_map["nl"] = "nl_NL";
188 compat_map["ru"] = "ru_RU";
189 compat_map["pl"] = "pl_PL";
190 compat_map["da"] = "da_DK";
191 compat_map["pt"] = "pt_PT";
192 compat_map["sk"] = "sk_SK";
193 compat_map["cs"] = "cs_CZ";
194 compat_map["uk"] = "uk_UA";
196 auto lang = config->language().get_value_or("en_GB");
197 if (compat_map.find(lang) != compat_map.end ()) {
198 lang = compat_map[lang];
201 checked_set (_language, lang);
203 checked_set (_check_for_updates, config->check_for_updates ());
204 checked_set (_check_for_test_updates, config->check_for_test_updates ());
206 setup_sensitivity ();
210 GeneralPage::setup_sensitivity ()
212 _language->Enable (_set_language->GetValue ());
213 _check_for_test_updates->Enable (_check_for_updates->GetValue ());
217 GeneralPage::set_language_changed ()
219 setup_sensitivity ();
220 if (_set_language->GetValue ()) {
223 Config::instance()->unset_language ();
228 GeneralPage::language_changed ()
230 int const sel = _language->GetSelection ();
232 Config::instance()->set_language (string_client_data (_language->GetClientObject (sel)));
234 Config::instance()->unset_language ();
239 GeneralPage::check_for_updates_changed ()
241 Config::instance()->set_check_for_updates (_check_for_updates->GetValue ());
245 GeneralPage::check_for_test_updates_changed ()
247 Config::instance()->set_check_for_test_updates (_check_for_test_updates->GetValue ());
250 CertificateChainEditor::CertificateChainEditor (
254 function<void (shared_ptr<dcp::CertificateChain>)> set,
255 function<shared_ptr<const dcp::CertificateChain> (void)> get,
256 function<bool (void)> nag_alter
258 : wxDialog (parent, wxID_ANY, title)
261 , _nag_alter (nag_alter)
263 _sizer = new wxBoxSizer (wxVERTICAL);
265 auto certificates_sizer = new wxBoxSizer (wxHORIZONTAL);
266 _sizer->Add (certificates_sizer, 0, wxALL, border);
268 _certificates = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (440, 150), wxLC_REPORT | wxLC_SINGLE_SEL);
273 ip.SetText (_("Type"));
275 _certificates->InsertColumn (0, ip);
281 ip.SetText (_("Thumbprint"));
284 wxFont font = ip.GetFont ();
285 font.SetFamily (wxFONTFAMILY_TELETYPE);
288 _certificates->InsertColumn (1, ip);
291 certificates_sizer->Add (_certificates, 1, wxEXPAND);
294 auto s = new wxBoxSizer (wxVERTICAL);
295 _add_certificate = new Button (this, _("Add..."));
296 s->Add (_add_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
297 _remove_certificate = new Button (this, _("Remove"));
298 s->Add (_remove_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
299 _export_certificate = new Button (this, _("Export certificate..."));
300 s->Add (_export_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
301 _export_chain = new Button (this, _("Export chain..."));
302 s->Add (_export_chain, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
303 certificates_sizer->Add (s, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
306 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
307 _sizer->Add (table, 1, wxALL | wxEXPAND, border);
310 add_label_to_sizer (table, this, _("Leaf private key"), true, wxGBPosition (r, 0));
311 _private_key = new StaticText (this, wxT(""));
312 wxFont font = _private_key->GetFont ();
313 font.SetFamily (wxFONTFAMILY_TELETYPE);
314 _private_key->SetFont (font);
315 table->Add (_private_key, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
316 _import_private_key = new Button (this, _("Import..."));
317 table->Add (_import_private_key, wxGBPosition (r, 2));
318 _export_private_key = new Button (this, _("Export..."));
319 table->Add (_export_private_key, wxGBPosition (r, 3));
322 _button_sizer = new wxBoxSizer (wxHORIZONTAL);
323 _remake_certificates = new Button (this, _("Re-make certificates and key..."));
324 _button_sizer->Add (_remake_certificates, 1, wxRIGHT, border);
325 table->Add (_button_sizer, wxGBPosition (r, 0), wxGBSpan (1, 4));
328 _private_key_bad = new StaticText (this, _("Leaf private key does not match leaf certificate!"));
329 font = *wxSMALL_FONT;
330 font.SetWeight (wxFONTWEIGHT_BOLD);
331 _private_key_bad->SetFont (font);
332 table->Add (_private_key_bad, wxGBPosition (r, 0), wxGBSpan (1, 3));
335 _add_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::add_certificate, this));
336 _remove_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::remove_certificate, this));
337 _export_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_certificate, this));
338 _certificates->Bind (wxEVT_LIST_ITEM_SELECTED, bind (&CertificateChainEditor::update_sensitivity, this));
339 _certificates->Bind (wxEVT_LIST_ITEM_DESELECTED, bind (&CertificateChainEditor::update_sensitivity, this));
340 _remake_certificates->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::remake_certificates, this));
341 _export_chain->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_chain, this));
342 _import_private_key->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::import_private_key, this));
343 _export_private_key->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_private_key, this));
345 auto buttons = CreateSeparatedButtonSizer (wxCLOSE);
347 _sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
350 SetSizerAndFit (_sizer);
352 update_certificate_list ();
353 update_private_key ();
354 update_sensitivity ();
358 CertificateChainEditor::add_button (wxWindow* button)
360 _button_sizer->Add (button, 0, wxLEFT | wxRIGHT, DCPOMATIC_SIZER_X_GAP);
365 CertificateChainEditor::add_certificate ()
367 auto d = new wxFileDialog (this, _("Select Certificate File"));
369 if (d->ShowModal() == wxID_OK) {
374 extra = c.read_string (dcp::file_to_string (wx_to_std (d->GetPath ())));
375 } catch (boost::filesystem::filesystem_error& e) {
376 error_dialog (this, _("Could not import certificate (%s)"), d->GetPath());
381 if (!extra.empty ()) {
384 _("This file contains other certificates (or other data) after its first certificate. "
385 "Only the first certificate will be used.")
388 auto chain = make_shared<dcp::CertificateChain>(*_get().get());
390 if (!chain->chain_valid ()) {
393 _("Adding this certificate would make the chain inconsistent, so it will not be added. "
394 "Add certificates in order from root to intermediate to leaf.")
399 update_certificate_list ();
401 } catch (dcp::MiscError& e) {
402 error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
408 update_sensitivity ();
412 CertificateChainEditor::remove_certificate ()
415 /* Cancel was clicked */
419 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
424 _certificates->DeleteItem (i);
425 auto chain = make_shared<dcp::CertificateChain>(*_get().get());
429 update_sensitivity ();
430 update_certificate_list ();
434 CertificateChainEditor::export_certificate ()
436 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
441 auto all = _get()->root_to_leaf();
443 wxString default_name;
445 default_name = "root.pem";
446 } else if (i == static_cast<int>(all.size() - 1)) {
447 default_name = "leaf.pem";
449 default_name = "intermediate.pem";
452 auto d = new wxFileDialog(
453 this, _("Select Certificate File"), wxEmptyString, default_name, wxT ("PEM files (*.pem)|*.pem"),
454 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
457 auto j = all.begin ();
458 for (int k = 0; k < i; ++k) {
462 if (d->ShowModal () == wxID_OK) {
463 boost::filesystem::path path (wx_to_std(d->GetPath()));
464 if (path.extension() != ".pem") {
467 dcp::File f(path, "w");
469 throw OpenFileError (path, errno, OpenFileError::WRITE);
472 string const s = j->certificate (true);
473 f.checked_write(s.c_str(), s.length());
479 CertificateChainEditor::export_chain ()
481 auto d = new wxFileDialog (
482 this, _("Select Chain File"), wxEmptyString, wxT("certificate_chain.pem"), wxT("PEM files (*.pem)|*.pem"),
483 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
486 if (d->ShowModal () == wxID_OK) {
487 boost::filesystem::path path (wx_to_std(d->GetPath()));
488 if (path.extension() != ".pem") {
491 dcp::File f(path, "w");
493 throw OpenFileError (path, errno, OpenFileError::WRITE);
496 auto const s = _get()->chain();
497 f.checked_write (s.c_str(), s.length());
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 dcp::File f(path, "w");
621 throw OpenFileError (path, errno, OpenFileError::WRITE);
624 auto const s = _get()->key().get ();
625 f.checked_write(s.c_str(), s.length());
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 kdm_buttons = new wxBoxSizer (wxVERTICAL);
652 auto export_decryption_certificate = new Button (_panel, _("Export KDM decryption leaf certificate..."));
653 kdm_buttons->Add (export_decryption_certificate, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
654 auto export_settings = new Button (_panel, _("Export all KDM decryption settings..."));
655 kdm_buttons->Add (export_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
656 auto import_settings = new Button (_panel, _("Import all KDM decryption settings..."));
657 kdm_buttons->Add (import_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
658 auto decryption_advanced = new Button (_panel, _("Advanced..."));
659 kdm_buttons->Add (decryption_advanced, 0);
661 sizer->Add (kdm_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_buttons = new wxBoxSizer (wxVERTICAL);
676 auto signing_advanced = new Button (_panel, _("Advanced..."));
677 signing_buttons->Add (signing_advanced, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
678 auto remake_signing = new Button (_panel, _("Re-make certificates and key..."));
679 signing_buttons->Add (remake_signing, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
681 sizer->Add (signing_buttons, 0, wxLEFT, _border);
683 signing_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::signing_advanced, this));
684 remake_signing->Bind (wxEVT_BUTTON, bind(&KeysPage::remake_signing, this));
689 KeysPage::remake_signing ()
691 auto d = new MakeChainDialog (_panel, Config::instance()->signer_chain());
693 if (d->ShowModal () == wxID_OK) {
694 Config::instance()->set_signer_chain(d->get());
700 KeysPage::decryption_advanced ()
702 auto c = new CertificateChainEditor (
703 _panel, _("Decrypting KDMs"), _border,
704 bind(&Config::set_decryption_chain, Config::instance(), _1),
705 bind(&Config::decryption_chain, Config::instance()),
706 bind(&KeysPage::nag_alter_decryption_chain, this)
713 KeysPage::signing_advanced ()
715 auto c = new CertificateChainEditor (
716 _panel, _("Signing DCPs and KDMs"), _border,
717 bind(&Config::set_signer_chain, Config::instance(), _1),
718 bind(&Config::signer_chain, Config::instance()),
726 KeysPage::export_decryption_chain_and_key ()
728 auto d = new wxFileDialog (
729 _panel, _("Select Export File"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom"),
730 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
733 if (d->ShowModal () == wxID_OK) {
734 boost::filesystem::path path (wx_to_std(d->GetPath()));
735 dcp::File f(path, "w");
737 throw OpenFileError (path, errno, OpenFileError::WRITE);
740 auto const chain = Config::instance()->decryption_chain()->chain();
741 f.checked_write (chain.c_str(), chain.length());
742 auto const key = Config::instance()->decryption_chain()->key();
743 DCPOMATIC_ASSERT (key);
744 f.checked_write(key->c_str(), key->length());
751 KeysPage::import_decryption_chain_and_key ()
753 if (NagDialog::maybe_nag (
755 Config::NAG_IMPORT_DECRYPTION_CHAIN,
756 _("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!"),
762 auto d = new wxFileDialog (
763 _panel, _("Select File To Import"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom")
766 if (d->ShowModal () == wxID_OK) {
767 auto new_chain = make_shared<dcp::CertificateChain>();
769 dcp::File f(wx_to_std(d->GetPath()), "r");
771 throw OpenFileError (f.path(), errno, OpenFileError::WRITE);
777 if (f.gets(buffer, 128) == 0) {
781 if (strncmp (buffer, "-----END CERTIFICATE-----", 25) == 0) {
782 new_chain->add (dcp::Certificate (current));
784 } else if (strncmp (buffer, "-----END RSA PRIVATE KEY-----", 29) == 0) {
785 new_chain->set_key (current);
790 if (new_chain->chain_valid() && new_chain->private_key_valid()) {
791 Config::instance()->set_decryption_chain (new_chain);
793 error_dialog (_panel, _("Invalid DCP-o-matic export file"));
800 KeysPage::nag_alter_decryption_chain ()
802 return NagDialog::maybe_nag (
804 Config::NAG_ALTER_DECRYPTION_CHAIN,
805 _("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!"),
811 KeysPage::export_decryption_certificate ()
813 auto config = Config::instance();
814 wxString default_name = "dcpomatic";
815 if (!config->dcp_creator().empty()) {
816 default_name += "_" + std_to_wx(careful_string_filter(config->dcp_creator()));
818 if (!config->dcp_issuer().empty()) {
819 default_name += "_" + std_to_wx(careful_string_filter(config->dcp_issuer()));
821 default_name += wxT("_kdm_decryption_cert.pem");
823 auto d = new wxFileDialog (
824 _panel, _("Select Certificate File"), wxEmptyString, default_name, wxT("PEM files (*.pem)|*.pem"),
825 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
828 if (d->ShowModal () == wxID_OK) {
829 boost::filesystem::path path (wx_to_std(d->GetPath()));
830 if (path.extension() != ".pem") {
833 dcp::File f(path, "w");
835 throw OpenFileError (path, errno, OpenFileError::WRITE);
838 auto const s = Config::instance()->decryption_chain()->leaf().certificate (true);
839 f.checked_write(s.c_str(), s.length());
846 SoundPage::GetName () const
854 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
855 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
859 _sound = new CheckBox (_panel, _("Play sound via"));
860 table->Add (_sound, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
861 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
862 _sound_output = new wxChoice (_panel, wxID_ANY);
863 s->Add (_sound_output, 0);
864 _sound_output_details = new wxStaticText (_panel, wxID_ANY, wxT(""));
865 s->Add (_sound_output_details, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, DCPOMATIC_SIZER_X_GAP);
866 table->Add (s, wxGBPosition(r, 1));
869 add_label_to_sizer (table, _panel, _("Mapping"), true, wxGBPosition(r, 0));
870 _map = new AudioMappingView (_panel, _("DCP"), _("DCP"), _("Output"), _("output"));
871 _map->SetSize (-1, 400);
872 table->Add (_map, wxGBPosition(r, 1), wxDefaultSpan, wxEXPAND);
875 _reset_to_default = new Button (_panel, _("Reset to default"));
876 table->Add (_reset_to_default, wxGBPosition(r, 1));
879 wxFont font = _sound_output_details->GetFont();
880 font.SetStyle (wxFONTSTYLE_ITALIC);
881 font.SetPointSize (font.GetPointSize() - 1);
882 _sound_output_details->SetFont (font);
884 RtAudio audio (DCPOMATIC_RTAUDIO_API);
885 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
887 auto dev = audio.getDeviceInfo (i);
888 if (dev.probed && dev.outputChannels > 0) {
889 _sound_output->Append (std_to_wx (dev.name));
891 } catch (RtAudioError&) {
892 /* Something went wrong so let's just ignore that device */
896 _sound->Bind (wxEVT_CHECKBOX, bind(&SoundPage::sound_changed, this));
897 _sound_output->Bind (wxEVT_CHOICE, bind(&SoundPage::sound_output_changed, this));
898 _map->Changed.connect (bind(&SoundPage::map_changed, this, _1));
899 _reset_to_default->Bind (wxEVT_BUTTON, bind(&SoundPage::reset_to_default, this));
903 SoundPage::reset_to_default ()
905 Config::instance()->set_audio_mapping_to_default ();
909 SoundPage::map_changed (AudioMapping m)
911 Config::instance()->set_audio_mapping (m);
915 SoundPage::sound_changed ()
917 Config::instance()->set_sound (_sound->GetValue ());
921 SoundPage::sound_output_changed ()
923 RtAudio audio (DCPOMATIC_RTAUDIO_API);
924 auto const so = get_sound_output();
925 string default_device;
927 default_device = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
928 } catch (RtAudioError&) {
931 if (!so || *so == default_device) {
932 Config::instance()->unset_sound_output ();
934 Config::instance()->set_sound_output (*so);
939 SoundPage::config_changed ()
941 auto config = Config::instance ();
943 checked_set (_sound, config->sound ());
945 auto const current_so = get_sound_output ();
946 optional<string> configured_so;
948 if (config->sound_output()) {
949 configured_so = config->sound_output().get();
951 /* No configured output means we should use the default */
952 RtAudio audio (DCPOMATIC_RTAUDIO_API);
954 configured_so = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
955 } catch (RtAudioError&) {
956 /* Probably no audio devices at all */
960 if (configured_so && current_so != configured_so) {
961 /* Update _sound_output with the configured value */
963 while (i < _sound_output->GetCount()) {
964 if (_sound_output->GetString(i) == std_to_wx(*configured_so)) {
965 _sound_output->SetSelection (i);
972 RtAudio audio (DCPOMATIC_RTAUDIO_API);
974 map<int, wxString> apis;
975 apis[RtAudio::MACOSX_CORE] = _("CoreAudio");
976 apis[RtAudio::WINDOWS_ASIO] = _("ASIO");
977 apis[RtAudio::WINDOWS_DS] = _("Direct Sound");
978 apis[RtAudio::WINDOWS_WASAPI] = _("WASAPI");
979 apis[RtAudio::UNIX_JACK] = _("JACK");
980 apis[RtAudio::LINUX_ALSA] = _("ALSA");
981 apis[RtAudio::LINUX_PULSE] = _("PulseAudio");
982 apis[RtAudio::LINUX_OSS] = _("OSS");
983 apis[RtAudio::RTAUDIO_DUMMY] = _("Dummy");
987 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
989 auto info = audio.getDeviceInfo(i);
990 if (info.name == *configured_so && info.outputChannels > 0) {
991 channels = info.outputChannels;
993 } catch (RtAudioError&) {
999 _sound_output_details->SetLabel (
1000 wxString::Format(_("%d channels on %s"), channels, apis[audio.getCurrentApi()])
1003 _map->set (Config::instance()->audio_mapping(channels));
1005 vector<NamedChannel> input;
1006 for (int i = 0; i < MAX_DCP_AUDIO_CHANNELS; ++i) {
1007 input.push_back (NamedChannel(short_audio_channel_name(i), i));
1009 _map->set_input_channels (input);
1011 vector<NamedChannel> output;
1012 for (int i = 0; i < channels; ++i) {
1013 output.push_back (NamedChannel(dcp::raw_convert<string>(i), i));
1015 _map->set_output_channels (output);
1017 setup_sensitivity ();
1021 SoundPage::setup_sensitivity ()
1023 _sound_output->Enable (_sound->GetValue());
1026 /** @return Currently-selected preview sound output in the dialogue */
1028 SoundPage::get_sound_output ()
1030 int const sel = _sound_output->GetSelection ();
1031 if (sel == wxNOT_FOUND) {
1032 return optional<string> ();
1035 return wx_to_std (_sound_output->GetString (sel));
1039 LocationsPage::LocationsPage (wxSize panel_size, int border)
1040 : Page (panel_size, border)
1046 LocationsPage::GetName () const
1048 return _("Locations");
1051 #ifdef DCPOMATIC_OSX
1053 LocationsPage::GetLargeIcon () const
1055 return wxBitmap(bitmap_path("locations"), wxBITMAP_TYPE_PNG);
1060 LocationsPage::setup ()
1064 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1065 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
1067 add_label_to_sizer (table, _panel, _("Content directory"), true, wxGBPosition (r, 0));
1068 _content_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1069 table->Add (_content_directory, wxGBPosition (r, 1));
1072 add_label_to_sizer (table, _panel, _("Playlist directory"), true, wxGBPosition (r, 0));
1073 _playlist_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1074 table->Add (_playlist_directory, wxGBPosition (r, 1));
1077 add_label_to_sizer (table, _panel, _("KDM directory"), true, wxGBPosition (r, 0));
1078 _kdm_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1079 table->Add (_kdm_directory, wxGBPosition (r, 1));
1082 _content_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::content_directory_changed, this));
1083 _playlist_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::playlist_directory_changed, this));
1084 _kdm_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::kdm_directory_changed, this));
1088 LocationsPage::config_changed ()
1090 auto config = Config::instance ();
1092 if (config->player_content_directory()) {
1093 checked_set (_content_directory, *config->player_content_directory());
1095 if (config->player_playlist_directory()) {
1096 checked_set (_playlist_directory, *config->player_playlist_directory());
1098 if (config->player_kdm_directory()) {
1099 checked_set (_kdm_directory, *config->player_kdm_directory());
1104 LocationsPage::content_directory_changed ()
1106 Config::instance()->set_player_content_directory(wx_to_std(_content_directory->GetPath()));
1110 LocationsPage::playlist_directory_changed ()
1112 Config::instance()->set_player_playlist_directory(wx_to_std(_playlist_directory->GetPath()));
1116 LocationsPage::kdm_directory_changed ()
1118 Config::instance()->set_player_kdm_directory(wx_to_std(_kdm_directory->GetPath()));