2 Copyright (C) 2012-2021 Carl Hetherington <cth@carlh.net>
4 This file is part of DCP-o-matic.
6 DCP-o-matic is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 DCP-o-matic is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
21 #include "config_dialog.h"
22 #include "static_text.h"
23 #include "check_box.h"
24 #include "nag_dialog.h"
25 #include "dcpomatic_button.h"
26 #include "audio_mapping_view.h"
27 #include <dcp/raw_convert.h>
35 using std::make_shared;
37 using boost::optional;
38 using std::shared_ptr;
39 using boost::function;
40 #if BOOST_VERSION >= 106100
41 using namespace boost::placeholders;
51 Page::Page (wxSize panel_size, int border)
54 , _panel_size (panel_size)
55 , _window_exists (false)
57 _config_connection = Config::instance()->Changed.connect (bind (&Page::config_changed_wrapper, this));
62 Page::CreateWindow (wxWindow* parent)
64 return create_window (parent);
69 Page::create_window (wxWindow* parent)
71 _panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size);
72 auto s = new wxBoxSizer (wxVERTICAL);
76 _window_exists = true;
79 _panel->Bind (wxEVT_DESTROY, bind (&Page::window_destroyed, this));
85 Page::config_changed_wrapper ()
93 Page::window_destroyed ()
95 _window_exists = false;
99 GeneralPage::GeneralPage (wxSize panel_size, int border)
100 : Page (panel_size, border)
107 GeneralPage::GetName () const
114 GeneralPage::add_language_controls (wxGridBagSizer* table, int& r)
116 _set_language = new CheckBox (_panel, _("Set language"));
117 table->Add (_set_language, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
118 _language = new wxChoice (_panel, wxID_ANY);
119 vector<pair<string, string>> languages;
120 languages.push_back (make_pair("Čeština", "cs_CZ"));
121 languages.push_back (make_pair("汉语/漢語", "zh_CN"));
122 languages.push_back (make_pair("Dansk", "da_DK"));
123 languages.push_back (make_pair("Deutsch", "de_DE"));
124 languages.push_back (make_pair("English", "en_GB"));
125 languages.push_back (make_pair("Español", "es_ES"));
126 languages.push_back (make_pair("Français", "fr_FR"));
127 languages.push_back (make_pair("Italiano", "it_IT"));
128 languages.push_back (make_pair("Nederlands", "nl_NL"));
129 languages.push_back (make_pair("Русский", "ru_RU"));
130 languages.push_back (make_pair("Polski", "pl_PL"));
131 languages.push_back (make_pair("Português europeu", "pt_PT"));
132 languages.push_back (make_pair("Português do Brasil", "pt_BR"));
133 languages.push_back (make_pair("Svenska", "sv_SE"));
134 languages.push_back (make_pair("Slovenský jazyk", "sk_SK"));
135 languages.push_back (make_pair("Türkçe", "tr_TR"));
136 languages.push_back (make_pair("українська мова", "uk_UA"));
137 checked_set (_language, languages);
138 table->Add (_language, wxGBPosition (r, 1));
141 auto restart = add_label_to_sizer (
142 table, _panel, _("(restart DCP-o-matic to see language changes)"), false, wxGBPosition (r, 0), wxGBSpan (1, 2)
144 wxFont font = restart->GetFont();
145 font.SetStyle (wxFONTSTYLE_ITALIC);
146 font.SetPointSize (font.GetPointSize() - 1);
147 restart->SetFont (font);
150 _set_language->Bind (wxEVT_CHECKBOX, bind (&GeneralPage::set_language_changed, this));
151 _language->Bind (wxEVT_CHOICE, bind (&GeneralPage::language_changed, this));
155 GeneralPage::add_update_controls (wxGridBagSizer* table, int& r)
157 _check_for_updates = new CheckBox (_panel, _("Check for updates on startup"));
158 table->Add (_check_for_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
161 _check_for_test_updates = new CheckBox (_panel, _("Check for testing updates on startup"));
162 table->Add (_check_for_test_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
165 _check_for_updates->Bind (wxEVT_CHECKBOX, bind (&GeneralPage::check_for_updates_changed, this));
166 _check_for_test_updates->Bind (wxEVT_CHECKBOX, bind (&GeneralPage::check_for_test_updates_changed, this));
170 GeneralPage::config_changed ()
172 auto config = Config::instance ();
174 checked_set (_set_language, static_cast<bool>(config->language()));
176 /* Backwards compatibility of config file */
178 map<string, string> compat_map;
179 compat_map["fr"] = "fr_FR";
180 compat_map["it"] = "it_IT";
181 compat_map["es"] = "es_ES";
182 compat_map["sv"] = "sv_SE";
183 compat_map["de"] = "de_DE";
184 compat_map["nl"] = "nl_NL";
185 compat_map["ru"] = "ru_RU";
186 compat_map["pl"] = "pl_PL";
187 compat_map["da"] = "da_DK";
188 compat_map["pt"] = "pt_PT";
189 compat_map["sk"] = "sk_SK";
190 compat_map["cs"] = "cs_CZ";
191 compat_map["uk"] = "uk_UA";
193 auto lang = config->language().get_value_or("en_GB");
194 if (compat_map.find(lang) != compat_map.end ()) {
195 lang = compat_map[lang];
198 checked_set (_language, lang);
200 checked_set (_check_for_updates, config->check_for_updates ());
201 checked_set (_check_for_test_updates, config->check_for_test_updates ());
203 setup_sensitivity ();
207 GeneralPage::setup_sensitivity ()
209 _language->Enable (_set_language->GetValue ());
210 _check_for_test_updates->Enable (_check_for_updates->GetValue ());
214 GeneralPage::set_language_changed ()
216 setup_sensitivity ();
217 if (_set_language->GetValue ()) {
220 Config::instance()->unset_language ();
225 GeneralPage::language_changed ()
227 int const sel = _language->GetSelection ();
229 Config::instance()->set_language (string_client_data (_language->GetClientObject (sel)));
231 Config::instance()->unset_language ();
236 GeneralPage::check_for_updates_changed ()
238 Config::instance()->set_check_for_updates (_check_for_updates->GetValue ());
242 GeneralPage::check_for_test_updates_changed ()
244 Config::instance()->set_check_for_test_updates (_check_for_test_updates->GetValue ());
247 CertificateChainEditor::CertificateChainEditor (
251 function<void (shared_ptr<dcp::CertificateChain>)> set,
252 function<shared_ptr<const dcp::CertificateChain> (void)> get,
253 function<bool (void)> nag_alter
255 : wxDialog (parent, wxID_ANY, title)
258 , _nag_alter (nag_alter)
260 _sizer = new wxBoxSizer (wxVERTICAL);
262 auto certificates_sizer = new wxBoxSizer (wxHORIZONTAL);
263 _sizer->Add (certificates_sizer, 0, wxALL, border);
265 _certificates = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (440, 150), wxLC_REPORT | wxLC_SINGLE_SEL);
270 ip.SetText (_("Type"));
272 _certificates->InsertColumn (0, ip);
278 ip.SetText (_("Thumbprint"));
281 wxFont font = ip.GetFont ();
282 font.SetFamily (wxFONTFAMILY_TELETYPE);
285 _certificates->InsertColumn (1, ip);
288 certificates_sizer->Add (_certificates, 1, wxEXPAND);
291 auto s = new wxBoxSizer (wxVERTICAL);
292 _add_certificate = new Button (this, _("Add..."));
293 s->Add (_add_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
294 _remove_certificate = new Button (this, _("Remove"));
295 s->Add (_remove_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
296 _export_certificate = new Button (this, _("Export certificate..."));
297 s->Add (_export_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
298 _export_chain = new Button (this, _("Export chain..."));
299 s->Add (_export_chain, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
300 certificates_sizer->Add (s, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
303 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
304 _sizer->Add (table, 1, wxALL | wxEXPAND, border);
307 add_label_to_sizer (table, this, _("Leaf private key"), true, wxGBPosition (r, 0));
308 _private_key = new StaticText (this, wxT(""));
309 wxFont font = _private_key->GetFont ();
310 font.SetFamily (wxFONTFAMILY_TELETYPE);
311 _private_key->SetFont (font);
312 table->Add (_private_key, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
313 _import_private_key = new Button (this, _("Import..."));
314 table->Add (_import_private_key, wxGBPosition (r, 2));
315 _export_private_key = new Button (this, _("Export..."));
316 table->Add (_export_private_key, wxGBPosition (r, 3));
319 _button_sizer = new wxBoxSizer (wxHORIZONTAL);
320 _remake_certificates = new Button (this, _("Re-make certificates and key..."));
321 _button_sizer->Add (_remake_certificates, 1, wxRIGHT, border);
322 table->Add (_button_sizer, wxGBPosition (r, 0), wxGBSpan (1, 4));
325 _private_key_bad = new StaticText (this, _("Leaf private key does not match leaf certificate!"));
326 font = *wxSMALL_FONT;
327 font.SetWeight (wxFONTWEIGHT_BOLD);
328 _private_key_bad->SetFont (font);
329 table->Add (_private_key_bad, wxGBPosition (r, 0), wxGBSpan (1, 3));
332 _add_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::add_certificate, this));
333 _remove_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::remove_certificate, this));
334 _export_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_certificate, this));
335 _certificates->Bind (wxEVT_LIST_ITEM_SELECTED, bind (&CertificateChainEditor::update_sensitivity, this));
336 _certificates->Bind (wxEVT_LIST_ITEM_DESELECTED, bind (&CertificateChainEditor::update_sensitivity, this));
337 _remake_certificates->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::remake_certificates, this));
338 _export_chain->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_chain, this));
339 _import_private_key->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::import_private_key, this));
340 _export_private_key->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_private_key, this));
342 auto buttons = CreateSeparatedButtonSizer (wxCLOSE);
344 _sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
347 SetSizerAndFit (_sizer);
349 update_certificate_list ();
350 update_private_key ();
351 update_sensitivity ();
355 CertificateChainEditor::add_button (wxWindow* button)
357 _button_sizer->Add (button, 0, wxLEFT | wxRIGHT, DCPOMATIC_SIZER_X_GAP);
362 CertificateChainEditor::add_certificate ()
364 auto d = new wxFileDialog (this, _("Select Certificate File"));
366 if (d->ShowModal() == wxID_OK) {
371 extra = c.read_string (dcp::file_to_string (wx_to_std (d->GetPath ())));
372 } catch (boost::filesystem::filesystem_error& e) {
373 error_dialog (this, _("Could not import certificate (%s)"), d->GetPath());
378 if (!extra.empty ()) {
381 _("This file contains other certificates (or other data) after its first certificate. "
382 "Only the first certificate will be used.")
385 auto chain = make_shared<dcp::CertificateChain>(*_get().get());
387 if (!chain->chain_valid ()) {
390 _("Adding this certificate would make the chain inconsistent, so it will not be added. "
391 "Add certificates in order from root to intermediate to leaf.")
396 update_certificate_list ();
398 } catch (dcp::MiscError& e) {
399 error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
405 update_sensitivity ();
409 CertificateChainEditor::remove_certificate ()
412 /* Cancel was clicked */
416 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
421 _certificates->DeleteItem (i);
422 auto chain = make_shared<dcp::CertificateChain>(*_get().get());
426 update_sensitivity ();
427 update_certificate_list ();
431 CertificateChainEditor::export_certificate ()
433 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
438 auto all = _get()->root_to_leaf();
440 wxString default_name;
442 default_name = "root.pem";
443 } else if (i == static_cast<int>(all.size() - 1)) {
444 default_name = "leaf.pem";
446 default_name = "intermediate.pem";
449 auto d = new wxFileDialog(
450 this, _("Select Certificate File"), wxEmptyString, default_name, wxT ("PEM files (*.pem)|*.pem"),
451 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
454 auto j = all.begin ();
455 for (int k = 0; k < i; ++k) {
459 if (d->ShowModal () == wxID_OK) {
460 boost::filesystem::path path (wx_to_std(d->GetPath()));
461 if (path.extension() != ".pem") {
464 auto f = fopen_boost (path, "w");
466 throw OpenFileError (path, errno, OpenFileError::WRITE);
469 string const s = j->certificate (true);
470 checked_fwrite (s.c_str(), s.length(), f, path);
477 CertificateChainEditor::export_chain ()
479 auto d = new wxFileDialog (
480 this, _("Select Chain File"), wxEmptyString, _("certificate_chain.pem"), wxT("PEM files (*.pem)|*.pem"),
481 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
484 if (d->ShowModal () == wxID_OK) {
485 boost::filesystem::path path (wx_to_std(d->GetPath()));
486 if (path.extension() != ".pem") {
489 auto f = fopen_boost (path, "w");
491 throw OpenFileError (path, errno, OpenFileError::WRITE);
494 auto const s = _get()->chain();
495 checked_fwrite (s.c_str(), s.length(), f, path);
503 CertificateChainEditor::update_certificate_list ()
505 _certificates->DeleteAllItems ();
507 auto certs = _get()->root_to_leaf();
508 for (auto const& i: certs) {
511 _certificates->InsertItem (item);
512 _certificates->SetItem (n, 1, std_to_wx (i.thumbprint ()));
515 _certificates->SetItem (n, 0, _("Root"));
516 } else if (n == (certs.size() - 1)) {
517 _certificates->SetItem (n, 0, _("Leaf"));
519 _certificates->SetItem (n, 0, _("Intermediate"));
525 static wxColour normal = _private_key_bad->GetForegroundColour ();
527 if (_get()->private_key_valid()) {
528 _private_key_bad->Hide ();
529 _private_key_bad->SetForegroundColour (normal);
531 _private_key_bad->Show ();
532 _private_key_bad->SetForegroundColour (wxColour (255, 0, 0));
537 CertificateChainEditor::remake_certificates ()
541 string subject_organization_name;
542 string subject_organizational_unit_name;
543 string root_common_name;
544 string intermediate_common_name;
545 string leaf_common_name;
547 auto all = chain->root_to_leaf ();
549 if (all.size() >= 1) {
551 subject_organization_name = chain->root().subject_organization_name ();
552 subject_organizational_unit_name = chain->root().subject_organizational_unit_name ();
553 root_common_name = chain->root().subject_common_name ();
556 if (all.size() >= 2) {
558 leaf_common_name = chain->leaf().subject_common_name ();
561 if (all.size() >= 3) {
562 /* Have an intermediate */
563 dcp::CertificateChain::List::iterator i = all.begin ();
565 intermediate_common_name = i->subject_common_name ();
569 /* Cancel was clicked */
573 auto d = new MakeChainDialog (
575 subject_organization_name,
576 subject_organizational_unit_name,
578 intermediate_common_name,
582 if (d->ShowModal () == wxID_OK) {
584 make_shared<dcp::CertificateChain> (
587 d->organisational_unit (),
588 d->root_common_name (),
589 d->intermediate_common_name (),
590 d->leaf_common_name ()
594 update_certificate_list ();
595 update_private_key ();
602 CertificateChainEditor::update_sensitivity ()
604 /* We can only remove the leaf certificate */
605 _remove_certificate->Enable (_certificates->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) == (_certificates->GetItemCount() - 1));
606 _export_certificate->Enable (_certificates->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
610 CertificateChainEditor::update_private_key ()
612 checked_set (_private_key, dcp::private_key_fingerprint (_get()->key().get()));
617 CertificateChainEditor::import_private_key ()
619 auto d = new wxFileDialog (this, _("Select Key File"));
621 if (d->ShowModal() == wxID_OK) {
623 boost::filesystem::path p (wx_to_std (d->GetPath ()));
624 if (boost::filesystem::file_size (p) > 8192) {
627 wxString::Format (_("Could not read key file; file is too long (%s)"), std_to_wx (p.string ()))
632 auto chain = make_shared<dcp::CertificateChain>(*_get().get());
633 chain->set_key (dcp::file_to_string (p));
635 update_private_key ();
636 } catch (dcp::MiscError& e) {
637 error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
643 update_sensitivity ();
647 CertificateChainEditor::export_private_key ()
649 auto key = _get()->key();
654 auto d = new wxFileDialog (
655 this, _("Select Key File"), wxEmptyString, _("private_key.pem"), wxT ("PEM files (*.pem)|*.pem"),
656 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
659 if (d->ShowModal () == wxID_OK) {
660 boost::filesystem::path path (wx_to_std(d->GetPath()));
661 if (path.extension() != ".pem") {
664 auto f = fopen_boost (path, "w");
666 throw OpenFileError (path, errno, OpenFileError::WRITE);
669 auto const s = _get()->key().get ();
670 checked_fwrite (s.c_str(), s.length(), f, path);
677 KeysPage::GetName () const
685 wxFont subheading_font (*wxNORMAL_FONT);
686 subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
688 auto sizer = _panel->GetSizer();
691 auto m = new StaticText (_panel, _("Decrypting KDMs"));
692 m->SetFont (subheading_font);
693 sizer->Add (m, 0, wxALL, _border);
696 auto buttons = new wxBoxSizer (wxVERTICAL);
698 auto export_decryption_certificate = new Button (_panel, _("Export KDM decryption leaf certificate..."));
699 buttons->Add (export_decryption_certificate, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
700 auto export_settings = new Button (_panel, _("Export all KDM decryption settings..."));
701 buttons->Add (export_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
702 auto import_settings = new Button (_panel, _("Import all KDM decryption settings..."));
703 buttons->Add (import_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
704 auto decryption_advanced = new Button (_panel, _("Advanced..."));
705 buttons->Add (decryption_advanced, 0);
707 sizer->Add (buttons, 0, wxLEFT, _border);
709 export_decryption_certificate->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_certificate, this));
710 export_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_chain_and_key, this));
711 import_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::import_decryption_chain_and_key, this));
712 decryption_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::decryption_advanced, this));
715 auto m = new StaticText (_panel, _("Signing DCPs and KDMs"));
716 m->SetFont (subheading_font);
717 sizer->Add (m, 0, wxALL, _border);
720 auto signing_advanced = new Button (_panel, _("Advanced..."));
721 sizer->Add (signing_advanced, 0, wxLEFT | wxBOTTOM, _border);
722 signing_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::signing_advanced, this));
726 KeysPage::decryption_advanced ()
728 auto c = new CertificateChainEditor (
729 _panel, _("Decrypting KDMs"), _border,
730 bind(&Config::set_decryption_chain, Config::instance(), _1),
731 bind(&Config::decryption_chain, Config::instance()),
732 bind(&KeysPage::nag_alter_decryption_chain, this)
739 KeysPage::signing_advanced ()
741 auto c = new CertificateChainEditor (
742 _panel, _("Signing DCPs and KDMs"), _border,
743 bind(&Config::set_signer_chain, Config::instance(), _1),
744 bind(&Config::signer_chain, Config::instance()),
752 KeysPage::export_decryption_chain_and_key ()
754 auto d = new wxFileDialog (
755 _panel, _("Select Export File"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom"),
756 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
759 if (d->ShowModal () == wxID_OK) {
760 boost::filesystem::path path (wx_to_std(d->GetPath()));
761 auto f = fopen_boost (path, "w");
763 throw OpenFileError (path, errno, OpenFileError::WRITE);
766 auto const chain = Config::instance()->decryption_chain()->chain();
767 checked_fwrite (chain.c_str(), chain.length(), f, path);
768 optional<string> const key = Config::instance()->decryption_chain()->key();
769 DCPOMATIC_ASSERT (key);
770 checked_fwrite (key->c_str(), key->length(), f, path);
778 KeysPage::import_decryption_chain_and_key ()
780 if (NagDialog::maybe_nag (
782 Config::NAG_IMPORT_DECRYPTION_CHAIN,
783 _("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!"),
789 auto d = new wxFileDialog (
790 _panel, _("Select File To Import"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom")
793 if (d->ShowModal () == wxID_OK) {
794 auto new_chain = make_shared<dcp::CertificateChain>();
796 FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "r");
798 throw OpenFileError (wx_to_std (d->GetPath ()), errno, OpenFileError::WRITE);
804 if (fgets (buffer, 128, f) == 0) {
808 if (strncmp (buffer, "-----END CERTIFICATE-----", 25) == 0) {
809 new_chain->add (dcp::Certificate (current));
811 } else if (strncmp (buffer, "-----END RSA PRIVATE KEY-----", 29) == 0) {
812 new_chain->set_key (current);
818 if (new_chain->chain_valid() && new_chain->private_key_valid()) {
819 Config::instance()->set_decryption_chain (new_chain);
821 error_dialog (_panel, _("Invalid DCP-o-matic export file"));
828 KeysPage::nag_alter_decryption_chain ()
830 return NagDialog::maybe_nag (
832 Config::NAG_ALTER_DECRYPTION_CHAIN,
833 _("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!"),
839 KeysPage::export_decryption_certificate ()
841 auto config = Config::instance();
842 wxString default_name = "dcpomatic";
843 if (!config->dcp_creator().empty()) {
844 default_name += "_" + std_to_wx(careful_string_filter(config->dcp_creator()));
846 if (!config->dcp_issuer().empty()) {
847 default_name += "_" + std_to_wx(careful_string_filter(config->dcp_issuer()));
849 /// TRANSLATORS: this is the suffix of the defautl filename used when exporting KDM decryption leaf certificates
850 default_name += _("_kdm_decryption_cert.pem");
852 auto d = new wxFileDialog (
853 _panel, _("Select Certificate File"), wxEmptyString, default_name, wxT("PEM files (*.pem)|*.pem"),
854 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
857 if (d->ShowModal () == wxID_OK) {
858 boost::filesystem::path path (wx_to_std(d->GetPath()));
859 if (path.extension() != ".pem") {
862 auto f = fopen_boost (path, "w");
864 throw OpenFileError (path, errno, OpenFileError::WRITE);
867 auto const s = Config::instance()->decryption_chain()->leaf().certificate (true);
868 checked_fwrite (s.c_str(), s.length(), f, path);
876 SoundPage::GetName () const
884 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
885 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
889 _sound = new CheckBox (_panel, _("Play sound via"));
890 table->Add (_sound, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
891 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
892 _sound_output = new wxChoice (_panel, wxID_ANY);
893 s->Add (_sound_output, 0);
894 _sound_output_details = new wxStaticText (_panel, wxID_ANY, wxT(""));
895 s->Add (_sound_output_details, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, DCPOMATIC_SIZER_X_GAP);
896 table->Add (s, wxGBPosition(r, 1));
899 add_label_to_sizer (table, _panel, _("Mapping"), true, wxGBPosition(r, 0));
900 _map = new AudioMappingView (_panel, _("DCP"), _("DCP"), _("Output"), _("output"));
901 _map->SetSize (-1, 600);
902 table->Add (_map, wxGBPosition(r, 1), wxDefaultSpan, wxEXPAND);
905 _reset_to_default = new Button (_panel, _("Reset to default"));
906 table->Add (_reset_to_default, wxGBPosition(r, 1));
909 wxFont font = _sound_output_details->GetFont();
910 font.SetStyle (wxFONTSTYLE_ITALIC);
911 font.SetPointSize (font.GetPointSize() - 1);
912 _sound_output_details->SetFont (font);
914 RtAudio audio (DCPOMATIC_RTAUDIO_API);
915 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
917 auto dev = audio.getDeviceInfo (i);
918 if (dev.probed && dev.outputChannels > 0) {
919 _sound_output->Append (std_to_wx (dev.name));
921 } catch (RtAudioError&) {
922 /* Something went wrong so let's just ignore that device */
926 _sound->Bind (wxEVT_CHECKBOX, bind(&SoundPage::sound_changed, this));
927 _sound_output->Bind (wxEVT_CHOICE, bind(&SoundPage::sound_output_changed, this));
928 _map->Changed.connect (bind(&SoundPage::map_changed, this, _1));
929 _reset_to_default->Bind (wxEVT_BUTTON, bind(&SoundPage::reset_to_default, this));
933 SoundPage::reset_to_default ()
935 Config::instance()->set_audio_mapping_to_default ();
939 SoundPage::map_changed (AudioMapping m)
941 Config::instance()->set_audio_mapping (m);
945 SoundPage::sound_changed ()
947 Config::instance()->set_sound (_sound->GetValue ());
951 SoundPage::sound_output_changed ()
953 RtAudio audio (DCPOMATIC_RTAUDIO_API);
954 auto const so = get_sound_output();
955 string default_device;
957 default_device = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
958 } catch (RtAudioError&) {
961 if (!so || *so == default_device) {
962 Config::instance()->unset_sound_output ();
964 Config::instance()->set_sound_output (*so);
969 SoundPage::config_changed ()
971 auto config = Config::instance ();
973 checked_set (_sound, config->sound ());
975 auto const current_so = get_sound_output ();
976 optional<string> configured_so;
978 if (config->sound_output()) {
979 configured_so = config->sound_output().get();
981 /* No configured output means we should use the default */
982 RtAudio audio (DCPOMATIC_RTAUDIO_API);
984 configured_so = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
985 } catch (RtAudioError&) {
986 /* Probably no audio devices at all */
990 if (configured_so && current_so != configured_so) {
991 /* Update _sound_output with the configured value */
993 while (i < _sound_output->GetCount()) {
994 if (_sound_output->GetString(i) == std_to_wx(*configured_so)) {
995 _sound_output->SetSelection (i);
1002 RtAudio audio (DCPOMATIC_RTAUDIO_API);
1004 map<int, wxString> apis;
1005 apis[RtAudio::MACOSX_CORE] = _("CoreAudio");
1006 apis[RtAudio::WINDOWS_ASIO] = _("ASIO");
1007 apis[RtAudio::WINDOWS_DS] = _("Direct Sound");
1008 apis[RtAudio::WINDOWS_WASAPI] = _("WASAPI");
1009 apis[RtAudio::UNIX_JACK] = _("JACK");
1010 apis[RtAudio::LINUX_ALSA] = _("ALSA");
1011 apis[RtAudio::LINUX_PULSE] = _("PulseAudio");
1012 apis[RtAudio::LINUX_OSS] = _("OSS");
1013 apis[RtAudio::RTAUDIO_DUMMY] = _("Dummy");
1016 if (configured_so) {
1017 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
1019 auto info = audio.getDeviceInfo(i);
1020 if (info.name == *configured_so && info.outputChannels > 0) {
1021 channels = info.outputChannels;
1023 } catch (RtAudioError&) {
1029 _sound_output_details->SetLabel (
1030 wxString::Format(_("%d channels on %s"), channels, apis[audio.getCurrentApi()])
1033 _map->set (Config::instance()->audio_mapping(channels));
1035 vector<NamedChannel> input;
1036 for (int i = 0; i < MAX_DCP_AUDIO_CHANNELS; ++i) {
1037 input.push_back (NamedChannel(short_audio_channel_name(i), i));
1039 _map->set_input_channels (input);
1041 vector<NamedChannel> output;
1042 for (int i = 0; i < channels; ++i) {
1043 output.push_back (NamedChannel(dcp::raw_convert<string>(i), i));
1045 _map->set_output_channels (output);
1047 setup_sensitivity ();
1051 SoundPage::setup_sensitivity ()
1053 _sound_output->Enable (_sound->GetValue());
1056 /** @return Currently-selected preview sound output in the dialogue */
1058 SoundPage::get_sound_output ()
1060 int const sel = _sound_output->GetSelection ();
1061 if (sel == wxNOT_FOUND) {
1062 return optional<string> ();
1065 return wx_to_std (_sound_output->GetString (sel));
1069 LocationsPage::LocationsPage (wxSize panel_size, int border)
1070 : Page (panel_size, border)
1076 LocationsPage::GetName () const
1078 return _("Locations");
1081 #ifdef DCPOMATIC_OSX
1083 LocationsPage::GetLargeIcon () const
1085 return wxBitmap ("locations", wxBITMAP_TYPE_PNG_RESOURCE);
1090 LocationsPage::setup ()
1094 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1095 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
1097 add_label_to_sizer (table, _panel, _("Content directory"), true, wxGBPosition (r, 0));
1098 _content_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1099 table->Add (_content_directory, wxGBPosition (r, 1));
1102 add_label_to_sizer (table, _panel, _("Playlist directory"), true, wxGBPosition (r, 0));
1103 _playlist_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1104 table->Add (_playlist_directory, wxGBPosition (r, 1));
1107 add_label_to_sizer (table, _panel, _("KDM directory"), true, wxGBPosition (r, 0));
1108 _kdm_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1109 table->Add (_kdm_directory, wxGBPosition (r, 1));
1112 _content_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::content_directory_changed, this));
1113 _playlist_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::playlist_directory_changed, this));
1114 _kdm_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::kdm_directory_changed, this));
1118 LocationsPage::config_changed ()
1120 auto config = Config::instance ();
1122 if (config->player_content_directory()) {
1123 checked_set (_content_directory, *config->player_content_directory());
1125 if (config->player_playlist_directory()) {
1126 checked_set (_playlist_directory, *config->player_playlist_directory());
1128 if (config->player_kdm_directory()) {
1129 checked_set (_kdm_directory, *config->player_kdm_directory());
1134 LocationsPage::content_directory_changed ()
1136 Config::instance()->set_player_content_directory(wx_to_std(_content_directory->GetPath()));
1140 LocationsPage::playlist_directory_changed ()
1142 Config::instance()->set_player_playlist_directory(wx_to_std(_playlist_directory->GetPath()));
1146 LocationsPage::kdm_directory_changed ()
1148 Config::instance()->set_player_kdm_directory(wx_to_std(_kdm_directory->GetPath()));