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"
29 #include <dcp/raw_convert.h>
34 using std::make_shared;
37 using std::shared_ptr;
41 using boost::optional;
42 #if BOOST_VERSION >= 106100
43 using namespace boost::placeholders;
54 Page::Page (wxSize panel_size, int border)
57 , _panel_size (panel_size)
58 , _window_exists (false)
60 _config_connection = Config::instance()->Changed.connect (bind (&Page::config_changed_wrapper, this));
65 Page::CreateWindow (wxWindow* parent)
67 return create_window (parent);
72 Page::create_window (wxWindow* parent)
74 _panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size);
75 auto s = new wxBoxSizer (wxVERTICAL);
79 _window_exists = true;
82 _panel->Bind (wxEVT_DESTROY, bind (&Page::window_destroyed, this));
88 Page::config_changed_wrapper ()
96 Page::window_destroyed ()
98 _window_exists = false;
102 GeneralPage::GeneralPage (wxSize panel_size, int border)
103 : Page (panel_size, border)
110 GeneralPage::GetName () const
117 GeneralPage::add_language_controls (wxGridBagSizer* table, int& r)
119 _set_language = new CheckBox (_panel, _("Set language"));
120 table->Add (_set_language, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
121 _language = new wxChoice (_panel, wxID_ANY);
122 vector<pair<string, string>> languages;
123 languages.push_back (make_pair("Čeština", "cs_CZ"));
124 languages.push_back (make_pair("汉语/漢語", "zh_CN"));
125 languages.push_back (make_pair("Dansk", "da_DK"));
126 languages.push_back (make_pair("Deutsch", "de_DE"));
127 languages.push_back (make_pair("English", "en_GB"));
128 languages.push_back (make_pair("Español", "es_ES"));
129 languages.push_back (make_pair("Français", "fr_FR"));
130 languages.push_back (make_pair("Italiano", "it_IT"));
131 languages.push_back (make_pair("Nederlands", "nl_NL"));
132 languages.push_back (make_pair("Русский", "ru_RU"));
133 languages.push_back (make_pair("Polski", "pl_PL"));
134 languages.push_back (make_pair("Português europeu", "pt_PT"));
135 languages.push_back (make_pair("Português do Brasil", "pt_BR"));
136 languages.push_back (make_pair("Svenska", "sv_SE"));
137 languages.push_back (make_pair("Slovenščina", "sl_SI"));
138 languages.push_back (make_pair("Slovenský jazyk", "sk_SK"));
139 // languages.push_back (make_pair("Türkçe", "tr_TR"));
140 languages.push_back (make_pair("українська мова", "uk_UA"));
141 checked_set (_language, languages);
142 table->Add (_language, wxGBPosition (r, 1));
145 auto restart = add_label_to_sizer (
146 table, _panel, _("(restart DCP-o-matic to see language changes)"), false, wxGBPosition (r, 0), wxGBSpan (1, 2)
148 wxFont font = restart->GetFont();
149 font.SetStyle (wxFONTSTYLE_ITALIC);
150 font.SetPointSize (font.GetPointSize() - 1);
151 restart->SetFont (font);
154 _set_language->Bind (wxEVT_CHECKBOX, bind (&GeneralPage::set_language_changed, this));
155 _language->Bind (wxEVT_CHOICE, bind (&GeneralPage::language_changed, this));
159 GeneralPage::add_update_controls (wxGridBagSizer* table, int& r)
161 _check_for_updates = new CheckBox (_panel, _("Check for updates on startup"));
162 table->Add (_check_for_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
165 _check_for_test_updates = new CheckBox (_panel, _("Check for testing updates on startup"));
166 table->Add (_check_for_test_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
169 _check_for_updates->Bind (wxEVT_CHECKBOX, bind (&GeneralPage::check_for_updates_changed, this));
170 _check_for_test_updates->Bind (wxEVT_CHECKBOX, bind (&GeneralPage::check_for_test_updates_changed, this));
174 GeneralPage::config_changed ()
176 auto config = Config::instance ();
178 checked_set (_set_language, static_cast<bool>(config->language()));
180 /* Backwards compatibility of config file */
182 map<string, string> compat_map;
183 compat_map["fr"] = "fr_FR";
184 compat_map["it"] = "it_IT";
185 compat_map["es"] = "es_ES";
186 compat_map["sv"] = "sv_SE";
187 compat_map["de"] = "de_DE";
188 compat_map["nl"] = "nl_NL";
189 compat_map["ru"] = "ru_RU";
190 compat_map["pl"] = "pl_PL";
191 compat_map["da"] = "da_DK";
192 compat_map["pt"] = "pt_PT";
193 compat_map["sk"] = "sk_SK";
194 compat_map["cs"] = "cs_CZ";
195 compat_map["uk"] = "uk_UA";
197 auto lang = config->language().get_value_or("en_GB");
198 if (compat_map.find(lang) != compat_map.end ()) {
199 lang = compat_map[lang];
202 checked_set (_language, lang);
204 checked_set (_check_for_updates, config->check_for_updates ());
205 checked_set (_check_for_test_updates, config->check_for_test_updates ());
207 setup_sensitivity ();
211 GeneralPage::setup_sensitivity ()
213 _language->Enable (_set_language->GetValue ());
214 _check_for_test_updates->Enable (_check_for_updates->GetValue ());
218 GeneralPage::set_language_changed ()
220 setup_sensitivity ();
221 if (_set_language->GetValue ()) {
224 Config::instance()->unset_language ();
229 GeneralPage::language_changed ()
231 int const sel = _language->GetSelection ();
233 Config::instance()->set_language (string_client_data (_language->GetClientObject (sel)));
235 Config::instance()->unset_language ();
240 GeneralPage::check_for_updates_changed ()
242 Config::instance()->set_check_for_updates (_check_for_updates->GetValue ());
246 GeneralPage::check_for_test_updates_changed ()
248 Config::instance()->set_check_for_test_updates (_check_for_test_updates->GetValue ());
251 CertificateChainEditor::CertificateChainEditor (
255 function<void (shared_ptr<dcp::CertificateChain>)> set,
256 function<shared_ptr<const dcp::CertificateChain> (void)> get,
257 function<bool (void)> nag_alter
259 : wxDialog (parent, wxID_ANY, title)
262 , _nag_alter (nag_alter)
264 _sizer = new wxBoxSizer (wxVERTICAL);
266 auto certificates_sizer = new wxBoxSizer (wxHORIZONTAL);
267 _sizer->Add (certificates_sizer, 0, wxALL, border);
269 _certificates = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (440, 150), wxLC_REPORT | wxLC_SINGLE_SEL);
274 ip.SetText (_("Type"));
276 _certificates->InsertColumn (0, ip);
282 ip.SetText (_("Thumbprint"));
285 wxFont font = ip.GetFont ();
286 font.SetFamily (wxFONTFAMILY_TELETYPE);
289 _certificates->InsertColumn (1, ip);
292 certificates_sizer->Add (_certificates, 1, wxEXPAND);
295 auto s = new wxBoxSizer (wxVERTICAL);
296 _add_certificate = new Button (this, _("Add..."));
297 s->Add (_add_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
298 _remove_certificate = new Button (this, _("Remove"));
299 s->Add (_remove_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
300 _export_certificate = new Button (this, _("Export certificate..."));
301 s->Add (_export_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
302 _export_chain = new Button (this, _("Export chain..."));
303 s->Add (_export_chain, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
304 certificates_sizer->Add (s, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
307 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
308 _sizer->Add (table, 1, wxALL | wxEXPAND, border);
311 add_label_to_sizer (table, this, _("Leaf private key"), true, wxGBPosition (r, 0));
312 _private_key = new StaticText (this, wxT(""));
313 wxFont font = _private_key->GetFont ();
314 font.SetFamily (wxFONTFAMILY_TELETYPE);
315 _private_key->SetFont (font);
316 table->Add (_private_key, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
317 _import_private_key = new Button (this, _("Import..."));
318 table->Add (_import_private_key, wxGBPosition (r, 2));
319 _export_private_key = new Button (this, _("Export..."));
320 table->Add (_export_private_key, wxGBPosition (r, 3));
323 _button_sizer = new wxBoxSizer (wxHORIZONTAL);
324 _remake_certificates = new Button (this, _("Re-make certificates and key..."));
325 _button_sizer->Add (_remake_certificates, 1, wxRIGHT, border);
326 table->Add (_button_sizer, wxGBPosition (r, 0), wxGBSpan (1, 4));
329 _private_key_bad = new StaticText (this, _("Leaf private key does not match leaf certificate!"));
330 font = *wxSMALL_FONT;
331 font.SetWeight (wxFONTWEIGHT_BOLD);
332 _private_key_bad->SetFont (font);
333 table->Add (_private_key_bad, wxGBPosition (r, 0), wxGBSpan (1, 3));
336 _add_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::add_certificate, this));
337 _remove_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::remove_certificate, this));
338 _export_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_certificate, this));
339 _certificates->Bind (wxEVT_LIST_ITEM_SELECTED, bind (&CertificateChainEditor::update_sensitivity, this));
340 _certificates->Bind (wxEVT_LIST_ITEM_DESELECTED, bind (&CertificateChainEditor::update_sensitivity, this));
341 _remake_certificates->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::remake_certificates, this));
342 _export_chain->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_chain, this));
343 _import_private_key->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::import_private_key, this));
344 _export_private_key->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_private_key, this));
346 auto buttons = CreateSeparatedButtonSizer (wxCLOSE);
348 _sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
351 SetSizerAndFit (_sizer);
353 update_certificate_list ();
354 update_private_key ();
355 update_sensitivity ();
359 CertificateChainEditor::add_button (wxWindow* button)
361 _button_sizer->Add (button, 0, wxLEFT | wxRIGHT, DCPOMATIC_SIZER_X_GAP);
366 CertificateChainEditor::add_certificate ()
368 auto d = new wxFileDialog (this, _("Select Certificate File"));
370 if (d->ShowModal() == wxID_OK) {
375 extra = c.read_string (dcp::file_to_string (wx_to_std (d->GetPath ())));
376 } catch (boost::filesystem::filesystem_error& e) {
377 error_dialog (this, _("Could not import certificate (%s)"), d->GetPath());
382 if (!extra.empty ()) {
385 _("This file contains other certificates (or other data) after its first certificate. "
386 "Only the first certificate will be used.")
389 auto chain = make_shared<dcp::CertificateChain>(*_get().get());
391 if (!chain->chain_valid ()) {
394 _("Adding this certificate would make the chain inconsistent, so it will not be added. "
395 "Add certificates in order from root to intermediate to leaf.")
400 update_certificate_list ();
402 } catch (dcp::MiscError& e) {
403 error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
409 update_sensitivity ();
413 CertificateChainEditor::remove_certificate ()
416 /* Cancel was clicked */
420 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
425 _certificates->DeleteItem (i);
426 auto chain = make_shared<dcp::CertificateChain>(*_get().get());
430 update_sensitivity ();
431 update_certificate_list ();
435 CertificateChainEditor::export_certificate ()
437 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
442 auto all = _get()->root_to_leaf();
444 wxString default_name;
446 default_name = "root.pem";
447 } else if (i == static_cast<int>(all.size() - 1)) {
448 default_name = "leaf.pem";
450 default_name = "intermediate.pem";
453 auto d = new wxFileDialog(
454 this, _("Select Certificate File"), wxEmptyString, default_name, wxT ("PEM files (*.pem)|*.pem"),
455 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
458 auto j = all.begin ();
459 for (int k = 0; k < i; ++k) {
463 if (d->ShowModal () == wxID_OK) {
464 boost::filesystem::path path (wx_to_std(d->GetPath()));
465 if (path.extension() != ".pem") {
468 dcp::File f(path, "w");
470 throw OpenFileError (path, errno, OpenFileError::WRITE);
473 string const s = j->certificate (true);
474 f.checked_write(s.c_str(), s.length());
480 CertificateChainEditor::export_chain ()
482 auto d = new wxFileDialog (
483 this, _("Select Chain File"), wxEmptyString, wxT("certificate_chain.pem"), wxT("PEM files (*.pem)|*.pem"),
484 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
487 if (d->ShowModal () == wxID_OK) {
488 boost::filesystem::path path (wx_to_std(d->GetPath()));
489 if (path.extension() != ".pem") {
492 dcp::File f(path, "w");
494 throw OpenFileError (path, errno, OpenFileError::WRITE);
497 auto const s = _get()->chain();
498 f.checked_write (s.c_str(), s.length());
505 CertificateChainEditor::update_certificate_list ()
507 _certificates->DeleteAllItems ();
509 auto certs = _get()->root_to_leaf();
510 for (auto const& i: certs) {
513 _certificates->InsertItem (item);
514 _certificates->SetItem (n, 1, std_to_wx (i.thumbprint ()));
517 _certificates->SetItem (n, 0, _("Root"));
518 } else if (n == (certs.size() - 1)) {
519 _certificates->SetItem (n, 0, _("Leaf"));
521 _certificates->SetItem (n, 0, _("Intermediate"));
527 static wxColour normal = _private_key_bad->GetForegroundColour ();
529 if (_get()->private_key_valid()) {
530 _private_key_bad->Hide ();
531 _private_key_bad->SetForegroundColour (normal);
533 _private_key_bad->Show ();
534 _private_key_bad->SetForegroundColour (wxColour (255, 0, 0));
539 CertificateChainEditor::remake_certificates ()
542 /* Cancel was clicked */
546 auto d = new MakeChainDialog (this, _get());
548 if (d->ShowModal () == wxID_OK) {
550 update_certificate_list ();
551 update_private_key ();
558 CertificateChainEditor::update_sensitivity ()
560 /* We can only remove the leaf certificate */
561 _remove_certificate->Enable (_certificates->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) == (_certificates->GetItemCount() - 1));
562 _export_certificate->Enable (_certificates->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
566 CertificateChainEditor::update_private_key ()
568 checked_set (_private_key, dcp::private_key_fingerprint (_get()->key().get()));
573 CertificateChainEditor::import_private_key ()
575 auto d = new wxFileDialog (this, _("Select Key File"));
577 if (d->ShowModal() == wxID_OK) {
579 boost::filesystem::path p (wx_to_std (d->GetPath ()));
580 if (boost::filesystem::file_size (p) > 8192) {
583 wxString::Format (_("Could not read key file; file is too long (%s)"), std_to_wx (p.string ()))
588 auto chain = make_shared<dcp::CertificateChain>(*_get().get());
589 chain->set_key (dcp::file_to_string (p));
591 update_private_key ();
592 } catch (std::exception& e) {
593 error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
599 update_sensitivity ();
603 CertificateChainEditor::export_private_key ()
605 auto key = _get()->key();
610 auto d = new wxFileDialog (
611 this, _("Select Key File"), wxEmptyString, wxT("private_key.pem"), wxT("PEM files (*.pem)|*.pem"),
612 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
615 if (d->ShowModal () == wxID_OK) {
616 boost::filesystem::path path (wx_to_std(d->GetPath()));
617 if (path.extension() != ".pem") {
620 dcp::File f(path, "w");
622 throw OpenFileError (path, errno, OpenFileError::WRITE);
625 auto const s = _get()->key().get ();
626 f.checked_write(s.c_str(), s.length());
632 KeysPage::GetName () const
640 wxFont subheading_font (*wxNORMAL_FONT);
641 subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
643 auto sizer = _panel->GetSizer();
646 auto m = new StaticText (_panel, _("Decrypting KDMs"));
647 m->SetFont (subheading_font);
648 sizer->Add (m, 0, wxALL | wxEXPAND, _border);
651 auto kdm_buttons = new wxBoxSizer (wxVERTICAL);
653 auto export_decryption_certificate = new Button (_panel, _("Export KDM decryption leaf certificate..."));
654 kdm_buttons->Add (export_decryption_certificate, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
655 auto export_settings = new Button (_panel, _("Export all KDM decryption settings..."));
656 kdm_buttons->Add (export_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
657 auto import_settings = new Button (_panel, _("Import all KDM decryption settings..."));
658 kdm_buttons->Add (import_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
659 auto decryption_advanced = new Button (_panel, _("Advanced..."));
660 kdm_buttons->Add (decryption_advanced, 0);
662 sizer->Add (kdm_buttons, 0, wxLEFT, _border);
664 export_decryption_certificate->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_certificate, this));
665 export_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_chain_and_key, this));
666 import_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::import_decryption_chain_and_key, this));
667 decryption_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::decryption_advanced, this));
670 auto m = new StaticText (_panel, _("Signing DCPs and KDMs"));
671 m->SetFont (subheading_font);
672 sizer->Add (m, 0, wxALL | wxEXPAND, _border);
675 auto signing_buttons = new wxBoxSizer (wxVERTICAL);
677 auto signing_advanced = new Button (_panel, _("Advanced..."));
678 signing_buttons->Add (signing_advanced, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
679 auto remake_signing = new Button (_panel, _("Re-make certificates and key..."));
680 signing_buttons->Add (remake_signing, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
682 sizer->Add (signing_buttons, 0, wxLEFT, _border);
684 signing_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::signing_advanced, this));
685 remake_signing->Bind (wxEVT_BUTTON, bind(&KeysPage::remake_signing, this));
690 KeysPage::remake_signing ()
692 auto d = new MakeChainDialog (_panel, Config::instance()->signer_chain());
694 if (d->ShowModal () == wxID_OK) {
695 Config::instance()->set_signer_chain(d->get());
701 KeysPage::decryption_advanced ()
703 auto c = new CertificateChainEditor (
704 _panel, _("Decrypting KDMs"), _border,
705 bind(&Config::set_decryption_chain, Config::instance(), _1),
706 bind(&Config::decryption_chain, Config::instance()),
707 bind(&KeysPage::nag_alter_decryption_chain, this)
714 KeysPage::signing_advanced ()
716 auto c = new CertificateChainEditor (
717 _panel, _("Signing DCPs and KDMs"), _border,
718 bind(&Config::set_signer_chain, Config::instance(), _1),
719 bind(&Config::signer_chain, Config::instance()),
727 KeysPage::export_decryption_chain_and_key ()
729 auto d = new wxFileDialog (
730 _panel, _("Select Export File"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom"),
731 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
734 if (d->ShowModal () == wxID_OK) {
735 boost::filesystem::path path (wx_to_std(d->GetPath()));
736 dcp::File f(path, "w");
738 throw OpenFileError (path, errno, OpenFileError::WRITE);
741 auto const chain = Config::instance()->decryption_chain()->chain();
742 f.checked_write (chain.c_str(), chain.length());
743 auto const key = Config::instance()->decryption_chain()->key();
744 DCPOMATIC_ASSERT (key);
745 f.checked_write(key->c_str(), key->length());
752 KeysPage::import_decryption_chain_and_key ()
754 if (NagDialog::maybe_nag (
756 Config::NAG_IMPORT_DECRYPTION_CHAIN,
757 _("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!"),
763 auto d = new wxFileDialog (
764 _panel, _("Select File To Import"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom")
767 if (d->ShowModal () == wxID_OK) {
768 auto new_chain = make_shared<dcp::CertificateChain>();
770 dcp::File f(wx_to_std(d->GetPath()), "r");
772 throw OpenFileError (f.path(), errno, OpenFileError::WRITE);
778 if (f.gets(buffer, 128) == 0) {
782 if (strncmp (buffer, "-----END CERTIFICATE-----", 25) == 0) {
783 new_chain->add (dcp::Certificate (current));
785 } else if (strncmp (buffer, "-----END RSA PRIVATE KEY-----", 29) == 0) {
786 new_chain->set_key (current);
791 if (new_chain->chain_valid() && new_chain->private_key_valid()) {
792 Config::instance()->set_decryption_chain (new_chain);
794 error_dialog (_panel, _("Invalid DCP-o-matic export file"));
801 KeysPage::nag_alter_decryption_chain ()
803 return NagDialog::maybe_nag (
805 Config::NAG_ALTER_DECRYPTION_CHAIN,
806 _("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!"),
812 KeysPage::export_decryption_certificate ()
814 auto config = Config::instance();
815 wxString default_name = "dcpomatic";
816 if (!config->dcp_creator().empty()) {
817 default_name += "_" + std_to_wx(careful_string_filter(config->dcp_creator()));
819 if (!config->dcp_issuer().empty()) {
820 default_name += "_" + std_to_wx(careful_string_filter(config->dcp_issuer()));
822 default_name += wxT("_kdm_decryption_cert.pem");
824 auto d = new wxFileDialog (
825 _panel, _("Select Certificate File"), wxEmptyString, default_name, wxT("PEM files (*.pem)|*.pem"),
826 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
829 if (d->ShowModal () == wxID_OK) {
830 boost::filesystem::path path (wx_to_std(d->GetPath()));
831 if (path.extension() != ".pem") {
834 dcp::File f(path, "w");
836 throw OpenFileError (path, errno, OpenFileError::WRITE);
839 auto const s = Config::instance()->decryption_chain()->leaf().certificate (true);
840 f.checked_write(s.c_str(), s.length());
847 SoundPage::GetName () const
855 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
856 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
860 _sound = new CheckBox (_panel, _("Play sound via"));
861 table->Add (_sound, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
862 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
863 _sound_output = new wxChoice (_panel, wxID_ANY);
864 s->Add (_sound_output, 0);
865 _sound_output_details = new wxStaticText (_panel, wxID_ANY, wxT(""));
866 s->Add (_sound_output_details, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, DCPOMATIC_SIZER_X_GAP);
867 table->Add (s, wxGBPosition(r, 1));
870 add_label_to_sizer (table, _panel, _("Mapping"), true, wxGBPosition(r, 0));
871 _map = new AudioMappingView (_panel, _("DCP"), _("DCP"), _("Output"), _("output"));
872 _map->SetSize (-1, 400);
873 table->Add (_map, wxGBPosition(r, 1), wxDefaultSpan, wxEXPAND);
876 _reset_to_default = new Button (_panel, _("Reset to default"));
877 table->Add (_reset_to_default, wxGBPosition(r, 1));
880 wxFont font = _sound_output_details->GetFont();
881 font.SetStyle (wxFONTSTYLE_ITALIC);
882 font.SetPointSize (font.GetPointSize() - 1);
883 _sound_output_details->SetFont (font);
885 RtAudio audio (DCPOMATIC_RTAUDIO_API);
886 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
888 auto dev = audio.getDeviceInfo (i);
889 if (dev.probed && dev.outputChannels > 0) {
890 _sound_output->Append (std_to_wx (dev.name));
892 } catch (RtAudioError&) {
893 /* Something went wrong so let's just ignore that device */
897 _sound->Bind (wxEVT_CHECKBOX, bind(&SoundPage::sound_changed, this));
898 _sound_output->Bind (wxEVT_CHOICE, bind(&SoundPage::sound_output_changed, this));
899 _map->Changed.connect (bind(&SoundPage::map_changed, this, _1));
900 _reset_to_default->Bind (wxEVT_BUTTON, bind(&SoundPage::reset_to_default, this));
904 SoundPage::reset_to_default ()
906 Config::instance()->set_audio_mapping_to_default ();
910 SoundPage::map_changed (AudioMapping m)
912 Config::instance()->set_audio_mapping (m);
916 SoundPage::sound_changed ()
918 Config::instance()->set_sound (_sound->GetValue ());
922 SoundPage::sound_output_changed ()
924 RtAudio audio (DCPOMATIC_RTAUDIO_API);
925 auto const so = get_sound_output();
926 string default_device;
928 default_device = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
929 } catch (RtAudioError&) {
932 if (!so || *so == default_device) {
933 Config::instance()->unset_sound_output ();
935 Config::instance()->set_sound_output (*so);
940 SoundPage::config_changed ()
942 auto config = Config::instance ();
944 checked_set (_sound, config->sound ());
946 auto const current_so = get_sound_output ();
947 optional<string> configured_so;
949 if (config->sound_output()) {
950 configured_so = config->sound_output().get();
952 /* No configured output means we should use the default */
953 RtAudio audio (DCPOMATIC_RTAUDIO_API);
955 configured_so = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
956 } catch (RtAudioError&) {
957 /* Probably no audio devices at all */
961 if (configured_so && current_so != configured_so) {
962 /* Update _sound_output with the configured value */
964 while (i < _sound_output->GetCount()) {
965 if (_sound_output->GetString(i) == std_to_wx(*configured_so)) {
966 _sound_output->SetSelection (i);
973 RtAudio audio (DCPOMATIC_RTAUDIO_API);
975 map<int, wxString> apis;
976 apis[RtAudio::MACOSX_CORE] = _("CoreAudio");
977 apis[RtAudio::WINDOWS_ASIO] = _("ASIO");
978 apis[RtAudio::WINDOWS_DS] = _("Direct Sound");
979 apis[RtAudio::WINDOWS_WASAPI] = _("WASAPI");
980 apis[RtAudio::UNIX_JACK] = _("JACK");
981 apis[RtAudio::LINUX_ALSA] = _("ALSA");
982 apis[RtAudio::LINUX_PULSE] = _("PulseAudio");
983 apis[RtAudio::LINUX_OSS] = _("OSS");
984 apis[RtAudio::RTAUDIO_DUMMY] = _("Dummy");
988 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
990 auto info = audio.getDeviceInfo(i);
991 if (info.name == *configured_so && info.outputChannels > 0) {
992 channels = info.outputChannels;
994 } catch (RtAudioError&) {
1000 _sound_output_details->SetLabel (
1001 wxString::Format(_("%d channels on %s"), channels, apis[audio.getCurrentApi()])
1004 _map->set (Config::instance()->audio_mapping(channels));
1006 vector<NamedChannel> input;
1007 for (int i = 0; i < MAX_DCP_AUDIO_CHANNELS; ++i) {
1008 input.push_back (NamedChannel(short_audio_channel_name(i), i));
1010 _map->set_input_channels (input);
1012 vector<NamedChannel> output;
1013 for (int i = 0; i < channels; ++i) {
1014 output.push_back (NamedChannel(dcp::raw_convert<string>(i), i));
1016 _map->set_output_channels (output);
1018 setup_sensitivity ();
1022 SoundPage::setup_sensitivity ()
1024 _sound_output->Enable (_sound->GetValue());
1027 /** @return Currently-selected preview sound output in the dialogue */
1029 SoundPage::get_sound_output ()
1031 int const sel = _sound_output->GetSelection ();
1032 if (sel == wxNOT_FOUND) {
1033 return optional<string> ();
1036 return wx_to_std (_sound_output->GetString (sel));
1040 LocationsPage::LocationsPage (wxSize panel_size, int border)
1041 : Page (panel_size, border)
1047 LocationsPage::GetName () const
1049 return _("Locations");
1052 #ifdef DCPOMATIC_OSX
1054 LocationsPage::GetLargeIcon () const
1056 return wxBitmap(bitmap_path("locations.png"), wxBITMAP_TYPE_PNG);
1061 LocationsPage::setup ()
1065 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1066 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
1068 add_label_to_sizer (table, _panel, _("Content directory"), true, wxGBPosition (r, 0));
1069 _content_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1070 table->Add (_content_directory, wxGBPosition (r, 1));
1073 add_label_to_sizer (table, _panel, _("Playlist directory"), true, wxGBPosition (r, 0));
1074 _playlist_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1075 table->Add (_playlist_directory, wxGBPosition (r, 1));
1078 add_label_to_sizer (table, _panel, _("KDM directory"), true, wxGBPosition (r, 0));
1079 _kdm_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1080 table->Add (_kdm_directory, wxGBPosition (r, 1));
1083 _content_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::content_directory_changed, this));
1084 _playlist_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::playlist_directory_changed, this));
1085 _kdm_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::kdm_directory_changed, this));
1089 LocationsPage::config_changed ()
1091 auto config = Config::instance ();
1093 if (config->player_content_directory()) {
1094 checked_set (_content_directory, *config->player_content_directory());
1096 if (config->player_playlist_directory()) {
1097 checked_set (_playlist_directory, *config->player_playlist_directory());
1099 if (config->player_kdm_directory()) {
1100 checked_set (_kdm_directory, *config->player_kdm_directory());
1105 LocationsPage::content_directory_changed ()
1107 Config::instance()->set_player_content_directory(wx_to_std(_content_directory->GetPath()));
1111 LocationsPage::playlist_directory_changed ()
1113 Config::instance()->set_player_playlist_directory(wx_to_std(_playlist_directory->GetPath()));
1117 LocationsPage::kdm_directory_changed ()
1119 Config::instance()->set_player_kdm_directory(wx_to_std(_kdm_directory->GetPath()));