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 wxButton* export_decryption_certificate = new Button (_panel, _("Export KDM decryption 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 wxFileDialog* d = new wxFileDialog (
842 _panel, _("Select Certificate File"), wxEmptyString, _("dcpomatic_kdm_decryption_cert.pem"), wxT ("PEM files (*.pem)|*.pem"),
843 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
846 if (d->ShowModal () == wxID_OK) {
847 boost::filesystem::path path (wx_to_std(d->GetPath()));
848 if (path.extension() != ".pem") {
851 auto f = fopen_boost (path, "w");
853 throw OpenFileError (path, errno, OpenFileError::WRITE);
856 auto const s = Config::instance()->decryption_chain()->leaf().certificate (true);
857 checked_fwrite (s.c_str(), s.length(), f, path);
865 SoundPage::GetName () const
873 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
874 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
878 _sound = new CheckBox (_panel, _("Play sound via"));
879 table->Add (_sound, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
880 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
881 _sound_output = new wxChoice (_panel, wxID_ANY);
882 s->Add (_sound_output, 0);
883 _sound_output_details = new wxStaticText (_panel, wxID_ANY, wxT(""));
884 s->Add (_sound_output_details, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, DCPOMATIC_SIZER_X_GAP);
885 table->Add (s, wxGBPosition(r, 1));
888 add_label_to_sizer (table, _panel, _("Mapping"), true, wxGBPosition(r, 0));
889 _map = new AudioMappingView (_panel, _("DCP"), _("DCP"), _("Output"), _("output"));
890 _map->SetSize (-1, 600);
891 table->Add (_map, wxGBPosition(r, 1), wxDefaultSpan, wxEXPAND);
894 _reset_to_default = new Button (_panel, _("Reset to default"));
895 table->Add (_reset_to_default, wxGBPosition(r, 1));
898 wxFont font = _sound_output_details->GetFont();
899 font.SetStyle (wxFONTSTYLE_ITALIC);
900 font.SetPointSize (font.GetPointSize() - 1);
901 _sound_output_details->SetFont (font);
903 RtAudio audio (DCPOMATIC_RTAUDIO_API);
904 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
906 auto dev = audio.getDeviceInfo (i);
907 if (dev.probed && dev.outputChannels > 0) {
908 _sound_output->Append (std_to_wx (dev.name));
910 } catch (RtAudioError&) {
911 /* Something went wrong so let's just ignore that device */
915 _sound->Bind (wxEVT_CHECKBOX, bind(&SoundPage::sound_changed, this));
916 _sound_output->Bind (wxEVT_CHOICE, bind(&SoundPage::sound_output_changed, this));
917 _map->Changed.connect (bind(&SoundPage::map_changed, this, _1));
918 _reset_to_default->Bind (wxEVT_BUTTON, bind(&SoundPage::reset_to_default, this));
922 SoundPage::reset_to_default ()
924 Config::instance()->set_audio_mapping_to_default ();
928 SoundPage::map_changed (AudioMapping m)
930 Config::instance()->set_audio_mapping (m);
934 SoundPage::sound_changed ()
936 Config::instance()->set_sound (_sound->GetValue ());
940 SoundPage::sound_output_changed ()
942 RtAudio audio (DCPOMATIC_RTAUDIO_API);
943 auto const so = get_sound_output();
944 string default_device;
946 default_device = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
947 } catch (RtAudioError&) {
950 if (!so || *so == default_device) {
951 Config::instance()->unset_sound_output ();
953 Config::instance()->set_sound_output (*so);
958 SoundPage::config_changed ()
960 auto config = Config::instance ();
962 checked_set (_sound, config->sound ());
964 auto const current_so = get_sound_output ();
965 optional<string> configured_so;
967 if (config->sound_output()) {
968 configured_so = config->sound_output().get();
970 /* No configured output means we should use the default */
971 RtAudio audio (DCPOMATIC_RTAUDIO_API);
973 configured_so = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
974 } catch (RtAudioError&) {
975 /* Probably no audio devices at all */
979 if (configured_so && current_so != configured_so) {
980 /* Update _sound_output with the configured value */
982 while (i < _sound_output->GetCount()) {
983 if (_sound_output->GetString(i) == std_to_wx(*configured_so)) {
984 _sound_output->SetSelection (i);
991 RtAudio audio (DCPOMATIC_RTAUDIO_API);
993 map<int, wxString> apis;
994 apis[RtAudio::MACOSX_CORE] = _("CoreAudio");
995 apis[RtAudio::WINDOWS_ASIO] = _("ASIO");
996 apis[RtAudio::WINDOWS_DS] = _("Direct Sound");
997 apis[RtAudio::WINDOWS_WASAPI] = _("WASAPI");
998 apis[RtAudio::UNIX_JACK] = _("JACK");
999 apis[RtAudio::LINUX_ALSA] = _("ALSA");
1000 apis[RtAudio::LINUX_PULSE] = _("PulseAudio");
1001 apis[RtAudio::LINUX_OSS] = _("OSS");
1002 apis[RtAudio::RTAUDIO_DUMMY] = _("Dummy");
1005 if (configured_so) {
1006 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
1008 auto info = audio.getDeviceInfo(i);
1009 if (info.name == *configured_so && info.outputChannels > 0) {
1010 channels = info.outputChannels;
1012 } catch (RtAudioError&) {
1018 _sound_output_details->SetLabel (
1019 wxString::Format(_("%d channels on %s"), channels, apis[audio.getCurrentApi()])
1022 _map->set (Config::instance()->audio_mapping(channels));
1024 vector<NamedChannel> input;
1025 for (int i = 0; i < MAX_DCP_AUDIO_CHANNELS; ++i) {
1026 input.push_back (NamedChannel(short_audio_channel_name(i), i));
1028 _map->set_input_channels (input);
1030 vector<NamedChannel> output;
1031 for (int i = 0; i < channels; ++i) {
1032 output.push_back (NamedChannel(dcp::raw_convert<string>(i), i));
1034 _map->set_output_channels (output);
1036 setup_sensitivity ();
1040 SoundPage::setup_sensitivity ()
1042 _sound_output->Enable (_sound->GetValue());
1045 /** @return Currently-selected preview sound output in the dialogue */
1047 SoundPage::get_sound_output ()
1049 int const sel = _sound_output->GetSelection ();
1050 if (sel == wxNOT_FOUND) {
1051 return optional<string> ();
1054 return wx_to_std (_sound_output->GetString (sel));
1058 LocationsPage::LocationsPage (wxSize panel_size, int border)
1059 : Page (panel_size, border)
1065 LocationsPage::GetName () const
1067 return _("Locations");
1070 #ifdef DCPOMATIC_OSX
1072 LocationsPage::GetLargeIcon () const
1074 return wxBitmap ("locations", wxBITMAP_TYPE_PNG_RESOURCE);
1079 LocationsPage::setup ()
1083 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1084 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
1086 add_label_to_sizer (table, _panel, _("Content directory"), true, wxGBPosition (r, 0));
1087 _content_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1088 table->Add (_content_directory, wxGBPosition (r, 1));
1091 add_label_to_sizer (table, _panel, _("Playlist directory"), true, wxGBPosition (r, 0));
1092 _playlist_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1093 table->Add (_playlist_directory, wxGBPosition (r, 1));
1096 add_label_to_sizer (table, _panel, _("KDM directory"), true, wxGBPosition (r, 0));
1097 _kdm_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1098 table->Add (_kdm_directory, wxGBPosition (r, 1));
1101 _content_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::content_directory_changed, this));
1102 _playlist_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::playlist_directory_changed, this));
1103 _kdm_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::kdm_directory_changed, this));
1107 LocationsPage::config_changed ()
1109 auto config = Config::instance ();
1111 if (config->player_content_directory()) {
1112 checked_set (_content_directory, *config->player_content_directory());
1114 if (config->player_playlist_directory()) {
1115 checked_set (_playlist_directory, *config->player_playlist_directory());
1117 if (config->player_kdm_directory()) {
1118 checked_set (_kdm_directory, *config->player_kdm_directory());
1123 LocationsPage::content_directory_changed ()
1125 Config::instance()->set_player_content_directory(wx_to_std(_content_directory->GetPath()));
1129 LocationsPage::playlist_directory_changed ()
1131 Config::instance()->set_player_playlist_directory(wx_to_std(_playlist_directory->GetPath()));
1135 LocationsPage::kdm_directory_changed ()
1137 Config::instance()->set_player_kdm_directory(wx_to_std(_kdm_directory->GetPath()));