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"
28 #include "lib/constants.h"
30 #include <dcp/raw_convert.h>
35 using std::make_shared;
38 using std::shared_ptr;
42 using boost::optional;
43 #if BOOST_VERSION >= 106100
44 using namespace boost::placeholders;
55 Page::Page (wxSize panel_size, int border)
58 , _panel_size (panel_size)
59 , _window_exists (false)
61 _config_connection = Config::instance()->Changed.connect (bind (&Page::config_changed_wrapper, this));
66 Page::CreateWindow (wxWindow* parent)
68 return create_window (parent);
73 Page::create_window (wxWindow* parent)
75 _panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size);
76 auto s = new wxBoxSizer (wxVERTICAL);
80 _window_exists = true;
83 _panel->Bind (wxEVT_DESTROY, bind (&Page::window_destroyed, this));
89 Page::config_changed_wrapper ()
97 Page::window_destroyed ()
99 _window_exists = false;
103 GeneralPage::GeneralPage (wxSize panel_size, int border)
104 : Page (panel_size, border)
111 GeneralPage::GetName () const
118 GeneralPage::add_language_controls (wxGridBagSizer* table, int& r)
120 _set_language = new CheckBox (_panel, _("Set language"));
121 table->Add (_set_language, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
122 _language = new wxChoice (_panel, wxID_ANY);
123 vector<pair<string, string>> languages;
124 languages.push_back (make_pair("Čeština", "cs_CZ"));
125 languages.push_back (make_pair("汉语/漢語", "zh_CN"));
126 languages.push_back (make_pair("Dansk", "da_DK"));
127 languages.push_back (make_pair("Deutsch", "de_DE"));
128 languages.push_back (make_pair("English", "en_GB"));
129 languages.push_back (make_pair("Español", "es_ES"));
130 languages.push_back (make_pair("Français", "fr_FR"));
131 languages.push_back (make_pair("Italiano", "it_IT"));
132 languages.push_back (make_pair("Nederlands", "nl_NL"));
133 languages.push_back (make_pair("Русский", "ru_RU"));
134 languages.push_back (make_pair("Polski", "pl_PL"));
135 languages.push_back (make_pair("Português europeu", "pt_PT"));
136 languages.push_back (make_pair("Português do Brasil", "pt_BR"));
137 languages.push_back (make_pair("Svenska", "sv_SE"));
138 languages.push_back (make_pair("Slovenščina", "sl_SI"));
139 languages.push_back (make_pair("Slovenský jazyk", "sk_SK"));
140 // languages.push_back (make_pair("Türkçe", "tr_TR"));
141 languages.push_back (make_pair("українська мова", "uk_UA"));
142 languages.push_back (make_pair("Magyar nyelv", "hu_HU"));
143 checked_set (_language, languages);
144 table->Add (_language, wxGBPosition (r, 1));
147 auto restart = add_label_to_sizer (
148 table, _panel, _("(restart DCP-o-matic to see language changes)"), false, wxGBPosition (r, 0), wxGBSpan (1, 2)
150 wxFont font = restart->GetFont();
151 font.SetStyle (wxFONTSTYLE_ITALIC);
152 font.SetPointSize (font.GetPointSize() - 1);
153 restart->SetFont (font);
156 _set_language->bind(&GeneralPage::set_language_changed, this);
157 _language->Bind (wxEVT_CHOICE, bind (&GeneralPage::language_changed, this));
161 GeneralPage::add_update_controls (wxGridBagSizer* table, int& r)
163 _check_for_updates = new CheckBox (_panel, _("Check for updates on startup"));
164 table->Add (_check_for_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
167 _check_for_test_updates = new CheckBox (_panel, _("Check for testing updates on startup"));
168 table->Add (_check_for_test_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
171 _check_for_updates->bind(&GeneralPage::check_for_updates_changed, this);
172 _check_for_test_updates->bind(&GeneralPage::check_for_test_updates_changed, this);
176 GeneralPage::config_changed ()
178 auto config = Config::instance ();
180 checked_set (_set_language, static_cast<bool>(config->language()));
182 /* Backwards compatibility of config file */
184 map<string, string> compat_map;
185 compat_map["fr"] = "fr_FR";
186 compat_map["it"] = "it_IT";
187 compat_map["es"] = "es_ES";
188 compat_map["sv"] = "sv_SE";
189 compat_map["de"] = "de_DE";
190 compat_map["nl"] = "nl_NL";
191 compat_map["ru"] = "ru_RU";
192 compat_map["pl"] = "pl_PL";
193 compat_map["da"] = "da_DK";
194 compat_map["pt"] = "pt_PT";
195 compat_map["sk"] = "sk_SK";
196 compat_map["cs"] = "cs_CZ";
197 compat_map["uk"] = "uk_UA";
199 auto lang = config->language().get_value_or("en_GB");
200 if (compat_map.find(lang) != compat_map.end ()) {
201 lang = compat_map[lang];
204 checked_set (_language, lang);
206 checked_set (_check_for_updates, config->check_for_updates ());
207 checked_set (_check_for_test_updates, config->check_for_test_updates ());
209 setup_sensitivity ();
213 GeneralPage::setup_sensitivity ()
215 _language->Enable (_set_language->GetValue ());
216 _check_for_test_updates->Enable (_check_for_updates->GetValue ());
220 GeneralPage::set_language_changed ()
222 setup_sensitivity ();
223 if (_set_language->GetValue ()) {
226 Config::instance()->unset_language ();
231 GeneralPage::language_changed ()
233 int const sel = _language->GetSelection ();
235 Config::instance()->set_language (string_client_data (_language->GetClientObject (sel)));
237 Config::instance()->unset_language ();
242 GeneralPage::check_for_updates_changed ()
244 Config::instance()->set_check_for_updates (_check_for_updates->GetValue ());
248 GeneralPage::check_for_test_updates_changed ()
250 Config::instance()->set_check_for_test_updates (_check_for_test_updates->GetValue ());
253 CertificateChainEditor::CertificateChainEditor (
257 function<void (shared_ptr<dcp::CertificateChain>)> set,
258 function<shared_ptr<const dcp::CertificateChain> (void)> get,
259 function<bool (void)> nag_alter
261 : wxDialog (parent, wxID_ANY, title)
264 , _nag_alter (nag_alter)
266 _sizer = new wxBoxSizer (wxVERTICAL);
268 auto certificates_sizer = new wxBoxSizer (wxHORIZONTAL);
269 _sizer->Add (certificates_sizer, 0, wxALL, border);
271 _certificates = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (440, 150), wxLC_REPORT | wxLC_SINGLE_SEL);
276 ip.SetText (_("Type"));
278 _certificates->InsertColumn (0, ip);
284 ip.SetText (_("Thumbprint"));
287 wxFont font = ip.GetFont ();
288 font.SetFamily (wxFONTFAMILY_TELETYPE);
291 _certificates->InsertColumn (1, ip);
294 certificates_sizer->Add (_certificates, 1, wxEXPAND);
297 auto s = new wxBoxSizer (wxVERTICAL);
298 _add_certificate = new Button (this, _("Add..."));
299 s->Add (_add_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
300 _remove_certificate = new Button (this, _("Remove"));
301 s->Add (_remove_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
302 _export_certificate = new Button (this, _("Export certificate..."));
303 s->Add (_export_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
304 _export_chain = new Button (this, _("Export chain..."));
305 s->Add (_export_chain, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
306 certificates_sizer->Add (s, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
309 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
310 _sizer->Add (table, 1, wxALL | wxEXPAND, border);
313 add_label_to_sizer (table, this, _("Leaf private key"), true, wxGBPosition (r, 0));
314 _private_key = new StaticText (this, wxT(""));
315 wxFont font = _private_key->GetFont ();
316 font.SetFamily (wxFONTFAMILY_TELETYPE);
317 _private_key->SetFont (font);
318 table->Add (_private_key, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
319 _import_private_key = new Button (this, _("Import..."));
320 table->Add (_import_private_key, wxGBPosition (r, 2));
321 _export_private_key = new Button (this, _("Export..."));
322 table->Add (_export_private_key, wxGBPosition (r, 3));
325 _button_sizer = new wxBoxSizer (wxHORIZONTAL);
326 _remake_certificates = new Button (this, _("Re-make certificates and key..."));
327 _button_sizer->Add (_remake_certificates, 1, wxRIGHT, border);
328 table->Add (_button_sizer, wxGBPosition (r, 0), wxGBSpan (1, 4));
331 _private_key_bad = new StaticText (this, _("Leaf private key does not match leaf certificate!"));
332 font = *wxSMALL_FONT;
333 font.SetWeight (wxFONTWEIGHT_BOLD);
334 _private_key_bad->SetFont (font);
335 table->Add (_private_key_bad, wxGBPosition (r, 0), wxGBSpan (1, 3));
338 _add_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::add_certificate, this));
339 _remove_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::remove_certificate, this));
340 _export_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_certificate, this));
341 _certificates->Bind (wxEVT_LIST_ITEM_SELECTED, bind (&CertificateChainEditor::update_sensitivity, this));
342 _certificates->Bind (wxEVT_LIST_ITEM_DESELECTED, bind (&CertificateChainEditor::update_sensitivity, this));
343 _remake_certificates->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::remake_certificates, this));
344 _export_chain->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_chain, this));
345 _import_private_key->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::import_private_key, this));
346 _export_private_key->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_private_key, this));
348 auto buttons = CreateSeparatedButtonSizer (wxCLOSE);
350 _sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
353 SetSizerAndFit (_sizer);
355 update_certificate_list ();
356 update_private_key ();
357 update_sensitivity ();
361 CertificateChainEditor::add_button (wxWindow* button)
363 _button_sizer->Add (button, 0, wxLEFT | wxRIGHT, DCPOMATIC_SIZER_X_GAP);
368 CertificateChainEditor::add_certificate ()
370 auto d = new wxFileDialog (this, _("Select Certificate File"));
372 if (d->ShowModal() == wxID_OK) {
377 extra = c.read_string (dcp::file_to_string (wx_to_std (d->GetPath ())));
378 } catch (boost::filesystem::filesystem_error& e) {
379 error_dialog (this, _("Could not import certificate (%s)"), d->GetPath());
384 if (!extra.empty ()) {
387 _("This file contains other certificates (or other data) after its first certificate. "
388 "Only the first certificate will be used.")
391 auto chain = make_shared<dcp::CertificateChain>(*_get().get());
393 if (!chain->chain_valid ()) {
396 _("Adding this certificate would make the chain inconsistent, so it will not be added. "
397 "Add certificates in order from root to intermediate to leaf.")
402 update_certificate_list ();
404 } catch (dcp::MiscError& e) {
405 error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
411 update_sensitivity ();
415 CertificateChainEditor::remove_certificate ()
418 /* Cancel was clicked */
422 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
427 _certificates->DeleteItem (i);
428 auto chain = make_shared<dcp::CertificateChain>(*_get().get());
432 update_sensitivity ();
433 update_certificate_list ();
437 CertificateChainEditor::export_certificate ()
439 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
444 auto all = _get()->root_to_leaf();
446 wxString default_name;
448 default_name = "root.pem";
449 } else if (i == static_cast<int>(all.size() - 1)) {
450 default_name = "leaf.pem";
452 default_name = "intermediate.pem";
455 auto d = new wxFileDialog(
456 this, _("Select Certificate File"), wxEmptyString, default_name, wxT ("PEM files (*.pem)|*.pem"),
457 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
460 auto j = all.begin ();
461 for (int k = 0; k < i; ++k) {
465 if (d->ShowModal () == wxID_OK) {
466 boost::filesystem::path path (wx_to_std(d->GetPath()));
467 if (path.extension() != ".pem") {
470 dcp::File f(path, "w");
472 throw OpenFileError (path, errno, OpenFileError::WRITE);
475 string const s = j->certificate (true);
476 f.checked_write(s.c_str(), s.length());
482 CertificateChainEditor::export_chain ()
484 auto d = new wxFileDialog (
485 this, _("Select Chain File"), wxEmptyString, wxT("certificate_chain.pem"), wxT("PEM files (*.pem)|*.pem"),
486 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
489 if (d->ShowModal () == wxID_OK) {
490 boost::filesystem::path path (wx_to_std(d->GetPath()));
491 if (path.extension() != ".pem") {
494 dcp::File f(path, "w");
496 throw OpenFileError (path, errno, OpenFileError::WRITE);
499 auto const s = _get()->chain();
500 f.checked_write (s.c_str(), s.length());
507 CertificateChainEditor::update_certificate_list ()
509 _certificates->DeleteAllItems ();
511 auto certs = _get()->root_to_leaf();
512 for (auto const& i: certs) {
515 _certificates->InsertItem (item);
516 _certificates->SetItem (n, 1, std_to_wx (i.thumbprint ()));
519 _certificates->SetItem (n, 0, _("Root"));
520 } else if (n == (certs.size() - 1)) {
521 _certificates->SetItem (n, 0, _("Leaf"));
523 _certificates->SetItem (n, 0, _("Intermediate"));
529 static wxColour normal = _private_key_bad->GetForegroundColour ();
531 if (_get()->private_key_valid()) {
532 _private_key_bad->Hide ();
533 _private_key_bad->SetForegroundColour (normal);
535 _private_key_bad->Show ();
536 _private_key_bad->SetForegroundColour (wxColour (255, 0, 0));
541 CertificateChainEditor::remake_certificates ()
544 /* Cancel was clicked */
548 auto d = new MakeChainDialog (this, _get());
550 if (d->ShowModal () == wxID_OK) {
552 update_certificate_list ();
553 update_private_key ();
560 CertificateChainEditor::update_sensitivity ()
562 /* We can only remove the leaf certificate */
563 _remove_certificate->Enable (_certificates->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) == (_certificates->GetItemCount() - 1));
564 _export_certificate->Enable (_certificates->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
568 CertificateChainEditor::update_private_key ()
570 checked_set (_private_key, dcp::private_key_fingerprint (_get()->key().get()));
575 CertificateChainEditor::import_private_key ()
577 auto d = new wxFileDialog (this, _("Select Key File"));
579 if (d->ShowModal() == wxID_OK) {
581 boost::filesystem::path p (wx_to_std (d->GetPath ()));
582 if (boost::filesystem::file_size (p) > 8192) {
585 wxString::Format (_("Could not read key file; file is too long (%s)"), std_to_wx (p.string ()))
590 auto chain = make_shared<dcp::CertificateChain>(*_get().get());
591 chain->set_key (dcp::file_to_string (p));
593 update_private_key ();
594 } catch (std::exception& e) {
595 error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
601 update_sensitivity ();
605 CertificateChainEditor::export_private_key ()
607 auto key = _get()->key();
612 auto d = new wxFileDialog (
613 this, _("Select Key File"), wxEmptyString, wxT("private_key.pem"), wxT("PEM files (*.pem)|*.pem"),
614 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
617 if (d->ShowModal () == wxID_OK) {
618 boost::filesystem::path path (wx_to_std(d->GetPath()));
619 if (path.extension() != ".pem") {
622 dcp::File f(path, "w");
624 throw OpenFileError (path, errno, OpenFileError::WRITE);
627 auto const s = _get()->key().get ();
628 f.checked_write(s.c_str(), s.length());
634 KeysPage::GetName () const
642 wxFont subheading_font (*wxNORMAL_FONT);
643 subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
645 auto sizer = _panel->GetSizer();
648 auto m = new StaticText (_panel, _("Decrypting KDMs"));
649 m->SetFont (subheading_font);
650 sizer->Add (m, 0, wxALL | wxEXPAND, _border);
653 auto kdm_buttons = new wxBoxSizer (wxVERTICAL);
655 auto export_decryption_certificate = new Button (_panel, _("Export KDM decryption leaf certificate..."));
656 kdm_buttons->Add (export_decryption_certificate, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
657 auto export_settings = new Button (_panel, _("Export all KDM decryption settings..."));
658 kdm_buttons->Add (export_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
659 auto import_settings = new Button (_panel, _("Import all KDM decryption settings..."));
660 kdm_buttons->Add (import_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
661 auto decryption_advanced = new Button (_panel, _("Advanced..."));
662 kdm_buttons->Add (decryption_advanced, 0);
664 sizer->Add (kdm_buttons, 0, wxLEFT, _border);
666 export_decryption_certificate->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_certificate, this));
667 export_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_chain_and_key, this));
668 import_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::import_decryption_chain_and_key, this));
669 decryption_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::decryption_advanced, this));
672 auto m = new StaticText (_panel, _("Signing DCPs and KDMs"));
673 m->SetFont (subheading_font);
674 sizer->Add (m, 0, wxALL | wxEXPAND, _border);
677 auto signing_buttons = new wxBoxSizer (wxVERTICAL);
679 auto signing_advanced = new Button (_panel, _("Advanced..."));
680 signing_buttons->Add (signing_advanced, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
681 auto remake_signing = new Button (_panel, _("Re-make certificates and key..."));
682 signing_buttons->Add (remake_signing, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
684 sizer->Add (signing_buttons, 0, wxLEFT, _border);
686 signing_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::signing_advanced, this));
687 remake_signing->Bind (wxEVT_BUTTON, bind(&KeysPage::remake_signing, this));
692 KeysPage::remake_signing ()
694 auto d = new MakeChainDialog (_panel, Config::instance()->signer_chain());
696 if (d->ShowModal () == wxID_OK) {
697 Config::instance()->set_signer_chain(d->get());
703 KeysPage::decryption_advanced ()
705 auto c = new CertificateChainEditor (
706 _panel, _("Decrypting KDMs"), _border,
707 bind(&Config::set_decryption_chain, Config::instance(), _1),
708 bind(&Config::decryption_chain, Config::instance()),
709 bind(&KeysPage::nag_alter_decryption_chain, this)
716 KeysPage::signing_advanced ()
718 auto c = new CertificateChainEditor (
719 _panel, _("Signing DCPs and KDMs"), _border,
720 bind(&Config::set_signer_chain, Config::instance(), _1),
721 bind(&Config::signer_chain, Config::instance()),
729 KeysPage::export_decryption_chain_and_key ()
731 auto d = new wxFileDialog (
732 _panel, _("Select Export File"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom"),
733 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
736 if (d->ShowModal () == wxID_OK) {
737 boost::filesystem::path path (wx_to_std(d->GetPath()));
738 dcp::File f(path, "w");
740 throw OpenFileError (path, errno, OpenFileError::WRITE);
743 auto const chain = Config::instance()->decryption_chain()->chain();
744 f.checked_write (chain.c_str(), chain.length());
745 auto const key = Config::instance()->decryption_chain()->key();
746 DCPOMATIC_ASSERT (key);
747 f.checked_write(key->c_str(), key->length());
754 KeysPage::import_decryption_chain_and_key ()
756 if (NagDialog::maybe_nag (
758 Config::NAG_IMPORT_DECRYPTION_CHAIN,
759 _("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!"),
765 auto d = new wxFileDialog (
766 _panel, _("Select File To Import"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom")
769 if (d->ShowModal () == wxID_OK) {
770 auto new_chain = make_shared<dcp::CertificateChain>();
772 dcp::File f(wx_to_std(d->GetPath()), "r");
774 throw OpenFileError (f.path(), errno, OpenFileError::WRITE);
780 if (f.gets(buffer, 128) == 0) {
784 if (strncmp (buffer, "-----END CERTIFICATE-----", 25) == 0) {
785 new_chain->add (dcp::Certificate (current));
787 } else if (strncmp (buffer, "-----END RSA PRIVATE KEY-----", 29) == 0) {
788 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 dcp::File f(path, "w");
838 throw OpenFileError (path, errno, OpenFileError::WRITE);
841 auto const s = Config::instance()->decryption_chain()->leaf().certificate (true);
842 f.checked_write(s.c_str(), s.length());
849 SoundPage::GetName () const
857 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
858 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
862 _sound = new CheckBox (_panel, _("Play sound via"));
863 table->Add (_sound, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
864 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
865 _sound_output = new wxChoice (_panel, wxID_ANY);
866 s->Add (_sound_output, 0);
867 _sound_output_details = new wxStaticText (_panel, wxID_ANY, wxT(""));
868 s->Add (_sound_output_details, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, DCPOMATIC_SIZER_X_GAP);
869 table->Add (s, wxGBPosition(r, 1));
872 add_label_to_sizer (table, _panel, _("Mapping"), true, wxGBPosition(r, 0));
873 _map = new AudioMappingView (_panel, _("DCP"), _("DCP"), _("Output"), _("output"));
874 _map->SetSize (-1, 400);
875 table->Add (_map, wxGBPosition(r, 1), wxDefaultSpan, wxEXPAND);
878 _reset_to_default = new Button (_panel, _("Reset to default"));
879 table->Add (_reset_to_default, wxGBPosition(r, 1));
882 wxFont font = _sound_output_details->GetFont();
883 font.SetStyle (wxFONTSTYLE_ITALIC);
884 font.SetPointSize (font.GetPointSize() - 1);
885 _sound_output_details->SetFont (font);
887 RtAudio audio (DCPOMATIC_RTAUDIO_API);
888 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
890 auto dev = audio.getDeviceInfo (i);
891 if (dev.probed && dev.outputChannels > 0) {
892 _sound_output->Append (std_to_wx (dev.name));
894 } catch (RtAudioError&) {
895 /* Something went wrong so let's just ignore that device */
899 _sound->bind(&SoundPage::sound_changed, this);
900 _sound_output->Bind (wxEVT_CHOICE, bind(&SoundPage::sound_output_changed, this));
901 _map->Changed.connect (bind(&SoundPage::map_changed, this, _1));
902 _reset_to_default->Bind (wxEVT_BUTTON, bind(&SoundPage::reset_to_default, this));
906 SoundPage::reset_to_default ()
908 Config::instance()->set_audio_mapping_to_default ();
912 SoundPage::map_changed (AudioMapping m)
914 Config::instance()->set_audio_mapping (m);
918 SoundPage::sound_changed ()
920 Config::instance()->set_sound (_sound->GetValue ());
924 SoundPage::sound_output_changed ()
926 RtAudio audio (DCPOMATIC_RTAUDIO_API);
927 auto const so = get_sound_output();
928 string default_device;
930 default_device = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
931 } catch (RtAudioError&) {
934 if (!so || *so == default_device) {
935 Config::instance()->unset_sound_output ();
937 Config::instance()->set_sound_output (*so);
942 SoundPage::config_changed ()
944 auto config = Config::instance ();
946 checked_set (_sound, config->sound ());
948 auto const current_so = get_sound_output ();
949 optional<string> configured_so;
951 if (config->sound_output()) {
952 configured_so = config->sound_output().get();
954 /* No configured output means we should use the default */
955 RtAudio audio (DCPOMATIC_RTAUDIO_API);
957 configured_so = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
958 } catch (RtAudioError&) {
959 /* Probably no audio devices at all */
963 if (configured_so && current_so != configured_so) {
964 /* Update _sound_output with the configured value */
966 while (i < _sound_output->GetCount()) {
967 if (_sound_output->GetString(i) == std_to_wx(*configured_so)) {
968 _sound_output->SetSelection (i);
975 RtAudio audio (DCPOMATIC_RTAUDIO_API);
977 map<int, wxString> apis;
978 apis[RtAudio::MACOSX_CORE] = _("CoreAudio");
979 apis[RtAudio::WINDOWS_ASIO] = _("ASIO");
980 apis[RtAudio::WINDOWS_DS] = _("Direct Sound");
981 apis[RtAudio::WINDOWS_WASAPI] = _("WASAPI");
982 apis[RtAudio::UNIX_JACK] = _("JACK");
983 apis[RtAudio::LINUX_ALSA] = _("ALSA");
984 apis[RtAudio::LINUX_PULSE] = _("PulseAudio");
985 apis[RtAudio::LINUX_OSS] = _("OSS");
986 apis[RtAudio::RTAUDIO_DUMMY] = _("Dummy");
990 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
992 auto info = audio.getDeviceInfo(i);
993 if (info.name == *configured_so && info.outputChannels > 0) {
994 channels = info.outputChannels;
996 } catch (RtAudioError&) {
1002 _sound_output_details->SetLabel (
1003 wxString::Format(_("%d channels on %s"), channels, apis[audio.getCurrentApi()])
1006 _map->set (Config::instance()->audio_mapping(channels));
1008 vector<NamedChannel> input;
1009 for (int i = 0; i < MAX_DCP_AUDIO_CHANNELS; ++i) {
1010 input.push_back (NamedChannel(short_audio_channel_name(i), i));
1012 _map->set_input_channels (input);
1014 vector<NamedChannel> output;
1015 for (int i = 0; i < channels; ++i) {
1016 output.push_back (NamedChannel(dcp::raw_convert<string>(i), i));
1018 _map->set_output_channels (output);
1020 setup_sensitivity ();
1024 SoundPage::setup_sensitivity ()
1026 _sound_output->Enable (_sound->GetValue());
1029 /** @return Currently-selected preview sound output in the dialogue */
1031 SoundPage::get_sound_output ()
1033 int const sel = _sound_output->GetSelection ();
1034 if (sel == wxNOT_FOUND) {
1035 return optional<string> ();
1038 return wx_to_std (_sound_output->GetString (sel));
1042 LocationsPage::LocationsPage (wxSize panel_size, int border)
1043 : Page (panel_size, border)
1049 LocationsPage::GetName () const
1051 return _("Locations");
1054 #ifdef DCPOMATIC_OSX
1056 LocationsPage::GetLargeIcon () const
1058 return wxBitmap(icon_path("locations"), wxBITMAP_TYPE_PNG);
1063 LocationsPage::setup ()
1067 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1068 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
1070 add_label_to_sizer (table, _panel, _("Content directory"), true, wxGBPosition (r, 0));
1071 _content_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1072 table->Add (_content_directory, wxGBPosition (r, 1));
1075 add_label_to_sizer (table, _panel, _("Playlist directory"), true, wxGBPosition (r, 0));
1076 _playlist_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1077 table->Add (_playlist_directory, wxGBPosition (r, 1));
1080 add_label_to_sizer (table, _panel, _("KDM directory"), true, wxGBPosition (r, 0));
1081 _kdm_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1082 table->Add (_kdm_directory, wxGBPosition (r, 1));
1085 _content_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::content_directory_changed, this));
1086 _playlist_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::playlist_directory_changed, this));
1087 _kdm_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::kdm_directory_changed, this));
1091 LocationsPage::config_changed ()
1093 auto config = Config::instance ();
1095 if (config->player_content_directory()) {
1096 checked_set (_content_directory, *config->player_content_directory());
1098 if (config->player_playlist_directory()) {
1099 checked_set (_playlist_directory, *config->player_playlist_directory());
1101 if (config->player_kdm_directory()) {
1102 checked_set (_kdm_directory, *config->player_kdm_directory());
1107 LocationsPage::content_directory_changed ()
1109 Config::instance()->set_player_content_directory(wx_to_std(_content_directory->GetPath()));
1113 LocationsPage::playlist_directory_changed ()
1115 Config::instance()->set_player_playlist_directory(wx_to_std(_playlist_directory->GetPath()));
1119 LocationsPage::kdm_directory_changed ()
1121 Config::instance()->set_player_kdm_directory(wx_to_std(_kdm_directory->GetPath()));