2 Copyright (C) 2012-2019 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>
36 using boost::optional;
37 using std::shared_ptr;
38 using boost::function;
39 #if BOOST_VERSION >= 106100
40 using namespace boost::placeholders;
50 Page::Page (wxSize panel_size, int border)
53 , _panel_size (panel_size)
54 , _window_exists (false)
56 _config_connection = Config::instance()->Changed.connect (bind (&Page::config_changed_wrapper, this));
61 Page::CreateWindow (wxWindow* parent)
63 return create_window (parent);
68 Page::create_window (wxWindow* parent)
70 _panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size);
71 wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
75 _window_exists = true;
78 _panel->Bind (wxEVT_DESTROY, bind (&Page::window_destroyed, this));
84 Page::config_changed_wrapper ()
92 Page::window_destroyed ()
94 _window_exists = false;
98 GeneralPage::GeneralPage (wxSize panel_size, int border)
99 : Page (panel_size, border)
106 GeneralPage::GetName () const
113 GeneralPage::add_language_controls (wxGridBagSizer* table, int& r)
115 _set_language = new CheckBox (_panel, _("Set language"));
116 table->Add (_set_language, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
117 _language = new wxChoice (_panel, wxID_ANY);
118 vector<pair<string, string> > languages;
119 languages.push_back (make_pair ("Čeština", "cs_CZ"));
120 languages.push_back (make_pair ("汉语/漢語", "zh_CN"));
121 languages.push_back (make_pair ("Dansk", "da_DK"));
122 languages.push_back (make_pair ("Deutsch", "de_DE"));
123 languages.push_back (make_pair ("English", "en_GB"));
124 languages.push_back (make_pair ("Español", "es_ES"));
125 languages.push_back (make_pair ("Français", "fr_FR"));
126 languages.push_back (make_pair ("Italiano", "it_IT"));
127 languages.push_back (make_pair ("Nederlands", "nl_NL"));
128 languages.push_back (make_pair ("Русский", "ru_RU"));
129 languages.push_back (make_pair ("Polski", "pl_PL"));
130 languages.push_back (make_pair ("Português europeu", "pt_PT"));
131 languages.push_back (make_pair ("Português do Brasil", "pt_BR"));
132 languages.push_back (make_pair ("Svenska", "sv_SE"));
133 languages.push_back (make_pair ("Slovenský jazyk", "sk_SK"));
134 languages.push_back (make_pair ("Türkçe", "tr_TR"));
135 languages.push_back (make_pair ("українська мова", "uk_UA"));
136 checked_set (_language, languages);
137 table->Add (_language, wxGBPosition (r, 1));
140 wxStaticText* restart = add_label_to_sizer (
141 table, _panel, _("(restart DCP-o-matic to see language changes)"), false, wxGBPosition (r, 0), wxGBSpan (1, 2)
143 wxFont font = restart->GetFont();
144 font.SetStyle (wxFONTSTYLE_ITALIC);
145 font.SetPointSize (font.GetPointSize() - 1);
146 restart->SetFont (font);
149 _set_language->Bind (wxEVT_CHECKBOX, bind (&GeneralPage::set_language_changed, this));
150 _language->Bind (wxEVT_CHOICE, bind (&GeneralPage::language_changed, this));
154 GeneralPage::add_update_controls (wxGridBagSizer* table, int& r)
156 _check_for_updates = new CheckBox (_panel, _("Check for updates on startup"));
157 table->Add (_check_for_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
160 _check_for_test_updates = new CheckBox (_panel, _("Check for testing updates on startup"));
161 table->Add (_check_for_test_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
164 _check_for_updates->Bind (wxEVT_CHECKBOX, bind (&GeneralPage::check_for_updates_changed, this));
165 _check_for_test_updates->Bind (wxEVT_CHECKBOX, bind (&GeneralPage::check_for_test_updates_changed, this));
169 GeneralPage::config_changed ()
171 Config* config = Config::instance ();
173 checked_set (_set_language, static_cast<bool>(config->language()));
175 /* Backwards compatibility of config file */
177 map<string, string> compat_map;
178 compat_map["fr"] = "fr_FR";
179 compat_map["it"] = "it_IT";
180 compat_map["es"] = "es_ES";
181 compat_map["sv"] = "sv_SE";
182 compat_map["de"] = "de_DE";
183 compat_map["nl"] = "nl_NL";
184 compat_map["ru"] = "ru_RU";
185 compat_map["pl"] = "pl_PL";
186 compat_map["da"] = "da_DK";
187 compat_map["pt"] = "pt_PT";
188 compat_map["sk"] = "sk_SK";
189 compat_map["cs"] = "cs_CZ";
190 compat_map["uk"] = "uk_UA";
192 string lang = config->language().get_value_or ("en_GB");
193 if (compat_map.find (lang) != compat_map.end ()) {
194 lang = compat_map[lang];
197 checked_set (_language, lang);
199 checked_set (_check_for_updates, config->check_for_updates ());
200 checked_set (_check_for_test_updates, config->check_for_test_updates ());
202 setup_sensitivity ();
206 GeneralPage::setup_sensitivity ()
208 _language->Enable (_set_language->GetValue ());
209 _check_for_test_updates->Enable (_check_for_updates->GetValue ());
213 GeneralPage::set_language_changed ()
215 setup_sensitivity ();
216 if (_set_language->GetValue ()) {
219 Config::instance()->unset_language ();
224 GeneralPage::language_changed ()
226 int const sel = _language->GetSelection ();
228 Config::instance()->set_language (string_client_data (_language->GetClientObject (sel)));
230 Config::instance()->unset_language ();
235 GeneralPage::check_for_updates_changed ()
237 Config::instance()->set_check_for_updates (_check_for_updates->GetValue ());
241 GeneralPage::check_for_test_updates_changed ()
243 Config::instance()->set_check_for_test_updates (_check_for_test_updates->GetValue ());
246 CertificateChainEditor::CertificateChainEditor (
250 function<void (shared_ptr<dcp::CertificateChain>)> set,
251 function<shared_ptr<const dcp::CertificateChain> (void)> get,
252 function<bool (void)> nag_alter
254 : wxDialog (parent, wxID_ANY, title)
257 , _nag_alter (nag_alter)
259 _sizer = new wxBoxSizer (wxVERTICAL);
261 wxBoxSizer* certificates_sizer = new wxBoxSizer (wxHORIZONTAL);
262 _sizer->Add (certificates_sizer, 0, wxALL, border);
264 _certificates = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (440, 150), wxLC_REPORT | wxLC_SINGLE_SEL);
269 ip.SetText (_("Type"));
271 _certificates->InsertColumn (0, ip);
277 ip.SetText (_("Thumbprint"));
280 wxFont font = ip.GetFont ();
281 font.SetFamily (wxFONTFAMILY_TELETYPE);
284 _certificates->InsertColumn (1, ip);
287 certificates_sizer->Add (_certificates, 1, wxEXPAND);
290 wxSizer* s = new wxBoxSizer (wxVERTICAL);
291 _add_certificate = new Button (this, _("Add..."));
292 s->Add (_add_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
293 _remove_certificate = new Button (this, _("Remove"));
294 s->Add (_remove_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
295 _export_certificate = new Button (this, _("Export certificate..."));
296 s->Add (_export_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
297 _export_chain = new Button (this, _("Export chain..."));
298 s->Add (_export_chain, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
299 certificates_sizer->Add (s, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
302 wxGridBagSizer* table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
303 _sizer->Add (table, 1, wxALL | wxEXPAND, border);
306 add_label_to_sizer (table, this, _("Leaf private key"), true, wxGBPosition (r, 0));
307 _private_key = new StaticText (this, wxT(""));
308 wxFont font = _private_key->GetFont ();
309 font.SetFamily (wxFONTFAMILY_TELETYPE);
310 _private_key->SetFont (font);
311 table->Add (_private_key, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
312 _import_private_key = new Button (this, _("Import..."));
313 table->Add (_import_private_key, wxGBPosition (r, 2));
314 _export_private_key = new Button (this, _("Export..."));
315 table->Add (_export_private_key, wxGBPosition (r, 3));
318 _button_sizer = new wxBoxSizer (wxHORIZONTAL);
319 _remake_certificates = new Button (this, _("Re-make certificates and key..."));
320 _button_sizer->Add (_remake_certificates, 1, wxRIGHT, border);
321 table->Add (_button_sizer, wxGBPosition (r, 0), wxGBSpan (1, 4));
324 _private_key_bad = new StaticText (this, _("Leaf private key does not match leaf certificate!"));
325 font = *wxSMALL_FONT;
326 font.SetWeight (wxFONTWEIGHT_BOLD);
327 _private_key_bad->SetFont (font);
328 table->Add (_private_key_bad, wxGBPosition (r, 0), wxGBSpan (1, 3));
331 _add_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::add_certificate, this));
332 _remove_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::remove_certificate, this));
333 _export_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_certificate, this));
334 _certificates->Bind (wxEVT_LIST_ITEM_SELECTED, bind (&CertificateChainEditor::update_sensitivity, this));
335 _certificates->Bind (wxEVT_LIST_ITEM_DESELECTED, bind (&CertificateChainEditor::update_sensitivity, this));
336 _remake_certificates->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::remake_certificates, this));
337 _export_chain->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_chain, this));
338 _import_private_key->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::import_private_key, this));
339 _export_private_key->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_private_key, this));
341 wxSizer* buttons = CreateSeparatedButtonSizer (wxCLOSE);
343 _sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
346 SetSizerAndFit (_sizer);
348 update_certificate_list ();
349 update_private_key ();
350 update_sensitivity ();
354 CertificateChainEditor::add_button (wxWindow* button)
356 _button_sizer->Add (button, 0, wxLEFT | wxRIGHT, DCPOMATIC_SIZER_X_GAP);
361 CertificateChainEditor::add_certificate ()
363 wxFileDialog* d = new wxFileDialog (this, _("Select Certificate File"));
365 if (d->ShowModal() == wxID_OK) {
370 extra = c.read_string (dcp::file_to_string (wx_to_std (d->GetPath ())));
371 } catch (boost::filesystem::filesystem_error& e) {
372 error_dialog (this, _("Could not import certificate (%s)"), d->GetPath());
377 if (!extra.empty ()) {
380 _("This file contains other certificates (or other data) after its first certificate. "
381 "Only the first certificate will be used.")
384 shared_ptr<dcp::CertificateChain> chain(new dcp::CertificateChain(*_get().get()));
386 if (!chain->chain_valid ()) {
389 _("Adding this certificate would make the chain inconsistent, so it will not be added. "
390 "Add certificates in order from root to intermediate to leaf.")
395 update_certificate_list ();
397 } catch (dcp::MiscError& e) {
398 error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
404 update_sensitivity ();
408 CertificateChainEditor::remove_certificate ()
411 /* Cancel was clicked */
415 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
420 _certificates->DeleteItem (i);
421 shared_ptr<dcp::CertificateChain> chain(new dcp::CertificateChain(*_get().get()));
425 update_sensitivity ();
426 update_certificate_list ();
430 CertificateChainEditor::export_certificate ()
432 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
437 wxFileDialog* d = new wxFileDialog (
438 this, _("Select Certificate File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
439 auto all = _get()->root_to_leaf();
441 wxString default_name;
443 default_name = "root.pem";
444 } else if (i == static_cast<int>(all.size() - 1)) {
445 default_name = "leaf.pem";
447 default_name = "intermediate.pem";
450 auto d = new wxFileDialog(
451 this, _("Select Certificate File"), wxEmptyString, default_name, wxT ("PEM files (*.pem)|*.pem"),
452 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
455 auto j = all.begin ();
456 for (int k = 0; k < i; ++k) {
460 if (d->ShowModal () == wxID_OK) {
461 boost::filesystem::path path (wx_to_std(d->GetPath()));
462 if (path.extension() != ".pem") {
465 auto f = fopen_boost (path, "w");
467 throw OpenFileError (path, errno, OpenFileError::WRITE);
470 string const s = j->certificate (true);
471 checked_fwrite (s.c_str(), s.length(), f, path);
478 CertificateChainEditor::export_chain ()
480 auto d = new wxFileDialog (
481 this, _("Select Chain File"), wxEmptyString, _("certificate_chain.pem"), wxT("PEM files (*.pem)|*.pem"),
482 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
485 if (d->ShowModal () == wxID_OK) {
486 boost::filesystem::path path (wx_to_std(d->GetPath()));
487 if (path.extension() != ".pem") {
490 auto f = fopen_boost (path, "w");
492 throw OpenFileError (path, errno, OpenFileError::WRITE);
495 string const s = _get()->chain();
496 checked_fwrite (s.c_str(), s.length(), f, path);
504 CertificateChainEditor::update_certificate_list ()
506 _certificates->DeleteAllItems ();
508 dcp::CertificateChain::List 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 ()
540 shared_ptr<const dcp::CertificateChain> chain = _get();
542 string subject_organization_name;
543 string subject_organizational_unit_name;
544 string root_common_name;
545 string intermediate_common_name;
546 string leaf_common_name;
548 dcp::CertificateChain::List all = chain->root_to_leaf ();
550 if (all.size() >= 1) {
552 subject_organization_name = chain->root().subject_organization_name ();
553 subject_organizational_unit_name = chain->root().subject_organizational_unit_name ();
554 root_common_name = chain->root().subject_common_name ();
557 if (all.size() >= 2) {
559 leaf_common_name = chain->leaf().subject_common_name ();
562 if (all.size() >= 3) {
563 /* Have an intermediate */
564 dcp::CertificateChain::List::iterator i = all.begin ();
566 intermediate_common_name = i->subject_common_name ();
570 /* Cancel was clicked */
574 MakeChainDialog* d = new MakeChainDialog (
576 subject_organization_name,
577 subject_organizational_unit_name,
579 intermediate_common_name,
583 if (d->ShowModal () == wxID_OK) {
585 shared_ptr<dcp::CertificateChain> (
586 new dcp::CertificateChain (
589 d->organisational_unit (),
590 d->root_common_name (),
591 d->intermediate_common_name (),
592 d->leaf_common_name ()
597 update_certificate_list ();
598 update_private_key ();
605 CertificateChainEditor::update_sensitivity ()
607 /* We can only remove the leaf certificate */
608 _remove_certificate->Enable (_certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) == (_certificates->GetItemCount() - 1));
609 _export_certificate->Enable (_certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
613 CertificateChainEditor::update_private_key ()
615 checked_set (_private_key, dcp::private_key_fingerprint (_get()->key().get()));
620 CertificateChainEditor::import_private_key ()
622 wxFileDialog* d = new wxFileDialog (this, _("Select Key File"));
624 if (d->ShowModal() == wxID_OK) {
626 boost::filesystem::path p (wx_to_std (d->GetPath ()));
627 if (boost::filesystem::file_size (p) > 8192) {
630 wxString::Format (_("Could not read key file; file is too long (%s)"), std_to_wx (p.string ()))
635 shared_ptr<dcp::CertificateChain> chain(new dcp::CertificateChain(*_get().get()));
636 chain->set_key (dcp::file_to_string (p));
638 update_private_key ();
639 } catch (dcp::MiscError& e) {
640 error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
646 update_sensitivity ();
650 CertificateChainEditor::export_private_key ()
652 optional<string> key = _get()->key();
657 auto d = new wxFileDialog (
658 this, _("Select Key File"), wxEmptyString, _("private_key.pem"), wxT ("PEM files (*.pem)|*.pem"),
659 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
662 if (d->ShowModal () == wxID_OK) {
663 boost::filesystem::path path (wx_to_std(d->GetPath()));
664 if (path.extension() != ".pem") {
667 auto f = fopen_boost (path, "w");
669 throw OpenFileError (path, errno, OpenFileError::WRITE);
672 string const s = _get()->key().get ();
673 checked_fwrite (s.c_str(), s.length(), f, path);
680 KeysPage::GetName () const
688 wxFont subheading_font (*wxNORMAL_FONT);
689 subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
691 wxSizer* sizer = _panel->GetSizer();
694 wxStaticText* m = new StaticText (_panel, _("Decrypting KDMs"));
695 m->SetFont (subheading_font);
696 sizer->Add (m, 0, wxALL, _border);
699 wxSizer* buttons = new wxBoxSizer (wxVERTICAL);
701 wxButton* export_decryption_certificate = new Button (_panel, _("Export KDM decryption certificate..."));
702 buttons->Add (export_decryption_certificate, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
703 wxButton* export_settings = new Button (_panel, _("Export all KDM decryption settings..."));
704 buttons->Add (export_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
705 wxButton* import_settings = new Button (_panel, _("Import all KDM decryption settings..."));
706 buttons->Add (import_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
707 wxButton* decryption_advanced = new Button (_panel, _("Advanced..."));
708 buttons->Add (decryption_advanced, 0);
710 sizer->Add (buttons, 0, wxLEFT, _border);
712 export_decryption_certificate->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_certificate, this));
713 export_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_chain_and_key, this));
714 import_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::import_decryption_chain_and_key, this));
715 decryption_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::decryption_advanced, this));
718 wxStaticText* m = new StaticText (_panel, _("Signing DCPs and KDMs"));
719 m->SetFont (subheading_font);
720 sizer->Add (m, 0, wxALL, _border);
723 wxButton* signing_advanced = new Button (_panel, _("Advanced..."));
724 sizer->Add (signing_advanced, 0, wxLEFT | wxBOTTOM, _border);
725 signing_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::signing_advanced, this));
729 KeysPage::decryption_advanced ()
731 CertificateChainEditor* c = new CertificateChainEditor (
732 _panel, _("Decrypting KDMs"), _border,
733 bind (&Config::set_decryption_chain, Config::instance (), _1),
734 bind (&Config::decryption_chain, Config::instance ()),
735 bind (&KeysPage::nag_alter_decryption_chain, this)
742 KeysPage::signing_advanced ()
744 CertificateChainEditor* c = new CertificateChainEditor (
745 _panel, _("Signing DCPs and KDMs"), _border,
746 bind (&Config::set_signer_chain, Config::instance (), _1),
747 bind (&Config::signer_chain, Config::instance ()),
755 KeysPage::export_decryption_chain_and_key ()
757 wxFileDialog* d = new wxFileDialog (
758 _panel, _("Select Export File"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom"),
759 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
762 if (d->ShowModal () == wxID_OK) {
763 boost::filesystem::path path (wx_to_std(d->GetPath()));
764 FILE* f = fopen_boost (path, "w");
766 throw OpenFileError (path, errno, OpenFileError::WRITE);
769 string const chain = Config::instance()->decryption_chain()->chain();
770 checked_fwrite (chain.c_str(), chain.length(), f, path);
771 optional<string> const key = Config::instance()->decryption_chain()->key();
772 DCPOMATIC_ASSERT (key);
773 checked_fwrite (key->c_str(), key->length(), f, path);
781 KeysPage::import_decryption_chain_and_key ()
783 if (NagDialog::maybe_nag (
785 Config::NAG_IMPORT_DECRYPTION_CHAIN,
786 _("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!"),
792 wxFileDialog* d = new wxFileDialog (
793 _panel, _("Select File To Import"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom")
796 if (d->ShowModal () == wxID_OK) {
797 shared_ptr<dcp::CertificateChain> new_chain(new dcp::CertificateChain());
799 FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "r");
801 throw OpenFileError (wx_to_std (d->GetPath ()), errno, OpenFileError::WRITE);
807 if (fgets (buffer, 128, f) == 0) {
811 if (strncmp (buffer, "-----END CERTIFICATE-----", 25) == 0) {
812 new_chain->add (dcp::Certificate (current));
814 } else if (strncmp (buffer, "-----END RSA PRIVATE KEY-----", 29) == 0) {
815 new_chain->set_key (current);
821 if (new_chain->chain_valid() && new_chain->private_key_valid()) {
822 Config::instance()->set_decryption_chain (new_chain);
824 error_dialog (_panel, _("Invalid DCP-o-matic export file"));
831 KeysPage::nag_alter_decryption_chain ()
833 return NagDialog::maybe_nag (
835 Config::NAG_ALTER_DECRYPTION_CHAIN,
836 _("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!"),
842 KeysPage::export_decryption_certificate ()
844 wxFileDialog* d = new wxFileDialog (
845 _panel, _("Select Certificate File"), wxEmptyString, _("dcpomatic_kdm_decryption_cert.pem"), wxT ("PEM files (*.pem)|*.pem"),
846 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
849 if (d->ShowModal () == wxID_OK) {
850 boost::filesystem::path path (wx_to_std(d->GetPath()));
851 if (path.extension() != ".pem") {
854 auto f = fopen_boost (path, "w");
856 throw OpenFileError (path, errno, OpenFileError::WRITE);
859 string const s = Config::instance()->decryption_chain()->leaf().certificate (true);
860 checked_fwrite (s.c_str(), s.length(), f, path);
868 SoundPage::GetName () const
876 wxGridBagSizer* table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
877 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
881 _sound = new CheckBox (_panel, _("Play sound via"));
882 table->Add (_sound, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
883 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
884 _sound_output = new wxChoice (_panel, wxID_ANY);
885 s->Add (_sound_output, 0);
886 _sound_output_details = new wxStaticText (_panel, wxID_ANY, wxT(""));
887 s->Add (_sound_output_details, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, DCPOMATIC_SIZER_X_GAP);
888 table->Add (s, wxGBPosition(r, 1));
891 add_label_to_sizer (table, _panel, _("Mapping"), true, wxGBPosition(r, 0));
892 _map = new AudioMappingView (_panel, _("DCP"), _("DCP"), _("Output"), _("output"));
893 _map->SetSize (-1, 600);
894 table->Add (_map, wxGBPosition(r, 1), wxDefaultSpan, wxEXPAND);
897 _reset_to_default = new Button (_panel, _("Reset to default"));
898 table->Add (_reset_to_default, wxGBPosition(r, 1));
901 wxFont font = _sound_output_details->GetFont();
902 font.SetStyle (wxFONTSTYLE_ITALIC);
903 font.SetPointSize (font.GetPointSize() - 1);
904 _sound_output_details->SetFont (font);
906 RtAudio audio (DCPOMATIC_RTAUDIO_API);
907 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
909 RtAudio::DeviceInfo dev = audio.getDeviceInfo (i);
910 if (dev.probed && dev.outputChannels > 0) {
911 _sound_output->Append (std_to_wx (dev.name));
913 } catch (RtAudioError&) {
914 /* Something went wrong so let's just ignore that device */
918 _sound->Bind (wxEVT_CHECKBOX, bind(&SoundPage::sound_changed, this));
919 _sound_output->Bind (wxEVT_CHOICE, bind(&SoundPage::sound_output_changed, this));
920 _map->Changed.connect (bind(&SoundPage::map_changed, this, _1));
921 _reset_to_default->Bind (wxEVT_BUTTON, bind(&SoundPage::reset_to_default, this));
925 SoundPage::reset_to_default ()
927 Config::instance()->set_audio_mapping_to_default ();
931 SoundPage::map_changed (AudioMapping m)
933 Config::instance()->set_audio_mapping (m);
937 SoundPage::sound_changed ()
939 Config::instance()->set_sound (_sound->GetValue ());
943 SoundPage::sound_output_changed ()
945 RtAudio audio (DCPOMATIC_RTAUDIO_API);
946 optional<string> const so = get_sound_output();
947 string default_device;
949 default_device = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
950 } catch (RtAudioError&) {
953 if (!so || *so == default_device) {
954 Config::instance()->unset_sound_output ();
956 Config::instance()->set_sound_output (*so);
961 SoundPage::config_changed ()
963 Config* config = Config::instance ();
965 checked_set (_sound, config->sound ());
967 optional<string> const current_so = get_sound_output ();
968 optional<string> configured_so;
970 if (config->sound_output()) {
971 configured_so = config->sound_output().get();
973 /* No configured output means we should use the default */
974 RtAudio audio (DCPOMATIC_RTAUDIO_API);
976 configured_so = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
977 } catch (RtAudioError&) {
978 /* Probably no audio devices at all */
982 if (configured_so && current_so != configured_so) {
983 /* Update _sound_output with the configured value */
985 while (i < _sound_output->GetCount()) {
986 if (_sound_output->GetString(i) == std_to_wx(*configured_so)) {
987 _sound_output->SetSelection (i);
994 RtAudio audio (DCPOMATIC_RTAUDIO_API);
996 map<int, wxString> apis;
997 apis[RtAudio::MACOSX_CORE] = _("CoreAudio");
998 apis[RtAudio::WINDOWS_ASIO] = _("ASIO");
999 apis[RtAudio::WINDOWS_DS] = _("Direct Sound");
1000 apis[RtAudio::WINDOWS_WASAPI] = _("WASAPI");
1001 apis[RtAudio::UNIX_JACK] = _("JACK");
1002 apis[RtAudio::LINUX_ALSA] = _("ALSA");
1003 apis[RtAudio::LINUX_PULSE] = _("PulseAudio");
1004 apis[RtAudio::LINUX_OSS] = _("OSS");
1005 apis[RtAudio::RTAUDIO_DUMMY] = _("Dummy");
1008 if (configured_so) {
1009 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
1011 RtAudio::DeviceInfo info = audio.getDeviceInfo(i);
1012 if (info.name == *configured_so && info.outputChannels > 0) {
1013 channels = info.outputChannels;
1015 } catch (RtAudioError&) {
1021 _sound_output_details->SetLabel (
1022 wxString::Format(_("%d channels on %s"), channels, apis[audio.getCurrentApi()])
1025 _map->set (Config::instance()->audio_mapping(channels));
1027 vector<NamedChannel> input;
1028 for (int i = 0; i < MAX_DCP_AUDIO_CHANNELS; ++i) {
1029 input.push_back (NamedChannel(short_audio_channel_name(i), i));
1031 _map->set_input_channels (input);
1033 vector<NamedChannel> output;
1034 for (int i = 0; i < channels; ++i) {
1035 output.push_back (NamedChannel(dcp::raw_convert<string>(i), i));
1037 _map->set_output_channels (output);
1039 setup_sensitivity ();
1043 SoundPage::setup_sensitivity ()
1045 _sound_output->Enable (_sound->GetValue());
1048 /** @return Currently-selected preview sound output in the dialogue */
1050 SoundPage::get_sound_output ()
1052 int const sel = _sound_output->GetSelection ();
1053 if (sel == wxNOT_FOUND) {
1054 return optional<string> ();
1057 return wx_to_std (_sound_output->GetString (sel));
1061 LocationsPage::LocationsPage (wxSize panel_size, int border)
1062 : Page (panel_size, border)
1068 LocationsPage::GetName () const
1070 return _("Locations");
1073 #ifdef DCPOMATIC_OSX
1075 LocationsPage::GetLargeIcon () const
1077 return wxBitmap ("locations", wxBITMAP_TYPE_PNG_RESOURCE);
1082 LocationsPage::setup ()
1086 wxGridBagSizer* table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1087 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
1089 add_label_to_sizer (table, _panel, _("Content directory"), true, wxGBPosition (r, 0));
1090 _content_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1091 table->Add (_content_directory, wxGBPosition (r, 1));
1094 add_label_to_sizer (table, _panel, _("Playlist directory"), true, wxGBPosition (r, 0));
1095 _playlist_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1096 table->Add (_playlist_directory, wxGBPosition (r, 1));
1099 add_label_to_sizer (table, _panel, _("KDM directory"), true, wxGBPosition (r, 0));
1100 _kdm_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1101 table->Add (_kdm_directory, wxGBPosition (r, 1));
1104 _content_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::content_directory_changed, this));
1105 _playlist_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::playlist_directory_changed, this));
1106 _kdm_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::kdm_directory_changed, this));
1110 LocationsPage::config_changed ()
1112 Config* config = Config::instance ();
1114 if (config->player_content_directory()) {
1115 checked_set (_content_directory, *config->player_content_directory());
1117 if (config->player_playlist_directory()) {
1118 checked_set (_playlist_directory, *config->player_playlist_directory());
1120 if (config->player_kdm_directory()) {
1121 checked_set (_kdm_directory, *config->player_kdm_directory());
1126 LocationsPage::content_directory_changed ()
1128 Config::instance()->set_player_content_directory(wx_to_std(_content_directory->GetPath()));
1132 LocationsPage::playlist_directory_changed ()
1134 Config::instance()->set_player_playlist_directory(wx_to_std(_playlist_directory->GetPath()));
1138 LocationsPage::kdm_directory_changed ()
1140 Config::instance()->set_player_kdm_directory(wx_to_std(_kdm_directory->GetPath()));