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/filesystem.h>
31 #include <dcp/raw_convert.h>
36 using std::make_shared;
39 using std::shared_ptr;
43 using boost::optional;
44 #if BOOST_VERSION >= 106100
45 using namespace boost::placeholders;
56 Page::Page (wxSize panel_size, int border)
59 , _panel_size (panel_size)
60 , _window_exists (false)
62 _config_connection = Config::instance()->Changed.connect (bind (&Page::config_changed_wrapper, this));
67 Page::CreateWindow (wxWindow* parent)
69 return create_window (parent);
74 Page::create_window (wxWindow* parent)
76 _panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size);
77 auto s = new wxBoxSizer (wxVERTICAL);
81 _window_exists = true;
84 _panel->Bind (wxEVT_DESTROY, bind (&Page::window_destroyed, this));
90 Page::config_changed_wrapper ()
98 Page::window_destroyed ()
100 _window_exists = false;
104 GeneralPage::GeneralPage (wxSize panel_size, int border)
105 : Page (panel_size, border)
112 GeneralPage::GetName () const
119 GeneralPage::add_language_controls (wxGridBagSizer* table, int& r)
121 _set_language = new CheckBox (_panel, _("Set language"));
122 table->Add (_set_language, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
123 _language = new wxChoice (_panel, wxID_ANY);
124 vector<pair<string, string>> languages;
125 languages.push_back (make_pair("Čeština", "cs_CZ"));
126 languages.push_back (make_pair("汉语/漢語", "zh_CN"));
127 languages.push_back (make_pair("Dansk", "da_DK"));
128 languages.push_back (make_pair("Deutsch", "de_DE"));
129 languages.push_back (make_pair("English", "en_GB"));
130 languages.push_back (make_pair("Español", "es_ES"));
131 languages.push_back (make_pair("فارسی", "fa_IR"));
132 languages.push_back (make_pair("Français", "fr_FR"));
133 languages.push_back (make_pair("Italiano", "it_IT"));
134 languages.push_back (make_pair("ქართული", "ka_KA"));
135 languages.push_back (make_pair("Nederlands", "nl_NL"));
136 languages.push_back (make_pair("Русский", "ru_RU"));
137 languages.push_back (make_pair("Polski", "pl_PL"));
138 languages.push_back (make_pair("Português europeu", "pt_PT"));
139 languages.push_back (make_pair("Português do Brasil", "pt_BR"));
140 languages.push_back (make_pair("Svenska", "sv_SE"));
141 languages.push_back (make_pair("Slovenščina", "sl_SI"));
142 languages.push_back (make_pair("Slovenský jazyk", "sk_SK"));
143 // languages.push_back (make_pair("Türkçe", "tr_TR"));
144 languages.push_back (make_pair("українська мова", "uk_UA"));
145 languages.push_back (make_pair("Magyar nyelv", "hu_HU"));
146 checked_set (_language, languages);
147 table->Add (_language, wxGBPosition (r, 1));
150 auto restart = add_label_to_sizer (
151 table, _panel, _("(restart DCP-o-matic to see language changes)"), false, wxGBPosition (r, 0), wxGBSpan (1, 2)
153 wxFont font = restart->GetFont();
154 font.SetStyle (wxFONTSTYLE_ITALIC);
155 font.SetPointSize (font.GetPointSize() - 1);
156 restart->SetFont (font);
159 _set_language->bind(&GeneralPage::set_language_changed, this);
160 _language->Bind (wxEVT_CHOICE, bind (&GeneralPage::language_changed, this));
164 GeneralPage::add_update_controls (wxGridBagSizer* table, int& r)
166 _check_for_updates = new CheckBox (_panel, _("Check for updates on startup"));
167 table->Add (_check_for_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
170 _check_for_test_updates = new CheckBox (_panel, _("Check for testing updates on startup"));
171 table->Add (_check_for_test_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
174 _check_for_updates->bind(&GeneralPage::check_for_updates_changed, this);
175 _check_for_test_updates->bind(&GeneralPage::check_for_test_updates_changed, this);
179 GeneralPage::config_changed ()
181 auto config = Config::instance ();
183 checked_set (_set_language, static_cast<bool>(config->language()));
185 /* Backwards compatibility of config file */
187 map<string, string> compat_map;
188 compat_map["fr"] = "fr_FR";
189 compat_map["it"] = "it_IT";
190 compat_map["es"] = "es_ES";
191 compat_map["sv"] = "sv_SE";
192 compat_map["de"] = "de_DE";
193 compat_map["nl"] = "nl_NL";
194 compat_map["ru"] = "ru_RU";
195 compat_map["pl"] = "pl_PL";
196 compat_map["da"] = "da_DK";
197 compat_map["pt"] = "pt_PT";
198 compat_map["sk"] = "sk_SK";
199 compat_map["cs"] = "cs_CZ";
200 compat_map["uk"] = "uk_UA";
202 auto lang = config->language().get_value_or("en_GB");
203 if (compat_map.find(lang) != compat_map.end ()) {
204 lang = compat_map[lang];
207 checked_set (_language, lang);
209 checked_set (_check_for_updates, config->check_for_updates ());
210 checked_set (_check_for_test_updates, config->check_for_test_updates ());
212 setup_sensitivity ();
216 GeneralPage::setup_sensitivity ()
218 _language->Enable (_set_language->GetValue ());
219 _check_for_test_updates->Enable (_check_for_updates->GetValue ());
223 GeneralPage::set_language_changed ()
225 setup_sensitivity ();
226 if (_set_language->GetValue ()) {
229 Config::instance()->unset_language ();
234 GeneralPage::language_changed ()
236 int const sel = _language->GetSelection ();
238 Config::instance()->set_language (string_client_data (_language->GetClientObject (sel)));
240 Config::instance()->unset_language ();
245 GeneralPage::check_for_updates_changed ()
247 Config::instance()->set_check_for_updates (_check_for_updates->GetValue ());
251 GeneralPage::check_for_test_updates_changed ()
253 Config::instance()->set_check_for_test_updates (_check_for_test_updates->GetValue ());
256 CertificateChainEditor::CertificateChainEditor (
260 function<void (shared_ptr<dcp::CertificateChain>)> set,
261 function<shared_ptr<const dcp::CertificateChain> (void)> get,
262 function<bool (void)> nag_alter
264 : wxDialog (parent, wxID_ANY, title)
267 , _nag_alter (nag_alter)
269 _sizer = new wxBoxSizer (wxVERTICAL);
271 auto certificates_sizer = new wxBoxSizer (wxHORIZONTAL);
272 _sizer->Add (certificates_sizer, 0, wxALL, border);
274 _certificates = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (440, 150), wxLC_REPORT | wxLC_SINGLE_SEL);
279 ip.SetText (_("Type"));
281 _certificates->InsertColumn (0, ip);
287 ip.SetText (_("Thumbprint"));
290 wxFont font = ip.GetFont ();
291 font.SetFamily (wxFONTFAMILY_TELETYPE);
294 _certificates->InsertColumn (1, ip);
297 certificates_sizer->Add (_certificates, 1, wxEXPAND);
300 auto s = new wxBoxSizer (wxVERTICAL);
301 _add_certificate = new Button (this, _("Add..."));
302 s->Add (_add_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
303 _remove_certificate = new Button (this, _("Remove"));
304 s->Add (_remove_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
305 _export_certificate = new Button (this, _("Export certificate..."));
306 s->Add (_export_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
307 _export_chain = new Button (this, _("Export chain..."));
308 s->Add (_export_chain, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
309 certificates_sizer->Add (s, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
312 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
313 _sizer->Add (table, 1, wxALL | wxEXPAND, border);
316 add_label_to_sizer (table, this, _("Leaf private key"), true, wxGBPosition (r, 0));
317 _private_key = new StaticText (this, wxT(""));
318 wxFont font = _private_key->GetFont ();
319 font.SetFamily (wxFONTFAMILY_TELETYPE);
320 _private_key->SetFont (font);
321 table->Add (_private_key, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
322 _import_private_key = new Button (this, _("Import..."));
323 table->Add (_import_private_key, wxGBPosition (r, 2));
324 _export_private_key = new Button (this, _("Export..."));
325 table->Add (_export_private_key, wxGBPosition (r, 3));
328 _button_sizer = new wxBoxSizer (wxHORIZONTAL);
329 _remake_certificates = new Button (this, _("Re-make certificates and key..."));
330 _button_sizer->Add (_remake_certificates, 1, wxRIGHT, border);
331 table->Add (_button_sizer, wxGBPosition (r, 0), wxGBSpan (1, 4));
334 _private_key_bad = new StaticText (this, _("Leaf private key does not match leaf certificate!"));
335 font = *wxSMALL_FONT;
336 font.SetWeight (wxFONTWEIGHT_BOLD);
337 _private_key_bad->SetFont (font);
338 table->Add (_private_key_bad, wxGBPosition (r, 0), wxGBSpan (1, 3));
341 _add_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::add_certificate, this));
342 _remove_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::remove_certificate, this));
343 _export_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_certificate, this));
344 _certificates->Bind (wxEVT_LIST_ITEM_SELECTED, bind (&CertificateChainEditor::update_sensitivity, this));
345 _certificates->Bind (wxEVT_LIST_ITEM_DESELECTED, bind (&CertificateChainEditor::update_sensitivity, this));
346 _remake_certificates->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::remake_certificates, this));
347 _export_chain->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_chain, this));
348 _import_private_key->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::import_private_key, this));
349 _export_private_key->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_private_key, this));
351 auto buttons = CreateSeparatedButtonSizer (wxCLOSE);
353 _sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
356 SetSizerAndFit (_sizer);
358 update_certificate_list ();
359 update_private_key ();
360 update_sensitivity ();
364 CertificateChainEditor::add_button (wxWindow* button)
366 _button_sizer->Add (button, 0, wxLEFT | wxRIGHT, DCPOMATIC_SIZER_X_GAP);
371 CertificateChainEditor::add_certificate ()
373 auto d = make_wx<wxFileDialog>(this, _("Select Certificate File"));
375 if (d->ShowModal() == wxID_OK) {
380 extra = c.read_string (dcp::file_to_string (wx_to_std (d->GetPath ())));
381 } catch (boost::filesystem::filesystem_error& e) {
382 error_dialog (this, _("Could not import certificate (%s)"), d->GetPath());
386 if (!extra.empty ()) {
389 _("This file contains other certificates (or other data) after its first certificate. "
390 "Only the first certificate will be used.")
393 auto chain = make_shared<dcp::CertificateChain>(*_get().get());
395 if (!chain->chain_valid ()) {
398 _("Adding this certificate would make the chain inconsistent, so it will not be added. "
399 "Add certificates in order from root to intermediate to leaf.")
404 update_certificate_list ();
406 } catch (dcp::MiscError& e) {
407 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 = make_wx<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) {
469 boost::filesystem::path path(wx_to_std(d->GetPath()));
470 if (path.extension() != ".pem") {
473 dcp::File f(path, "w");
475 throw OpenFileError(path, errno, OpenFileError::WRITE);
478 string const s = j->certificate(true);
479 f.checked_write(s.c_str(), s.length());
483 CertificateChainEditor::export_chain ()
485 auto d = make_wx<wxFileDialog>(
486 this, _("Select Chain File"), wxEmptyString, wxT("certificate_chain.pem"), wxT("PEM files (*.pem)|*.pem"),
487 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
490 if (d->ShowModal() != wxID_OK) {
494 boost::filesystem::path path(wx_to_std(d->GetPath()));
495 if (path.extension() != ".pem") {
498 dcp::File f(path, "w");
500 throw OpenFileError(path, errno, OpenFileError::WRITE);
503 auto const s = _get()->chain();
504 f.checked_write(s.c_str(), s.length());
508 CertificateChainEditor::update_certificate_list ()
510 _certificates->DeleteAllItems ();
512 auto certs = _get()->root_to_leaf();
513 for (auto const& i: certs) {
516 _certificates->InsertItem (item);
517 _certificates->SetItem (n, 1, std_to_wx (i.thumbprint ()));
520 _certificates->SetItem (n, 0, _("Root"));
521 } else if (n == (certs.size() - 1)) {
522 _certificates->SetItem (n, 0, _("Leaf"));
524 _certificates->SetItem (n, 0, _("Intermediate"));
530 static wxColour normal = _private_key_bad->GetForegroundColour ();
532 if (_get()->private_key_valid()) {
533 _private_key_bad->Hide ();
534 _private_key_bad->SetForegroundColour (normal);
536 _private_key_bad->Show ();
537 _private_key_bad->SetForegroundColour (wxColour (255, 0, 0));
542 CertificateChainEditor::remake_certificates ()
545 /* Cancel was clicked */
549 auto d = make_wx<MakeChainDialog>(this, _get());
551 if (d->ShowModal () == wxID_OK) {
553 update_certificate_list ();
554 update_private_key ();
559 CertificateChainEditor::update_sensitivity ()
561 /* We can only remove the leaf certificate */
562 _remove_certificate->Enable (_certificates->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) == (_certificates->GetItemCount() - 1));
563 _export_certificate->Enable (_certificates->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
567 CertificateChainEditor::update_private_key ()
569 checked_set (_private_key, dcp::private_key_fingerprint (_get()->key().get()));
574 CertificateChainEditor::import_private_key ()
576 auto d = make_wx<wxFileDialog>(this, _("Select Key File"));
578 if (d->ShowModal() == wxID_OK) {
580 boost::filesystem::path p (wx_to_std (d->GetPath ()));
581 if (dcp::filesystem::file_size(p) > 8192) {
584 wxString::Format (_("Could not read key file; file is too long (%s)"), std_to_wx (p.string ()))
589 auto chain = make_shared<dcp::CertificateChain>(*_get().get());
590 chain->set_key (dcp::file_to_string (p));
592 update_private_key ();
593 } catch (std::exception& e) {
594 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 = make_wx<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());
630 KeysPage::GetName () const
638 wxFont subheading_font (*wxNORMAL_FONT);
639 subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
641 auto sizer = _panel->GetSizer();
644 auto m = new StaticText (_panel, _("Decrypting KDMs"));
645 m->SetFont (subheading_font);
646 sizer->Add (m, 0, wxALL | wxEXPAND, _border);
649 auto kdm_buttons = new wxBoxSizer (wxVERTICAL);
651 auto export_decryption_certificate = new Button (_panel, _("Export KDM decryption leaf certificate..."));
652 kdm_buttons->Add (export_decryption_certificate, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
653 auto export_settings = new Button (_panel, _("Export all KDM decryption settings..."));
654 kdm_buttons->Add (export_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
655 auto import_settings = new Button (_panel, _("Import all KDM decryption settings..."));
656 kdm_buttons->Add (import_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
657 auto decryption_advanced = new Button (_panel, _("Advanced..."));
658 kdm_buttons->Add (decryption_advanced, 0);
660 sizer->Add (kdm_buttons, 0, wxLEFT, _border);
662 export_decryption_certificate->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_certificate, this));
663 export_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_chain_and_key, this));
664 import_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::import_decryption_chain_and_key, this));
665 decryption_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::decryption_advanced, this));
668 auto m = new StaticText (_panel, _("Signing DCPs and KDMs"));
669 m->SetFont (subheading_font);
670 sizer->Add (m, 0, wxALL | wxEXPAND, _border);
673 auto signing_buttons = new wxBoxSizer (wxVERTICAL);
675 auto signing_advanced = new Button (_panel, _("Advanced..."));
676 signing_buttons->Add (signing_advanced, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
677 auto remake_signing = new Button (_panel, _("Re-make certificates and key..."));
678 signing_buttons->Add (remake_signing, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
680 sizer->Add (signing_buttons, 0, wxLEFT, _border);
682 signing_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::signing_advanced, this));
683 remake_signing->Bind (wxEVT_BUTTON, bind(&KeysPage::remake_signing, this));
688 KeysPage::remake_signing ()
690 auto d = new MakeChainDialog (_panel, Config::instance()->signer_chain());
692 if (d->ShowModal () == wxID_OK) {
693 Config::instance()->set_signer_chain(d->get());
699 KeysPage::decryption_advanced ()
701 auto c = new CertificateChainEditor (
702 _panel, _("Decrypting KDMs"), _border,
703 bind(&Config::set_decryption_chain, Config::instance(), _1),
704 bind(&Config::decryption_chain, Config::instance()),
705 bind(&KeysPage::nag_alter_decryption_chain, this)
712 KeysPage::signing_advanced ()
714 auto c = new CertificateChainEditor (
715 _panel, _("Signing DCPs and KDMs"), _border,
716 bind(&Config::set_signer_chain, Config::instance(), _1),
717 bind(&Config::signer_chain, Config::instance()),
725 KeysPage::export_decryption_chain_and_key ()
727 auto d = make_wx<wxFileDialog>(
728 _panel, _("Select Export File"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom"),
729 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
732 if (d->ShowModal() != wxID_OK) {
736 boost::filesystem::path path(wx_to_std(d->GetPath()));
737 dcp::File f(path, "w");
739 throw OpenFileError(path, errno, OpenFileError::WRITE);
742 auto const chain = Config::instance()->decryption_chain()->chain();
743 f.checked_write(chain.c_str(), chain.length());
744 auto const key = Config::instance()->decryption_chain()->key();
745 DCPOMATIC_ASSERT(key);
746 f.checked_write(key->c_str(), key->length());
750 KeysPage::import_decryption_chain_and_key ()
752 if (NagDialog::maybe_nag (
754 Config::NAG_IMPORT_DECRYPTION_CHAIN,
755 _("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!"),
761 auto d = make_wx<wxFileDialog>(
762 _panel, _("Select File To Import"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom")
765 if (d->ShowModal() != wxID_OK) {
769 auto new_chain = make_shared<dcp::CertificateChain>();
771 dcp::File f(wx_to_std(d->GetPath()), "r");
773 throw OpenFileError(f.path(), errno, OpenFileError::WRITE);
779 if (f.gets(buffer, 128) == 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);
792 if (new_chain->chain_valid() && new_chain->private_key_valid()) {
793 Config::instance()->set_decryption_chain(new_chain);
795 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 = make_wx<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) {
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());
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 table->Add (_map, wxGBPosition(r, 1), wxDefaultSpan, wxEXPAND);
874 _reset_to_default = new Button (_panel, _("Reset to default"));
875 table->Add (_reset_to_default, wxGBPosition(r, 1));
878 wxFont font = _sound_output_details->GetFont();
879 font.SetStyle (wxFONTSTYLE_ITALIC);
880 font.SetPointSize (font.GetPointSize() - 1);
881 _sound_output_details->SetFont (font);
883 RtAudio audio (DCPOMATIC_RTAUDIO_API);
884 #if (RTAUDIO_VERSION_MAJOR >= 6)
885 for (auto device_id: audio.getDeviceIds()) {
886 auto dev = audio.getDeviceInfo(device_id);
887 if (dev.outputChannels > 0) {
888 _sound_output->Append(std_to_wx(dev.name));
892 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
894 auto dev = audio.getDeviceInfo (i);
895 if (dev.probed && dev.outputChannels > 0) {
896 _sound_output->Append (std_to_wx (dev.name));
898 } catch (RtAudioError&) {
899 /* Something went wrong so let's just ignore that device */
904 _sound->bind(&SoundPage::sound_changed, this);
905 _sound_output->Bind (wxEVT_CHOICE, bind(&SoundPage::sound_output_changed, this));
906 _map->Changed.connect (bind(&SoundPage::map_changed, this, _1));
907 _reset_to_default->Bind (wxEVT_BUTTON, bind(&SoundPage::reset_to_default, this));
911 SoundPage::reset_to_default ()
913 Config::instance()->set_audio_mapping_to_default ();
917 SoundPage::map_changed (AudioMapping m)
919 Config::instance()->set_audio_mapping (m);
923 SoundPage::sound_changed ()
925 Config::instance()->set_sound (_sound->GetValue ());
929 SoundPage::sound_output_changed ()
931 RtAudio audio (DCPOMATIC_RTAUDIO_API);
932 auto const so = get_sound_output();
933 string default_device;
934 #if (RTAUDIO_VERSION_MAJOR >= 6)
935 default_device = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
938 default_device = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
939 } catch (RtAudioError&) {
943 if (!so || *so == default_device) {
944 Config::instance()->unset_sound_output ();
946 Config::instance()->set_sound_output (*so);
951 SoundPage::config_changed ()
953 auto config = Config::instance ();
955 checked_set (_sound, config->sound ());
957 auto const current_so = get_sound_output ();
958 optional<string> configured_so;
960 if (config->sound_output()) {
961 configured_so = config->sound_output().get();
963 /* No configured output means we should use the default */
964 RtAudio audio (DCPOMATIC_RTAUDIO_API);
965 #if (RTAUDIO_VERSION_MAJOR >= 6)
966 configured_so = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
969 configured_so = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
970 } catch (RtAudioError&) {
971 /* Probably no audio devices at all */
976 if (configured_so && current_so != configured_so) {
977 /* Update _sound_output with the configured value */
979 while (i < _sound_output->GetCount()) {
980 if (_sound_output->GetString(i) == std_to_wx(*configured_so)) {
981 _sound_output->SetSelection (i);
988 RtAudio audio (DCPOMATIC_RTAUDIO_API);
990 map<int, wxString> apis;
991 apis[RtAudio::MACOSX_CORE] = _("CoreAudio");
992 apis[RtAudio::WINDOWS_ASIO] = _("ASIO");
993 apis[RtAudio::WINDOWS_DS] = _("Direct Sound");
994 apis[RtAudio::WINDOWS_WASAPI] = _("WASAPI");
995 apis[RtAudio::UNIX_JACK] = _("JACK");
996 apis[RtAudio::LINUX_ALSA] = _("ALSA");
997 apis[RtAudio::LINUX_PULSE] = _("PulseAudio");
998 apis[RtAudio::LINUX_OSS] = _("OSS");
999 apis[RtAudio::RTAUDIO_DUMMY] = _("Dummy");
1002 if (configured_so) {
1003 #if (RTAUDIO_VERSION_MAJOR >= 6)
1004 for (auto device_id: audio.getDeviceIds()) {
1005 auto info = audio.getDeviceInfo(device_id);
1006 if (info.name == *configured_so && info.outputChannels > 0) {
1007 channels = info.outputChannels;
1011 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
1013 auto info = audio.getDeviceInfo(i);
1014 if (info.name == *configured_so && info.outputChannels > 0) {
1015 channels = info.outputChannels;
1017 } catch (RtAudioError&) {
1024 _sound_output_details->SetLabel (
1025 wxString::Format(_("%d channels on %s"), channels, apis[audio.getCurrentApi()])
1028 _map->set (Config::instance()->audio_mapping(channels));
1030 vector<NamedChannel> input;
1031 for (int i = 0; i < MAX_DCP_AUDIO_CHANNELS; ++i) {
1032 input.push_back (NamedChannel(short_audio_channel_name(i), i));
1034 _map->set_input_channels (input);
1036 vector<NamedChannel> output;
1037 for (int i = 0; i < channels; ++i) {
1038 output.push_back (NamedChannel(dcp::raw_convert<string>(i), i));
1040 _map->set_output_channels (output);
1042 setup_sensitivity ();
1046 SoundPage::setup_sensitivity ()
1048 _sound_output->Enable (_sound->GetValue());
1051 /** @return Currently-selected preview sound output in the dialogue */
1053 SoundPage::get_sound_output ()
1055 int const sel = _sound_output->GetSelection ();
1056 if (sel == wxNOT_FOUND) {
1057 return optional<string> ();
1060 return wx_to_std (_sound_output->GetString (sel));
1064 LocationsPage::LocationsPage (wxSize panel_size, int border)
1065 : Page (panel_size, border)
1071 LocationsPage::GetName () const
1073 return _("Locations");
1076 #ifdef DCPOMATIC_OSX
1078 LocationsPage::GetLargeIcon () const
1080 return wxBitmap(icon_path("locations"), wxBITMAP_TYPE_PNG);
1085 LocationsPage::setup ()
1089 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1090 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
1092 add_label_to_sizer (table, _panel, _("Content directory"), true, wxGBPosition (r, 0));
1093 _content_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1094 table->Add (_content_directory, wxGBPosition (r, 1));
1097 add_label_to_sizer (table, _panel, _("Playlist directory"), true, wxGBPosition (r, 0));
1098 _playlist_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1099 table->Add (_playlist_directory, wxGBPosition (r, 1));
1102 add_label_to_sizer (table, _panel, _("KDM directory"), true, wxGBPosition (r, 0));
1103 _kdm_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1104 table->Add (_kdm_directory, wxGBPosition (r, 1));
1107 _content_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::content_directory_changed, this));
1108 _playlist_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::playlist_directory_changed, this));
1109 _kdm_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::kdm_directory_changed, this));
1113 LocationsPage::config_changed ()
1115 auto config = Config::instance ();
1117 if (config->player_content_directory()) {
1118 checked_set (_content_directory, *config->player_content_directory());
1120 if (config->player_playlist_directory()) {
1121 checked_set (_playlist_directory, *config->player_playlist_directory());
1123 if (config->player_kdm_directory()) {
1124 checked_set (_kdm_directory, *config->player_kdm_directory());
1129 LocationsPage::content_directory_changed ()
1131 Config::instance()->set_player_content_directory(wx_to_std(_content_directory->GetPath()));
1135 LocationsPage::playlist_directory_changed ()
1137 Config::instance()->set_player_playlist_directory(wx_to_std(_playlist_directory->GetPath()));
1141 LocationsPage::kdm_directory_changed ()
1143 Config::instance()->set_player_kdm_directory(wx_to_std(_kdm_directory->GetPath()));